#!/usr/bin/env node /** * Command-line API evolution checker * This script runs the API evolution detection system from the command line * and provides detailed output about missing or changed endpoints. */ const fs = require('fs'); const path = require('path'); // Simple implementation of the evolution checker for Node.js class CommandLineEvolutionChecker { constructor() { this.schemaPath = path.join(__dirname, 'openapi-schema.json'); this.implementedEndpoints = new Set([ 'GET:/ai-voicebot/api/admin/names', 'POST:/ai-voicebot/api/admin/set_password', 'POST:/ai-voicebot/api/admin/clear_password', 'GET:/ai-voicebot/api/health', 'GET:/ai-voicebot/api/session', 'GET:/ai-voicebot/api/lobby', 'POST:/ai-voicebot/api/lobby/{session_id}' ]); } loadSchema() { try { if (!fs.existsSync(this.schemaPath)) { throw new Error(`Schema file not found at: ${this.schemaPath}`); } const schemaContent = fs.readFileSync(this.schemaPath, 'utf8'); return JSON.parse(schemaContent); } catch (error) { throw new Error(`Failed to load OpenAPI schema: ${error.message}`); } } extractEndpoints(schema) { const endpoints = []; if (!schema.paths) { throw new Error('No paths found in OpenAPI schema'); } Object.keys(schema.paths).forEach(path => { // Skip the generic proxy endpoint if (path === '/ai-voicebot/{path}') { return; } const pathObj = schema.paths[path]; Object.keys(pathObj).forEach(method => { const operation = pathObj[method]; const endpointKey = `${method.toUpperCase()}:${path}`; endpoints.push({ path, method: method.toUpperCase(), operationId: operation.operationId, summary: operation.summary, implemented: this.implementedEndpoints.has(endpointKey), endpointKey }); }); }); return endpoints; } generateMethodName(endpoint) { if (endpoint.operationId) { // Convert snake_case operation ID to camelCase return endpoint.operationId.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase()); } // Fallback: generate from path and method const pathParts = endpoint.path.split('/').filter(part => part && !part.startsWith('{')); const lastPart = pathParts[pathParts.length - 1] || 'endpoint'; const method = endpoint.method.toLowerCase(); if (method === 'get') { return `get${lastPart.charAt(0).toUpperCase() + lastPart.slice(1)}`; } else { return `${method}${lastPart.charAt(0).toUpperCase() + lastPart.slice(1)}`; } } generateImplementationStub(endpoint) { const methodName = this.generateMethodName(endpoint); const hasPathParams = endpoint.path.includes('{'); const needsBody = ['POST', 'PUT', 'PATCH'].includes(endpoint.method); let params = []; let requestOptions = `{ method: '${endpoint.method}'`; // Add path parameters if (hasPathParams) { const pathParams = endpoint.path.match(/\{([^}]+)\}/g) || []; pathParams.forEach(param => { const paramName = param.replace(/[{}]/g, ''); params.push(`${paramName}: string`); }); } // Add body parameter for methods that typically need one if (needsBody) { params.push('data?: any'); requestOptions += ', body: data'; } requestOptions += ' }'; const paramString = params.length > 0 ? params.join(', ') : ''; return ` async ${methodName}(${paramString}): Promise { return this.request('${endpoint.path}', ${requestOptions}); }`; } async runCheck() { console.log('🔍 API Evolution Check'); console.log('='.repeat(50)); try { const schema = this.loadSchema(); const endpoints = this.extractEndpoints(schema); const implemented = endpoints.filter(ep => ep.implemented); const unimplemented = endpoints.filter(ep => !ep.implemented); console.log(`📊 Summary:`); console.log(` Total endpoints: ${endpoints.length}`); console.log(` Implemented: ${implemented.length}`); console.log(` Unimplemented: ${unimplemented.length}`); console.log(''); if (unimplemented.length === 0) { console.log('✅ All API endpoints are implemented in ApiClient!'); return; } console.log('⚠️ Unimplemented API endpoints:'); unimplemented.forEach(endpoint => { console.log(` • ${endpoint.method} ${endpoint.path}`); if (endpoint.summary) { console.log(` ${endpoint.summary}`); } }); console.log(''); console.log('💡 Implementation suggestions:'); console.log('Add these methods to the ApiClient class:'); console.log(''); unimplemented.forEach(endpoint => { console.log(this.generateImplementationStub(endpoint)); console.log(''); }); console.log('📝 Next steps:'); console.log('1. Copy the suggested methods above into client/src/api-client.ts'); console.log('2. Update the implementedEndpoints Set in api-evolution-checker.ts'); console.log('3. Add convenience methods to the appropriate API namespaces'); console.log('4. Update type imports if needed'); } catch (error) { console.error('❌ Error during API evolution check:', error.message); process.exit(1); } } } // Run the check if this script is executed directly if (require.main === module) { const checker = new CommandLineEvolutionChecker(); checker.runCheck(); } module.exports = { CommandLineEvolutionChecker };