publishBuild
uses node:path
Put a build made in a directory of its own (staged, on the same filesystem) in its place, <apps>/<hash>/, at
once: a rename. A build already there stays, unless replace says otherwise of it (another tcm's, or --force),
and staged is dropped: a build is content-addressed, so it's the one this would be. One replaced is moved aside
first (to aside, then removed), so a reader finds the old build or the new one, each whole. The manifest of the
build in place.
import type { BuildManifest } from "#codebase/tcmts/cli/web"; import fs from "node:fs"; import path from "node:path"; export function publishBuild( apps: string, hash: string, staged: string, aside: string, replace: (there: BuildManifest) => boolean, ): BuildManifest { const to = path.join(apps, hash); const manifestIn = (dir: string) => { try { return JSON.parse(fs.readFileSync(path.join(dir, "tcm-app.json"), "utf8")) as BuildManifest; } catch { return undefined; } }; const made = manifestIn(staged); if (!made) throw new Error(`${staged} holds no build`); fs.mkdirSync(apps, { recursive: true }); for (let tries = 0; ; tries++) { try { fs.renameSync(staged, to); break; } catch (e) { const code = (e as NodeJS.ErrnoException).code; if (!["ENOTEMPTY", "EEXIST", "EPERM", "EACCES"].includes(code ?? "") || tries > 20) throw e; } // (one without a manifest is a build an older tcm left half-made) const there = manifestIn(to); if (there && !replace(there)) { fs.rmSync(staged, { recursive: true, force: true }); return there; } // (another build may put its own in between: then that's looked at, as this one was) fs.rmSync(aside, { recursive: true, force: true }); try { fs.renameSync(to, aside); } catch (e) { if ((e as NodeJS.ErrnoException).code !== "ENOENT") throw e; } } fs.rmSync(aside, { recursive: true, force: true }); return made; }