/** * cli:split-component-registry — validate.ts * * Fail-closed gates, in order (pattern of scaffold-pwa/validate.ts): * 1. Zod parse (defaults applied). * 2. projectPath exists + assertWebProjectRoot (never operate on a backend tree). * 3. detectFrontendMode(projectPath) MUST be 'client' — the monolith itself is * a client-mode marker (lib/detector), the socle never carries one. * 4. registryFile exists on disk (nothing to split otherwise). */ import fs from 'node:fs' import path from 'node:path' import { assertWebProjectRoot } from '../../../../../lib/fs.js' import { detectFrontendMode } from '../../../../../lib/detector.js' import { SplitComponentRegistryInputSchema, type SplitComponentRegistryInput, type ValidationResult, } from './types.js' export async function validate( raw: unknown, ): Promise { const parsed = SplitComponentRegistryInputSchema.safeParse(raw) if (!parsed.success) { return { valid: false, errors: parsed.error.issues.map((i) => `[${i.path.join('.')}] ${i.message}`), warnings: [], } } const spec = parsed.data try { assertWebProjectRoot(spec.projectPath) } catch (err) { return { valid: false, errors: [err instanceof Error ? err.message : String(err)], warnings: [] } } const mode = await detectFrontendMode(spec.projectPath) if (mode.mode !== 'client') { return { valid: false, errors: [ `split-component-registry only operates on a CLIENT app (detected mode: '${mode.mode}'). ` + `The socle manages its own registry; 'unknown' refuses rather than guesses.`, ], warnings: [], } } const registryAbs = path.join(spec.projectPath, spec.registryFile) if (!fs.existsSync(registryAbs)) { return { valid: false, errors: [`registryFile not found: ${spec.registryFile} (resolved: ${registryAbs}) — nothing to split.`], warnings: [], } } return { valid: true, errors: [], warnings: [], spec } }