#!/usr/bin/env -S bun --no-env-file --config=/dev/null // gstack-design-build — the receipt behind /design-review --build. The loop // edits and looks; this decides what it proved (lib/design-build.ts). // // Usage: gstack-design-build plan --inventory --run-dir [--waivers ] // gstack-design-build preflight --inventory [--grant-remote ...] // gstack-design-build admit // gstack-design-build verify-start --inventory --run-dir // gstack-design-build receipt --inventory --run-dir --rounds [--preview ] [--waivers ] [--out-root ] // gstack-design-build check --inventory --receipt // // Prints exactly one "DESIGN_BUILD: code= …" line for every // outcome (preflight and admit print it only when they stop the run). Exit 0 // clean or waived (and ok), 1 open, 4 incomplete, unrendered or stale, 3 usage. import * as fs from 'node:fs'; import * as path from 'node:path'; import { execFileSync } from 'node:child_process'; import { resolveStateRoot } from '../lib/state-root'; import { runScriptSync } from '../lib/script-invocation'; import { admit, BuildUsageError, check, parseInventory, plan, preflight, receipt, servedStates, verifyStart } from '../lib/design-build'; const USAGE = 'usage: gstack-design-build plan|preflight|admit|verify-start|receipt|check (see docs/howto-preview-a-frontend.md#build-receipt)'; function flags(argv: string[]): { pos: string[]; one: Map; many: Map } { const pos: string[] = []; const one = new Map(); const many = new Map(); for (let i = 0; i < argv.length; i++) { const a = argv[i]!; if (!a.startsWith('--')) { pos.push(a); continue; } const v = argv[++i]; if (v === undefined) throw new BuildUsageError('USAGE', `${a} needs a value`, USAGE); one.set(a, v); many.set(a, [...(many.get(a) ?? []), v]); } return { pos, one, many }; } function repoRoot(): string { return execFileSync('git', ['rev-parse', '--show-toplevel'], { encoding: 'utf8', timeout: 30_000 }).trim(); } function slug(): string { const out = runScriptSync(path.join(import.meta.dir, 'gstack-slug'), [], { encoding: 'utf8', timeout: 30_000 }).stdout ?? ''; return /^SLUG=(.+)$/m.exec(out)?.[1]?.trim() || 'unknown'; } async function main(argv: string[]): Promise { const [cmd, ...rest] = argv; const f = flags(rest); const need = (k: string): string => { const v = f.one.get(k); if (!v) throw new BuildUsageError('USAGE', `${cmd} needs ${k}`, USAGE); return v; }; const inventory = () => parseInventory(fs.readFileSync(need('--inventory'), 'utf8')); switch (cmd) { case 'plan': for (const l of plan(inventory(), path.resolve(need('--run-dir')), f.one.get('--waivers'))) console.log(l); return 0; case 'preflight': { const r = await preflight(inventory(), f.many.get('--grant-remote') ?? []); for (const l of r.lines) console.log(l); return r.exit; } case 'admit': { if (!f.pos[0]) throw new BuildUsageError('USAGE', 'admit needs the gstack-skill-start output file', USAGE); const r = admit(fs.readFileSync(f.pos[0], 'utf8')); for (const l of r.lines) console.log(l); return r.exit; } case 'verify-start': console.log(verifyStart(repoRoot(), inventory(), path.resolve(need('--run-dir')))); return 0; case 'receipt': { const outRoot = f.one.get('--out-root') ?? path.join(resolveStateRoot(), 'projects', slug(), 'designs'); const inv = inventory(); const r = receipt({ repo: repoRoot(), inv, runDir: path.resolve(need('--run-dir')), rounds: Number(need('--rounds')), previewDir: f.one.get('--preview'), served: await servedStates(inv), waivers: f.one.get('--waivers'), outRoot }); console.log(r.line); return r.exit; } case 'check': { const r = check(repoRoot(), inventory(), need('--receipt')); console.log(r.line); return r.exit; } default: throw new BuildUsageError('USAGE', `unknown command ${cmd ?? '(none)'}`, USAGE); } } main(process.argv.slice(2)).then((code) => process.exit(code), (e) => { if (e instanceof BuildUsageError) console.error(`gstack-design-build: ${e.code}: ${e.message} | fix: ${e.fix}`); else console.error(`gstack-design-build: ${(e as Error).message}`); process.exit(3); });