zero-doc 1.0.4 → 1.0.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -34,23 +34,19 @@ var __importStar = (this && this.__importStar) || (function () {
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.AIAnalyzer = void 0;
37
- const generative_ai_1 = require("@google/generative-ai");
38
37
  const fs = __importStar(require("fs"));
39
38
  const path = __importStar(require("path"));
40
39
  const glob_1 = require("glob");
41
40
  class AIAnalyzer {
42
41
  constructor(options) {
43
- this.genAI = new generative_ai_1.GoogleGenerativeAI(options.apiKey);
44
- this.model = this.genAI.getGenerativeModel({ model: 'gemini-3-flash-preview' });
42
+ this.apiUrl = options.apiUrl;
45
43
  this.projectRoot = options.projectRoot;
46
44
  this.filePatterns = options.filePatterns;
47
45
  }
48
46
  async analyzeProject() {
49
- // Find all relevant files
50
47
  const files = await this.findProjectFiles();
51
- // Read and analyze files
52
48
  const fileContents = [];
53
- for (const file of files.slice(0, 50)) { // Limit to 50 files for now
49
+ for (const file of files.slice(0, 50)) {
54
50
  try {
55
51
  const content = fs.readFileSync(file, 'utf-8');
56
52
  fileContents.push({ path: path.relative(this.projectRoot, file), content });
@@ -58,40 +54,34 @@ class AIAnalyzer {
58
54
  catch (error) {
59
55
  }
60
56
  }
61
- // Analyze with AI
57
+ if (fileContents.length === 0) {
58
+ throw new Error('No files found to analyze. Check your file patterns and project structure.');
59
+ }
62
60
  let endpoints = await this.analyzeWithAI(fileContents);
63
- // Filter duplicates and root routes
64
61
  endpoints = this.filterAndCleanEndpoints(endpoints);
65
- // Detect base URL from common prefix
66
62
  const baseUrl = this.detectBaseUrl(endpoints);
67
- // Detect programming language
68
63
  const language = this.detectLanguage(files);
69
- // Calculate stats
70
- const stats = this.calculateStats(endpoints);
64
+ const framework = this.detectFramework(files, endpoints);
71
65
  const inventory = {
72
66
  version: '1.0.0',
73
67
  generatedAt: new Date().toISOString(),
74
- baseUrl: baseUrl || 'https://api.example.com',
68
+ baseUrl: baseUrl,
75
69
  project: {
76
- name: this.extractProjectName(),
77
- version: this.extractProjectVersion(),
78
- description: this.extractProjectDescription(),
79
- framework: this.detectFramework(endpoints, language),
70
+ name: '',
71
+ version: '',
72
+ description: '',
73
+ framework: framework,
80
74
  language: language,
81
75
  },
82
- endpoints,
83
- stats,
76
+ endpoints: endpoints,
77
+ stats: {
78
+ totalEndpoints: endpoints.length,
79
+ byMethod: endpoints.reduce((acc, ep) => {
80
+ acc[ep.method] = (acc[ep.method] || 0) + 1;
81
+ return acc;
82
+ }, {}),
83
+ },
84
84
  };
85
- // Validate inventory structure
86
- if (!inventory.endpoints || !Array.isArray(inventory.endpoints)) {
87
- inventory.endpoints = [];
88
- }
89
- if (!inventory.stats) {
90
- inventory.stats = {
91
- totalEndpoints: inventory.endpoints.length,
92
- byMethod: {},
93
- };
94
- }
95
85
  return inventory;
96
86
  }
97
87
  async findProjectFiles() {
@@ -101,43 +91,51 @@ class AIAnalyzer {
101
91
  const files = await (0, glob_1.glob)(pattern, {
102
92
  cwd: this.projectRoot,
103
93
  absolute: true,
104
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'],
94
+ ignore: [
95
+ '**/node_modules/**',
96
+ '**/dist/**',
97
+ '**/build/**',
98
+ '**/.git/**',
99
+ '**/coverage/**',
100
+ '**/*.test.*',
101
+ '**/*.spec.*',
102
+ ],
105
103
  });
106
104
  allFiles.push(...files);
107
105
  }
108
106
  catch (error) {
109
- // Silently ignore pattern errors
110
107
  }
111
108
  }
112
- const uniqueFiles = [...new Set(allFiles)];
109
+ const uniqueFiles = [...new Set(allFiles)].sort();
113
110
  if (uniqueFiles.length === 0) {
114
- // Try alternative patterns
115
111
  const alternativePatterns = [
116
112
  '**/*.ts',
117
113
  '**/*.js',
118
- '**/*.tsx',
119
- '**/*.jsx',
120
- 'src/**/*.ts',
121
- 'src/**/*.js',
122
- 'app/**/*.ts',
123
- 'app/**/*.js',
124
- 'lib/**/*.ts',
125
- 'lib/**/*.js',
114
+ '**/*.py',
115
+ '**/*.java',
116
+ '**/*.go',
117
+ '**/*.rs',
118
+ '**/*.php',
119
+ '**/*.rb',
126
120
  ];
127
- for (const altPattern of alternativePatterns) {
121
+ for (const pattern of alternativePatterns) {
128
122
  try {
129
- const files = await (0, glob_1.glob)(altPattern, {
123
+ const files = await (0, glob_1.glob)(pattern, {
130
124
  cwd: this.projectRoot,
131
125
  absolute: true,
132
- ignore: ['**/node_modules/**', '**/dist/**', '**/build/**', '**/.git/**'],
126
+ ignore: [
127
+ '**/node_modules/**',
128
+ '**/dist/**',
129
+ '**/build/**',
130
+ '**/.git/**',
131
+ ],
133
132
  });
134
133
  if (files.length > 0) {
135
134
  uniqueFiles.push(...files);
136
- break; // Use first pattern that finds files
135
+ break;
137
136
  }
138
137
  }
139
138
  catch (error) {
140
- // Continue
141
139
  }
142
140
  }
143
141
  }
@@ -145,64 +143,210 @@ class AIAnalyzer {
145
143
  }
146
144
  async analyzeWithAI(files) {
147
145
  const prompt = this.buildAnalysisPrompt(files);
146
+ if (!this.apiUrl) {
147
+ throw new Error('Proxy API URL not configured');
148
+ }
149
+ const url = this.apiUrl.trim();
150
+ if (!url.startsWith('http://') && !url.startsWith('https://')) {
151
+ throw new Error(`Invalid API URL: "${url}". URL must start with http:// or https://`);
152
+ }
148
153
  try {
149
- const result = await this.model.generateContent(prompt);
150
- const response = await result.response;
151
- const text = response.text();
152
- // Parse JSON from response
153
- const jsonMatch = text.match(/\{[\s\S]*\}/);
154
- if (!jsonMatch) {
155
- throw new Error('Failed to parse JSON from AI response');
154
+ const response = await fetch(url, {
155
+ method: 'POST',
156
+ headers: {
157
+ 'Content-Type': 'application/json',
158
+ },
159
+ body: JSON.stringify({ prompt }),
160
+ });
161
+ if (!response.ok) {
162
+ const errorText = await response.text().catch(() => 'No error details');
163
+ throw new Error(`Proxy API error (${response.status}): ${response.statusText}. ${errorText}`);
156
164
  }
157
- const aiResult = JSON.parse(jsonMatch[0]);
158
- if (!aiResult.endpoints || !Array.isArray(aiResult.endpoints)) {
159
- throw new Error('Invalid response format from AI');
165
+ const responseText = await response.text();
166
+ let endpoints = [];
167
+ try {
168
+ const parsed = JSON.parse(responseText);
169
+ if (parsed.endpoints && Array.isArray(parsed.endpoints)) {
170
+ endpoints = parsed.endpoints;
171
+ }
172
+ else if (Array.isArray(parsed)) {
173
+ endpoints = parsed;
174
+ }
175
+ else if (parsed.data && Array.isArray(parsed.data)) {
176
+ endpoints = parsed.data;
177
+ }
178
+ else if (parsed.response && typeof parsed.response === 'string') {
179
+ try {
180
+ const innerParsed = JSON.parse(parsed.response);
181
+ if (Array.isArray(innerParsed)) {
182
+ endpoints = innerParsed;
183
+ }
184
+ else if (innerParsed.endpoints && Array.isArray(innerParsed.endpoints)) {
185
+ endpoints = innerParsed.endpoints;
186
+ }
187
+ }
188
+ catch {
189
+ const jsonMatch = parsed.response.match(/\{[\s\S]*"endpoints"[\s\S]*\}/);
190
+ if (jsonMatch) {
191
+ const extracted = JSON.parse(jsonMatch[0]);
192
+ if (extracted.endpoints && Array.isArray(extracted.endpoints)) {
193
+ endpoints = extracted.endpoints;
194
+ }
195
+ }
196
+ }
197
+ }
198
+ if (endpoints.length === 0) {
199
+ throw new Error(`Invalid response format. Expected { endpoints: [...] } or array of endpoints. Received: ${JSON.stringify(parsed).substring(0, 200)}`);
200
+ }
160
201
  }
161
- // Validate and map endpoints
162
- const mappedEndpoints = aiResult.endpoints.map((ep, index) => {
163
- // Ensure method is uppercase
164
- const method = (ep.method || 'GET').toUpperCase();
165
- if (!['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(method)) {
202
+ catch (parseError) {
203
+ const codeBlockMatch = responseText.match(/```(?:json)?\s*(\{[\s\S]*\})\s*```/);
204
+ if (codeBlockMatch) {
205
+ try {
206
+ const extracted = JSON.parse(codeBlockMatch[1]);
207
+ if (extracted.endpoints && Array.isArray(extracted.endpoints)) {
208
+ endpoints = extracted.endpoints;
209
+ }
210
+ else if (Array.isArray(extracted)) {
211
+ endpoints = extracted;
212
+ }
213
+ }
214
+ catch {
215
+ }
166
216
  }
167
- return {
168
- id: `ep-${index}`,
169
- method: (['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].includes(method) ? method : 'GET'),
170
- path: ep.path || '/',
171
- group: ep.group || 'Other',
172
- file: ep.file || 'unknown',
173
- line: ep.line || 0,
174
- handler: {
175
- name: ep.handler?.name,
176
- isAsync: ep.handler?.isAsync || false,
177
- parameters: ep.handler?.parameters || [],
178
- },
179
- parameters: Array.isArray(ep.parameters) ? ep.parameters : [],
180
- requestBody: ep.requestBody || undefined,
181
- responses: Array.isArray(ep.responses) ? ep.responses : [],
182
- authentication: ep.authentication || undefined,
183
- headers: Array.isArray(ep.headers) ? ep.headers : [],
184
- metadata: {
185
- title: ep.metadata?.title || ep.title || `${method} ${ep.path || '/'}`,
186
- description: ep.metadata?.description || ep.description || '',
187
- aiGenerated: true,
188
- },
189
- };
190
- });
191
- // Log first endpoint for debugging
192
- // Logs removed for clean progress bar output
193
- return mappedEndpoints;
217
+ if (endpoints.length === 0) {
218
+ throw new Error(`Failed to parse API response. Expected JSON with endpoints array. Response preview: ${responseText.substring(0, 300)}`);
219
+ }
220
+ }
221
+ return this.mapEndpoints(endpoints);
194
222
  }
195
223
  catch (error) {
196
- throw error;
224
+ if (error instanceof TypeError && error.message.includes('fetch failed')) {
225
+ throw new Error(`Failed to connect to API at ${this.apiUrl}. Check if the server is running and the URL is correct.`);
226
+ }
227
+ throw new Error(`AI analysis failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
197
228
  }
198
229
  }
230
+ mapEndpoints(endpoints) {
231
+ return endpoints.map((ep, index) => {
232
+ const method = (ep.method || 'GET').toUpperCase();
233
+ return {
234
+ id: `ep-${index}`,
235
+ method: method,
236
+ path: ep.path || '/',
237
+ group: ep.group || 'Default',
238
+ file: ep.file || '',
239
+ line: ep.line || 0,
240
+ handler: ep.handler || {
241
+ name: 'anonymous',
242
+ isAsync: false,
243
+ parameters: [],
244
+ },
245
+ parameters: ep.parameters || [],
246
+ requestBody: ep.requestBody || { required: false, fields: [] },
247
+ responses: ep.responses || [],
248
+ authentication: ep.authentication || { required: false, type: 'none' },
249
+ headers: ep.headers || [],
250
+ metadata: ep.metadata || { title: '', description: '' },
251
+ };
252
+ });
253
+ }
254
+ filterAndCleanEndpoints(endpoints) {
255
+ const seen = new Map();
256
+ for (const endpoint of endpoints) {
257
+ const key = `${endpoint.method}:${endpoint.path}`;
258
+ if (endpoint.path === '/' && endpoint.group === 'Root') {
259
+ continue;
260
+ }
261
+ if (!seen.has(key)) {
262
+ seen.set(key, endpoint);
263
+ }
264
+ }
265
+ return Array.from(seen.values());
266
+ }
267
+ detectBaseUrl(endpoints) {
268
+ if (endpoints.length === 0)
269
+ return 'https://api.example.com';
270
+ const paths = endpoints.map(e => e.path).filter(p => p && p !== '/');
271
+ if (paths.length === 0)
272
+ return 'https://api.example.com';
273
+ const firstPath = paths[0];
274
+ let commonPrefix = '';
275
+ for (let i = 0; i < firstPath.length; i++) {
276
+ const char = firstPath[i];
277
+ if (paths.every(p => p[i] === char)) {
278
+ commonPrefix += char;
279
+ }
280
+ else {
281
+ break;
282
+ }
283
+ }
284
+ const lastSlash = commonPrefix.lastIndexOf('/');
285
+ if (lastSlash > 0) {
286
+ return `https://api.example.com${commonPrefix.substring(0, lastSlash + 1)}`;
287
+ }
288
+ return 'https://api.example.com';
289
+ }
290
+ detectLanguage(files) {
291
+ const extensions = new Map();
292
+ for (const file of files) {
293
+ const ext = path.extname(file).toLowerCase();
294
+ const langMap = {
295
+ '.ts': 'TypeScript',
296
+ '.js': 'JavaScript',
297
+ '.py': 'Python',
298
+ '.java': 'Java',
299
+ '.go': 'Go',
300
+ '.rs': 'Rust',
301
+ '.php': 'PHP',
302
+ '.rb': 'Ruby',
303
+ };
304
+ if (langMap[ext]) {
305
+ extensions.set(langMap[ext], (extensions.get(langMap[ext]) || 0) + 1);
306
+ }
307
+ }
308
+ if (extensions.size === 0)
309
+ return 'Unknown';
310
+ const sorted = Array.from(extensions.entries()).sort((a, b) => b[1] - a[1]);
311
+ return sorted[0][0];
312
+ }
313
+ detectFramework(files, endpoints) {
314
+ const fileNames = files.map(f => path.basename(f).toLowerCase());
315
+ const allFiles = files.join(' ');
316
+ if (fileNames.some(f => f.includes('express')) || allFiles.includes('express')) {
317
+ return 'express';
318
+ }
319
+ if (fileNames.some(f => f.includes('fastify')) || allFiles.includes('fastify')) {
320
+ return 'fastify';
321
+ }
322
+ if (fileNames.some(f => f.includes('koa')) || allFiles.includes('koa')) {
323
+ return 'koa';
324
+ }
325
+ if (fileNames.some(f => f.includes('django')) || allFiles.includes('django')) {
326
+ return 'django';
327
+ }
328
+ if (fileNames.some(f => f.includes('flask')) || allFiles.includes('flask')) {
329
+ return 'flask';
330
+ }
331
+ if (fileNames.some(f => f.includes('spring')) || allFiles.includes('spring')) {
332
+ return 'spring';
333
+ }
334
+ return 'unknown';
335
+ }
199
336
  buildAnalysisPrompt(files) {
337
+ if (files.length === 0) {
338
+ throw new Error('No project files found to analyze');
339
+ }
200
340
  const filesContext = files.map(f => `// File: ${f.path}\n${f.content}`).join('\n\n---\n\n');
201
- return `You are an expert API documentation analyzer. Your task is to analyze ANY programming language codebase and extract ALL API endpoints with COMPLETE information.
341
+ return `You are an expert API documentation analyzer. Your task is to analyze the provided codebase files and extract ALL API endpoints with COMPLETE information.
202
342
 
203
- PROJECT FILES:
343
+ IMPORTANT: Below are the ACTUAL SOURCE CODE FILES from the user's project. Analyze these files to find all API endpoints, routes, handlers, parameters, request bodies, responses, and authentication requirements.
344
+
345
+ PROJECT FILES TO ANALYZE (${files.length} file${files.length > 1 ? 's' : ''}):
204
346
  ${filesContext}
205
347
 
348
+ ---
349
+
206
350
  CRITICAL INSTRUCTIONS:
207
351
  1. Analyze the ENTIRE codebase structure to understand how routes are organized
208
352
  2. Follow ALL route prefixes and middleware chains (router.use, app.use, etc.)
@@ -219,21 +363,21 @@ For EACH endpoint found, you MUST extract:
219
363
  2. FULL PATH: Complete path including all prefixes
220
364
  - Follow router.use('/prefix', router) chains
221
365
  - Combine prefixes correctly
222
- - Example: router.use('/api/instagram', instagramRoutes) + router.get('/auth', ...) = /api/instagram/auth
366
+ - Example: router.use('/api/users', userRoutes) + router.get('/auth', ...) = /api/users/auth
223
367
 
224
- 3. GROUP NAME: Based on the RESOURCE, not controller
225
- - /api/instagram/auth → "Auth" (NOT "Instagram")
226
- - /api/instagram/auth/callback → "Auth" (same group as /auth)
227
- - /api/instagram/posts/:userId → "Posts" (NOT "Instagram")
228
- - Group related endpoints together (auth + auth/callback)
229
- - DO NOT create a "Root" group unless absolutely necessary
230
- - Skip root routes (path: "/") that are just status/health checks
368
+ 3. GROUP NAME: Based on the RESOURCE, not controller/service name
369
+ - /api/users/auth → "Auth" (NOT "Users")
370
+ - /api/users/auth/callback → "Auth" (same group as /auth)
371
+ - /api/users/posts/:userId → "Posts" (NOT "Users")
372
+ - Group related endpoints together (auth + auth/callback belong in same group)
231
373
 
232
374
  4. PARAMETERS: Extract ALL parameters from:
233
375
  - Path parameters: /users/:id → {name: "id", in: "path"}
234
376
  - Query parameters: req.query.page → {name: "page", in: "query"}
235
377
  - Body parameters: req.body.email, const {email, password} = req.body → {name: "email", in: "body"}
236
378
  - Header parameters: req.headers.authorization → {name: "Authorization", in: "header"}
379
+ - IMPORTANT: HTTP headers are case-insensitive (X-API-Key, x-api-key, X-Api-Key are all the same)
380
+ - DO NOT duplicate headers if you see different casings - use only ONE entry with the most common casing
237
381
 
238
382
  5. REQUEST BODY: Complete structure if POST/PUT/PATCH
239
383
  - Extract all fields from req.body or request.body
@@ -254,10 +398,16 @@ For EACH endpoint found, you MUST extract:
254
398
  8. HEADERS: Extract required headers
255
399
  - From req.headers.fieldName or request.headers.fieldName
256
400
  - Include Authorization if authentication is required
401
+ - CRITICAL: HTTP headers are case-insensitive - X-API-Key, x-api-key, X-Api-Key are THE SAME header
402
+ - If you see the same header with different casings, include it ONLY ONCE (use the most common casing)
403
+ - DO NOT create duplicate header entries for the same header with different casings
257
404
 
258
405
  9. HANDLER INFO: Function name, async status, parameters
259
406
 
260
- Return a JSON object with this EXACT structure:
407
+ OUTPUT FORMAT - YOU MUST RETURN EXACTLY THIS STRUCTURE:
408
+
409
+ Return a JSON object with this EXACT structure. DO NOT wrap it in markdown code blocks, DO NOT add explanations, DO NOT add any text before or after. Return ONLY the raw JSON:
410
+
261
411
  {
262
412
  "endpoints": [
263
413
  {
@@ -315,7 +465,7 @@ Return a JSON object with this EXACT structure:
315
465
  ],
316
466
  "authentication": {
317
467
  "required": true,
318
- "type": "bearer|basic|api-key|custom",
468
+ "type": "bearer|basic|api-key|custom|none",
319
469
  "description": "Authentication description"
320
470
  },
321
471
  "headers": [
@@ -335,168 +485,29 @@ Return a JSON object with this EXACT structure:
335
485
  ]
336
486
  }
337
487
 
338
- CRITICAL:
339
- - Return ONLY valid JSON, no markdown, no code blocks, no explanations
340
- - Extract EVERYTHING from the code - be thorough
341
- - Group by resource name, not controller name
342
- - Include ALL responses found in the code
343
- - Schema should be direct object, not wrapped in {type: "object", properties: {...}}
344
- - DO NOT create duplicate endpoints
345
- - DO NOT include root routes (path: "/") unless absolutely necessary`;
346
- }
347
- filterAndCleanEndpoints(endpoints) {
348
- // Remove duplicates based on method + path
349
- const seen = new Set();
350
- const filtered = [];
351
- for (const endpoint of endpoints) {
352
- const key = `${endpoint.method}:${endpoint.path}`;
353
- // Skip duplicates
354
- if (seen.has(key)) {
355
- continue;
356
- }
357
- // Skip root routes unless they have meaningful content
358
- if (endpoint.path === '/' && (!endpoint.metadata?.title || endpoint.metadata.title === 'API Root' || endpoint.metadata.title === 'GET /')) {
359
- // Only skip if it's a simple root route without meaningful content
360
- if (!endpoint.parameters.length && (!endpoint.responses || endpoint.responses.length === 0)) {
361
- continue;
362
- }
363
- }
364
- seen.add(key);
365
- filtered.push(endpoint);
366
- }
367
- return filtered;
368
- }
369
- detectBaseUrl(endpoints) {
370
- if (endpoints.length === 0)
371
- return undefined;
372
- // Find common prefix in all paths (excluding root)
373
- const paths = endpoints.map(ep => ep.path).filter(p => p !== '/' && p.length > 1);
374
- if (paths.length === 0)
375
- return undefined;
376
- // Find longest common prefix
377
- let commonPrefix = paths[0];
378
- for (let i = 1; i < paths.length; i++) {
379
- const path = paths[i];
380
- let j = 0;
381
- while (j < commonPrefix.length && j < path.length && commonPrefix[j] === path[j]) {
382
- j++;
383
- }
384
- commonPrefix = commonPrefix.substring(0, j);
385
- }
386
- // Extract base URL (e.g., /api/instagram from /api/instagram/auth)
387
- if (commonPrefix && commonPrefix.length > 1) {
388
- // Find complete segments
389
- const segments = commonPrefix.split('/').filter(s => s);
390
- if (segments.length >= 2) {
391
- // Take first 2 segments as base (e.g., /api/instagram)
392
- const basePath = '/' + segments.slice(0, 2).join('/');
393
- // Remove base path from endpoint paths
394
- endpoints.forEach(ep => {
395
- if (ep.path.startsWith(basePath)) {
396
- ep.path = ep.path.substring(basePath.length) || '/';
397
- }
398
- });
399
- return `https://api.example.com${basePath}`;
400
- }
401
- else if (segments.length === 1) {
402
- const basePath = '/' + segments[0];
403
- // Remove base path from endpoint paths
404
- endpoints.forEach(ep => {
405
- if (ep.path.startsWith(basePath)) {
406
- ep.path = ep.path.substring(basePath.length) || '/';
407
- }
408
- });
409
- return `https://api.example.com${basePath}`;
410
- }
411
- }
412
- return undefined;
413
- }
414
- detectLanguage(files) {
415
- const extensions = new Map();
416
- for (const file of files) {
417
- const ext = path.extname(file).toLowerCase();
418
- if (ext) {
419
- extensions.set(ext, (extensions.get(ext) || 0) + 1);
420
- }
421
- }
422
- // Find most common extension
423
- let maxCount = 0;
424
- let mostCommon = 'unknown';
425
- for (const [ext, count] of extensions.entries()) {
426
- if (count > maxCount) {
427
- maxCount = count;
428
- mostCommon = ext;
429
- }
430
- }
431
- const languageMap = {
432
- '.ts': 'TypeScript',
433
- '.js': 'JavaScript',
434
- '.tsx': 'TypeScript React',
435
- '.jsx': 'JavaScript React',
436
- '.py': 'Python',
437
- '.java': 'Java',
438
- '.go': 'Go',
439
- '.rs': 'Rust',
440
- '.php': 'PHP',
441
- '.rb': 'Ruby',
442
- };
443
- return languageMap[mostCommon] || 'unknown';
444
- }
445
- detectFramework(endpoints, language) {
446
- // Simple detection based on common patterns
447
- if (language.includes('TypeScript') || language.includes('JavaScript')) {
448
- return 'express'; // Default for Node.js
449
- }
450
- return 'unknown';
451
- }
452
- calculateStats(endpoints) {
453
- const byMethod = {};
454
- endpoints.forEach(endpoint => {
455
- byMethod[endpoint.method] = (byMethod[endpoint.method] || 0) + 1;
456
- });
457
- return {
458
- totalEndpoints: endpoints.length,
459
- byMethod,
460
- };
461
- }
462
- extractProjectName() {
463
- try {
464
- const packageJsonPath = path.join(this.projectRoot, 'package.json');
465
- if (fs.existsSync(packageJsonPath)) {
466
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
467
- return packageJson.name;
468
- }
469
- }
470
- catch (error) {
471
- // Ignore
472
- }
473
- return undefined;
474
- }
475
- extractProjectVersion() {
476
- try {
477
- const packageJsonPath = path.join(this.projectRoot, 'package.json');
478
- if (fs.existsSync(packageJsonPath)) {
479
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
480
- return packageJson.version;
481
- }
482
- }
483
- catch (error) {
484
- // Ignore
485
- }
486
- return undefined;
487
- }
488
- extractProjectDescription() {
489
- try {
490
- const packageJsonPath = path.join(this.projectRoot, 'package.json');
491
- if (fs.existsSync(packageJsonPath)) {
492
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
493
- return packageJson.description;
494
- }
495
- }
496
- catch (error) {
497
- // Ignore
498
- }
499
- return undefined;
488
+ CRITICAL OUTPUT REQUIREMENTS:
489
+ 1. Return ONLY the raw JSON object, starting with { and ending with }
490
+ 2. DO NOT wrap in markdown code blocks (no triple backticks)
491
+ 3. DO NOT add any text before or after the JSON
492
+ 4. DO NOT add explanations, comments, or any other text
493
+ 5. The root object MUST have an "endpoints" key containing an array
494
+ 6. If no endpoints are found, return: { "endpoints": [] }
495
+ 7. The JSON MUST be valid and parseable
496
+ 8. Extract EVERYTHING from the code - be thorough
497
+ 9. Group by resource name, not controller name
498
+ 10. Include ALL responses found in the code
499
+ 11. Schema should be direct object, not wrapped in {type: "object", properties: {...}}
500
+
501
+ EXAMPLE OF CORRECT OUTPUT:
502
+ {"endpoints":[{"method":"GET","path":"/api/users","group":"Users","file":"src/routes/users.ts","line":10,"handler":{"name":"getUsers","isAsync":true,"parameters":["req","res"]},"parameters":[],"requestBody":{"required":false,"fields":[]},"responses":[{"statusCode":200,"type":"application/json","description":"List of users","schema":{"users":{"type":"array"}}}],"authentication":{"required":false,"type":"none"},"headers":[],"metadata":{"title":"Get Users","description":"Retrieve all users"}}]}
503
+
504
+ DO NOT return this format (WRONG):
505
+ Markdown code blocks with triple backticks
506
+ DO NOT return this format (WRONG):
507
+ Text explanations before or after the JSON
508
+
509
+ Return ONLY this format (CORRECT):
510
+ { "endpoints": [...] }`;
500
511
  }
501
512
  }
502
513
  exports.AIAnalyzer = AIAnalyzer;
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import 'dotenv/config';
2
+ export {};