import { existsSync, readFileSync } from 'node:fs'; import { join, relative, resolve } from 'node:path'; import { walkFiles } from './file-rules.js'; const allowedSchemaVersions = new Set([1]); const readJson = (path) => JSON.parse(readFileSync(path, 'utf8')); export const validateSource = (sourceDir, config) => { const sourceRoot = resolve(sourceDir); const errors = []; const warnings = []; if (!allowedSchemaVersions.has(config.schemaVersion)) { errors.push(`Unsupported schemaVersion: ${config.schemaVersion}`); } if (config.type !== 'bridgeable-components') { errors.push(`Unsupported type: ${config.type}`); } for (const [key, path] of Object.entries(config.paths)) { if (!path || typeof path !== 'string') { errors.push(`paths.${key} must be a non-empty string.`); continue; } if (!existsSync(join(sourceRoot, path))) { warnings.push(`Configured path is missing: ${path}`); } } if (!existsSync(join(sourceRoot, config.entry.globalStyles))) { warnings.push(`Global style entry is missing: ${config.entry.globalStyles}`); } let specCount = 0; walkFiles(sourceRoot, (absolutePath) => { if (!absolutePath.endsWith('.component.json')) { return; } specCount += 1; const relativePath = relative(sourceRoot, absolutePath); let spec; try { spec = readJson(absolutePath); } catch (error) { errors.push(`Invalid JSON in ${relativePath}: ${error.message}`); return; } const expectedType = relativePath.startsWith(`${config.paths.components}/`) ? 'component' : 'section'; if (spec.type && spec.type !== expectedType) { errors.push(`${relativePath} has type "${spec.type}" but expected "${expectedType}".`); } if (!spec.name || typeof spec.name !== 'string') { errors.push(`${relativePath} must define a string "name".`); } }); if (specCount === 0) { warnings.push('No *.component.json files were found.'); } return { valid: errors.length === 0, errors, warnings, specCount }; };