// `archilyzer publish status` and `archilyzer publish now` (release 18 S3). // // publish status [--json] the publish status every surface reads // (publish/publishState.ts readPublishStatus): one // row per target with the chips' words, the lane, // and the plan Publish now would run // publish now run that plan: update-index when stale, then each // policy target's build and deploy — never forced // // THE CLI HAS NO QUEUE. Where the editor's Publish now enqueues the whole plan // on its `publish` queue (enqueuePublishRun), `publish now` runs the plan's // stages one after another IN THIS PROCESS, each under the publish lock — // exactly what the queue would do: the same stage bodies, in the same order, // with the same on-disk preconditions (a build carries `indexAfter`, a deploy // `builtAfter`), so a stage whose precondition is not met refuses (exit 3) and // the rest are still tried. The index update runs as the same child the // editor spawns (its heap cap), as `publish index` does. import { setKillChildTrees, killChildTreesNow } from "../jobs/runChild"; import { getPaths, type Paths } from "../lib/paths"; import { planPublishRun, type PublishStatus, type PublishTargetStatus } from "../publish/publishPlan"; import { readPublishStatus } from "../publish/publishState"; import { STAGE_EXIT, runStage } from "../publish/stageRun"; import type { StageRequest } from "../publish/stages"; import { cliRunId, publishIndex } from "./publish"; type Out = { log: (s: string) => void; error: (s: string) => void }; function pad(s: string, n: number): string { return s.length >= n ? s : s + " ".repeat(n - s.length); } function targetRow(t: PublishTargetStatus): string { const c = t.chips; return [ pad(t.target, 14), pad(`[${t.policy}]`, 13), `built: ${c.built.text}`, `deployed: ${c.deployed.text}`, `live: ${c.live.text}`, `next: ${t.next}`, ].join(" | "); } /** The status as lines (exported for the test). */ export function statusLines(s: PublishStatus): string[] { const out: string[] = []; out.push(`index ${s.index.chip.text}`); out.push(`lane ${s.lane.chip.text} — ${s.lane.reason}`); out.push(""); for (const t of [...s.sites, s.hub, s.homepage]) out.push(targetRow(t)); out.push(""); if (s.plan.steps.length === 0) out.push("Publish now would run nothing."); else { out.push("Publish now would run:"); for (const step of s.plan.steps) { const where = step.preview ? ` --preview ${step.preview}` : step.to === "local" ? " --to local" : ""; out.push(` ${step.kind} ${step.target}${where} — ${step.reason}`); } } for (const skip of s.plan.skipped) out.push(` (skipped ${skip.kind} ${skip.target}: ${skip.reason})`); return out; } export async function publishStatusRow( a: { json?: boolean; paths?: Paths }, out: Out = console, ): Promise { const status = await readPublishStatus(a.paths ?? getPaths(), { laneKnown: false }); if (a.json) out.log(JSON.stringify(status, null, 2)); else for (const line of statusLines(status)) out.log(line); return 0; } /** * Run Publish now's plan in this process, one stage at a time, as the * editor's queue would. Exit: 0 when every stage ran or was a no-op, else * the worst stage's code (130 at once on a cancel). */ export async function publishNow( a: { paths?: Paths; signal?: AbortSignal } = {}, out: Out = console, ): Promise { const paths = a.paths ?? getPaths(); const status = await readPublishStatus(paths, { laneKnown: false }); const plan = planPublishRun(status, { deploys: "policy" }); const runId = cliRunId(); for (const skip of plan.skipped) out.log(`[publish] skipped ${skip.kind} ${skip.target}: ${skip.reason}`); if (plan.steps.length === 0) { out.log("[publish] nothing to do: the index and every policy target are current"); return 0; } out.log(`[publish] run ${runId}: ${plan.steps.map((s) => `${s.kind} ${s.target}`).join(", ")}`); const ac = new AbortController(); const onSignal = () => { if (!ac.signal.aborted) return ac.abort(); killChildTreesNow("SIGKILL"); process.exit(STAGE_EXIT.cancelled); }; const signal = a.signal ?? ac.signal; if (!a.signal) { process.on("SIGINT", onSignal); process.on("SIGTERM", onSignal); } setKillChildTrees(true); let worst = 0; try { for (const step of plan.steps) { if (signal.aborted) return STAGE_EXIT.cancelled; const { reason: _reason, previewUrl: _previewUrl, ...rest } = step; const req: StageRequest = { ...rest, runId }; const code = req.kind === "update-index" ? await publishIndex({ paths, runId }) : (await runStage(req, { paths, signal })).code; if (code === STAGE_EXIT.cancelled) return code; // A failure (1) outranks a refusal (3) or a usage error (2). if (code !== 0 && worst !== STAGE_EXIT.failed) worst = code; } } finally { if (!a.signal) { process.off("SIGINT", onSignal); process.off("SIGTERM", onSignal); } } return worst; }