#!/usr/bin/env node /** * cli:aggregate-outbox — Aggregate every per-entity offline-write outbox module * into src/extensions/outbox.generated.ts (imported by src/main.tsx BEFORE * initOutbox(); scaffold-pwa wires that import). * * ALWAYS emits the file — even with zero modules — so main.tsx's unconditional * import can never dangle. Re-run after scaffold-api-client adds/removes an * offline-write entity. */ import { parseArgs } from 'node:util' import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs' import { dirname, resolve } from 'node:path' import { validate } from './validate.js' import { generate } from './generate.js' import { generateEnvelope, failGenerate, printEnvelope } from '../../../../../lib/output.js' import { readSpecArg } from '../../../../../lib/spec-arg.js' import { assertWebProjectRoot, safeJoinPath } from '../../../../../lib/fs.js' const COMMAND = 'aggregate-outbox' async function main(): Promise { const { values } = parseArgs({ options: { spec: { type: 'string' }, 'spec-file': { type: 'string' }, dry_run: { type: 'boolean', default: false }, }, strict: true, }) const specSrc = readSpecArg(values) if ('error' in specSrc) { printEnvelope(failGenerate(COMMAND, [specSrc.error])) process.exit(1) } let raw: unknown try { raw = JSON.parse(specSrc.raw) } catch { printEnvelope(failGenerate(COMMAND, ['Invalid JSON in --spec'])) process.exit(1) } const v = await validate(raw) if (!v.valid || !v.webRootAbs) { printEnvelope(failGenerate(COMMAND, v.errors)) process.exit(1) } const webRoot = v.webRootAbs const result = generate({ webRootAbs: webRoot }) if (result.errors.length > 0) { printEnvelope(failGenerate(COMMAND, result.errors)) process.exit(1) } if (values.dry_run) { printEnvelope( generateEnvelope(COMMAND, { data: { dryRun: true, webRoot, moduleCount: result.modules.length, modules: result.modules.map((m) => m.file), resources: result.modules.flatMap((m) => m.resources), files: result.files.map((f) => f.path), }, warnings: result.warnings, }), ) process.exit(0) } try { assertWebProjectRoot(webRoot) } catch (e) { printEnvelope(failGenerate(COMMAND, [e instanceof Error ? e.message : String(e)])) process.exit(1) } const filesCreated: string[] = [] const filesModified: string[] = [] for (const file of result.files) { const abs = resolve(safeJoinPath(webRoot, file.path)) const exists = existsSync(abs) if (exists && readFileSync(abs, 'utf-8') === file.content) continue // up to date mkdirSync(dirname(abs), { recursive: true }) writeFileSync(abs, file.content, 'utf-8') ;(exists ? filesModified : filesCreated).push(abs) } const nextSteps: string[] = [ "Ensure src/main.tsx imports './extensions/outbox.generated' BEFORE calling initOutbox() — scaffold-pwa wires this (audit DEV-PWA-008 checks the order).", 'Re-run this CLI whenever scaffold-api-client adds or removes an offline-write outbox module.', ] printEnvelope( generateEnvelope(COMMAND, { data: { webRoot, moduleCount: result.modules.length, modules: result.modules.map((m) => m.file), resources: result.modules.flatMap((m) => m.resources), }, filesCreated, filesModified, warnings: result.warnings, nextSteps, }), ) } void main()