testMirrorReleasesInGit
a mirror's releases are kept in git with its files, and made again in a fresh codebase
import { plainEnv, strip, tcmBin, tempDir } from "#codebase/tcmts/cli/test/e2e/harness"; import assert from "node:assert/strict"; import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; export const testMirrorReleasesInGit = () => { const root = tempDir("mirror"); const repo = path.join(root, "repo"); const lib = path.join(repo, "lib"); const first = path.join(root, "first"); const second = path.join(root, "second"); const run = (cwd: string, ...args: string[]) => { const r = spawnSync(process.execPath, [tcmBin(), ...args], { cwd, encoding: "utf8", env: { ...plainEnv(), TCM_AUTHOR: "ada" } }); const out = strip(r.stdout + r.stderr); if (r.status !== 0) throw new Error(`tcm ${args.join(" ")} exited ${r.status}:\n${out}`); return out; }; const sh = (...args: string[]) => { const r = spawnSync("git", ["-C", repo, ...args], { encoding: "utf8" }); assert.equal(r.status, 0, r.stderr); return r.stdout.trim(); }; fs.mkdirSync(lib, { recursive: true }); fs.writeFileSync(path.join(repo, "package.json"), JSON.stringify({ type: "module" })); fs.writeFileSync(path.join(lib, "index.ts"), `/** Shapes. */\nexport const manifest = { name: "shapes" };\n`); fs.writeFileSync(path.join(lib, "area.ts"), `export const square = (s: number): number => s * s;\n`); sh("init", "-q"); sh("add", "."); sh("-c", "user.email=a@b", "-c", "user.name=a", "commit", "-qm", "shapes"); for (const d of [first, second]) { fs.mkdirSync(d); run(d, "init"); fs.rmSync(path.join(d, "scratch.ts")); } run(first, "import", lib, "--into", "geo", "--mirror"); // a release is made from a commit, and recorded with the files const commit = sh("rev-parse", "HEAD"); assert.match(run(first, "release", "shapes"), /recorded in .*tcm-releases\.json, from commit/); const records = JSON.parse(fs.readFileSync(path.join(lib, "tcm-releases.json"), "utf8")) as { version: string; commit: string }[]; assert.deepEqual(records.map((r) => [r.version, r.commit]), [["0.1.0", commit]]); sh("add", "."); sh("-c", "user.email=a@b", "-c", "user.name=a", "commit", "-qm", "shapes 0.1.0"); // the next one, after a change made through tcm; a change git doesn't have yet stops it fs.writeFileSync(path.join(first, "scratch.ts"), `// tcm: namespace geo.area\nexport const cube = (s: number): number => s * s * s;\n`); run(first, "add"); assert.throws(() => run(first, "release", "shapes"), /has changes git doesn't have yet; commit them first/); sh("add", "."); sh("-c", "user.email=a@b", "-c", "user.name=a", "commit", "-qm", "cube"); assert.match(run(first, "release", "shapes"), /released shapes@0\.1\.1/); sh("add", "."); sh("-c", "user.email=a@b", "-c", "user.name=a", "commit", "-qm", "shapes 0.1.1"); // a codebase linked to the mirror afresh has both releases, with their notes, at the same code assert.match(run(second, "import", lib, "--into", "geo", "--mirror"), /made 2 releases again from git/); const releases = (d: string) => run(d, "releases").replace(/#\w+/g, ""); assert.equal(releases(second), releases(first)); assert.equal(run(second, "changelog", "shapes"), run(first, "changelog", "shapes")); assert.equal(run(second, "release", "check", "shapes"), run(first, "release", "check", "shapes")); assert.match(run(second, "release", "check", "shapes"), /after 0\.1\.1\): no API changes/); // and packs as the release it's at assert.match(run(second, "pack", "shapes", "--out", path.join(root, "packed")), /packed shapes@0\.1\.1/); // each release is at its own history entry: the earlier one packs as it was, without cube assert.match(run(second, "pack", "shapes@0.1.0", "--out", path.join(root, "packed-0.1.0")), /packed shapes@0\.1\.0/); assert.equal(fs.readdirSync(path.join(root, "packed-0.1.0", "modules")).length < fs.readdirSync(path.join(root, "packed", "modules")).length, true); }