UNPKG

@tryloop/oats

Version:

🌾 OATS - OpenAPI TypeScript Sync. The missing link between your OpenAPI specs and TypeScript applications. Automatically watch, generate, and sync TypeScript clients from your API definitions.

304 lines • 10.7 kB
/** * OATS Validate Command * * Validates OATS configuration files * * @module @oatsjs/cli/validate */ import { existsSync, readFileSync } from 'fs'; import { join, resolve, basename } from 'path'; import chalk from 'chalk'; import ora from 'ora'; import { validateConfig } from '../config/schema.js'; import { loadConfigFromFile, findConfigFile } from '../config/loader.js'; /** * Validate OATS configuration */ export async function validate(options) { let configPath; let fullPath; if (options.config) { configPath = options.config; fullPath = join(process.cwd(), configPath); } else { const foundPath = findConfigFile(); if (!foundPath) { console.error(chalk.red('\nāŒ No configuration file found')); process.exit(1); } fullPath = foundPath; configPath = basename(foundPath); } console.log(chalk.yellow(`\nšŸ” Validating ${configPath}...\n`)); // Check if config file exists if (!existsSync(fullPath)) { console.error(chalk.red(`āŒ Configuration file not found: ${configPath}`)); console.log(chalk.dim('\nCreate a configuration with: oats init')); process.exit(1); } try { // Load configuration (supports both .json and .ts) const config = await loadConfigFromFile(fullPath); // Validate configuration schema const validation = validateConfig(config); if (!validation.valid) { console.error(chalk.red('āŒ Configuration is invalid:\n')); validation.errors.forEach((error) => { console.error(chalk.red(` • ${error.path}: ${error.message}`)); if (error.value !== undefined) { console.error(chalk.dim(` Current value: ${JSON.stringify(error.value)}`)); } }); if (!options.strict) { console.log(chalk.dim('\nRun with --strict for additional checks')); } process.exit(1); } // Show warnings if (validation.warnings.length > 0) { console.warn(chalk.yellow('āš ļø Configuration warnings:\n')); validation.warnings.forEach((warning) => { console.warn(chalk.yellow(` • ${warning.message}`)); if (warning.suggestion) { console.warn(chalk.dim(` šŸ’” ${warning.suggestion}`)); } }); } else { ora().succeed('Configuration schema is valid!'); } // Check paths exist console.log(chalk.yellow('\nšŸ” Checking service paths...\n')); const pathChecks = await checkPaths(config); displayPathChecks(pathChecks); // Strict mode checks let strictResults = []; if (options.strict) { console.log(chalk.yellow('\nšŸ” Running strict validation...\n')); strictResults = await performStrictChecks(config); displayStrictResults(strictResults); } // Check for dependencies console.log(chalk.yellow('\nšŸ” Checking dependencies...\n')); const depChecks = await checkDependencies(config); displayDependencyChecks(depChecks); // Final summary const hasErrors = pathChecks.some((p) => p.required && !p.exists) || (options.strict && strictResults.some((r) => !r.passed)); if (hasErrors) { console.log(chalk.red('\nāŒ Validation completed with errors')); process.exit(1); } else if (validation.warnings.length > 0 || pathChecks.some((p) => !p.exists)) { console.log(chalk.yellow('\nāš ļø Validation completed with warnings')); } else { console.log(); // Empty line ora().succeed('Configuration is fully valid!'); console.log(chalk.dim('\nYou can now run: oats start')); } } catch (error) { console.error(chalk.red('āŒ Failed to validate configuration:')); console.error(chalk.red(error instanceof Error ? error.message : String(error))); process.exit(1); } } /** * Check if configured paths exist */ async function checkPaths(config) { const checks = []; // Backend path const backendPath = resolve(process.cwd(), config.services.backend.path); checks.push({ name: 'Backend', path: config.services.backend.path, required: true, exists: existsSync(backendPath), absolutePath: backendPath, }); // API spec path const apiSpecPath = resolve(backendPath, config.services.backend.apiSpec.path); checks.push({ name: 'API Spec', path: join(config.services.backend.path, config.services.backend.apiSpec.path), required: false, exists: existsSync(apiSpecPath), absolutePath: apiSpecPath, }); // Client path const clientPath = resolve(process.cwd(), config.services.client.path); checks.push({ name: 'Client', path: config.services.client.path, required: true, exists: existsSync(clientPath), absolutePath: clientPath, }); // Frontend path (optional) if (config.services.frontend) { const frontendPath = resolve(process.cwd(), config.services.frontend.path); checks.push({ name: 'Frontend', path: config.services.frontend.path, required: false, exists: existsSync(frontendPath), absolutePath: frontendPath, }); } return checks; } /** * Display path check results */ function displayPathChecks(checks) { checks.forEach(({ name, path, required, exists }) => { if (exists) { ora().succeed(`${name}: ${path}`); } else if (required) { ora().fail(`${name}: ${path} (not found - required)`); } else { ora().warn(`${name}: ${path} (not found - optional)`); } }); } /** * Perform strict validation checks */ async function performStrictChecks(config) { const results = []; // Check package.json files exist const paths = [ config.services.backend.path, config.services.client.path, config.services.frontend?.path, ].filter(Boolean); for (const path of paths) { const packageJsonPath = resolve(process.cwd(), path, 'package.json'); results.push({ check: `package.json in ${path}`, passed: existsSync(packageJsonPath), message: existsSync(packageJsonPath) ? 'Found' : 'Missing package.json - is this a Node.js project?', }); } // Check for start scripts for (const path of paths) { const packageJsonPath = resolve(process.cwd(), path, 'package.json'); if (existsSync(packageJsonPath)) { try { const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); const hasDevScript = packageJson.scripts?.dev || packageJson.scripts?.start; results.push({ check: `Dev script in ${path}`, passed: Boolean(hasDevScript), message: hasDevScript ? 'Found dev/start script' : 'No dev or start script found', }); } catch { results.push({ check: `Dev script in ${path}`, passed: false, message: 'Could not read package.json', }); } } } // Check client package name matches const clientPackageJsonPath = resolve(process.cwd(), config.services.client.path, 'package.json'); if (existsSync(clientPackageJsonPath)) { try { const packageJson = JSON.parse(readFileSync(clientPackageJsonPath, 'utf-8')); const nameMatches = packageJson.name === config.services.client.packageName; results.push({ check: 'Client package name', passed: nameMatches, message: nameMatches ? 'Package name matches configuration' : `Package name mismatch: ${packageJson.name} !== ${config.services.client.packageName}`, }); } catch { // Ignore } } return results; } /** * Display strict check results */ function displayStrictResults(results) { results.forEach(({ check, passed, message }) => { if (passed) { ora().succeed(`${check}: ${message}`); } else { ora().fail(`${check}: ${message}`); } }); } /** * Check dependencies */ async function checkDependencies(config) { const checks = []; // Check if generator is installed (for non-custom generators) if (config.services.client.generator !== 'custom') { const clientPath = resolve(process.cwd(), config.services.client.path); const packageJsonPath = resolve(clientPath, 'package.json'); if (existsSync(packageJsonPath)) { try { const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8')); const deps = { ...packageJson.dependencies, ...packageJson.devDependencies, }; const hasGenerator = config.services.client.generator in deps; checks.push({ name: config.services.client.generator, type: 'generator', installed: hasGenerator, required: true, }); } catch { checks.push({ name: config.services.client.generator, type: 'generator', installed: false, required: true, }); } } } return checks; } /** * Display dependency check results */ function displayDependencyChecks(checks) { if (checks.length === 0) { console.log(chalk.dim('No specific dependencies to check')); return; } checks.forEach(({ name, type, installed, required }) => { if (installed) { ora().succeed(`${name} (${type})`); } else if (required) { ora().fail(`${name} (${type}) - not installed`); } else { ora().warn(`${name} (${type}) - not installed`); } }); } //# sourceMappingURL=validate.js.map