#!/usr/bin/env node const fs = require('fs'); const path = require('path'); const YAML = require('js-yaml'); const { glob } = require('glob'); class OpenAPIValidator { constructor() { this.errors = []; this.warnings = []; this.stats = { totalFiles: 0, validFiles: 0, invalidFiles: 0 }; } // 验证YAML文件格式 validateYAMLSyntax(filePath) { try { const content = fs.readFileSync(filePath, 'utf8'); YAML.load(content); return { valid: true }; } catch (error) { return { valid: false, error: `YAML语法错误: ${error.message}` }; } } // 验证OpenAPI规范 validateOpenAPISpec(filePath) { try { const content = fs.readFileSync(filePath, 'utf8'); const doc = YAML.load(content); // 基本结构检查 if (filePath.endsWith('openapi.yaml')) { return this.validateMainSpec(doc); } else if (filePath.includes('/schemas/')) { return this.validateSchemas(doc); } else if (filePath.includes('/paths/')) { return this.validatePaths(doc); } else if (filePath.includes('/components/')) { return this.validateComponents(doc); } return { valid: true }; } catch (error) { return { valid: false, error: `规范验证错误: ${error.message}` }; } } // 验证主文档 validateMainSpec(doc) { const errors = []; if (!doc.openapi) { errors.push('缺少 openapi 版本号'); } else if (!doc.openapi.startsWith('3.0')) { errors.push('openapi 版本应为 3.0.x'); } if (!doc.info) { errors.push('缺少 info 部分'); } else { if (!doc.info.title) errors.push('缺少 info.title'); if (!doc.info.version) errors.push('缺少 info.version'); } if (!doc.paths) { errors.push('缺少 paths 部分'); } return { valid: errors.length === 0, errors: errors }; } // 验证数据模型 validateSchemas(doc) { const errors = []; for (const [name, schema] of Object.entries(doc)) { if (typeof schema !== 'object') continue; // 检查必要字段 if (!schema.type && !schema.$ref && !schema.allOf && !schema.oneOf && !schema.anyOf) { errors.push(`模型 ${name} 缺少 type 或引用定义`); } // 检查描述 if (schema.type === 'object' && schema.properties) { for (const [propName, prop] of Object.entries(schema.properties)) { if (!prop.description) { errors.push(`模型 ${name}.${propName} 缺少描述`); } } } } return { valid: errors.length === 0, errors: errors }; } // 验证路径定义 validatePaths(doc) { const errors = []; for (const [pathName, pathItem] of Object.entries(doc)) { if (typeof pathItem !== 'object') continue; for (const [method, operation] of Object.entries(pathItem)) { if (!['get', 'post', 'put', 'patch', 'delete', 'head', 'options'].includes(method)) { continue; } // 检查必要字段 if (!operation.tags) { errors.push(`路径 ${pathName}.${method} 缺少 tags`); } if (!operation.summary) { errors.push(`路径 ${pathName}.${method} 缺少 summary`); } if (!operation.description) { errors.push(`路径 ${pathName}.${method} 缺少 description`); } if (!operation.operationId) { errors.push(`路径 ${pathName}.${method} 缺少 operationId`); } if (!operation.responses) { errors.push(`路径 ${pathName}.${method} 缺少 responses`); } } } return { valid: errors.length === 0, errors: errors }; } // 验证组件定义 validateComponents(doc) { const errors = []; // 基本组件结构检查 for (const [name, component] of Object.entries(doc)) { if (typeof component !== 'object') continue; // 检查是否有必要的属性 if (!component.description && !component.$ref) { errors.push(`组件 ${name} 建议添加描述`); } } return { valid: errors.length === 0, errors: errors }; } // 验证单个文件 async validateFile(filePath) { console.log(`验证文件: ${filePath}`); this.stats.totalFiles++; // YAML语法验证 const syntaxResult = this.validateYAMLSyntax(filePath); if (!syntaxResult.valid) { this.errors.push(`${filePath}: ${syntaxResult.error}`); this.stats.invalidFiles++; return false; } // OpenAPI规范验证 const specResult = this.validateOpenAPISpec(filePath); if (!specResult.valid) { this.stats.invalidFiles++; if (specResult.errors) { specResult.errors.forEach(error => { this.errors.push(`${filePath}: ${error}`); }); } else { this.errors.push(`${filePath}: ${specResult.error}`); } return false; } this.stats.validFiles++; return true; } // 验证所有文档文件 async validateAll() { console.log('🔍 开始验证 OpenAPI 文档...\n'); try { // 查找所有YAML文件 const yamlFiles = await glob('docs/**/*.yaml', { cwd: process.cwd(), absolute: true }); if (yamlFiles.length === 0) { console.log('⚠️ 未找到任何 YAML 文件'); return false; } console.log(`找到 ${yamlFiles.length} 个 YAML 文件\n`); // 验证每个文件 for (const file of yamlFiles) { await this.validateFile(file); } return this.generateReport(); } catch (error) { console.error('验证过程中出现错误:', error); return false; } } // 生成验证报告 generateReport() { console.log('\n📊 验证报告'); console.log('='.repeat(50)); console.log(`总文件数: ${this.stats.totalFiles}`); console.log(`有效文件: ${this.stats.validFiles}`); console.log(`无效文件: ${this.stats.invalidFiles}`); if (this.errors.length > 0) { console.log('\n❌ 发现的错误:'); this.errors.forEach((error, index) => { console.log(`${index + 1}. ${error}`); }); } if (this.warnings.length > 0) { console.log('\n⚠️ 警告信息:'); this.warnings.forEach((warning, index) => { console.log(`${index + 1}. ${warning}`); }); } const isValid = this.errors.length === 0; if (isValid) { console.log('\n✅ 所有文档验证通过!'); } else { console.log('\n❌ 文档验证失败,请修复上述错误。'); } return isValid; } } // 主执行函数 async function main() { const validator = new OpenAPIValidator(); const isValid = await validator.validateAll(); // 设置退出码 process.exit(isValid ? 0 : 1); } // 如果直接运行此脚本 if (require.main === module) { main().catch(error => { console.error('验证脚本执行失败:', error); process.exit(1); }); } module.exports = OpenAPIValidator;