testPackageReleases

tcmts.cli.test.e2e.releases.testPackageReleases · #g3qkgawk · term

a package's releases: versions from the changes, notes from the codebase, drafts for the words

import { codebase } from "#codebase/tcmts/cli/test/e2e/releases";
import assert from "node:assert/strict";
import fs from "node:fs";
import path from "node:path";

export const testPackageReleases = () => {
  const a = codebase("one");
  a.save(
    `// tcm: namespace lib\nexport const manifest = { name: "squares" };\n/** Squares a number. Quickly. */\nexport const square = (x: number): number => x * x;\nexport const helper = (x: number): number => x + 1;\n`,
    "add",
  );
  a.save(`// tcm: namespace other\nexport const detail = 1;\n`, "add");
  assert.match(
    a.tcm("release"),
    /released squares@0\.1\.0 as #\w{8}: its first release\n\n## 0\.1\.0 \(\d{4}-\d\d-\d\d\)\n\nThe first release: 2 definition\(s\)\./,
  );

  // a breaking change: the version follows (a minor step, before 1.0), and nothing smaller is accepted
  a.save(
    `// tcm: namespace lib\n/** Cubes a number. */\nexport const cube = (x: number): number => x * x * x;\nexport const square = (x: number): number => x ** 2;\n`,
  );
  a.tcm("rm", "lib.helper");
  a.save(`// tcm: namespace other\nexport const detail = 2;\n`);
  assert.match(
    a.tcm("release", "check"),
    /squares's next release: 0\.2\.0 \(after 0\.1\.0\): breaking changes\n\n {2}major {2}lib\.helper removed\n {9}definition_removed: code that uses it no longer compiles/,
  );
  assert.match(
    a.run("release", "@0.1.1").out,
    /0\.1\.1 is too small a step from 0\.1\.0: breaking changes \(lib\.helper\) call for 0\.2\.0/,
  );

  // a draft: people write above the marker; below it is regenerated, so editing it changes nothing
  assert.match(
    a.tcm("release", "--draft"),
    /wrote release-squares\.md \(squares's next release, 0\.2\.0 as things stand\)/,
  );
  const draft = fs.readFileSync(a.file("release-squares.md"), "utf8");
  fs.writeFileSync(
    a.file("release-squares.md"),
    draft
      .replace(
        "<!-- what this release is about, for the people who use it -->",
        "Cubes, and a faster `square`.",
      )
      .replace(/- `lib\.helper` removed\n/, ""),
  );
  const out = a.tcm("release");
  assert.match(out, /released squares@0\.2\.0 .*: breaking changes/);
  assert.match(
    out,
    /Cubes, and a faster `square`\.\n\n### Breaking changes\n\n- `lib\.helper` removed\n\n### Added\n\n- `lib\.cube` `\(x: number\) => number`: Cubes a number\.\n\n### Changed\n\n- `lib\.square` \(new implementation, same API\)$/m,
  );
  assert.doesNotMatch(out, /other\.detail/, "only the package's own definitions");
  assert.ok(
    !fs.existsSync(a.file("release-squares.md")),
    "the draft's words are in the release now",
  );

  // a version can be further than the changes call for, never less; releases don't change; todo holds them up
  a.save(`// tcm: namespace lib\nexport const cube = (x: number): number => x ** 3;\n`);
  assert.match(a.run("release", "squares@0.2.0").out, /squares@0\.2\.0 is already released/);
  // (left to fix: area can't follow square's change; square as it was, it's nothing to fix)
  a.save(
    `// tcm: namespace lib\nimport { square } from "#codebase/lib";\nexport const area = (r: number): number => square(r) * 3;\n`,
    "add",
  );
  fs.writeFileSync(
    a.file("scratch.ts"),
    `// tcm: namespace lib\nexport const square = (x: number): string => String(x * x);\n`,
  );
  a.run("update", a.file("scratch.ts"));
  assert.match(a.run("release").out, /squares has work left \(`tcm todo`\)/);
  a.save(`// tcm: namespace lib\nexport const square = (x: number): number => x ** 2;\n`);
  assert.match(a.tcm("release", "1.0.0"), /released squares@1\.0\.0 .*: as asked/);
  // with nothing new there's no release to make, unless one is asked for by version
  assert.match(a.run("release").out, /squares has nothing new since 1\.0\.0: no release to make/);

  // listed, rendered as a changelog, and usable wherever a history entry is
  assert.match(
    a.tcm("releases"),
    /^squares {2}lib\n {2}1\.0\.0 .*\n {2}0\.2\.0 .* Cubes, and a faster `square`\.\n {2}0\.1\.0 /,
  );
  const log = a.tcm("changelog");
  assert.match(log, /^# Changelog\n\n## 1\.0\.0[\s\S]*## 0\.2\.0[\s\S]*## 0\.1\.0/);
  assert.doesNotMatch(log, /<!--/);
  a.tcm("changelog", "--out", "CHANGELOG.md");
  assert.equal(fs.readFileSync(a.file("CHANGELOG.md"), "utf8"), log);
  assert.match(a.tcm("diff", "squares@0.2.0"), /changes since squares@0\.2\.0[\s\S]*lib\.cube/);
  assert.match(a.tcm("release", "view", "@0.2.0"), /^## 0\.2\.0 .*\n\nCubes/);

  // packing a release packs it, at its version, with its changelog, whatever's on the branch now
  a.save(`// tcm: namespace lib\nexport const later = 1;\n`, "add");
  a.tcm("pack", "squares@0.2.0");
  const pkg = path.join(a.dir, ".tcm", "pack", "squares");
  assert.equal(
    JSON.parse(fs.readFileSync(path.join(pkg, "package.json"), "utf8")).version,
    "0.2.0",
  );
  const changes = fs.readFileSync(path.join(pkg, "CHANGELOG.md"), "utf8");
  assert.match(changes, /## 0\.2\.0[\s\S]*## 0\.1\.0/);
  assert.doesNotMatch(changes, /1\.0\.0/);
  const index = fs.readFileSync(path.join(pkg, "index.d.ts"), "utf8");
  assert.match(index, / as cube /);
  assert.doesNotMatch(index, / as later | as manifest /);
}

uses