genbox 1.0.11 → 1.0.12
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/create.js +139 -9
- package/dist/commands/init.js +154 -84
- package/package.json +1 -1
package/dist/commands/create.js
CHANGED
|
@@ -348,6 +348,64 @@ function displayResolvedConfig(resolved) {
|
|
|
348
348
|
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
349
349
|
console.log('');
|
|
350
350
|
}
|
|
351
|
+
/**
|
|
352
|
+
* Parse .env.genbox file into segregated sections
|
|
353
|
+
*/
|
|
354
|
+
function parseEnvGenboxSections(content) {
|
|
355
|
+
const sections = new Map();
|
|
356
|
+
let currentSection = 'GLOBAL';
|
|
357
|
+
let currentContent = [];
|
|
358
|
+
for (const line of content.split('\n')) {
|
|
359
|
+
const sectionMatch = line.match(/^# === ([^=]+) ===$/);
|
|
360
|
+
if (sectionMatch) {
|
|
361
|
+
// Save previous section
|
|
362
|
+
if (currentContent.length > 0) {
|
|
363
|
+
sections.set(currentSection, currentContent.join('\n').trim());
|
|
364
|
+
}
|
|
365
|
+
currentSection = sectionMatch[1].trim();
|
|
366
|
+
currentContent = [];
|
|
367
|
+
}
|
|
368
|
+
else if (currentSection !== 'END') {
|
|
369
|
+
currentContent.push(line);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
// Save last section
|
|
373
|
+
if (currentContent.length > 0 && currentSection !== 'END') {
|
|
374
|
+
sections.set(currentSection, currentContent.join('\n').trim());
|
|
375
|
+
}
|
|
376
|
+
return sections;
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Build env content for a specific app by combining GLOBAL + app-specific sections
|
|
380
|
+
*/
|
|
381
|
+
function buildAppEnvContent(sections, appName, apiUrl) {
|
|
382
|
+
const parts = [];
|
|
383
|
+
// Always include GLOBAL section
|
|
384
|
+
const globalSection = sections.get('GLOBAL');
|
|
385
|
+
if (globalSection) {
|
|
386
|
+
parts.push(globalSection);
|
|
387
|
+
}
|
|
388
|
+
// Include app-specific section if exists
|
|
389
|
+
const appSection = sections.get(appName);
|
|
390
|
+
if (appSection) {
|
|
391
|
+
parts.push(appSection);
|
|
392
|
+
}
|
|
393
|
+
let envContent = parts.join('\n\n');
|
|
394
|
+
// Expand ${API_URL} references
|
|
395
|
+
envContent = envContent.replace(/\$\{API_URL\}/g, apiUrl);
|
|
396
|
+
// Keep only actual env vars (filter out pure comment lines but keep var definitions)
|
|
397
|
+
envContent = envContent
|
|
398
|
+
.split('\n')
|
|
399
|
+
.filter(line => {
|
|
400
|
+
const trimmed = line.trim();
|
|
401
|
+
// Keep empty lines, lines with = (even if commented), and non-comment lines
|
|
402
|
+
return trimmed === '' || trimmed.includes('=') || !trimmed.startsWith('#');
|
|
403
|
+
})
|
|
404
|
+
.join('\n')
|
|
405
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
406
|
+
.trim();
|
|
407
|
+
return envContent;
|
|
408
|
+
}
|
|
351
409
|
/**
|
|
352
410
|
* Build API payload from resolved config
|
|
353
411
|
*/
|
|
@@ -368,24 +426,82 @@ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
|
|
|
368
426
|
}
|
|
369
427
|
// Build files bundle
|
|
370
428
|
const files = [];
|
|
429
|
+
// Track env files to move in setup script (staging approach to avoid blocking git clone)
|
|
430
|
+
const envFilesToMove = [];
|
|
371
431
|
// Send .env.genbox content to server for each app
|
|
372
432
|
const envGenboxPath = path.join(process.cwd(), '.env.genbox');
|
|
373
433
|
if (fs.existsSync(envGenboxPath)) {
|
|
374
|
-
const
|
|
375
|
-
//
|
|
434
|
+
const rawEnvContent = fs.readFileSync(envGenboxPath, 'utf-8');
|
|
435
|
+
// Parse into sections
|
|
436
|
+
const sections = parseEnvGenboxSections(rawEnvContent);
|
|
437
|
+
// Parse GLOBAL section to get API URL values
|
|
438
|
+
const globalSection = sections.get('GLOBAL') || '';
|
|
439
|
+
const envVarsFromFile = {};
|
|
440
|
+
for (const line of globalSection.split('\n')) {
|
|
441
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
442
|
+
if (match) {
|
|
443
|
+
let value = match[2].trim();
|
|
444
|
+
// Remove quotes if present
|
|
445
|
+
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
446
|
+
(value.startsWith("'") && value.endsWith("'"))) {
|
|
447
|
+
value = value.slice(1, -1);
|
|
448
|
+
}
|
|
449
|
+
envVarsFromFile[match[1]] = value;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
// Determine API_URL based on profile's connect_to setting
|
|
453
|
+
const connectTo = resolved.profile ?
|
|
454
|
+
(config.profiles?.[resolved.profile]?.connect_to) : undefined;
|
|
455
|
+
let apiUrl;
|
|
456
|
+
if (connectTo) {
|
|
457
|
+
// Use the environment-specific API URL (e.g., STAGING_API_URL)
|
|
458
|
+
const envApiVarName = `${connectTo.toUpperCase()}_API_URL`;
|
|
459
|
+
apiUrl = envVarsFromFile[envApiVarName] || resolved.env['API_URL'] || 'http://localhost:3050';
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
// Use local API URL
|
|
463
|
+
apiUrl = envVarsFromFile['LOCAL_API_URL'] || 'http://localhost:3050';
|
|
464
|
+
}
|
|
465
|
+
// Add env file for each app - filtered by selected apps only
|
|
376
466
|
for (const app of resolved.apps) {
|
|
377
467
|
const appPath = config.apps[app.name]?.path || app.name;
|
|
378
468
|
const repoPath = resolved.repos.find(r => r.name === app.name)?.path ||
|
|
379
469
|
(resolved.repos[0]?.path ? `${resolved.repos[0].path}/${appPath}` : `/home/dev/${config.project.name}/${appPath}`);
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
470
|
+
// Check if this app has microservices (sections like api/gateway, api/auth)
|
|
471
|
+
const servicesSections = Array.from(sections.keys()).filter(s => s.startsWith(`${app.name}/`));
|
|
472
|
+
if (servicesSections.length > 0) {
|
|
473
|
+
// App has microservices - create env file for each service
|
|
474
|
+
for (const serviceSectionName of servicesSections) {
|
|
475
|
+
const serviceName = serviceSectionName.split('/')[1];
|
|
476
|
+
// Build service-specific env content (GLOBAL + service section)
|
|
477
|
+
const serviceEnvContent = buildAppEnvContent(sections, serviceSectionName, apiUrl);
|
|
478
|
+
const stagingName = `${app.name}-${serviceName}.env`;
|
|
479
|
+
const targetPath = `${repoPath}/apps/${serviceName}/.env`;
|
|
480
|
+
files.push({
|
|
481
|
+
path: `/home/dev/.env-staging/${stagingName}`,
|
|
482
|
+
content: serviceEnvContent,
|
|
483
|
+
permissions: '0644',
|
|
484
|
+
});
|
|
485
|
+
envFilesToMove.push({ stagingName, targetPath });
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
else {
|
|
489
|
+
// Regular app - build app-specific env content (GLOBAL + app section)
|
|
490
|
+
const appEnvContent = buildAppEnvContent(sections, app.name, apiUrl);
|
|
491
|
+
files.push({
|
|
492
|
+
path: `/home/dev/.env-staging/${app.name}.env`,
|
|
493
|
+
content: appEnvContent,
|
|
494
|
+
permissions: '0644',
|
|
495
|
+
});
|
|
496
|
+
envFilesToMove.push({
|
|
497
|
+
stagingName: `${app.name}.env`,
|
|
498
|
+
targetPath: `${repoPath}/.env`,
|
|
499
|
+
});
|
|
500
|
+
}
|
|
385
501
|
}
|
|
386
502
|
}
|
|
387
503
|
// Add setup script if generated
|
|
388
|
-
const setupScript = generateSetupScript(resolved, config);
|
|
504
|
+
const setupScript = generateSetupScript(resolved, config, envFilesToMove);
|
|
389
505
|
if (setupScript) {
|
|
390
506
|
files.push({
|
|
391
507
|
path: '/home/dev/setup-genbox.sh',
|
|
@@ -431,13 +547,27 @@ function buildPayload(resolved, config, publicKey, privateKey, configLoader) {
|
|
|
431
547
|
/**
|
|
432
548
|
* Generate setup script
|
|
433
549
|
*/
|
|
434
|
-
function generateSetupScript(resolved, config) {
|
|
550
|
+
function generateSetupScript(resolved, config, envFilesToMove = []) {
|
|
435
551
|
const lines = [
|
|
436
552
|
'#!/bin/bash',
|
|
437
553
|
'# Generated by genbox create',
|
|
438
554
|
'set -e',
|
|
439
555
|
'',
|
|
440
556
|
];
|
|
557
|
+
// Move .env files from staging to their correct locations
|
|
558
|
+
// This runs after git clone has completed
|
|
559
|
+
if (envFilesToMove.length > 0) {
|
|
560
|
+
lines.push('# Move .env files from staging to app directories');
|
|
561
|
+
for (const { stagingName, targetPath } of envFilesToMove) {
|
|
562
|
+
lines.push(`if [ -f "/home/dev/.env-staging/${stagingName}" ]; then`);
|
|
563
|
+
lines.push(` mkdir -p "$(dirname "${targetPath}")"`);
|
|
564
|
+
lines.push(` mv "/home/dev/.env-staging/${stagingName}" "${targetPath}"`);
|
|
565
|
+
lines.push(` echo "Moved .env to ${targetPath}"`);
|
|
566
|
+
lines.push('fi');
|
|
567
|
+
}
|
|
568
|
+
lines.push('rm -rf /home/dev/.env-staging 2>/dev/null || true');
|
|
569
|
+
lines.push('');
|
|
570
|
+
}
|
|
441
571
|
// Change to project directory
|
|
442
572
|
if (resolved.repos.length > 0) {
|
|
443
573
|
lines.push(`cd ${resolved.repos[0].path} || exit 1`);
|
package/dist/commands/init.js
CHANGED
|
@@ -103,13 +103,15 @@ function detectAppGitRepos(apps, rootDir) {
|
|
|
103
103
|
return repos;
|
|
104
104
|
}
|
|
105
105
|
/**
|
|
106
|
-
* Find .env files in app directories
|
|
106
|
+
* Find .env files in app directories (including nested microservices)
|
|
107
107
|
*/
|
|
108
108
|
function findAppEnvFiles(apps, rootDir) {
|
|
109
109
|
const envFiles = [];
|
|
110
110
|
const envPatterns = ['.env', '.env.local', '.env.development'];
|
|
111
111
|
for (const app of apps) {
|
|
112
112
|
const appDir = path_1.default.join(rootDir, app.path);
|
|
113
|
+
// Check for direct .env file in app directory
|
|
114
|
+
let foundDirectEnv = false;
|
|
113
115
|
for (const pattern of envPatterns) {
|
|
114
116
|
const envPath = path_1.default.join(appDir, pattern);
|
|
115
117
|
if (fs_1.default.existsSync(envPath)) {
|
|
@@ -118,7 +120,35 @@ function findAppEnvFiles(apps, rootDir) {
|
|
|
118
120
|
envFile: pattern,
|
|
119
121
|
fullPath: envPath,
|
|
120
122
|
});
|
|
121
|
-
|
|
123
|
+
foundDirectEnv = true;
|
|
124
|
+
break;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// Check for nested microservices (e.g., api/apps/*)
|
|
128
|
+
const appsSubdir = path_1.default.join(appDir, 'apps');
|
|
129
|
+
if (fs_1.default.existsSync(appsSubdir) && fs_1.default.statSync(appsSubdir).isDirectory()) {
|
|
130
|
+
try {
|
|
131
|
+
const services = fs_1.default.readdirSync(appsSubdir);
|
|
132
|
+
for (const service of services) {
|
|
133
|
+
const serviceDir = path_1.default.join(appsSubdir, service);
|
|
134
|
+
if (!fs_1.default.statSync(serviceDir).isDirectory())
|
|
135
|
+
continue;
|
|
136
|
+
for (const pattern of envPatterns) {
|
|
137
|
+
const envPath = path_1.default.join(serviceDir, pattern);
|
|
138
|
+
if (fs_1.default.existsSync(envPath)) {
|
|
139
|
+
envFiles.push({
|
|
140
|
+
appName: `${app.name}/${service}`,
|
|
141
|
+
envFile: pattern,
|
|
142
|
+
fullPath: envPath,
|
|
143
|
+
isService: true,
|
|
144
|
+
});
|
|
145
|
+
break;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
// Ignore errors reading subdirectories
|
|
122
152
|
}
|
|
123
153
|
}
|
|
124
154
|
}
|
|
@@ -454,6 +484,20 @@ exports.initCommand = new commander_1.Command('init')
|
|
|
454
484
|
});
|
|
455
485
|
fs_1.default.writeFileSync(configPath, yamlContent);
|
|
456
486
|
console.log(chalk_1.default.green(`\n✔ Configuration saved to ${CONFIG_FILENAME}`));
|
|
487
|
+
// Add API URLs from environments to envVarsToAdd
|
|
488
|
+
// Always add LOCAL_API_URL for local development
|
|
489
|
+
envVarsToAdd['LOCAL_API_URL'] = 'http://localhost:3050';
|
|
490
|
+
if (v3Config.environments) {
|
|
491
|
+
for (const [envName, envConfig] of Object.entries(v3Config.environments)) {
|
|
492
|
+
const apiUrl = envConfig.api?.api ||
|
|
493
|
+
envConfig.api?.url ||
|
|
494
|
+
envConfig.api?.gateway;
|
|
495
|
+
if (apiUrl) {
|
|
496
|
+
const varName = `${envName.toUpperCase()}_API_URL`;
|
|
497
|
+
envVarsToAdd[varName] = apiUrl;
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
457
501
|
// Generate .env.genbox
|
|
458
502
|
await setupEnvFile(projectName, v3Config, nonInteractive, scan, isMultiRepo, envVarsToAdd, overwriteExisting);
|
|
459
503
|
// Show warnings
|
|
@@ -467,26 +511,35 @@ exports.initCommand = new commander_1.Command('init')
|
|
|
467
511
|
// Show API URL guidance if environments are configured
|
|
468
512
|
if (v3Config.environments && Object.keys(v3Config.environments).length > 0) {
|
|
469
513
|
console.log('');
|
|
470
|
-
console.log(chalk_1.default.
|
|
514
|
+
console.log(chalk_1.default.blue('=== API URL Configuration ==='));
|
|
515
|
+
console.log(chalk_1.default.dim('The following API URLs were added to .env.genbox:'));
|
|
471
516
|
console.log('');
|
|
517
|
+
console.log(chalk_1.default.dim(' LOCAL_API_URL=http://localhost:3050'));
|
|
472
518
|
for (const [envName, envConfig] of Object.entries(v3Config.environments)) {
|
|
473
519
|
const apiUrl = envConfig.api?.api ||
|
|
474
520
|
envConfig.api?.url ||
|
|
475
521
|
envConfig.api?.gateway;
|
|
476
522
|
if (apiUrl) {
|
|
477
|
-
|
|
478
|
-
console.log(chalk_1.default.
|
|
479
|
-
console.log(chalk_1.default.cyan(` VITE_AUTH_SERVICE_URL="${apiUrl}"`));
|
|
480
|
-
console.log(chalk_1.default.cyan(` NEXT_PUBLIC_API_BASE_URL="${apiUrl}"`));
|
|
481
|
-
console.log('');
|
|
523
|
+
const varName = `${envName.toUpperCase()}_API_URL`;
|
|
524
|
+
console.log(chalk_1.default.dim(` ${varName}=${apiUrl}`));
|
|
482
525
|
}
|
|
483
526
|
}
|
|
527
|
+
console.log('');
|
|
528
|
+
console.log(chalk_1.default.yellow('To use dynamic API URLs:'));
|
|
529
|
+
console.log(chalk_1.default.dim(' Use ${API_URL} in your app env vars, e.g.:'));
|
|
530
|
+
console.log(chalk_1.default.cyan(' VITE_API_BASE_URL=${API_URL}'));
|
|
531
|
+
console.log(chalk_1.default.cyan(' NEXT_PUBLIC_API_URL=${API_URL}'));
|
|
532
|
+
console.log('');
|
|
533
|
+
console.log(chalk_1.default.dim(' At create time, ${API_URL} expands based on profile:'));
|
|
534
|
+
console.log(chalk_1.default.dim(' • connect_to: staging → uses STAGING_API_URL'));
|
|
535
|
+
console.log(chalk_1.default.dim(' • connect_to: production → uses PRODUCTION_API_URL'));
|
|
536
|
+
console.log(chalk_1.default.dim(' • local/no connect_to → uses LOCAL_API_URL'));
|
|
484
537
|
}
|
|
485
538
|
// Next steps
|
|
486
539
|
console.log('');
|
|
487
540
|
console.log(chalk_1.default.bold('Next steps:'));
|
|
488
541
|
console.log(chalk_1.default.dim(` 1. Review and edit ${CONFIG_FILENAME}`));
|
|
489
|
-
console.log(chalk_1.default.dim(` 2. Update
|
|
542
|
+
console.log(chalk_1.default.dim(` 2. Update ${ENV_FILENAME} to use API URL variables where needed`));
|
|
490
543
|
console.log(chalk_1.default.dim(` 3. Run 'genbox profiles' to see available profiles`));
|
|
491
544
|
console.log(chalk_1.default.dim(` 4. Run 'genbox create <name> --profile <profile>' to create an environment`));
|
|
492
545
|
}
|
|
@@ -764,7 +817,7 @@ async function setupEnvironments(scan, config, isMultiRepo = false) {
|
|
|
764
817
|
return Object.keys(environments).length > 0 ? environments : undefined;
|
|
765
818
|
}
|
|
766
819
|
/**
|
|
767
|
-
* Setup .env.genbox file
|
|
820
|
+
* Setup .env.genbox file with segregated app sections
|
|
768
821
|
*/
|
|
769
822
|
async function setupEnvFile(projectName, config, nonInteractive = false, scan, isMultiRepo = false, extraEnvVars = {}, overwriteExisting = false) {
|
|
770
823
|
const envPath = path_1.default.join(process.cwd(), ENV_FILENAME);
|
|
@@ -778,66 +831,84 @@ async function setupEnvFile(projectName, config, nonInteractive = false, scan, i
|
|
|
778
831
|
return;
|
|
779
832
|
}
|
|
780
833
|
}
|
|
834
|
+
// Build segregated content with GLOBAL section first
|
|
835
|
+
let segregatedContent = `# Genbox Environment Variables
|
|
836
|
+
# Project: ${projectName}
|
|
837
|
+
# DO NOT COMMIT THIS FILE
|
|
838
|
+
#
|
|
839
|
+
# This file uses segregated sections for each app/service.
|
|
840
|
+
# At 'genbox create' time, only GLOBAL + selected app sections are used.
|
|
841
|
+
# Use \${API_URL} for dynamic API URLs based on profile's connect_to setting.
|
|
842
|
+
|
|
843
|
+
# === GLOBAL ===
|
|
844
|
+
# These variables are always included regardless of which apps are selected
|
|
845
|
+
|
|
846
|
+
`;
|
|
847
|
+
// Add global env vars
|
|
848
|
+
for (const [key, value] of Object.entries(extraEnvVars)) {
|
|
849
|
+
segregatedContent += `${key}=${value}\n`;
|
|
850
|
+
}
|
|
851
|
+
// Add GIT authentication placeholder if not already added
|
|
852
|
+
if (!extraEnvVars['GIT_TOKEN']) {
|
|
853
|
+
segregatedContent += `# GIT_TOKEN=ghp_xxxxxxxxxxxx\n`;
|
|
854
|
+
}
|
|
855
|
+
segregatedContent += `
|
|
856
|
+
# Database URLs (used by profiles with database mode)
|
|
857
|
+
# STAGING_MONGODB_URL=mongodb+srv://user:password@staging.mongodb.net
|
|
858
|
+
# PROD_MONGODB_URL=mongodb+srv://readonly:password@prod.mongodb.net
|
|
859
|
+
|
|
860
|
+
`;
|
|
781
861
|
// For multi-repo: find env files in app directories
|
|
782
862
|
if (isMultiRepo && scan) {
|
|
783
863
|
const appEnvFiles = findAppEnvFiles(scan.apps, process.cwd());
|
|
784
864
|
if (appEnvFiles.length > 0 && !nonInteractive) {
|
|
785
865
|
console.log('');
|
|
786
866
|
console.log(chalk_1.default.blue('=== Environment Files ==='));
|
|
787
|
-
|
|
867
|
+
// Group by app type
|
|
868
|
+
const directApps = appEnvFiles.filter(e => !e.isService);
|
|
869
|
+
const serviceApps = appEnvFiles.filter(e => e.isService);
|
|
870
|
+
if (directApps.length > 0) {
|
|
871
|
+
console.log(chalk_1.default.dim(`Found ${directApps.length} app env files`));
|
|
872
|
+
}
|
|
873
|
+
if (serviceApps.length > 0) {
|
|
874
|
+
console.log(chalk_1.default.dim(`Found ${serviceApps.length} microservice env files`));
|
|
875
|
+
}
|
|
788
876
|
const envChoices = appEnvFiles.map(env => ({
|
|
789
|
-
name: `${env.appName}
|
|
877
|
+
name: env.isService ? `${env.appName} (service)` : env.appName,
|
|
790
878
|
value: env.fullPath,
|
|
791
879
|
checked: true,
|
|
792
880
|
}));
|
|
793
881
|
const selectedEnvFiles = await prompts.checkbox({
|
|
794
|
-
message: 'Select .env files to
|
|
882
|
+
message: 'Select .env files to include in .env.genbox:',
|
|
795
883
|
choices: envChoices,
|
|
796
884
|
});
|
|
797
885
|
if (selectedEnvFiles.length > 0) {
|
|
798
|
-
let mergedContent = `# Genbox Environment Variables
|
|
799
|
-
# Merged from: ${selectedEnvFiles.map(f => path_1.default.relative(process.cwd(), f)).join(', ')}
|
|
800
|
-
# DO NOT COMMIT THIS FILE
|
|
801
|
-
#
|
|
802
|
-
# Add staging/production URLs:
|
|
803
|
-
# STAGING_MONGODB_URL=mongodb+srv://...
|
|
804
|
-
# STAGING_REDIS_URL=redis://...
|
|
805
|
-
# PROD_MONGODB_URL=mongodb+srv://...
|
|
806
|
-
#
|
|
807
|
-
# Git authentication:
|
|
808
|
-
# GIT_TOKEN=ghp_xxxxxxxxxxxx
|
|
809
|
-
|
|
810
|
-
`;
|
|
811
886
|
for (const envFilePath of selectedEnvFiles) {
|
|
812
887
|
const appInfo = appEnvFiles.find(e => e.fullPath === envFilePath);
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
888
|
+
if (!appInfo)
|
|
889
|
+
continue;
|
|
890
|
+
const content = fs_1.default.readFileSync(envFilePath, 'utf8').trim();
|
|
891
|
+
// Add section header and content
|
|
892
|
+
segregatedContent += `# === ${appInfo.appName} ===\n`;
|
|
893
|
+
segregatedContent += content;
|
|
894
|
+
segregatedContent += '\n\n';
|
|
817
895
|
}
|
|
818
|
-
fs_1.default.writeFileSync(envPath, mergedContent);
|
|
819
|
-
console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} from ${selectedEnvFiles.length} app env files`));
|
|
820
896
|
}
|
|
821
897
|
}
|
|
822
898
|
else if (appEnvFiles.length > 0 && nonInteractive) {
|
|
823
899
|
// Non-interactive: merge all env files
|
|
824
|
-
let mergedContent = `# Genbox Environment Variables
|
|
825
|
-
# Merged from app directories
|
|
826
|
-
# DO NOT COMMIT THIS FILE
|
|
827
|
-
|
|
828
|
-
`;
|
|
829
900
|
for (const envFile of appEnvFiles) {
|
|
830
|
-
const content = fs_1.default.readFileSync(envFile.fullPath, 'utf8');
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
901
|
+
const content = fs_1.default.readFileSync(envFile.fullPath, 'utf8').trim();
|
|
902
|
+
segregatedContent += `# === ${envFile.appName} ===\n`;
|
|
903
|
+
segregatedContent += content;
|
|
904
|
+
segregatedContent += '\n\n';
|
|
834
905
|
}
|
|
835
|
-
fs_1.default.writeFileSync(envPath, mergedContent);
|
|
836
|
-
console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} from ${appEnvFiles.length} app env files`));
|
|
837
906
|
}
|
|
838
907
|
}
|
|
839
|
-
// If no env
|
|
840
|
-
|
|
908
|
+
// If no app env files found, check for root .env
|
|
909
|
+
const hasAppSections = segregatedContent.includes('# === ') &&
|
|
910
|
+
!segregatedContent.endsWith('# === GLOBAL ===\n');
|
|
911
|
+
if (!hasAppSections) {
|
|
841
912
|
const existingEnvFiles = ['.env.local', '.env', '.env.development'];
|
|
842
913
|
let existingEnvPath;
|
|
843
914
|
for (const envFile of existingEnvFiles) {
|
|
@@ -849,51 +920,28 @@ async function setupEnvFile(projectName, config, nonInteractive = false, scan, i
|
|
|
849
920
|
}
|
|
850
921
|
if (existingEnvPath) {
|
|
851
922
|
const copyExisting = nonInteractive ? true : await prompts.confirm({
|
|
852
|
-
message: `Found ${path_1.default.basename(existingEnvPath)}.
|
|
923
|
+
message: `Found ${path_1.default.basename(existingEnvPath)}. Include in ${ENV_FILENAME}?`,
|
|
853
924
|
default: true,
|
|
854
925
|
});
|
|
855
926
|
if (copyExisting) {
|
|
856
|
-
const content = fs_1.default.readFileSync(existingEnvPath, 'utf8');
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
# Add staging/production URLs:
|
|
862
|
-
# STAGING_MONGODB_URL=mongodb+srv://...
|
|
863
|
-
# STAGING_REDIS_URL=redis://...
|
|
864
|
-
# PROD_MONGODB_URL=mongodb+srv://...
|
|
865
|
-
#
|
|
866
|
-
# Git authentication:
|
|
867
|
-
# GIT_TOKEN=ghp_xxxxxxxxxxxx
|
|
868
|
-
|
|
869
|
-
`;
|
|
870
|
-
fs_1.default.writeFileSync(envPath, header + content);
|
|
871
|
-
console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} from ${path_1.default.basename(existingEnvPath)}`));
|
|
872
|
-
}
|
|
873
|
-
}
|
|
874
|
-
else {
|
|
875
|
-
const createEnv = nonInteractive ? true : await prompts.confirm({
|
|
876
|
-
message: `Create ${ENV_FILENAME} template?`,
|
|
877
|
-
default: true,
|
|
878
|
-
});
|
|
879
|
-
if (createEnv) {
|
|
880
|
-
const template = generateEnvTemplate(projectName, config);
|
|
881
|
-
fs_1.default.writeFileSync(envPath, template);
|
|
882
|
-
console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} template`));
|
|
927
|
+
const content = fs_1.default.readFileSync(existingEnvPath, 'utf8').trim();
|
|
928
|
+
segregatedContent += `# === root ===\n`;
|
|
929
|
+
segregatedContent += `# From ${path_1.default.basename(existingEnvPath)}\n`;
|
|
930
|
+
segregatedContent += content;
|
|
931
|
+
segregatedContent += '\n\n';
|
|
883
932
|
}
|
|
884
933
|
}
|
|
885
934
|
}
|
|
886
|
-
//
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
}
|
|
896
|
-
fs_1.default.writeFileSync(envPath, content.trim() + '\n' + extraSection);
|
|
935
|
+
// Add END marker
|
|
936
|
+
segregatedContent += `# === END ===\n`;
|
|
937
|
+
// Write the file
|
|
938
|
+
fs_1.default.writeFileSync(envPath, segregatedContent);
|
|
939
|
+
const sectionCount = (segregatedContent.match(/# === [^=]+ ===/g) || []).length - 2; // Exclude GLOBAL and END
|
|
940
|
+
if (sectionCount > 0) {
|
|
941
|
+
console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} with ${sectionCount} app section(s)`));
|
|
942
|
+
}
|
|
943
|
+
else {
|
|
944
|
+
console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME}`));
|
|
897
945
|
}
|
|
898
946
|
// Add to .gitignore
|
|
899
947
|
const gitignorePath = path_1.default.join(process.cwd(), '.gitignore');
|
|
@@ -915,6 +963,19 @@ function generateEnvTemplate(projectName, config) {
|
|
|
915
963
|
'# DO NOT COMMIT THIS FILE',
|
|
916
964
|
'',
|
|
917
965
|
'# ============================================',
|
|
966
|
+
'# API URL CONFIGURATION',
|
|
967
|
+
'# ============================================',
|
|
968
|
+
'# Use ${API_URL} in your app env vars (e.g., VITE_API_BASE_URL=${API_URL})',
|
|
969
|
+
'# At create time, ${API_URL} expands based on profile:',
|
|
970
|
+
'# - connect_to: staging → uses STAGING_API_URL',
|
|
971
|
+
'# - connect_to: production → uses PRODUCTION_API_URL',
|
|
972
|
+
'# - local/no connect_to → uses LOCAL_API_URL',
|
|
973
|
+
'',
|
|
974
|
+
'LOCAL_API_URL=http://localhost:3050',
|
|
975
|
+
'STAGING_API_URL=https://api.staging.example.com',
|
|
976
|
+
'# PRODUCTION_API_URL=https://api.example.com',
|
|
977
|
+
'',
|
|
978
|
+
'# ============================================',
|
|
918
979
|
'# STAGING ENVIRONMENT',
|
|
919
980
|
'# ============================================',
|
|
920
981
|
'',
|
|
@@ -957,6 +1018,15 @@ function generateEnvTemplate(projectName, config) {
|
|
|
957
1018
|
'STRIPE_SECRET_KEY=sk_test_xxx',
|
|
958
1019
|
'STRIPE_WEBHOOK_SECRET=whsec_xxx',
|
|
959
1020
|
'',
|
|
1021
|
+
'# ============================================',
|
|
1022
|
+
'# APPLICATION ENV VARS',
|
|
1023
|
+
'# ============================================',
|
|
1024
|
+
'# Use ${API_URL} for dynamic API URLs',
|
|
1025
|
+
'',
|
|
1026
|
+
'# Example:',
|
|
1027
|
+
'# VITE_API_BASE_URL=${API_URL}',
|
|
1028
|
+
'# NEXT_PUBLIC_API_URL=${API_URL}',
|
|
1029
|
+
'',
|
|
960
1030
|
];
|
|
961
1031
|
return lines.join('\n');
|
|
962
1032
|
}
|