181 lines
5.7 KiB
JavaScript
181 lines
5.7 KiB
JavaScript
#!/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<any> {
|
|
return this.request<any>('${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 };
|