/** * scaffold-ui-primitives/validate.ts — Args validation. */ import { existsSync, statSync } from 'node:fs'; import path from 'node:path'; import { ScaffoldUiPrimitivesInputSchema, type ScaffoldUiPrimitivesInput } from './types.js'; import { readInstalledSmartstackVersion, meetsFloor, MODULE_AVAILABILITY_MIN_VERSION } from './version.js'; export interface ValidationResult { valid: boolean; spec: ScaffoldUiPrimitivesInput | null; errors: string[]; warnings: string[]; } export function validate(raw: unknown): ValidationResult { const parsed = ScaffoldUiPrimitivesInputSchema.safeParse(raw); if (!parsed.success) { return { valid: false, spec: null, errors: parsed.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`), warnings: [], }; } const spec = parsed.data; const errors: string[] = []; const warnings: string[] = []; const abs = path.resolve(spec.projectPath); if (!existsSync(abs)) errors.push(`projectPath does not exist: ${abs}`); else if (!statSync(abs).isDirectory()) errors.push(`projectPath is not a directory: ${abs}`); else if (!existsSync(path.join(abs, 'src'))) { warnings.push(`projectPath has no "src" folder yet — it will be created.`); } // Resolve the package version once, here, so `generate` stays pure. A project below the // floor still generates — with a permissive stub — and is TOLD so. const resolved: ScaffoldUiPrimitivesInput = { ...spec, smartstackVersion: spec.smartstackVersion ?? readInstalledSmartstackVersion(spec.projectPath), }; if (errors.length === 0 && !meetsFloor(resolved.smartstackVersion, MODULE_AVAILABILITY_MIN_VERSION)) { warnings.push( `@atlashub/smartstack ${resolved.smartstackVersion ?? '(not declared)'} predates ${MODULE_AVAILABILITY_MIN_VERSION}: ` + `useModuleAvailability is emitted as a PERMISSIVE STUB (every module reads as available, so cross-module ` + `related tabs stay visible exactly as before). Run \`ss upgrade\` to activate the tenant-catalogue guard.`, ); } return { valid: errors.length === 0, spec: resolved, errors, warnings }; }