genbox 1.0.4 → 1.0.5
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/commands/init.js +347 -113
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -51,6 +51,79 @@ const config_generator_1 = require("../scanner/config-generator");
|
|
|
51
51
|
const scan_1 = require("../scan");
|
|
52
52
|
const CONFIG_FILENAME = 'genbox.yaml';
|
|
53
53
|
const ENV_FILENAME = '.env.genbox';
|
|
54
|
+
/**
|
|
55
|
+
* Detect git repositories in app directories (for multi-repo workspaces)
|
|
56
|
+
*/
|
|
57
|
+
function detectAppGitRepos(apps, rootDir) {
|
|
58
|
+
const { execSync } = require('child_process');
|
|
59
|
+
const repos = [];
|
|
60
|
+
for (const app of apps) {
|
|
61
|
+
const appDir = path_1.default.join(rootDir, app.path);
|
|
62
|
+
const gitDir = path_1.default.join(appDir, '.git');
|
|
63
|
+
if (!fs_1.default.existsSync(gitDir))
|
|
64
|
+
continue;
|
|
65
|
+
try {
|
|
66
|
+
const remote = execSync('git remote get-url origin', {
|
|
67
|
+
cwd: appDir,
|
|
68
|
+
stdio: 'pipe',
|
|
69
|
+
encoding: 'utf8',
|
|
70
|
+
}).trim();
|
|
71
|
+
if (!remote)
|
|
72
|
+
continue;
|
|
73
|
+
const isSSH = remote.startsWith('git@') || remote.startsWith('ssh://');
|
|
74
|
+
let provider = 'other';
|
|
75
|
+
if (remote.includes('github.com'))
|
|
76
|
+
provider = 'github';
|
|
77
|
+
else if (remote.includes('gitlab.com'))
|
|
78
|
+
provider = 'gitlab';
|
|
79
|
+
else if (remote.includes('bitbucket.org'))
|
|
80
|
+
provider = 'bitbucket';
|
|
81
|
+
let branch = 'main';
|
|
82
|
+
try {
|
|
83
|
+
branch = execSync('git rev-parse --abbrev-ref HEAD', {
|
|
84
|
+
cwd: appDir,
|
|
85
|
+
stdio: 'pipe',
|
|
86
|
+
encoding: 'utf8',
|
|
87
|
+
}).trim();
|
|
88
|
+
}
|
|
89
|
+
catch { }
|
|
90
|
+
repos.push({
|
|
91
|
+
appName: app.name,
|
|
92
|
+
appPath: app.path,
|
|
93
|
+
remote,
|
|
94
|
+
type: isSSH ? 'ssh' : 'https',
|
|
95
|
+
provider,
|
|
96
|
+
branch,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
// No git remote in this directory
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return repos;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Find .env files in app directories
|
|
107
|
+
*/
|
|
108
|
+
function findAppEnvFiles(apps, rootDir) {
|
|
109
|
+
const envFiles = [];
|
|
110
|
+
const envPatterns = ['.env', '.env.local', '.env.development'];
|
|
111
|
+
for (const app of apps) {
|
|
112
|
+
const appDir = path_1.default.join(rootDir, app.path);
|
|
113
|
+
for (const pattern of envPatterns) {
|
|
114
|
+
const envPath = path_1.default.join(appDir, pattern);
|
|
115
|
+
if (fs_1.default.existsSync(envPath)) {
|
|
116
|
+
envFiles.push({
|
|
117
|
+
appName: app.name,
|
|
118
|
+
envFile: pattern,
|
|
119
|
+
fullPath: envPath,
|
|
120
|
+
});
|
|
121
|
+
break; // Only take the first match per app
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
return envFiles;
|
|
126
|
+
}
|
|
54
127
|
exports.initCommand = new commander_1.Command('init')
|
|
55
128
|
.description('Initialize a new Genbox configuration')
|
|
56
129
|
.option('--v2', 'Use legacy v2 format (single-app only)')
|
|
@@ -187,10 +260,52 @@ exports.initCommand = new commander_1.Command('init')
|
|
|
187
260
|
v3Config.defaults = {};
|
|
188
261
|
}
|
|
189
262
|
v3Config.defaults.size = serverSize;
|
|
190
|
-
// Git repository setup
|
|
191
|
-
|
|
263
|
+
// Git repository setup - different handling for multi-repo vs single-repo
|
|
264
|
+
const isMultiRepo = scan.structure.type === 'hybrid';
|
|
265
|
+
if (isMultiRepo) {
|
|
266
|
+
// Multi-repo workspace: detect git repos in app directories
|
|
267
|
+
const appGitRepos = detectAppGitRepos(scan.apps, process.cwd());
|
|
268
|
+
if (appGitRepos.length > 0 && !nonInteractive) {
|
|
269
|
+
console.log('');
|
|
270
|
+
console.log(chalk_1.default.blue('=== Git Repositories ==='));
|
|
271
|
+
console.log(chalk_1.default.dim(`Found ${appGitRepos.length} git repositories in app directories`));
|
|
272
|
+
const repoChoices = appGitRepos.map(repo => ({
|
|
273
|
+
name: `${repo.appName} - ${repo.remote}`,
|
|
274
|
+
value: repo.appName,
|
|
275
|
+
checked: true, // Default to include all
|
|
276
|
+
}));
|
|
277
|
+
const selectedRepos = await prompts.checkbox({
|
|
278
|
+
message: 'Select repositories to include:',
|
|
279
|
+
choices: repoChoices,
|
|
280
|
+
});
|
|
281
|
+
if (selectedRepos.length > 0) {
|
|
282
|
+
v3Config.repos = {};
|
|
283
|
+
for (const repoName of selectedRepos) {
|
|
284
|
+
const repo = appGitRepos.find(r => r.appName === repoName);
|
|
285
|
+
v3Config.repos[repo.appName] = {
|
|
286
|
+
url: repo.remote,
|
|
287
|
+
path: `/home/dev/${projectName}/${repo.appPath}`,
|
|
288
|
+
branch: repo.branch !== 'main' && repo.branch !== 'master' ? repo.branch : undefined,
|
|
289
|
+
auth: repo.type === 'ssh' ? 'ssh' : 'token',
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
else if (appGitRepos.length > 0) {
|
|
295
|
+
// Non-interactive: include all repos
|
|
296
|
+
v3Config.repos = {};
|
|
297
|
+
for (const repo of appGitRepos) {
|
|
298
|
+
v3Config.repos[repo.appName] = {
|
|
299
|
+
url: repo.remote,
|
|
300
|
+
path: `/home/dev/${projectName}/${repo.appPath}`,
|
|
301
|
+
auth: repo.type === 'ssh' ? 'ssh' : 'token',
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
else if (scan.git) {
|
|
307
|
+
// Single repo or monorepo with root git
|
|
192
308
|
if (nonInteractive) {
|
|
193
|
-
// Use detected git config with defaults
|
|
194
309
|
const repoName = path_1.default.basename(scan.git.remote, '.git').replace(/.*[:/]/, '');
|
|
195
310
|
v3Config.repos = {
|
|
196
311
|
[repoName]: {
|
|
@@ -210,7 +325,8 @@ exports.initCommand = new commander_1.Command('init')
|
|
|
210
325
|
}
|
|
211
326
|
}
|
|
212
327
|
}
|
|
213
|
-
else if (!nonInteractive) {
|
|
328
|
+
else if (!nonInteractive && !isMultiRepo) {
|
|
329
|
+
// Only ask to add repo for non-multi-repo projects
|
|
214
330
|
const addRepo = await prompts.confirm({
|
|
215
331
|
message: 'No git remote detected. Add a repository?',
|
|
216
332
|
default: false,
|
|
@@ -239,66 +355,55 @@ exports.initCommand = new commander_1.Command('init')
|
|
|
239
355
|
}
|
|
240
356
|
// Environment configuration (skip in non-interactive mode)
|
|
241
357
|
if (!nonInteractive) {
|
|
242
|
-
const envConfig = await setupEnvironments(scan, v3Config);
|
|
358
|
+
const envConfig = await setupEnvironments(scan, v3Config, isMultiRepo);
|
|
243
359
|
if (envConfig) {
|
|
244
360
|
v3Config.environments = envConfig;
|
|
245
361
|
}
|
|
246
362
|
}
|
|
247
|
-
// Script selection (skip in non-interactive mode)
|
|
363
|
+
// Script selection - always show multi-select UI (skip in non-interactive mode)
|
|
248
364
|
if (!nonInteractive) {
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
if (
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
const dir = script.path.includes('/') ? script.path.split('/')[0] : '(root)';
|
|
264
|
-
const existing = scriptsByDir.get(dir) || [];
|
|
265
|
-
existing.push(script);
|
|
266
|
-
scriptsByDir.set(dir, existing);
|
|
267
|
-
}
|
|
268
|
-
// Show grouped scripts
|
|
269
|
-
for (const [dir, scripts] of scriptsByDir) {
|
|
270
|
-
console.log(chalk_1.default.dim(` ${dir}/`));
|
|
271
|
-
for (const s of scripts.slice(0, 5)) {
|
|
272
|
-
console.log(chalk_1.default.dim(` - ${s.name}`));
|
|
273
|
-
}
|
|
274
|
-
if (scripts.length > 5) {
|
|
275
|
-
console.log(chalk_1.default.dim(` ... and ${scripts.length - 5} more`));
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
// Let user select scripts
|
|
279
|
-
const scriptChoices = fullScan.scripts.map(s => ({
|
|
280
|
-
name: `${s.path} (${s.stage})`,
|
|
281
|
-
value: s.path,
|
|
282
|
-
checked: s.path.startsWith('scripts/'), // Default select scripts/ directory
|
|
283
|
-
}));
|
|
284
|
-
const selectedScripts = await prompts.checkbox({
|
|
285
|
-
message: 'Select scripts to include:',
|
|
286
|
-
choices: scriptChoices,
|
|
287
|
-
});
|
|
288
|
-
if (selectedScripts.length > 0) {
|
|
289
|
-
v3Config.scripts = fullScan.scripts
|
|
290
|
-
.filter(s => selectedScripts.includes(s.path))
|
|
291
|
-
.map(s => ({
|
|
292
|
-
name: s.name,
|
|
293
|
-
path: s.path,
|
|
294
|
-
stage: s.stage,
|
|
295
|
-
}));
|
|
296
|
-
}
|
|
365
|
+
// Scan for scripts
|
|
366
|
+
const scriptsSpinner = (0, ora_1.default)('Scanning for scripts...').start();
|
|
367
|
+
const fullScan = await scanner.scan(process.cwd(), { exclude, skipScripts: false });
|
|
368
|
+
scriptsSpinner.stop();
|
|
369
|
+
if (fullScan.scripts.length > 0) {
|
|
370
|
+
console.log('');
|
|
371
|
+
console.log(chalk_1.default.blue('=== Setup Scripts ==='));
|
|
372
|
+
// Group scripts by directory for display
|
|
373
|
+
const scriptsByDir = new Map();
|
|
374
|
+
for (const script of fullScan.scripts) {
|
|
375
|
+
const dir = script.path.includes('/') ? script.path.split('/')[0] : '(root)';
|
|
376
|
+
const existing = scriptsByDir.get(dir) || [];
|
|
377
|
+
existing.push(script);
|
|
378
|
+
scriptsByDir.set(dir, existing);
|
|
297
379
|
}
|
|
298
|
-
|
|
299
|
-
|
|
380
|
+
// Show grouped scripts
|
|
381
|
+
for (const [dir, scripts] of scriptsByDir) {
|
|
382
|
+
console.log(chalk_1.default.dim(` ${dir}/ (${scripts.length} scripts)`));
|
|
383
|
+
}
|
|
384
|
+
// Let user select scripts with multi-select
|
|
385
|
+
const scriptChoices = fullScan.scripts.map(s => ({
|
|
386
|
+
name: `${s.path} (${s.stage})`,
|
|
387
|
+
value: s.path,
|
|
388
|
+
checked: s.path.startsWith('scripts/'), // Default select scripts/ directory
|
|
389
|
+
}));
|
|
390
|
+
const selectedScripts = await prompts.checkbox({
|
|
391
|
+
message: 'Select scripts to include (space to toggle, enter to confirm):',
|
|
392
|
+
choices: scriptChoices,
|
|
393
|
+
});
|
|
394
|
+
if (selectedScripts.length > 0) {
|
|
395
|
+
v3Config.scripts = fullScan.scripts
|
|
396
|
+
.filter(s => selectedScripts.includes(s.path))
|
|
397
|
+
.map(s => ({
|
|
398
|
+
name: s.name,
|
|
399
|
+
path: s.path,
|
|
400
|
+
stage: s.stage,
|
|
401
|
+
}));
|
|
300
402
|
}
|
|
301
403
|
}
|
|
404
|
+
else {
|
|
405
|
+
console.log(chalk_1.default.dim('No scripts found.'));
|
|
406
|
+
}
|
|
302
407
|
}
|
|
303
408
|
// Save configuration
|
|
304
409
|
const yamlContent = yaml.dump(v3Config, {
|
|
@@ -309,7 +414,7 @@ exports.initCommand = new commander_1.Command('init')
|
|
|
309
414
|
fs_1.default.writeFileSync(configPath, yamlContent);
|
|
310
415
|
console.log(chalk_1.default.green(`\n✔ Configuration saved to ${CONFIG_FILENAME}`));
|
|
311
416
|
// Generate .env.genbox
|
|
312
|
-
await setupEnvFile(projectName, v3Config, nonInteractive);
|
|
417
|
+
await setupEnvFile(projectName, v3Config, nonInteractive, scan, isMultiRepo);
|
|
313
418
|
// Show warnings
|
|
314
419
|
if (generated.warnings.length > 0) {
|
|
315
420
|
console.log('');
|
|
@@ -480,7 +585,7 @@ async function setupGitAuth(gitInfo, projectName) {
|
|
|
480
585
|
/**
|
|
481
586
|
* Setup staging/production environments
|
|
482
587
|
*/
|
|
483
|
-
async function setupEnvironments(scan, config) {
|
|
588
|
+
async function setupEnvironments(scan, config, isMultiRepo = false) {
|
|
484
589
|
const setupEnvs = await prompts.confirm({
|
|
485
590
|
message: 'Configure staging/production environments?',
|
|
486
591
|
default: true,
|
|
@@ -493,37 +598,108 @@ async function setupEnvironments(scan, config) {
|
|
|
493
598
|
console.log(chalk_1.default.dim('These URLs will be used when connecting to external services.'));
|
|
494
599
|
console.log(chalk_1.default.dim('Actual secrets go in .env.genbox'));
|
|
495
600
|
console.log('');
|
|
496
|
-
const stagingApiUrl = await prompts.input({
|
|
497
|
-
message: 'Staging API URL (leave empty to skip):',
|
|
498
|
-
default: '',
|
|
499
|
-
});
|
|
500
601
|
const environments = {};
|
|
501
|
-
if (
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
602
|
+
if (isMultiRepo) {
|
|
603
|
+
// For multi-repo: configure API URLs per backend app
|
|
604
|
+
const backendApps = scan.apps.filter(a => a.type === 'backend' || a.type === 'api');
|
|
605
|
+
if (backendApps.length > 0) {
|
|
606
|
+
console.log(chalk_1.default.dim('Configure staging API URLs for each backend service:'));
|
|
607
|
+
const stagingApi = {};
|
|
608
|
+
for (const app of backendApps) {
|
|
609
|
+
const url = await prompts.input({
|
|
610
|
+
message: ` ${app.name} staging URL (leave empty to skip):`,
|
|
611
|
+
default: '',
|
|
612
|
+
});
|
|
613
|
+
if (url) {
|
|
614
|
+
stagingApi[app.name] = url;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
if (Object.keys(stagingApi).length > 0) {
|
|
618
|
+
environments.staging = {
|
|
619
|
+
description: 'Staging environment',
|
|
620
|
+
api: stagingApi,
|
|
621
|
+
mongodb: { url: '${STAGING_MONGODB_URL}' },
|
|
622
|
+
redis: { url: '${STAGING_REDIS_URL}' },
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
else {
|
|
627
|
+
// No backend apps, just ask for a single URL
|
|
628
|
+
const stagingApiUrl = await prompts.input({
|
|
629
|
+
message: 'Staging API URL (leave empty to skip):',
|
|
630
|
+
default: '',
|
|
631
|
+
});
|
|
632
|
+
if (stagingApiUrl) {
|
|
633
|
+
environments.staging = {
|
|
634
|
+
description: 'Staging environment',
|
|
635
|
+
api: { gateway: stagingApiUrl },
|
|
636
|
+
mongodb: { url: '${STAGING_MONGODB_URL}' },
|
|
637
|
+
redis: { url: '${STAGING_REDIS_URL}' },
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
else {
|
|
643
|
+
// Single repo: simple single URL
|
|
644
|
+
const stagingApiUrl = await prompts.input({
|
|
645
|
+
message: 'Staging API URL (leave empty to skip):',
|
|
646
|
+
default: '',
|
|
647
|
+
});
|
|
648
|
+
if (stagingApiUrl) {
|
|
649
|
+
environments.staging = {
|
|
650
|
+
description: 'Staging environment',
|
|
651
|
+
api: { gateway: stagingApiUrl },
|
|
652
|
+
mongodb: { url: '${STAGING_MONGODB_URL}' },
|
|
653
|
+
redis: { url: '${STAGING_REDIS_URL}' },
|
|
654
|
+
};
|
|
655
|
+
}
|
|
508
656
|
}
|
|
509
657
|
const setupProd = await prompts.confirm({
|
|
510
658
|
message: 'Also configure production environment?',
|
|
511
659
|
default: false,
|
|
512
660
|
});
|
|
513
661
|
if (setupProd) {
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
662
|
+
if (isMultiRepo) {
|
|
663
|
+
const backendApps = scan.apps.filter(a => a.type === 'backend' || a.type === 'api');
|
|
664
|
+
if (backendApps.length > 0) {
|
|
665
|
+
console.log(chalk_1.default.dim('Configure production API URLs for each backend service:'));
|
|
666
|
+
const prodApi = {};
|
|
667
|
+
for (const app of backendApps) {
|
|
668
|
+
const url = await prompts.input({
|
|
669
|
+
message: ` ${app.name} production URL:`,
|
|
670
|
+
default: '',
|
|
671
|
+
});
|
|
672
|
+
if (url) {
|
|
673
|
+
prodApi[app.name] = url;
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
if (Object.keys(prodApi).length > 0) {
|
|
677
|
+
environments.production = {
|
|
678
|
+
description: 'Production (use with caution)',
|
|
679
|
+
api: prodApi,
|
|
680
|
+
mongodb: {
|
|
681
|
+
url: '${PROD_MONGODB_URL}',
|
|
682
|
+
read_only: true,
|
|
683
|
+
},
|
|
684
|
+
};
|
|
685
|
+
}
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
const prodApiUrl = await prompts.input({
|
|
690
|
+
message: 'Production API URL:',
|
|
691
|
+
default: '',
|
|
692
|
+
});
|
|
693
|
+
if (prodApiUrl) {
|
|
694
|
+
environments.production = {
|
|
695
|
+
description: 'Production (use with caution)',
|
|
696
|
+
api: { gateway: prodApiUrl },
|
|
697
|
+
mongodb: {
|
|
698
|
+
url: '${PROD_MONGODB_URL}',
|
|
699
|
+
read_only: true,
|
|
700
|
+
},
|
|
701
|
+
};
|
|
702
|
+
}
|
|
527
703
|
}
|
|
528
704
|
}
|
|
529
705
|
return Object.keys(environments).length > 0 ? environments : undefined;
|
|
@@ -531,31 +707,89 @@ async function setupEnvironments(scan, config) {
|
|
|
531
707
|
/**
|
|
532
708
|
* Setup .env.genbox file
|
|
533
709
|
*/
|
|
534
|
-
async function setupEnvFile(projectName, config, nonInteractive = false) {
|
|
710
|
+
async function setupEnvFile(projectName, config, nonInteractive = false, scan, isMultiRepo = false) {
|
|
535
711
|
const envPath = path_1.default.join(process.cwd(), ENV_FILENAME);
|
|
536
712
|
if (fs_1.default.existsSync(envPath)) {
|
|
537
713
|
console.log(chalk_1.default.dim(` ${ENV_FILENAME} already exists, skipping...`));
|
|
538
714
|
return;
|
|
539
715
|
}
|
|
540
|
-
//
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
716
|
+
// For multi-repo: find env files in app directories
|
|
717
|
+
if (isMultiRepo && scan) {
|
|
718
|
+
const appEnvFiles = findAppEnvFiles(scan.apps, process.cwd());
|
|
719
|
+
if (appEnvFiles.length > 0 && !nonInteractive) {
|
|
720
|
+
console.log('');
|
|
721
|
+
console.log(chalk_1.default.blue('=== Environment Files ==='));
|
|
722
|
+
console.log(chalk_1.default.dim(`Found .env files in ${appEnvFiles.length} app directories`));
|
|
723
|
+
const envChoices = appEnvFiles.map(env => ({
|
|
724
|
+
name: `${env.appName}/${env.envFile}`,
|
|
725
|
+
value: env.fullPath,
|
|
726
|
+
checked: true,
|
|
727
|
+
}));
|
|
728
|
+
const selectedEnvFiles = await prompts.checkbox({
|
|
729
|
+
message: 'Select .env files to merge into .env.genbox:',
|
|
730
|
+
choices: envChoices,
|
|
731
|
+
});
|
|
732
|
+
if (selectedEnvFiles.length > 0) {
|
|
733
|
+
let mergedContent = `# Genbox Environment Variables
|
|
734
|
+
# Merged from: ${selectedEnvFiles.map(f => path_1.default.relative(process.cwd(), f)).join(', ')}
|
|
735
|
+
# DO NOT COMMIT THIS FILE
|
|
736
|
+
#
|
|
737
|
+
# Add staging/production URLs:
|
|
738
|
+
# STAGING_MONGODB_URL=mongodb+srv://...
|
|
739
|
+
# STAGING_REDIS_URL=redis://...
|
|
740
|
+
# PROD_MONGODB_URL=mongodb+srv://...
|
|
741
|
+
#
|
|
742
|
+
# Git authentication:
|
|
743
|
+
# GIT_TOKEN=ghp_xxxxxxxxxxxx
|
|
744
|
+
|
|
745
|
+
`;
|
|
746
|
+
for (const envFilePath of selectedEnvFiles) {
|
|
747
|
+
const appInfo = appEnvFiles.find(e => e.fullPath === envFilePath);
|
|
748
|
+
const content = fs_1.default.readFileSync(envFilePath, 'utf8');
|
|
749
|
+
mergedContent += `\n# === ${appInfo?.appName || path_1.default.dirname(envFilePath)} ===\n`;
|
|
750
|
+
mergedContent += content;
|
|
751
|
+
mergedContent += '\n';
|
|
752
|
+
}
|
|
753
|
+
fs_1.default.writeFileSync(envPath, mergedContent);
|
|
754
|
+
console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} from ${selectedEnvFiles.length} app env files`));
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
else if (appEnvFiles.length > 0 && nonInteractive) {
|
|
758
|
+
// Non-interactive: merge all env files
|
|
759
|
+
let mergedContent = `# Genbox Environment Variables
|
|
760
|
+
# Merged from app directories
|
|
761
|
+
# DO NOT COMMIT THIS FILE
|
|
762
|
+
|
|
763
|
+
`;
|
|
764
|
+
for (const envFile of appEnvFiles) {
|
|
765
|
+
const content = fs_1.default.readFileSync(envFile.fullPath, 'utf8');
|
|
766
|
+
mergedContent += `\n# === ${envFile.appName} ===\n`;
|
|
767
|
+
mergedContent += content;
|
|
768
|
+
mergedContent += '\n';
|
|
769
|
+
}
|
|
770
|
+
fs_1.default.writeFileSync(envPath, mergedContent);
|
|
771
|
+
console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} from ${appEnvFiles.length} app env files`));
|
|
548
772
|
}
|
|
549
773
|
}
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
const
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
774
|
+
// If no env file created yet, check for root .env
|
|
775
|
+
if (!fs_1.default.existsSync(envPath)) {
|
|
776
|
+
const existingEnvFiles = ['.env.local', '.env', '.env.development'];
|
|
777
|
+
let existingEnvPath;
|
|
778
|
+
for (const envFile of existingEnvFiles) {
|
|
779
|
+
const fullPath = path_1.default.join(process.cwd(), envFile);
|
|
780
|
+
if (fs_1.default.existsSync(fullPath)) {
|
|
781
|
+
existingEnvPath = fullPath;
|
|
782
|
+
break;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
if (existingEnvPath) {
|
|
786
|
+
const copyExisting = nonInteractive ? true : await prompts.confirm({
|
|
787
|
+
message: `Found ${path_1.default.basename(existingEnvPath)}. Copy to ${ENV_FILENAME}?`,
|
|
788
|
+
default: true,
|
|
789
|
+
});
|
|
790
|
+
if (copyExisting) {
|
|
791
|
+
const content = fs_1.default.readFileSync(existingEnvPath, 'utf8');
|
|
792
|
+
const header = `# Genbox Environment Variables
|
|
559
793
|
# Generated from ${path_1.default.basename(existingEnvPath)}
|
|
560
794
|
# DO NOT COMMIT THIS FILE
|
|
561
795
|
#
|
|
@@ -568,20 +802,20 @@ async function setupEnvFile(projectName, config, nonInteractive = false) {
|
|
|
568
802
|
# GIT_TOKEN=ghp_xxxxxxxxxxxx
|
|
569
803
|
|
|
570
804
|
`;
|
|
571
|
-
|
|
572
|
-
|
|
805
|
+
fs_1.default.writeFileSync(envPath, header + content);
|
|
806
|
+
console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} from ${path_1.default.basename(existingEnvPath)}`));
|
|
807
|
+
}
|
|
573
808
|
}
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} template`));
|
|
809
|
+
else {
|
|
810
|
+
const createEnv = nonInteractive ? true : await prompts.confirm({
|
|
811
|
+
message: `Create ${ENV_FILENAME} template?`,
|
|
812
|
+
default: true,
|
|
813
|
+
});
|
|
814
|
+
if (createEnv) {
|
|
815
|
+
const template = generateEnvTemplate(projectName, config);
|
|
816
|
+
fs_1.default.writeFileSync(envPath, template);
|
|
817
|
+
console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} template`));
|
|
818
|
+
}
|
|
585
819
|
}
|
|
586
820
|
}
|
|
587
821
|
// Add to .gitignore
|