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/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
- // Display final URL with formatting
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
- // Get AI API key - try multiple sources in order of priority
113
- const provider = options.aiProvider || config.ai?.provider || 'gemini';
114
- // 1. Try built-in config (from dist/config.json - set during build)
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 builtInConfig = JSON.parse(fs.readFileSync(builtInConfigPath, 'utf-8'));
120
- aiApiKey = builtInConfig.apiKey;
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
- // 2. Fallback to environment variable (for development/testing)
127
- if (!aiApiKey) {
128
- aiApiKey = process.env.GEMINI_API_KEY;
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] // Single pattern with braces
160
- : inputPattern.split(',').map(p => p.trim()); // Multiple patterns separated by comma
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
- apiKey: aiApiKey,
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
- // Get AI API key - try multiple sources in order of priority
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
- aiApiKey = builtInConfig.apiKey;
244
+ apiUrl = builtInConfig.apiUrl;
221
245
  }
222
246
  catch (error) {
223
- // Ignore errors reading built-in config
224
247
  }
225
248
  }
226
- // 2. Fallback to environment variable (for development/testing)
227
- if (!aiApiKey) {
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 key not configured');
233
- console.error('💡 This should not happen. Please contact support.');
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] // Single pattern with braces
258
- : inputPattern.split(',').map(p => p.trim()); // Multiple patterns separated by comma
274
+ ? [inputPattern]
275
+ : inputPattern.split(',').map(p => p.trim());
259
276
  const analyzer = new ai_analyzer_1.AIAnalyzer({
260
- apiKey: aiApiKey,
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
- // Step 2: Create temporary directory
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
- // Step 7: Start Vite dev server (suppress Vite output)
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
@@ -1,3 +1,3 @@
1
1
  {
2
- "apiKey": null
2
+ "apiUrl": ""
3
3
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zero-doc",
3
- "version": "1.0.4",
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",
@@ -1,40 +1,46 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * Script to inject API key into a config file during build
4
- * This file is NOT committed to the repository
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
- const distDir = path.join(__dirname, '..', 'dist');
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
- // Get API key from environment variable
14
- const apiKey = process.env.ZERO_DOC_API_KEY || process.env.GEMINI_API_KEY;
15
-
16
- if (!apiKey) {
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 key
33
+ // Create config file with API URL and Client Key
32
34
  const config = {
33
- apiKey: apiKey,
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
- console.log('✅ Config file created successfully');
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