replayReleases
uses node:fs
Make a mirror's recorded releases again, in a codebase that doesn't have them: for each, in order, its commit's
files are imported (importAt) and the release is put at the history entry that makes. The files are taken
from git into a directory inside the repository, so packages resolve as they did, and workspace packages
(@tcm/core, say) are linked to their copies there rather than to today's.
import type { Codebase } from "#codebase/tcmts/cli/codebase"; import { git, linkPackages, RECORDS, releaseRecords } from "#codebase/tcmts/cli/mirror"; import { dim } from "#codebase/tcmts/cli/style"; import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; export function replayReleases(cb: Codebase, dir: string, importAt: (root: string, label: string) => boolean, namespaceOf: (pkg: string) => string | undefined): number { const records = releaseRecords(dir).filter((r) => !cb.store.release(r.package, r.version)); if (!records.length) return 0; const top = git(dir, "rev-parse", "--show-toplevel"); if (!top) throw new Error(`${dir} has release records (${RECORDS}) but isn't in a git repository to make them from`); let made = 0; for (const r of records) { const at = fs.mkdtempSync(path.join(top, ".tcm-release-")); try { // what was mirrored then, which may not be what is now const rel = r.dir; const tar = spawnSync("sh", ["-c", `git -C "$0" archive "$1" -- "$2" | tar -x -C "$3"`, top, r.commit, rel, at], { encoding: "utf8" }); if (tar.status !== 0) throw new Error(`can't take ${r.package}@${r.version}'s files from git (commit ${r.commit.slice(0, 8)}): ${tar.stderr.trim()}`); const root = path.join(at, rel); linkPackages(root, dir, at); console.log(dim(`making ${r.package}@${r.version} again from git (${r.commit.slice(0, 8)})…`)); if (!importAt(root, `${path.relative(process.cwd(), dir) || "."} at ${r.commit.slice(0, 8)}`)) throw new Error(`${r.package}@${r.version}'s files (commit ${r.commit.slice(0, 8)}) didn't import`); // its own history entry, which what's imported next doesn't replace (a command's changes are otherwise one) cb.store.endBatch(); const namespace = namespaceOf(r.package); if (namespace === undefined) throw new Error(`${r.package}@${r.version}: its commit has no package ${r.package}`); cb.store.putRelease({ package: r.package, version: r.version, namespace, causal: cb.store.head(), notes: r.notes, author: r.author, ts: r.ts }); made++; } finally { fs.rmSync(at, { recursive: true, force: true }); } } return made; }
uses
- tcmts.cli.codebase.Codebase
- tcmts.cli.mirror.RECORDS
- tcmts.cli.mirror.git
- tcmts.cli.mirror.linkPackages
- tcmts.cli.mirror.releaseRecords
- tcmts.cli.style.dim