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.
- package/README.md +1 -1
- package/dist/ai-analyzer.d.ts +4 -8
- package/dist/ai-analyzer.js +274 -263
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +86 -151
- package/dist/config.json +1 -1
- package/package.json +4 -1
- package/scripts/inject-config.js +25 -19
- package/viewer/src/App.tsx +8 -45
- package/viewer/src/components/CodeSnippet.tsx +0 -1
- package/viewer/vite.config.ts +17 -1
package/dist/cli.js
CHANGED
|
@@ -34,7 +34,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
34
34
|
};
|
|
35
35
|
})();
|
|
36
36
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
-
require("dotenv/config");
|
|
38
37
|
const commander_1 = require("commander");
|
|
39
38
|
const ai_analyzer_1 = require("./ai-analyzer");
|
|
40
39
|
const fs = __importStar(require("fs"));
|
|
@@ -42,7 +41,6 @@ const path = __importStar(require("path"));
|
|
|
42
41
|
const child_process_1 = require("child_process");
|
|
43
42
|
const tmp = __importStar(require("tmp"));
|
|
44
43
|
const fse = __importStar(require("fs-extra"));
|
|
45
|
-
// Simple progress bar
|
|
46
44
|
function updateProgress(current, total, label = '') {
|
|
47
45
|
const percentage = Math.min(100, Math.round((current / total) * 100));
|
|
48
46
|
const barLength = 30;
|
|
@@ -51,26 +49,24 @@ function updateProgress(current, total, label = '') {
|
|
|
51
49
|
const bar = '█'.repeat(filled) + '░'.repeat(empty);
|
|
52
50
|
process.stdout.write(`\r${label} [${bar}] ${percentage}%`);
|
|
53
51
|
if (percentage === 100) {
|
|
54
|
-
// Clear the progress bar line
|
|
55
52
|
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
|
|
56
53
|
}
|
|
57
54
|
}
|
|
58
|
-
|
|
55
|
+
function clearProgress() {
|
|
56
|
+
process.stdout.write('\r' + ' '.repeat(process.stdout.columns || 80) + '\r');
|
|
57
|
+
}
|
|
59
58
|
function displayUrl(url) {
|
|
60
|
-
// ANSI color codes
|
|
61
59
|
const reset = '\x1b[0m';
|
|
62
60
|
const red = '\x1b[31m';
|
|
63
61
|
const blue = '\x1b[34m';
|
|
64
62
|
const bold = '\x1b[1m';
|
|
65
63
|
const underline = '\x1b[4m';
|
|
66
|
-
// ➜ in red, "Local:" in bold, URL in blue with underline
|
|
67
64
|
process.stdout.write(`${red}➜${reset} ${bold}Local:${reset} ${blue}${underline}${url}${reset}\n`);
|
|
68
65
|
}
|
|
69
66
|
commander_1.program
|
|
70
67
|
.name('zero-doc')
|
|
71
68
|
.description('Zero-Config API Documentation Generator')
|
|
72
69
|
.version('1.0.0');
|
|
73
|
-
// Init command
|
|
74
70
|
commander_1.program
|
|
75
71
|
.command('init')
|
|
76
72
|
.description('Initialize zero-doc in your project')
|
|
@@ -92,7 +88,6 @@ commander_1.program
|
|
|
92
88
|
console.log('✅ Created zero-doc.config.json');
|
|
93
89
|
console.log('💡 Run: zero-doc generate');
|
|
94
90
|
});
|
|
95
|
-
// Generate command
|
|
96
91
|
commander_1.program
|
|
97
92
|
.command('generate')
|
|
98
93
|
.description('Generate API inventory from project using AI')
|
|
@@ -102,41 +97,28 @@ commander_1.program
|
|
|
102
97
|
.option('--ai-provider <provider>', 'AI provider: gemini', 'gemini')
|
|
103
98
|
.action(async (options) => {
|
|
104
99
|
console.log('🤖 Analyzing project with AI...\n');
|
|
105
|
-
// Load config if exists
|
|
106
100
|
const configPath = path.join(process.cwd(), 'zero-doc.config.json');
|
|
107
101
|
let config = {};
|
|
108
102
|
if (fs.existsSync(configPath)) {
|
|
109
103
|
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
110
104
|
}
|
|
111
105
|
try {
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
let aiApiKey;
|
|
116
|
-
const builtInConfigPath = path.join(__dirname, 'config.json');
|
|
117
|
-
if (fs.existsSync(builtInConfigPath)) {
|
|
106
|
+
let generateApiUrl;
|
|
107
|
+
const generateConfigPath = path.join(__dirname, 'config.json');
|
|
108
|
+
if (fs.existsSync(generateConfigPath)) {
|
|
118
109
|
try {
|
|
119
|
-
const
|
|
120
|
-
|
|
110
|
+
const generateConfig = JSON.parse(fs.readFileSync(generateConfigPath, 'utf-8'));
|
|
111
|
+
generateApiUrl = generateConfig.apiUrl;
|
|
121
112
|
}
|
|
122
113
|
catch (error) {
|
|
123
|
-
// Ignore errors reading built-in config
|
|
124
114
|
}
|
|
125
115
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
// 3. Last resort: config file (not recommended, but kept for backward compatibility)
|
|
131
|
-
if (!aiApiKey) {
|
|
132
|
-
aiApiKey = config.ai?.apiKey;
|
|
133
|
-
}
|
|
134
|
-
if (!aiApiKey) {
|
|
135
|
-
console.error('❌ Error: API key not configured');
|
|
136
|
-
console.error('💡 This should not happen. Please contact support.');
|
|
116
|
+
if (!generateApiUrl || generateApiUrl.trim() === '') {
|
|
117
|
+
console.error('❌ Error: API configuration not found');
|
|
118
|
+
console.error('💡 This should not happen. The package should include the configuration.');
|
|
119
|
+
console.error('💡 Please reinstall: npm install -g zero-doc');
|
|
137
120
|
process.exit(1);
|
|
138
121
|
}
|
|
139
|
-
// Read package.json for project info
|
|
140
122
|
let projectName = options.name || config.name;
|
|
141
123
|
let projectVersion;
|
|
142
124
|
let projectDescription;
|
|
@@ -149,31 +131,25 @@ commander_1.program
|
|
|
149
131
|
projectDescription = packageJson.description;
|
|
150
132
|
}
|
|
151
133
|
catch (error) {
|
|
152
|
-
// Ignore errors reading package.json
|
|
153
134
|
}
|
|
154
135
|
}
|
|
155
|
-
// Analyze with AI
|
|
156
136
|
const inputPattern = options.input || config.input || 'src/**/*.{ts,js}';
|
|
157
|
-
// Handle glob patterns with braces - don't split if pattern contains {}
|
|
158
137
|
const patterns = inputPattern.includes('{') && inputPattern.includes('}')
|
|
159
|
-
? [inputPattern]
|
|
160
|
-
: inputPattern.split(',').map(p => p.trim());
|
|
138
|
+
? [inputPattern]
|
|
139
|
+
: inputPattern.split(',').map(p => p.trim());
|
|
161
140
|
console.log(`📋 Using patterns: ${patterns.join(', ')}\n`);
|
|
162
141
|
const analyzer = new ai_analyzer_1.AIAnalyzer({
|
|
163
|
-
|
|
142
|
+
apiUrl: generateApiUrl,
|
|
164
143
|
projectRoot: process.cwd(),
|
|
165
144
|
filePatterns: patterns,
|
|
166
145
|
});
|
|
167
146
|
const inventory = await analyzer.analyzeProject();
|
|
168
|
-
// Set project info
|
|
169
147
|
inventory.project.name = inventory.project.name || projectName;
|
|
170
148
|
inventory.project.version = inventory.project.version || projectVersion;
|
|
171
149
|
inventory.project.description = inventory.project.description || projectDescription;
|
|
172
|
-
// Set baseUrl default
|
|
173
150
|
if (!inventory.baseUrl) {
|
|
174
151
|
inventory.baseUrl = 'https://api.example.com';
|
|
175
152
|
}
|
|
176
|
-
// Save to file
|
|
177
153
|
const outputPath = options.output || config.output || 'api-inventory.json';
|
|
178
154
|
const json = JSON.stringify(inventory, null, 2);
|
|
179
155
|
const dir = path.dirname(outputPath);
|
|
@@ -196,45 +172,89 @@ commander_1.program
|
|
|
196
172
|
process.exit(1);
|
|
197
173
|
}
|
|
198
174
|
});
|
|
199
|
-
// Preview command
|
|
200
175
|
commander_1.program
|
|
201
176
|
.command('preview')
|
|
202
177
|
.description('Generate docs and start local preview server')
|
|
203
178
|
.action(async () => {
|
|
204
|
-
// Load config if exists
|
|
205
179
|
const configPath = path.join(process.cwd(), 'zero-doc.config.json');
|
|
206
180
|
let config = {};
|
|
207
181
|
if (fs.existsSync(configPath)) {
|
|
208
182
|
config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
|
|
209
183
|
}
|
|
184
|
+
let vite = null;
|
|
185
|
+
let tmpDir = null;
|
|
186
|
+
const cleanup = async () => {
|
|
187
|
+
clearProgress();
|
|
188
|
+
process.stdout.write('\n');
|
|
189
|
+
console.log('Finalizing project...');
|
|
190
|
+
if (vite && !vite.killed && vite.pid) {
|
|
191
|
+
try {
|
|
192
|
+
if (process.platform === 'win32') {
|
|
193
|
+
const killProcess = (0, child_process_1.spawn)('taskkill', ['/F', '/T', '/PID', vite.pid.toString()], {
|
|
194
|
+
stdio: 'pipe',
|
|
195
|
+
shell: false,
|
|
196
|
+
windowsHide: true
|
|
197
|
+
});
|
|
198
|
+
killProcess.stdout?.on('data', () => { });
|
|
199
|
+
killProcess.stderr?.on('data', () => { });
|
|
200
|
+
await new Promise((resolve) => {
|
|
201
|
+
killProcess.on('close', () => resolve());
|
|
202
|
+
killProcess.on('error', () => resolve());
|
|
203
|
+
setTimeout(() => resolve(), 2000);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
else {
|
|
207
|
+
vite.kill('SIGTERM');
|
|
208
|
+
}
|
|
209
|
+
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
210
|
+
}
|
|
211
|
+
catch (error) {
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
if (tmpDir) {
|
|
215
|
+
let retries = 5;
|
|
216
|
+
while (retries > 0) {
|
|
217
|
+
try {
|
|
218
|
+
tmpDir.removeCallback();
|
|
219
|
+
break;
|
|
220
|
+
}
|
|
221
|
+
catch (error) {
|
|
222
|
+
if ((error.code === 'EBUSY' || error.code === 'ENOENT') && retries > 1) {
|
|
223
|
+
const waitTime = process.platform === 'win32' ? 1000 : 500;
|
|
224
|
+
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
225
|
+
retries--;
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
break;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
process.exit(0);
|
|
234
|
+
};
|
|
235
|
+
process.on('SIGINT', cleanup);
|
|
236
|
+
process.on('SIGTERM', cleanup);
|
|
210
237
|
try {
|
|
211
238
|
updateProgress(0, 100, 'Analyzing project');
|
|
212
|
-
|
|
213
|
-
const provider = config.ai?.provider || 'gemini';
|
|
214
|
-
// 1. Try built-in config (from dist/config.json - set during build)
|
|
215
|
-
let aiApiKey;
|
|
239
|
+
let apiUrl;
|
|
216
240
|
const builtInConfigPath = path.join(__dirname, 'config.json');
|
|
217
241
|
if (fs.existsSync(builtInConfigPath)) {
|
|
218
242
|
try {
|
|
219
243
|
const builtInConfig = JSON.parse(fs.readFileSync(builtInConfigPath, 'utf-8'));
|
|
220
|
-
|
|
244
|
+
apiUrl = builtInConfig.apiUrl;
|
|
221
245
|
}
|
|
222
246
|
catch (error) {
|
|
223
|
-
// Ignore errors reading built-in config
|
|
224
247
|
}
|
|
225
248
|
}
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
aiApiKey = process.env.GEMINI_API_KEY;
|
|
229
|
-
}
|
|
230
|
-
if (!aiApiKey) {
|
|
249
|
+
if (!apiUrl || apiUrl.trim() === '') {
|
|
250
|
+
clearProgress();
|
|
231
251
|
process.stdout.write('\n');
|
|
232
|
-
console.error('❌ Error: API
|
|
233
|
-
console.error('💡 This should not happen.
|
|
252
|
+
console.error('❌ Error: API configuration not found');
|
|
253
|
+
console.error('💡 This should not happen. The package should include the configuration.');
|
|
254
|
+
console.error('💡 Please reinstall: npm install -g zero-doc');
|
|
234
255
|
process.exit(1);
|
|
235
256
|
}
|
|
236
257
|
updateProgress(10, 100, 'Analyzing project');
|
|
237
|
-
// Read package.json for project info
|
|
238
258
|
let projectName = config.name;
|
|
239
259
|
let projectVersion;
|
|
240
260
|
let projectDescription;
|
|
@@ -247,52 +267,43 @@ commander_1.program
|
|
|
247
267
|
projectDescription = packageJson.description;
|
|
248
268
|
}
|
|
249
269
|
catch (error) {
|
|
250
|
-
// Ignore errors reading package.json
|
|
251
270
|
}
|
|
252
271
|
}
|
|
253
|
-
// Analyze with AI - use config or default pattern
|
|
254
272
|
const inputPattern = config.input || 'src/**/*.{ts,js,py,java,go,rs,php,rb}';
|
|
255
|
-
// Handle glob patterns with braces - don't split if pattern contains {}
|
|
256
273
|
const patterns = inputPattern.includes('{') && inputPattern.includes('}')
|
|
257
|
-
? [inputPattern]
|
|
258
|
-
: inputPattern.split(',').map(p => p.trim());
|
|
274
|
+
? [inputPattern]
|
|
275
|
+
: inputPattern.split(',').map(p => p.trim());
|
|
259
276
|
const analyzer = new ai_analyzer_1.AIAnalyzer({
|
|
260
|
-
|
|
277
|
+
apiUrl: apiUrl,
|
|
261
278
|
projectRoot: process.cwd(),
|
|
262
279
|
filePatterns: patterns,
|
|
263
280
|
});
|
|
264
281
|
updateProgress(20, 100, 'Analyzing project');
|
|
265
282
|
const inventory = await analyzer.analyzeProject();
|
|
266
|
-
// Set project info
|
|
267
283
|
inventory.project.name = inventory.project.name || projectName;
|
|
268
284
|
inventory.project.version = inventory.project.version || projectVersion;
|
|
269
285
|
inventory.project.description = inventory.project.description || projectDescription;
|
|
270
|
-
// Set baseUrl default
|
|
271
286
|
if (!inventory.baseUrl) {
|
|
272
287
|
inventory.baseUrl = 'https://api.example.com';
|
|
273
288
|
}
|
|
274
289
|
updateProgress(40, 100, 'Setting up viewer');
|
|
275
|
-
|
|
276
|
-
const tmpDir = tmp.dirSync({
|
|
290
|
+
tmpDir = tmp.dirSync({
|
|
277
291
|
prefix: 'zero-doc-',
|
|
278
292
|
unsafeCleanup: true
|
|
279
293
|
});
|
|
280
294
|
const tempViewerPath = path.join(tmpDir.name, 'viewer');
|
|
281
|
-
// Step 3: Find viewer source (works when installed or in development)
|
|
282
295
|
let viewerSourcePath;
|
|
283
|
-
// Try installed package path first (when installed globally)
|
|
284
|
-
// __dirname will be dist/ when compiled, so ../viewer is correct
|
|
285
296
|
const installedViewerPath = path.join(__dirname, '../viewer');
|
|
286
297
|
if (fs.existsSync(installedViewerPath)) {
|
|
287
298
|
viewerSourcePath = installedViewerPath;
|
|
288
299
|
}
|
|
289
300
|
else {
|
|
290
|
-
// Try development path (when running from src/)
|
|
291
301
|
const devViewerPath = path.join(__dirname, '../viewer');
|
|
292
302
|
if (fs.existsSync(devViewerPath)) {
|
|
293
303
|
viewerSourcePath = devViewerPath;
|
|
294
304
|
}
|
|
295
305
|
else {
|
|
306
|
+
clearProgress();
|
|
296
307
|
process.stdout.write('\n');
|
|
297
308
|
console.error('❌ Error: Viewer app not found');
|
|
298
309
|
console.error('💡 Expected viewer at:', installedViewerPath);
|
|
@@ -301,10 +312,8 @@ commander_1.program
|
|
|
301
312
|
}
|
|
302
313
|
}
|
|
303
314
|
updateProgress(50, 100, 'Setting up viewer');
|
|
304
|
-
// Step 4: Copy viewer files to temp directory
|
|
305
315
|
await fse.copy(viewerSourcePath, tempViewerPath, {
|
|
306
316
|
filter: (src) => {
|
|
307
|
-
// Skip node_modules, dist, and other build artifacts
|
|
308
317
|
const relativePath = path.relative(viewerSourcePath, src);
|
|
309
318
|
return !relativePath.includes('node_modules') &&
|
|
310
319
|
!relativePath.includes('dist') &&
|
|
@@ -312,12 +321,10 @@ commander_1.program
|
|
|
312
321
|
}
|
|
313
322
|
});
|
|
314
323
|
updateProgress(60, 100, 'Preparing files');
|
|
315
|
-
// Step 5: Copy generated JSON to temp viewer public folder
|
|
316
324
|
const tempPublicPath = path.join(tempViewerPath, 'public');
|
|
317
325
|
if (!fs.existsSync(tempPublicPath)) {
|
|
318
326
|
fs.mkdirSync(tempPublicPath, { recursive: true });
|
|
319
327
|
}
|
|
320
|
-
// Validate inventory before saving
|
|
321
328
|
if (!inventory.endpoints || !Array.isArray(inventory.endpoints)) {
|
|
322
329
|
inventory.endpoints = [];
|
|
323
330
|
}
|
|
@@ -327,11 +334,9 @@ commander_1.program
|
|
|
327
334
|
byMethod: {},
|
|
328
335
|
};
|
|
329
336
|
}
|
|
330
|
-
// Ensure baseUrl exists
|
|
331
337
|
if (!inventory.baseUrl) {
|
|
332
338
|
inventory.baseUrl = 'https://api.example.com';
|
|
333
339
|
}
|
|
334
|
-
// Validate JSON before saving
|
|
335
340
|
try {
|
|
336
341
|
const jsonContent = JSON.stringify(inventory, null, 2);
|
|
337
342
|
JSON.parse(jsonContent);
|
|
@@ -347,11 +352,11 @@ commander_1.program
|
|
|
347
352
|
}
|
|
348
353
|
}
|
|
349
354
|
catch (error) {
|
|
355
|
+
clearProgress();
|
|
350
356
|
process.stdout.write('\n');
|
|
351
357
|
console.error('❌ Error saving JSON:', error);
|
|
352
358
|
throw error;
|
|
353
359
|
}
|
|
354
|
-
// Verify JSON file exists and is valid before proceeding
|
|
355
360
|
const jsonPath = path.join(tempPublicPath, 'api-inventory.json');
|
|
356
361
|
if (!fs.existsSync(jsonPath)) {
|
|
357
362
|
throw new Error('JSON file was not created successfully');
|
|
@@ -360,7 +365,6 @@ commander_1.program
|
|
|
360
365
|
if (fileStats.size === 0) {
|
|
361
366
|
throw new Error('JSON file is empty');
|
|
362
367
|
}
|
|
363
|
-
// Verify JSON is valid
|
|
364
368
|
try {
|
|
365
369
|
const testContent = fs.readFileSync(jsonPath, 'utf-8');
|
|
366
370
|
const testParsed = JSON.parse(testContent);
|
|
@@ -369,12 +373,12 @@ commander_1.program
|
|
|
369
373
|
}
|
|
370
374
|
}
|
|
371
375
|
catch (error) {
|
|
376
|
+
clearProgress();
|
|
372
377
|
process.stdout.write('\n');
|
|
373
378
|
console.error('❌ JSON validation failed:', error);
|
|
374
379
|
throw error;
|
|
375
380
|
}
|
|
376
381
|
updateProgress(70, 100, 'Installing dependencies');
|
|
377
|
-
// Step 6: Install dependencies in temp directory
|
|
378
382
|
const installProcess = (0, child_process_1.spawn)('npm', ['install'], {
|
|
379
383
|
cwd: tempViewerPath,
|
|
380
384
|
stdio: 'ignore',
|
|
@@ -392,17 +396,14 @@ commander_1.program
|
|
|
392
396
|
installProcess.on('error', reject);
|
|
393
397
|
});
|
|
394
398
|
updateProgress(90, 100, 'Starting server');
|
|
395
|
-
|
|
396
|
-
const vite = (0, child_process_1.spawn)('npm', ['run', 'dev'], {
|
|
399
|
+
vite = (0, child_process_1.spawn)('npm', ['run', 'dev'], {
|
|
397
400
|
cwd: tempViewerPath,
|
|
398
401
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
399
402
|
shell: true
|
|
400
403
|
});
|
|
401
404
|
let serverReady = false;
|
|
402
|
-
// Intercept and filter Vite output
|
|
403
405
|
vite.stdout?.on('data', (data) => {
|
|
404
406
|
const output = data.toString();
|
|
405
|
-
// Filter out Vite-specific messages completely
|
|
406
407
|
if (output.includes('Local:') || output.includes('Network:') || output.includes('➜')) {
|
|
407
408
|
return;
|
|
408
409
|
}
|
|
@@ -412,14 +413,12 @@ commander_1.program
|
|
|
412
413
|
displayUrl('http://localhost:7777/');
|
|
413
414
|
}
|
|
414
415
|
else if (!output.includes('VITE') && !output.includes('vite') && !output.trim().startsWith('➜')) {
|
|
415
|
-
// Only show non-Vite messages (errors, etc.)
|
|
416
416
|
if (!serverReady) {
|
|
417
417
|
process.stderr.write(data);
|
|
418
418
|
}
|
|
419
419
|
}
|
|
420
420
|
});
|
|
421
421
|
vite.stderr?.on('data', (data) => {
|
|
422
|
-
// Show errors but filter Vite branding
|
|
423
422
|
if (!serverReady) {
|
|
424
423
|
const output = data.toString();
|
|
425
424
|
if (!output.includes('VITE') && !output.includes('vite')) {
|
|
@@ -427,69 +426,8 @@ commander_1.program
|
|
|
427
426
|
}
|
|
428
427
|
}
|
|
429
428
|
});
|
|
430
|
-
// Cleanup on exit
|
|
431
|
-
const cleanup = async () => {
|
|
432
|
-
process.stdout.write('\n');
|
|
433
|
-
// Kill Vite process first (and all child processes on Windows)
|
|
434
|
-
if (vite && !vite.killed && vite.pid) {
|
|
435
|
-
try {
|
|
436
|
-
if (process.platform === 'win32') {
|
|
437
|
-
// On Windows, kill the entire process tree without confirmation
|
|
438
|
-
// Use /F to force kill and /T to kill child processes
|
|
439
|
-
const killProcess = (0, child_process_1.spawn)('taskkill', ['/F', '/T', '/PID', vite.pid.toString()], {
|
|
440
|
-
stdio: 'pipe',
|
|
441
|
-
shell: false, // Don't use shell to avoid confirmation prompts
|
|
442
|
-
windowsHide: true
|
|
443
|
-
});
|
|
444
|
-
// Consume output to prevent any prompts
|
|
445
|
-
killProcess.stdout?.on('data', () => { });
|
|
446
|
-
killProcess.stderr?.on('data', () => { });
|
|
447
|
-
// Wait for kill command to complete
|
|
448
|
-
await new Promise((resolve) => {
|
|
449
|
-
killProcess.on('close', () => resolve());
|
|
450
|
-
killProcess.on('error', () => resolve());
|
|
451
|
-
// Timeout after 2 seconds
|
|
452
|
-
setTimeout(() => resolve(), 2000);
|
|
453
|
-
});
|
|
454
|
-
}
|
|
455
|
-
else {
|
|
456
|
-
vite.kill('SIGTERM');
|
|
457
|
-
}
|
|
458
|
-
// Wait for process to close and files to be released
|
|
459
|
-
await new Promise(resolve => setTimeout(resolve, 1500));
|
|
460
|
-
}
|
|
461
|
-
catch (error) {
|
|
462
|
-
// Ignore errors when killing
|
|
463
|
-
}
|
|
464
|
-
}
|
|
465
|
-
// Try to remove temp directory with retries
|
|
466
|
-
let retries = 5;
|
|
467
|
-
let lastError = null;
|
|
468
|
-
while (retries > 0) {
|
|
469
|
-
try {
|
|
470
|
-
tmpDir.removeCallback();
|
|
471
|
-
break;
|
|
472
|
-
}
|
|
473
|
-
catch (error) {
|
|
474
|
-
lastError = error;
|
|
475
|
-
if ((error.code === 'EBUSY' || error.code === 'ENOENT') && retries > 1) {
|
|
476
|
-
// Wait a bit more and retry (longer wait on Windows)
|
|
477
|
-
const waitTime = process.platform === 'win32' ? 1000 : 500;
|
|
478
|
-
await new Promise(resolve => setTimeout(resolve, waitTime));
|
|
479
|
-
retries--;
|
|
480
|
-
}
|
|
481
|
-
else {
|
|
482
|
-
// If it's the last retry or different error, just log and continue
|
|
483
|
-
break;
|
|
484
|
-
}
|
|
485
|
-
}
|
|
486
|
-
}
|
|
487
|
-
// Silently handle cleanup errors
|
|
488
|
-
process.exit(0);
|
|
489
|
-
};
|
|
490
|
-
process.on('SIGINT', cleanup);
|
|
491
|
-
process.on('SIGTERM', cleanup);
|
|
492
429
|
vite.on('error', (error) => {
|
|
430
|
+
clearProgress();
|
|
493
431
|
process.stdout.write('\n');
|
|
494
432
|
console.error('❌ Error starting preview server:', error);
|
|
495
433
|
cleanup();
|
|
@@ -499,11 +437,12 @@ commander_1.program
|
|
|
499
437
|
});
|
|
500
438
|
}
|
|
501
439
|
catch (error) {
|
|
440
|
+
clearProgress();
|
|
441
|
+
process.stdout.write('\n');
|
|
502
442
|
console.error('❌ Error:', error instanceof Error ? error.message : error);
|
|
503
443
|
process.exit(1);
|
|
504
444
|
}
|
|
505
445
|
});
|
|
506
|
-
// Deploy command (placeholder)
|
|
507
446
|
commander_1.program
|
|
508
447
|
.command('deploy')
|
|
509
448
|
.description('Deploy documentation to zero-doc cloud')
|
|
@@ -518,9 +457,5 @@ commander_1.program
|
|
|
518
457
|
console.log('🚀 Deploying documentation...\n');
|
|
519
458
|
console.log('⚠️ Deploy feature coming soon!');
|
|
520
459
|
console.log('💡 For now, you can serve the viewer locally with: zero-doc preview');
|
|
521
|
-
// TODO: Implement actual deployment
|
|
522
|
-
// - Upload JSON to backend
|
|
523
|
-
// - Get unique URL
|
|
524
|
-
// - Return docs.zerodoc.com/user/project
|
|
525
460
|
});
|
|
526
461
|
commander_1.program.parse();
|
package/dist/config.json
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zero-doc",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Zero-Config API Documentation Generator - Generate beautiful API docs from your code automatically",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -30,6 +30,9 @@
|
|
|
30
30
|
"viewer",
|
|
31
31
|
"scripts"
|
|
32
32
|
],
|
|
33
|
+
".npmignore": [
|
|
34
|
+
"dist/config.json"
|
|
35
|
+
],
|
|
33
36
|
"scripts": {
|
|
34
37
|
"build": "tsc && npm run inject-config && npm run copy-viewer",
|
|
35
38
|
"inject-config": "node scripts/inject-config.js",
|
package/scripts/inject-config.js
CHANGED
|
@@ -1,40 +1,46 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Script to inject API
|
|
4
|
-
*
|
|
3
|
+
* Script to inject API URL and Client Key into config.json during build
|
|
4
|
+
* Reads from .env file in project root OR environment variables (CI/CD)
|
|
5
|
+
* This config will be included in the npm package
|
|
5
6
|
*/
|
|
6
7
|
|
|
7
8
|
const fs = require('fs');
|
|
8
9
|
const path = require('path');
|
|
10
|
+
const dotenv = require('dotenv');
|
|
9
11
|
|
|
10
|
-
|
|
12
|
+
// Project root (where package.json is)
|
|
13
|
+
const projectRoot = path.join(__dirname, '..');
|
|
14
|
+
const distDir = path.join(projectRoot, 'dist');
|
|
11
15
|
const configPath = path.join(distDir, 'config.json');
|
|
16
|
+
const envPath = path.join(projectRoot, '.env');
|
|
12
17
|
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
console.warn('⚠️ Warning: ZERO_DOC_API_KEY not set. Build will continue but API key must be set at runtime.');
|
|
18
|
-
// Create empty config file
|
|
19
|
-
if (!fs.existsSync(distDir)) {
|
|
20
|
-
fs.mkdirSync(distDir, { recursive: true });
|
|
21
|
-
}
|
|
22
|
-
fs.writeFileSync(configPath, JSON.stringify({ apiKey: null }, null, 2));
|
|
23
|
-
process.exit(0);
|
|
18
|
+
// Load .env from project root (if exists)
|
|
19
|
+
if (fs.existsSync(envPath)) {
|
|
20
|
+
dotenv.config({ path: envPath });
|
|
21
|
+
console.log('📁 Loaded .env from project root');
|
|
24
22
|
}
|
|
25
23
|
|
|
24
|
+
// Get API URL and Client Key from environment variables
|
|
25
|
+
// Priority: Environment variable (CI/CD) > .env file
|
|
26
|
+
const apiUrl = process.env.ZERO_DOC_API_URL;
|
|
27
|
+
|
|
26
28
|
// Ensure dist directory exists
|
|
27
29
|
if (!fs.existsSync(distDir)) {
|
|
28
30
|
fs.mkdirSync(distDir, { recursive: true });
|
|
29
31
|
}
|
|
30
32
|
|
|
31
|
-
// Create config file with API
|
|
33
|
+
// Create config file with API URL and Client Key
|
|
32
34
|
const config = {
|
|
33
|
-
|
|
34
|
-
provider: 'gemini',
|
|
35
|
-
model: 'gemini-3-pro-preview'
|
|
35
|
+
apiUrl: apiUrl || ''
|
|
36
36
|
};
|
|
37
37
|
|
|
38
38
|
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
|
|
39
|
-
|
|
39
|
+
|
|
40
|
+
if (apiUrl) {
|
|
41
|
+
console.log('✅ Config file created with API URL');
|
|
42
|
+
} else {
|
|
43
|
+
console.warn('⚠️ Warning: ZERO_DOC_API_URL not found. Config file created but empty.');
|
|
44
|
+
console.warn(' Set ZERO_DOC_API_URL in .env or CI/CD environment variables.');
|
|
45
|
+
}
|
|
40
46
|
|