#!/usr/bin/env node /** * scaffold-screen-controller — index.ts * * Reads pagespecs/..md filtered by (section, entity), generates * the {EntityPlural}ScreenController.cs + one {Entity}{View}ScreenDto per * read screen, and writes them under spec.projectPath. Returns a JSON * envelope on stdout (success | error | files generated count | todos). * * Exit code 0 on success, 1 on filesystem/parse failure, 2 on invalid spec. */ import { parseArgs } from 'node:util' import { mkdirSync, writeFileSync } from 'node:fs' import { dirname, join, resolve } from 'node:path' import { generate, legacyPaths } from './generate.js' import { guardedRm } from '../../../../../lib/guarded-rm.js' import { parsePagespecsForSection } from './parse-pagespec.js' import type { Result } from './types.js' import { validate } from './validate.js' async function main(): Promise { const args = parseArgs({ options: { spec: { type: 'string' }, json: { type: 'boolean', default: false }, }, allowPositionals: false, }) if (!args.values.spec) { emit(args.values.json, { success: false, error: '--spec is required', filesGenerated: 0, files: [], todos: [], }) process.exit(2) } let raw: unknown try { raw = JSON.parse(args.values.spec) } catch { emit(args.values.json, { success: false, error: '--spec must be valid JSON', filesGenerated: 0, files: [], todos: [], }) process.exit(2) } const v = validate(raw) if (!v.valid) { emit(args.values.json, { success: false, error: v.errors.join('; '), filesGenerated: 0, files: [], todos: [], }) process.exit(2) } const spec = v.data! // Parse pagespecs filtered to (section, entity). const { pagespecs, warnings } = parsePagespecsForSection(spec.moduleDir, spec.section, spec.entity) if (pagespecs.length === 0) { emit(args.values.json, { success: false, error: `No pagespecs found in ${spec.moduleDir}/pagespecs/ matching section="${spec.section}" entity="${spec.entity}". Warnings: ${warnings.map(w => `${w.file}: ${w.reason}`).join('; ') || '(none)'}`, filesGenerated: 0, files: [], todos: [], }) process.exit(1) } // Generate + write const out = generate(spec, pagespecs) // Idempotent relocate: delete the pre-classification copies (per-module Screens/ // bucket) before writing the new //
/ files. // Guarded sweep (lib/guarded-rm): an @customised file at a legacy path is preserved. guardedRm(legacyPaths(spec, out.files), { outdir: spec.projectPath }) for (const f of out.files) { const abs = resolve(join(spec.projectPath, f.path)) mkdirSync(dirname(abs), { recursive: true }) writeFileSync(abs, f.content, 'utf8') } emit(args.values.json, { success: true, filesGenerated: out.files.length, files: out.files, todos: out.todos, }) process.exit(0) } function emit(json: boolean | undefined, payload: Result): void { if (json) { console.log(JSON.stringify(payload, null, 2)) return } if (!payload.success) { console.error(`✗ ${payload.error}`) return } console.log(`✓ scaffold-screen-controller: ${payload.filesGenerated} file(s) generated`) for (const f of payload.files) console.log(` - ${f.path}`) if (payload.todos.length > 0) { console.log('') console.log(` TODOs (${payload.todos.length}):`) for (const t of payload.todos) console.log(` - ${t}`) } } main().catch(err => { console.error('Fatal:', (err as Error).message) process.exit(2) })