/** * cli:scaffold-controller — validate.ts */ import { ScaffoldControllerInputSchema, type ScaffoldControllerInput, type ValidationResult } from './types.js' export function validateStructure(raw: unknown): ValidationResult { const result = ScaffoldControllerInputSchema.safeParse(raw) if (result.success) return { valid: true, errors: [], warnings: [] } return { valid: false, errors: result.error.issues.map(i => `[${i.path.join('.')}] ${i.message}`), warnings: [] } } export function validate(raw: unknown): ValidationResult { const structural = validateStructure(raw) if (!structural.valid) return structural // Defence in depth behind lib/page-spec-actions' superRefine (a literal // spec can bypass the pipeline): a GET custom action returning NoContent is // a READ with no response contract — the emitted [HttpGet] answers 204 and // the computed value is unreadable (DEV-API-025 / PRD-124). const spec = ScaffoldControllerInputSchema.parse(raw) const errors: string[] = [] for (const a of spec.customActions) { if (a.httpMethod === 'GET' && a.responseDto === 'NoContent') { errors.push( `[customActions.${a.code}] a GET action MUST declare a responseDto (its [HttpGet] would return ` + `NoContent() — a read whose computed value nobody can receive). Declare the result DTO or ` + `requalify as a POST state mutation.`, ) } } return errors.length > 0 ? { valid: false, errors, warnings: [] } : structural }