zero-doc 1.0.0
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.
- package/dist/ai-enricher.d.ts +15 -0
- package/dist/ai-enricher.js +103 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +312 -0
- package/dist/gemini-enricher.d.ts +14 -0
- package/dist/gemini-enricher.js +96 -0
- package/dist/generator.d.ts +22 -0
- package/dist/generator.js +113 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +26 -0
- package/dist/parser.d.ts +14 -0
- package/dist/parser.js +176 -0
- package/dist/types.d.ts +51 -0
- package/dist/types.js +2 -0
- package/package.json +57 -0
- package/scripts/copy-viewer.js +30 -0
- package/viewer/README.md +41 -0
- package/viewer/index.html +14 -0
- package/viewer/package.json +26 -0
- package/viewer/postcss.config.js +6 -0
- package/viewer/public/api-inventory.json +151 -0
- package/viewer/src/App.tsx +79 -0
- package/viewer/src/components/CodeSnippet.tsx +136 -0
- package/viewer/src/components/EndpointView.tsx +106 -0
- package/viewer/src/components/ParameterTable.tsx +71 -0
- package/viewer/src/components/Sidebar.tsx +244 -0
- package/viewer/src/contexts/ThemeContext.tsx +62 -0
- package/viewer/src/index.css +32 -0
- package/viewer/src/main.tsx +11 -0
- package/viewer/src/types.ts +54 -0
- package/viewer/tailwind.config.js +13 -0
- package/viewer/tsconfig.json +22 -0
- package/viewer/tsconfig.node.json +11 -0
- package/viewer/vite.config.ts +11 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { RouteEndpoint } from './types';
|
|
2
|
+
export interface AIEnrichmentOptions {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
model?: string;
|
|
5
|
+
batchSize?: number;
|
|
6
|
+
}
|
|
7
|
+
export declare class AIEnricher {
|
|
8
|
+
private openai;
|
|
9
|
+
private model;
|
|
10
|
+
constructor(options: AIEnrichmentOptions);
|
|
11
|
+
enrichEndpoint(endpoint: RouteEndpoint): Promise<RouteEndpoint>;
|
|
12
|
+
enrichEndpoints(endpoints: RouteEndpoint[]): Promise<RouteEndpoint[]>;
|
|
13
|
+
private buildPrompt;
|
|
14
|
+
private applyEnrichment;
|
|
15
|
+
}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.AIEnricher = void 0;
|
|
7
|
+
const openai_1 = __importDefault(require("openai"));
|
|
8
|
+
class AIEnricher {
|
|
9
|
+
constructor(options) {
|
|
10
|
+
this.openai = new openai_1.default({
|
|
11
|
+
apiKey: options.apiKey,
|
|
12
|
+
});
|
|
13
|
+
this.model = options.model || 'gpt-4o-mini';
|
|
14
|
+
}
|
|
15
|
+
async enrichEndpoint(endpoint) {
|
|
16
|
+
const prompt = this.buildPrompt(endpoint);
|
|
17
|
+
try {
|
|
18
|
+
const response = await this.openai.chat.completions.create({
|
|
19
|
+
model: this.model,
|
|
20
|
+
messages: [
|
|
21
|
+
{
|
|
22
|
+
role: 'system',
|
|
23
|
+
content: 'You are an API documentation expert. Analyze the endpoint and provide structured information in JSON format.',
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
role: 'user',
|
|
27
|
+
content: prompt,
|
|
28
|
+
},
|
|
29
|
+
],
|
|
30
|
+
response_format: { type: 'json_object' },
|
|
31
|
+
temperature: 0.3,
|
|
32
|
+
});
|
|
33
|
+
const result = JSON.parse(response.choices[0].message.content || '{}');
|
|
34
|
+
return this.applyEnrichment(endpoint, result);
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
console.error(`Failed to enrich ${endpoint.method} ${endpoint.path}:`, error);
|
|
38
|
+
return endpoint;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
async enrichEndpoints(endpoints) {
|
|
42
|
+
console.log(`🤖 Enriching ${endpoints.length} endpoints with AI...\n`);
|
|
43
|
+
const enriched = [];
|
|
44
|
+
for (const endpoint of endpoints) {
|
|
45
|
+
console.log(` Processing: ${endpoint.method} ${endpoint.path}`);
|
|
46
|
+
const result = await this.enrichEndpoint(endpoint);
|
|
47
|
+
enriched.push(result);
|
|
48
|
+
}
|
|
49
|
+
console.log('\n✨ AI enrichment complete!\n');
|
|
50
|
+
return enriched;
|
|
51
|
+
}
|
|
52
|
+
buildPrompt(endpoint) {
|
|
53
|
+
return `Analyze this API endpoint and provide enrichment data:
|
|
54
|
+
|
|
55
|
+
METHOD: ${endpoint.method}
|
|
56
|
+
PATH: ${endpoint.path}
|
|
57
|
+
ASYNC: ${endpoint.handler.isAsync}
|
|
58
|
+
PARAMETERS: ${JSON.stringify(endpoint.parameters, null, 2)}
|
|
59
|
+
|
|
60
|
+
Provide a JSON response with:
|
|
61
|
+
{
|
|
62
|
+
"title": "A friendly title (e.g., 'Create New User')",
|
|
63
|
+
"description": "Technical description explaining what this endpoint does",
|
|
64
|
+
"parameters": [
|
|
65
|
+
{
|
|
66
|
+
"name": "parameter name",
|
|
67
|
+
"inferredType": "string|number|boolean|object|array",
|
|
68
|
+
"description": "what this parameter is for"
|
|
69
|
+
}
|
|
70
|
+
]
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
Rules:
|
|
74
|
+
- Title should be action-oriented and concise (2-4 words)
|
|
75
|
+
- Description should explain the business logic
|
|
76
|
+
- Infer types based on parameter names (e.g., 'email' is string, 'page' is number, 'isActive' is boolean)
|
|
77
|
+
- Provide practical descriptions for each parameter`;
|
|
78
|
+
}
|
|
79
|
+
applyEnrichment(endpoint, aiResult) {
|
|
80
|
+
const enriched = { ...endpoint };
|
|
81
|
+
enriched.metadata = {
|
|
82
|
+
...enriched.metadata,
|
|
83
|
+
title: aiResult.title || endpoint.metadata?.title,
|
|
84
|
+
description: aiResult.description || endpoint.metadata?.description,
|
|
85
|
+
aiGenerated: true,
|
|
86
|
+
};
|
|
87
|
+
if (aiResult.parameters && Array.isArray(aiResult.parameters)) {
|
|
88
|
+
enriched.parameters = enriched.parameters.map((param) => {
|
|
89
|
+
const aiParam = aiResult.parameters.find((p) => p.name === param.name);
|
|
90
|
+
if (aiParam) {
|
|
91
|
+
return {
|
|
92
|
+
...param,
|
|
93
|
+
inferredType: aiParam.inferredType || param.inferredType,
|
|
94
|
+
description: aiParam.description || param.description,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
return param;
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
return enriched;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
exports.AIEnricher = AIEnricher;
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,312 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
require("dotenv/config");
|
|
38
|
+
const commander_1 = require("commander");
|
|
39
|
+
const generator_1 = require("./generator");
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const child_process_1 = require("child_process");
|
|
43
|
+
const tmp = __importStar(require("tmp"));
|
|
44
|
+
const fse = __importStar(require("fs-extra"));
|
|
45
|
+
commander_1.program
|
|
46
|
+
.name('zero-doc')
|
|
47
|
+
.description('Zero-Config API Documentation Generator')
|
|
48
|
+
.version('1.0.0');
|
|
49
|
+
// Init command
|
|
50
|
+
commander_1.program
|
|
51
|
+
.command('init')
|
|
52
|
+
.description('Initialize zero-doc in your project')
|
|
53
|
+
.action(() => {
|
|
54
|
+
const config = {
|
|
55
|
+
input: 'src/**/*.ts',
|
|
56
|
+
output: 'api-inventory.json',
|
|
57
|
+
tsconfig: 'tsconfig.json',
|
|
58
|
+
ai: {
|
|
59
|
+
enabled: false,
|
|
60
|
+
provider: 'gemini',
|
|
61
|
+
model: 'gemini-2.5-flash'
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
const configPath = path.join(process.cwd(), 'zero-doc.config.json');
|
|
65
|
+
if (fs.existsSync(configPath)) {
|
|
66
|
+
console.log('⚠️ zero-doc.config.json already exists');
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
70
|
+
console.log('✅ Created zero-doc.config.json');
|
|
71
|
+
console.log('💡 Edit the config file and run: zero-doc generate');
|
|
72
|
+
});
|
|
73
|
+
// Generate command
|
|
74
|
+
commander_1.program
|
|
75
|
+
.command('generate')
|
|
76
|
+
.description('Generate API inventory from project')
|
|
77
|
+
.option('-i, --input <pattern>', 'Source files glob pattern')
|
|
78
|
+
.option('-o, --output <path>', 'Output file path')
|
|
79
|
+
.option('-c, --tsconfig <path>', 'Path to tsconfig.json')
|
|
80
|
+
.option('-n, --name <name>', 'Project name')
|
|
81
|
+
.option('--ai', 'Enable AI enrichment')
|
|
82
|
+
.option('--ai-provider <provider>', 'AI provider: openai or gemini', 'gemini')
|
|
83
|
+
.option('--ai-model <model>', 'AI model: gemini-2.5-flash (fast) or gemini-2.5-pro (smart)', 'gemini-2.5-flash')
|
|
84
|
+
.action(async (options) => {
|
|
85
|
+
console.log('🔍 Analyzing code...\n');
|
|
86
|
+
// Load config if exists
|
|
87
|
+
const configPath = path.join(process.cwd(), 'zero-doc.config.json');
|
|
88
|
+
let config = {};
|
|
89
|
+
if (fs.existsSync(configPath)) {
|
|
90
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
91
|
+
}
|
|
92
|
+
try {
|
|
93
|
+
let aiApiKey;
|
|
94
|
+
const aiEnabled = options.ai !== undefined ? options.ai : config.ai?.enabled;
|
|
95
|
+
if (aiEnabled) {
|
|
96
|
+
const provider = options.aiProvider || config.ai?.provider || 'gemini';
|
|
97
|
+
if (provider === 'gemini') {
|
|
98
|
+
aiApiKey = process.env.GEMINI_API_KEY;
|
|
99
|
+
if (!aiApiKey) {
|
|
100
|
+
console.error('❌ Error: --ai with gemini requires GEMINI_API_KEY environment variable');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
aiApiKey = process.env.OPENAI_API_KEY;
|
|
106
|
+
if (!aiApiKey) {
|
|
107
|
+
console.error('❌ Error: --ai with openai requires OPENAI_API_KEY environment variable');
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const generator = new generator_1.APIInventoryGenerator({
|
|
113
|
+
tsConfigPath: options.tsconfig || config.tsconfig,
|
|
114
|
+
projectName: options.name || config.name,
|
|
115
|
+
aiEnrichment: aiEnabled ? {
|
|
116
|
+
enabled: true,
|
|
117
|
+
apiKey: aiApiKey,
|
|
118
|
+
model: options.aiModel || config.ai?.model || 'gemini-2.5-flash',
|
|
119
|
+
provider: options.aiProvider || config.ai?.provider || 'gemini',
|
|
120
|
+
} : undefined,
|
|
121
|
+
});
|
|
122
|
+
const inputPattern = options.input || config.input || 'src/**/*.ts';
|
|
123
|
+
generator.addSourceFiles(inputPattern);
|
|
124
|
+
const inventory = await generator.generate();
|
|
125
|
+
const outputPath = options.output || config.output || 'api-inventory.json';
|
|
126
|
+
await generator.saveToFile(outputPath);
|
|
127
|
+
console.log('✅ Analysis complete!\n');
|
|
128
|
+
console.log(`📊 Statistics:`);
|
|
129
|
+
console.log(` Total endpoints: ${inventory.stats.totalEndpoints}`);
|
|
130
|
+
console.log(` Framework: ${inventory.project.framework}`);
|
|
131
|
+
if (aiEnabled) {
|
|
132
|
+
console.log(` AI Provider: ${options.aiProvider || config.ai?.provider || 'gemini'}`);
|
|
133
|
+
console.log(` AI Model: ${options.aiModel || config.ai?.model || 'gemini-2.5-flash'}`);
|
|
134
|
+
}
|
|
135
|
+
console.log('');
|
|
136
|
+
Object.entries(inventory.stats.byMethod).forEach(([method, count]) => {
|
|
137
|
+
console.log(` ${method}: ${count}`);
|
|
138
|
+
});
|
|
139
|
+
console.log(`\n💾 File generated: ${outputPath}`);
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
console.error('❌ Error:', error instanceof Error ? error.message : error);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
// Preview command
|
|
147
|
+
commander_1.program
|
|
148
|
+
.command('preview')
|
|
149
|
+
.description('Generate docs and start local preview server')
|
|
150
|
+
.option('-i, --input <pattern>', 'Source files glob pattern')
|
|
151
|
+
.option('-c, --tsconfig <path>', 'Path to tsconfig.json')
|
|
152
|
+
.option('-n, --name <name>', 'Project name')
|
|
153
|
+
.option('--ai', 'Enable AI enrichment')
|
|
154
|
+
.option('--ai-provider <provider>', 'AI provider: openai or gemini', 'gemini')
|
|
155
|
+
.option('--ai-model <model>', 'AI model', 'gemini-2.5-flash')
|
|
156
|
+
.action(async (options) => {
|
|
157
|
+
console.log('🚀 Starting preview...\n');
|
|
158
|
+
// Load config if exists
|
|
159
|
+
const configPath = path.join(process.cwd(), 'zero-doc.config.json');
|
|
160
|
+
let config = {};
|
|
161
|
+
if (fs.existsSync(configPath)) {
|
|
162
|
+
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
163
|
+
}
|
|
164
|
+
try {
|
|
165
|
+
// Step 1: Generate API inventory
|
|
166
|
+
console.log('🔍 Analyzing code...\n');
|
|
167
|
+
let aiApiKey;
|
|
168
|
+
const aiEnabled = options.ai !== undefined ? options.ai : config.ai?.enabled;
|
|
169
|
+
if (aiEnabled) {
|
|
170
|
+
const provider = options.aiProvider || config.ai?.provider || 'gemini';
|
|
171
|
+
if (provider === 'gemini') {
|
|
172
|
+
aiApiKey = process.env.GEMINI_API_KEY;
|
|
173
|
+
if (!aiApiKey) {
|
|
174
|
+
console.error('❌ Error: --ai with gemini requires GEMINI_API_KEY environment variable');
|
|
175
|
+
process.exit(1);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
aiApiKey = process.env.OPENAI_API_KEY;
|
|
180
|
+
if (!aiApiKey) {
|
|
181
|
+
console.error('❌ Error: --ai with openai requires OPENAI_API_KEY environment variable');
|
|
182
|
+
process.exit(1);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const generator = new generator_1.APIInventoryGenerator({
|
|
187
|
+
tsConfigPath: options.tsconfig || config.tsconfig,
|
|
188
|
+
projectName: options.name || config.name,
|
|
189
|
+
aiEnrichment: aiEnabled ? {
|
|
190
|
+
enabled: true,
|
|
191
|
+
apiKey: aiApiKey,
|
|
192
|
+
model: options.aiModel || config.ai?.model || 'gemini-2.5-flash',
|
|
193
|
+
provider: options.aiProvider || config.ai?.provider || 'gemini',
|
|
194
|
+
} : undefined,
|
|
195
|
+
});
|
|
196
|
+
const inputPattern = options.input || config.input || 'src/**/*.ts';
|
|
197
|
+
generator.addSourceFiles(inputPattern);
|
|
198
|
+
const inventory = await generator.generate();
|
|
199
|
+
console.log('✅ Analysis complete!\n');
|
|
200
|
+
console.log(`📊 Found ${inventory.stats.totalEndpoints} endpoints\n`);
|
|
201
|
+
// Step 2: Create temporary directory
|
|
202
|
+
const tmpDir = tmp.dirSync({
|
|
203
|
+
prefix: 'zero-doc-',
|
|
204
|
+
unsafeCleanup: true
|
|
205
|
+
});
|
|
206
|
+
const tempViewerPath = path.join(tmpDir.name, 'viewer');
|
|
207
|
+
console.log('📦 Setting up temporary viewer...\n');
|
|
208
|
+
// Step 3: Find viewer source (works in monorepo or when installed)
|
|
209
|
+
let viewerSourcePath;
|
|
210
|
+
// Try installed package path first (when installed globally)
|
|
211
|
+
const installedViewerPath = path.join(__dirname, '../viewer');
|
|
212
|
+
if (fs.existsSync(installedViewerPath)) {
|
|
213
|
+
viewerSourcePath = installedViewerPath;
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
// Try monorepo path (development)
|
|
217
|
+
const monorepoViewerPath = path.join(__dirname, '../../apps/viewer');
|
|
218
|
+
if (fs.existsSync(monorepoViewerPath)) {
|
|
219
|
+
viewerSourcePath = monorepoViewerPath;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
console.error('❌ Error: Viewer app not found');
|
|
223
|
+
console.log('💡 Make sure you are in the monorepo or have installed zero-doc globally');
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// Step 4: Copy viewer files to temp directory
|
|
228
|
+
await fse.copy(viewerSourcePath, tempViewerPath, {
|
|
229
|
+
filter: (src) => {
|
|
230
|
+
// Skip node_modules, dist, and other build artifacts
|
|
231
|
+
const relativePath = path.relative(viewerSourcePath, src);
|
|
232
|
+
return !relativePath.includes('node_modules') &&
|
|
233
|
+
!relativePath.includes('dist') &&
|
|
234
|
+
!relativePath.startsWith('.git');
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
// Step 5: Copy generated JSON to temp viewer public folder
|
|
238
|
+
const tempPublicPath = path.join(tempViewerPath, 'public');
|
|
239
|
+
if (!fs.existsSync(tempPublicPath)) {
|
|
240
|
+
fs.mkdirSync(tempPublicPath, { recursive: true });
|
|
241
|
+
}
|
|
242
|
+
const jsonContent = JSON.stringify(inventory, null, 2);
|
|
243
|
+
fs.writeFileSync(path.join(tempPublicPath, 'api-inventory.json'), jsonContent, 'utf-8');
|
|
244
|
+
// Step 6: Install dependencies in temp directory
|
|
245
|
+
console.log('📥 Installing dependencies...\n');
|
|
246
|
+
const installProcess = (0, child_process_1.spawn)('npm', ['install'], {
|
|
247
|
+
cwd: tempViewerPath,
|
|
248
|
+
stdio: 'inherit',
|
|
249
|
+
shell: true
|
|
250
|
+
});
|
|
251
|
+
await new Promise((resolve, reject) => {
|
|
252
|
+
installProcess.on('close', (code) => {
|
|
253
|
+
if (code === 0) {
|
|
254
|
+
resolve();
|
|
255
|
+
}
|
|
256
|
+
else {
|
|
257
|
+
reject(new Error(`npm install failed with code ${code}`));
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
installProcess.on('error', reject);
|
|
261
|
+
});
|
|
262
|
+
// Step 7: Start Vite dev server
|
|
263
|
+
console.log('\n🚀 Starting preview server...\n');
|
|
264
|
+
console.log('📖 Documentation available at: http://localhost:5173\n');
|
|
265
|
+
console.log('💡 Press Ctrl+C to stop and clean up\n');
|
|
266
|
+
const vite = (0, child_process_1.spawn)('npm', ['run', 'dev'], {
|
|
267
|
+
cwd: tempViewerPath,
|
|
268
|
+
stdio: 'inherit',
|
|
269
|
+
shell: true
|
|
270
|
+
});
|
|
271
|
+
// Cleanup on exit
|
|
272
|
+
const cleanup = () => {
|
|
273
|
+
console.log('\n\n🧹 Cleaning up temporary files...');
|
|
274
|
+
tmpDir.removeCallback();
|
|
275
|
+
process.exit(0);
|
|
276
|
+
};
|
|
277
|
+
process.on('SIGINT', cleanup);
|
|
278
|
+
process.on('SIGTERM', cleanup);
|
|
279
|
+
vite.on('error', (error) => {
|
|
280
|
+
console.error('❌ Error starting preview server:', error);
|
|
281
|
+
cleanup();
|
|
282
|
+
});
|
|
283
|
+
vite.on('close', (code) => {
|
|
284
|
+
cleanup();
|
|
285
|
+
});
|
|
286
|
+
}
|
|
287
|
+
catch (error) {
|
|
288
|
+
console.error('❌ Error:', error instanceof Error ? error.message : error);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
// Deploy command (placeholder)
|
|
293
|
+
commander_1.program
|
|
294
|
+
.command('deploy')
|
|
295
|
+
.description('Deploy documentation to zero-doc cloud')
|
|
296
|
+
.option('--token <token>', 'API token for authentication')
|
|
297
|
+
.action(async (options) => {
|
|
298
|
+
const inventoryPath = path.join(process.cwd(), 'api-inventory.json');
|
|
299
|
+
if (!fs.existsSync(inventoryPath)) {
|
|
300
|
+
console.error('❌ Error: api-inventory.json not found');
|
|
301
|
+
console.log('💡 Run: zero-doc generate first');
|
|
302
|
+
process.exit(1);
|
|
303
|
+
}
|
|
304
|
+
console.log('🚀 Deploying documentation...\n');
|
|
305
|
+
console.log('⚠️ Deploy feature coming soon!');
|
|
306
|
+
console.log('💡 For now, you can serve the viewer locally with: zero-doc preview');
|
|
307
|
+
// TODO: Implement actual deployment
|
|
308
|
+
// - Upload JSON to backend
|
|
309
|
+
// - Get unique URL
|
|
310
|
+
// - Return docs.zerodoc.com/user/project
|
|
311
|
+
});
|
|
312
|
+
commander_1.program.parse();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { RouteEndpoint } from './types';
|
|
2
|
+
export interface GeminiEnricherOptions {
|
|
3
|
+
apiKey: string;
|
|
4
|
+
model?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class GeminiEnricher {
|
|
7
|
+
private genAI;
|
|
8
|
+
private model;
|
|
9
|
+
constructor(options: GeminiEnricherOptions);
|
|
10
|
+
enrichEndpoint(endpoint: RouteEndpoint): Promise<RouteEndpoint>;
|
|
11
|
+
enrichEndpoints(endpoints: RouteEndpoint[]): Promise<RouteEndpoint[]>;
|
|
12
|
+
private buildPrompt;
|
|
13
|
+
private applyEnrichment;
|
|
14
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.GeminiEnricher = void 0;
|
|
4
|
+
const generative_ai_1 = require("@google/generative-ai");
|
|
5
|
+
class GeminiEnricher {
|
|
6
|
+
constructor(options) {
|
|
7
|
+
this.genAI = new generative_ai_1.GoogleGenerativeAI(options.apiKey);
|
|
8
|
+
// Use Gemini 2.5 Flash by default (fast and free tier friendly)
|
|
9
|
+
// Available models: gemini-2.5-flash, gemini-2.5-pro, gemini-2.0-flash
|
|
10
|
+
const modelName = options.model || 'gemini-2.5-flash';
|
|
11
|
+
this.model = this.genAI.getGenerativeModel({ model: modelName });
|
|
12
|
+
}
|
|
13
|
+
async enrichEndpoint(endpoint) {
|
|
14
|
+
const prompt = this.buildPrompt(endpoint);
|
|
15
|
+
try {
|
|
16
|
+
const result = await this.model.generateContent(prompt);
|
|
17
|
+
const response = await result.response;
|
|
18
|
+
const text = response.text();
|
|
19
|
+
// Parse JSON from response
|
|
20
|
+
const jsonMatch = text.match(/\{[\s\S]*\}/);
|
|
21
|
+
if (!jsonMatch) {
|
|
22
|
+
console.error(`Failed to parse JSON from Gemini response for ${endpoint.method} ${endpoint.path}`);
|
|
23
|
+
return endpoint;
|
|
24
|
+
}
|
|
25
|
+
const aiResult = JSON.parse(jsonMatch[0]);
|
|
26
|
+
return this.applyEnrichment(endpoint, aiResult);
|
|
27
|
+
}
|
|
28
|
+
catch (error) {
|
|
29
|
+
console.error(`Failed to enrich ${endpoint.method} ${endpoint.path}:`, error);
|
|
30
|
+
return endpoint;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
async enrichEndpoints(endpoints) {
|
|
34
|
+
console.log(`🤖 Enriching ${endpoints.length} endpoints with Gemini AI...\n`);
|
|
35
|
+
const enriched = [];
|
|
36
|
+
for (const endpoint of endpoints) {
|
|
37
|
+
console.log(` Processing: ${endpoint.method} ${endpoint.path}`);
|
|
38
|
+
const result = await this.enrichEndpoint(endpoint);
|
|
39
|
+
enriched.push(result);
|
|
40
|
+
}
|
|
41
|
+
console.log('\n✨ AI enrichment complete!\n');
|
|
42
|
+
return enriched;
|
|
43
|
+
}
|
|
44
|
+
buildPrompt(endpoint) {
|
|
45
|
+
return `Analyze this API endpoint and provide enrichment data.
|
|
46
|
+
|
|
47
|
+
METHOD: ${endpoint.method}
|
|
48
|
+
PATH: ${endpoint.path}
|
|
49
|
+
ASYNC: ${endpoint.handler.isAsync}
|
|
50
|
+
PARAMETERS: ${JSON.stringify(endpoint.parameters, null, 2)}
|
|
51
|
+
|
|
52
|
+
Provide a JSON response with this EXACT structure:
|
|
53
|
+
{
|
|
54
|
+
"title": "A friendly title (e.g., 'Create New User')",
|
|
55
|
+
"description": "Technical description explaining what this endpoint does",
|
|
56
|
+
"parameters": [
|
|
57
|
+
{
|
|
58
|
+
"name": "parameter name",
|
|
59
|
+
"inferredType": "string|number|boolean|object|array",
|
|
60
|
+
"description": "what this parameter is for"
|
|
61
|
+
}
|
|
62
|
+
]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
Rules:
|
|
66
|
+
- Title should be action-oriented and concise (2-4 words)
|
|
67
|
+
- Description should explain the business logic
|
|
68
|
+
- Infer types based on parameter names (e.g., 'email' is string, 'page' is number, 'isActive' is boolean)
|
|
69
|
+
- Provide practical descriptions for each parameter
|
|
70
|
+
- Return ONLY valid JSON, no markdown, no extra text`;
|
|
71
|
+
}
|
|
72
|
+
applyEnrichment(endpoint, aiResult) {
|
|
73
|
+
const enriched = { ...endpoint };
|
|
74
|
+
enriched.metadata = {
|
|
75
|
+
...enriched.metadata,
|
|
76
|
+
title: aiResult.title || endpoint.metadata?.title,
|
|
77
|
+
description: aiResult.description || endpoint.metadata?.description,
|
|
78
|
+
aiGenerated: true,
|
|
79
|
+
};
|
|
80
|
+
if (aiResult.parameters && Array.isArray(aiResult.parameters)) {
|
|
81
|
+
enriched.parameters = enriched.parameters.map((param) => {
|
|
82
|
+
const aiParam = aiResult.parameters.find((p) => p.name === param.name);
|
|
83
|
+
if (aiParam) {
|
|
84
|
+
return {
|
|
85
|
+
...param,
|
|
86
|
+
inferredType: aiParam.inferredType || param.inferredType,
|
|
87
|
+
description: aiParam.description || param.description,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
return param;
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
return enriched;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
exports.GeminiEnricher = GeminiEnricher;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { APIInventory } from './types';
|
|
2
|
+
export interface GeneratorOptions {
|
|
3
|
+
tsConfigPath?: string;
|
|
4
|
+
projectName?: string;
|
|
5
|
+
aiEnrichment?: {
|
|
6
|
+
enabled: boolean;
|
|
7
|
+
apiKey: string;
|
|
8
|
+
model?: string;
|
|
9
|
+
provider?: 'openai' | 'gemini';
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export declare class APIInventoryGenerator {
|
|
13
|
+
private parser;
|
|
14
|
+
private projectName?;
|
|
15
|
+
private aiEnricher?;
|
|
16
|
+
constructor(options: GeneratorOptions | string, projectName?: string);
|
|
17
|
+
addSourceFiles(pattern: string): void;
|
|
18
|
+
generate(): Promise<APIInventory>;
|
|
19
|
+
private detectFramework;
|
|
20
|
+
private calculateStats;
|
|
21
|
+
saveToFile(outputPath: string): Promise<void>;
|
|
22
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.APIInventoryGenerator = void 0;
|
|
37
|
+
const parser_1 = require("./parser");
|
|
38
|
+
const ai_enricher_1 = require("./ai-enricher");
|
|
39
|
+
const gemini_enricher_1 = require("./gemini-enricher");
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
class APIInventoryGenerator {
|
|
43
|
+
constructor(options, projectName) {
|
|
44
|
+
if (typeof options === 'string') {
|
|
45
|
+
this.parser = new parser_1.RouteParser(options);
|
|
46
|
+
this.projectName = projectName;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
this.parser = new parser_1.RouteParser(options.tsConfigPath);
|
|
50
|
+
this.projectName = options.projectName;
|
|
51
|
+
if (options.aiEnrichment?.enabled && options.aiEnrichment.apiKey) {
|
|
52
|
+
const provider = options.aiEnrichment.provider || 'gemini';
|
|
53
|
+
if (provider === 'gemini') {
|
|
54
|
+
this.aiEnricher = new gemini_enricher_1.GeminiEnricher({
|
|
55
|
+
apiKey: options.aiEnrichment.apiKey,
|
|
56
|
+
model: options.aiEnrichment.model,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
else {
|
|
60
|
+
this.aiEnricher = new ai_enricher_1.AIEnricher({
|
|
61
|
+
apiKey: options.aiEnrichment.apiKey,
|
|
62
|
+
model: options.aiEnrichment.model,
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
addSourceFiles(pattern) {
|
|
69
|
+
this.parser.addSourceFiles(pattern);
|
|
70
|
+
}
|
|
71
|
+
async generate() {
|
|
72
|
+
let endpoints = this.parser.parse();
|
|
73
|
+
if (this.aiEnricher) {
|
|
74
|
+
endpoints = await this.aiEnricher.enrichEndpoints(endpoints);
|
|
75
|
+
}
|
|
76
|
+
const stats = this.calculateStats(endpoints);
|
|
77
|
+
return {
|
|
78
|
+
version: '1.0.0',
|
|
79
|
+
generatedAt: new Date().toISOString(),
|
|
80
|
+
project: {
|
|
81
|
+
name: this.projectName,
|
|
82
|
+
framework: this.detectFramework(endpoints),
|
|
83
|
+
},
|
|
84
|
+
endpoints,
|
|
85
|
+
stats,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
detectFramework(endpoints) {
|
|
89
|
+
// Simple logic: can be improved by analyzing imports
|
|
90
|
+
return 'express'; // TODO: auto-detect
|
|
91
|
+
}
|
|
92
|
+
calculateStats(endpoints) {
|
|
93
|
+
const byMethod = {};
|
|
94
|
+
endpoints.forEach(endpoint => {
|
|
95
|
+
byMethod[endpoint.method] = (byMethod[endpoint.method] || 0) + 1;
|
|
96
|
+
});
|
|
97
|
+
return {
|
|
98
|
+
totalEndpoints: endpoints.length,
|
|
99
|
+
byMethod,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
async saveToFile(outputPath) {
|
|
103
|
+
const inventory = await this.generate();
|
|
104
|
+
const json = JSON.stringify(inventory, null, 2);
|
|
105
|
+
// Ensure directory exists
|
|
106
|
+
const dir = path.dirname(outputPath);
|
|
107
|
+
if (!fs.existsSync(dir)) {
|
|
108
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
109
|
+
}
|
|
110
|
+
fs.writeFileSync(outputPath, json, 'utf-8');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
exports.APIInventoryGenerator = APIInventoryGenerator;
|