/** * cli:scaffold-tests-from-ac — validate.ts * * Two-stage: (a) Zod schema validation on the input spec, * (b) Filesystem existence checks (moduleDir + projectPath must exist). * * Returns a {valid, errors[], warnings[], data} envelope. No side effects. */ import { existsSync, statSync } from 'node:fs' import { ScaffoldTestsFromAcSpecSchema, type ScaffoldTestsFromAcSpec } from './types.js' export interface ValidationResult { valid: boolean errors: string[] warnings: string[] data?: ScaffoldTestsFromAcSpec } export function validate(raw: unknown): ValidationResult { // Stage A — Zod const parsed = ScaffoldTestsFromAcSpecSchema.safeParse(raw) if (!parsed.success) { const errors = parsed.error.issues.map(i => `${i.path.join('.') || '(root)'}: ${i.message}`) return { valid: false, errors, warnings: [] } } const spec = parsed.data const errors: string[] = [] const warnings: string[] = [] // Stage B — Filesystem if (!existsSync(spec.moduleDir)) { errors.push(`moduleDir does not exist: ${spec.moduleDir}`) } else if (!statSync(spec.moduleDir).isDirectory()) { errors.push(`moduleDir is not a directory: ${spec.moduleDir}`) } if (!existsSync(spec.projectPath)) { errors.push(`projectPath does not exist: ${spec.projectPath}`) } else if (!statSync(spec.projectPath).isDirectory()) { errors.push(`projectPath is not a directory: ${spec.projectPath}`) } // Soft check: namespace shape (informational) if (spec.namespace && !/^[A-Z][A-Za-z0-9]*(\.[A-Z][A-Za-z0-9]*)+$/.test(spec.namespace)) { warnings.push( `namespace "${spec.namespace}" does not look like Pascal.Dot.Notation — accepted as-is, but emitted code may not compile.` ) } return { valid: errors.length === 0, errors, warnings, data: errors.length === 0 ? spec : undefined, } }