makeBuild
uses node:fs
tcm builds make <entry>: a build of the codebase's tcm (its package whose command is tcm) as it was at a
history entry, kept with the others in root (builds/<id>) to run as they are. It's packed from the codebase as
it was then, by this tcm, and installed beside a clone of the known-good install's dependencies when they're the
same (seconds), or by npm. One kept already is that one.
import { buildBin, KEEP_BUILDS, keptBuilds, pruneKeptBuilds } from "#codebase/tcmts/cli/builds"; import type { TcmBuild } from "#codebase/tcmts/cli/builds"; import type { Codebase } from "#codebase/tcmts/cli/codebase"; import { causalArg } from "#codebase/tcmts/cli/main"; import { packagesIn } from "#codebase/tcmts/cli/manifests"; import { packPackage, rowsAt } from "#codebase/tcmts/cli/releases"; import { UsageError } from "#codebase/tcmts/cli/ui"; import { shortHash } from "#codebase/tcmts/core/hash"; import { spawnSync } from "node:child_process"; import fs from "node:fs"; import path from "node:path"; export function makeBuild( cb: Codebase, root: string, entry: string, ): { build: TcmBuild; made: boolean } { const head = causalArg(cb, entry); const kept = keptBuilds(root).find((b) => b.head === head); if (kept) return { build: kept, made: false }; const pkg = packagesIn(cb, rowsAt(cb, head)).find((p) => p.manifest.bin.tcm); if (!pkg) throw new UsageError( `no package of the codebase had a command called tcm at ${shortHash(head)}: there's no tcm to build`, ); const work = fs.mkdtempSync(path.join(root, ".making-")); try { const out = path.join(work, "package"); packPackage(cb, pkg.name, undefined, out, head); const dependencies = (dir: string) => JSON.stringify( ( JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8")) as { dependencies?: unknown; } ).dependencies ?? {}, ); const build = path.join(work, "build"); const good = path.join(root, "known-good"); const goodBin = buildBin(good); const goodPackage = goodBin && path.dirname(path.dirname(goodBin)); if (goodPackage && dependencies(goodPackage) === dependencies(out)) { // the known-good's dependencies, at the same versions: a clone of its install, with this package in it fs.cpSync(good, build, { recursive: true, mode: fs.constants.COPYFILE_FICLONE }); for (const f of fs.readdirSync(build)) if (/^(VERIFIED|verified\.json|build\.json)$|\.tgz$/.test(f)) fs.rmSync(path.join(build, f)); fs.rmSync(path.join(build, path.relative(good, goodPackage)), { recursive: true, force: true, }); } else { fs.mkdirSync(build, { recursive: true }); if (dependencies(out) !== "{}") { fs.writeFileSync( path.join(build, "package.json"), JSON.stringify({ private: true, dependencies: JSON.parse(dependencies(out)) }, null, 2), ); const npm = spawnSync("npm", ["install", "--no-audit", "--no-fund", "--loglevel=error"], { cwd: build, encoding: "utf8", }); if (npm.status !== 0) throw new Error( `installing ${pkg.name}'s dependencies failed:\n${npm.stdout}${npm.stderr}`, ); } } const installed = path.join(build, "node_modules", pkg.name); fs.mkdirSync(path.dirname(installed), { recursive: true }); fs.cpSync(out, installed, { recursive: true }); fs.writeFileSync( path.join(build, "build.json"), `${JSON.stringify( { head, when: new Date().toISOString().slice(0, 16).replace("T", " "), package: pkg.name, }, null, 2, )}\n`, ); const dir = path.join(root, "builds", head.slice(0, 8)); fs.rmSync(dir, { recursive: true, force: true }); fs.mkdirSync(path.dirname(dir), { recursive: true }); fs.renameSync(build, dir); pruneKeptBuilds(root, KEEP_BUILDS); const made = keptBuilds(root).find((b) => b.dir === dir); if (!made) throw new Error(`the build made in ${dir} has no bin/tcm.js`); return { build: made, made: true }; } finally { fs.rmSync(work, { recursive: true, force: true }); } }
uses
- tcmts.cli.builds.KEEP_BUILDS
- tcmts.cli.builds.TcmBuild
- tcmts.cli.builds.buildBin
- tcmts.cli.builds.keptBuilds
- tcmts.cli.builds.pruneKeptBuilds
- tcmts.cli.codebase.Codebase
- tcmts.cli.main.causalArg
- tcmts.cli.manifests.packagesIn
- tcmts.cli.releases.packPackage
- tcmts.cli.releases.rowsAt
- tcmts.cli.ui.UsageError
- tcmts.core.hash.shortHash