replayReleases

tcmts.cli.mirror.replayReleases · #faehtry6 · term · runs on the server

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

used by