testPackageReleases
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 /); }