genbox 1.0.11 → 1.0.13
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/config.js +273 -0
- package/dist/commands/create.js +139 -9
- package/dist/commands/init.js +154 -84
- package/dist/commands/migrate.js +246 -0
- package/dist/commands/resolve.js +243 -0
- package/dist/commands/scan.js +354 -0
- package/dist/commands/validate.js +529 -0
- package/dist/config-explainer.js +379 -0
- package/dist/detected-config.js +145 -0
- package/dist/index.js +12 -1
- package/dist/migration.js +334 -0
- package/dist/profile-resolver.js +8 -3
- package/dist/schema-v3.js +36 -0
- package/dist/schema-v4.js +54 -0
- package/dist/strict-mode.js +288 -0
- package/package.json +1 -1
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Config Command
|
|
4
|
+
*
|
|
5
|
+
* Provides configuration inspection and debugging tools:
|
|
6
|
+
* - genbox config show - Show resolved configuration
|
|
7
|
+
* - genbox config show --explain - Show where each value comes from
|
|
8
|
+
* - genbox config diff <a> <b> - Compare two profiles
|
|
9
|
+
*/
|
|
10
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
13
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
14
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
15
|
+
}
|
|
16
|
+
Object.defineProperty(o, k2, desc);
|
|
17
|
+
}) : (function(o, m, k, k2) {
|
|
18
|
+
if (k2 === undefined) k2 = k;
|
|
19
|
+
o[k2] = m[k];
|
|
20
|
+
}));
|
|
21
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
22
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
23
|
+
}) : function(o, v) {
|
|
24
|
+
o["default"] = v;
|
|
25
|
+
});
|
|
26
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
27
|
+
var ownKeys = function(o) {
|
|
28
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
29
|
+
var ar = [];
|
|
30
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
31
|
+
return ar;
|
|
32
|
+
};
|
|
33
|
+
return ownKeys(o);
|
|
34
|
+
};
|
|
35
|
+
return function (mod) {
|
|
36
|
+
if (mod && mod.__esModule) return mod;
|
|
37
|
+
var result = {};
|
|
38
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
39
|
+
__setModuleDefault(result, mod);
|
|
40
|
+
return result;
|
|
41
|
+
};
|
|
42
|
+
})();
|
|
43
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
44
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
45
|
+
};
|
|
46
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
47
|
+
exports.configCommand = void 0;
|
|
48
|
+
const commander_1 = require("commander");
|
|
49
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
50
|
+
const yaml = __importStar(require("js-yaml"));
|
|
51
|
+
const config_loader_1 = require("../config-loader");
|
|
52
|
+
const config_explainer_1 = require("../config-explainer");
|
|
53
|
+
exports.configCommand = new commander_1.Command('config')
|
|
54
|
+
.description('Inspect and debug configuration')
|
|
55
|
+
.addCommand(createShowCommand())
|
|
56
|
+
.addCommand(createDiffCommand());
|
|
57
|
+
function createShowCommand() {
|
|
58
|
+
return new commander_1.Command('show')
|
|
59
|
+
.description('Show resolved configuration')
|
|
60
|
+
.argument('[path]', 'Specific config path to show (e.g., apps.web, profiles.quick)')
|
|
61
|
+
.option('--explain', 'Show where each value comes from')
|
|
62
|
+
.option('--profile <profile>', 'Show configuration for a specific profile')
|
|
63
|
+
.option('--json', 'Output as JSON')
|
|
64
|
+
.option('--yaml', 'Output as YAML (default)')
|
|
65
|
+
.action(async (configPath, options) => {
|
|
66
|
+
const configLoader = new config_loader_1.ConfigLoader();
|
|
67
|
+
const loadResult = await configLoader.load();
|
|
68
|
+
if (!loadResult.found || !loadResult.config) {
|
|
69
|
+
console.log(chalk_1.default.red('No genbox.yaml found in current directory or parent directories'));
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
const config = loadResult.config;
|
|
73
|
+
if (options.explain) {
|
|
74
|
+
await showWithExplain(config, loadResult, configPath, options.profile);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
await showConfig(config, configPath, options);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
function createDiffCommand() {
|
|
82
|
+
return new commander_1.Command('diff')
|
|
83
|
+
.description('Compare two profiles')
|
|
84
|
+
.argument('<profile1>', 'First profile name')
|
|
85
|
+
.argument('<profile2>', 'Second profile name')
|
|
86
|
+
.action(async (profile1, profile2) => {
|
|
87
|
+
const configLoader = new config_loader_1.ConfigLoader();
|
|
88
|
+
const loadResult = await configLoader.load();
|
|
89
|
+
if (!loadResult.found || !loadResult.config) {
|
|
90
|
+
console.log(chalk_1.default.red('No genbox.yaml found'));
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
const config = loadResult.config;
|
|
94
|
+
const p1 = configLoader.getProfile(config, profile1);
|
|
95
|
+
const p2 = configLoader.getProfile(config, profile2);
|
|
96
|
+
if (!p1) {
|
|
97
|
+
console.log(chalk_1.default.red(`Profile '${profile1}' not found`));
|
|
98
|
+
process.exit(1);
|
|
99
|
+
}
|
|
100
|
+
if (!p2) {
|
|
101
|
+
console.log(chalk_1.default.red(`Profile '${profile2}' not found`));
|
|
102
|
+
process.exit(1);
|
|
103
|
+
}
|
|
104
|
+
showProfileDiff(profile1, p1, profile2, p2, config);
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
async function showConfig(config, path, options) {
|
|
108
|
+
let data = config;
|
|
109
|
+
// If profile specified, show that profile's resolved config
|
|
110
|
+
if (options.profile) {
|
|
111
|
+
const configLoader = new config_loader_1.ConfigLoader();
|
|
112
|
+
const profile = configLoader.getProfile(config, options.profile);
|
|
113
|
+
if (!profile) {
|
|
114
|
+
console.log(chalk_1.default.red(`Profile '${options.profile}' not found`));
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
data = profile;
|
|
118
|
+
}
|
|
119
|
+
// Navigate to specific path if provided
|
|
120
|
+
if (path) {
|
|
121
|
+
const parts = path.split('.');
|
|
122
|
+
let current = data;
|
|
123
|
+
for (const part of parts) {
|
|
124
|
+
if (current && typeof current === 'object' && part in current) {
|
|
125
|
+
current = current[part];
|
|
126
|
+
}
|
|
127
|
+
else {
|
|
128
|
+
console.log(chalk_1.default.red(`Path '${path}' not found in configuration`));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
data = current;
|
|
133
|
+
}
|
|
134
|
+
// Output
|
|
135
|
+
if (options.json) {
|
|
136
|
+
console.log(JSON.stringify(data, null, 2));
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
console.log(yaml.dump(data, { lineWidth: 120, noRefs: true }));
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
async function showWithExplain(config, loadResult, path, profileName) {
|
|
143
|
+
const explainer = new config_explainer_1.ConfigExplainer(loadResult);
|
|
144
|
+
console.log(chalk_1.default.bold.cyan('\n📋 Configuration Analysis\n'));
|
|
145
|
+
// Show sources
|
|
146
|
+
console.log(chalk_1.default.bold('Config Sources (priority order):'));
|
|
147
|
+
for (const source of loadResult.sources) {
|
|
148
|
+
const icon = source.type === 'workspace' ? '🏢' : source.type === 'project' ? '📁' : '👤';
|
|
149
|
+
console.log(` ${icon} ${source.type}: ${source.path}`);
|
|
150
|
+
}
|
|
151
|
+
console.log();
|
|
152
|
+
// If path specified, show just that path
|
|
153
|
+
if (path) {
|
|
154
|
+
const explanation = explainer.explain(path, profileName);
|
|
155
|
+
printExplanation(path, explanation);
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
// Show key configuration values with explanations
|
|
159
|
+
console.log(chalk_1.default.bold('Key Values:\n'));
|
|
160
|
+
// Project info
|
|
161
|
+
printExplanation('project.name', explainer.explain('project.name'));
|
|
162
|
+
printExplanation('project.structure', explainer.explain('project.structure'));
|
|
163
|
+
// Apps
|
|
164
|
+
if (config.apps) {
|
|
165
|
+
console.log(chalk_1.default.bold('\nApps:'));
|
|
166
|
+
for (const appName of Object.keys(config.apps)) {
|
|
167
|
+
console.log(chalk_1.default.cyan(`\n ${appName}:`));
|
|
168
|
+
printExplanation(` apps.${appName}.type`, explainer.explain(`apps.${appName}.type`), 4);
|
|
169
|
+
printExplanation(` apps.${appName}.port`, explainer.explain(`apps.${appName}.port`), 4);
|
|
170
|
+
printExplanation(` apps.${appName}.framework`, explainer.explain(`apps.${appName}.framework`), 4);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
// Profiles
|
|
174
|
+
if (config.profiles && profileName) {
|
|
175
|
+
console.log(chalk_1.default.bold(`\nProfile '${profileName}':`));
|
|
176
|
+
printExplanation(' size', explainer.explain('size', profileName), 4);
|
|
177
|
+
printExplanation(' apps', explainer.explain('apps', profileName), 4);
|
|
178
|
+
printExplanation(' connect_to', explainer.explain('connect_to', profileName), 4);
|
|
179
|
+
printExplanation(' database.mode', explainer.explain('database.mode', profileName), 4);
|
|
180
|
+
}
|
|
181
|
+
// Defaults
|
|
182
|
+
if (config.defaults) {
|
|
183
|
+
console.log(chalk_1.default.bold('\nDefaults:'));
|
|
184
|
+
printExplanation(' defaults.size', explainer.explain('defaults.size'), 4);
|
|
185
|
+
printExplanation(' defaults.branch', explainer.explain('defaults.branch'), 4);
|
|
186
|
+
}
|
|
187
|
+
// Detection warnings
|
|
188
|
+
const warnings = explainer.getDetectionWarnings();
|
|
189
|
+
if (warnings.length > 0) {
|
|
190
|
+
console.log(chalk_1.default.bold.yellow('\n⚠️ Detection Warnings:\n'));
|
|
191
|
+
for (const warning of warnings) {
|
|
192
|
+
console.log(chalk_1.default.yellow(` • ${warning}`));
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
console.log();
|
|
196
|
+
}
|
|
197
|
+
function printExplanation(path, sources, indent = 0) {
|
|
198
|
+
const pad = ' '.repeat(indent);
|
|
199
|
+
if (sources.length === 0) {
|
|
200
|
+
console.log(`${pad}${chalk_1.default.dim(path)}: ${chalk_1.default.dim('(not set)')}`);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const finalSource = sources.find(s => s.used) || sources[0];
|
|
204
|
+
const valueStr = formatValue(finalSource.value);
|
|
205
|
+
console.log(`${pad}${chalk_1.default.white(path)}: ${chalk_1.default.green(valueStr)}`);
|
|
206
|
+
for (const source of sources) {
|
|
207
|
+
const icon = source.used ? chalk_1.default.green('✓') : chalk_1.default.dim('○');
|
|
208
|
+
const sourceLabel = getSourceLabel(source);
|
|
209
|
+
const value = source.value !== undefined ? formatValue(source.value) : chalk_1.default.dim('(not set)');
|
|
210
|
+
if (source.used) {
|
|
211
|
+
console.log(`${pad} ${icon} ${sourceLabel}: ${value}`);
|
|
212
|
+
if (source.detectedFrom) {
|
|
213
|
+
console.log(`${pad} └─ ${chalk_1.default.dim(`detected from: ${source.detectedFrom}`)}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
else {
|
|
217
|
+
console.log(`${pad} ${icon} ${chalk_1.default.dim(sourceLabel)}: ${chalk_1.default.dim(String(value))}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
function getSourceLabel(source) {
|
|
222
|
+
switch (source.source) {
|
|
223
|
+
case 'cli':
|
|
224
|
+
return 'CLI flag';
|
|
225
|
+
case 'profile':
|
|
226
|
+
return `Profile '${source.profileName || 'unknown'}'`;
|
|
227
|
+
case 'project':
|
|
228
|
+
return 'genbox.yaml (explicit)';
|
|
229
|
+
case 'workspace':
|
|
230
|
+
return 'workspace genbox.yaml';
|
|
231
|
+
case 'detected':
|
|
232
|
+
return 'detected.yaml';
|
|
233
|
+
case 'default':
|
|
234
|
+
return 'schema default';
|
|
235
|
+
case 'inferred':
|
|
236
|
+
return 'inferred';
|
|
237
|
+
default:
|
|
238
|
+
return source.source;
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
function formatValue(value) {
|
|
242
|
+
if (value === undefined)
|
|
243
|
+
return '(undefined)';
|
|
244
|
+
if (value === null)
|
|
245
|
+
return '(null)';
|
|
246
|
+
if (Array.isArray(value))
|
|
247
|
+
return `[${value.join(', ')}]`;
|
|
248
|
+
if (typeof value === 'object')
|
|
249
|
+
return JSON.stringify(value);
|
|
250
|
+
return String(value);
|
|
251
|
+
}
|
|
252
|
+
function showProfileDiff(name1, profile1, name2, profile2, config) {
|
|
253
|
+
console.log(chalk_1.default.bold.cyan(`\n📊 Comparing profiles: ${name1} vs ${name2}\n`));
|
|
254
|
+
const allKeys = new Set([
|
|
255
|
+
...Object.keys(profile1),
|
|
256
|
+
...Object.keys(profile2),
|
|
257
|
+
]);
|
|
258
|
+
let hasDiff = false;
|
|
259
|
+
for (const key of allKeys) {
|
|
260
|
+
const v1 = profile1[key];
|
|
261
|
+
const v2 = profile2[key];
|
|
262
|
+
if (JSON.stringify(v1) !== JSON.stringify(v2)) {
|
|
263
|
+
hasDiff = true;
|
|
264
|
+
console.log(chalk_1.default.bold(` ${key}:`));
|
|
265
|
+
console.log(chalk_1.default.red(` - ${name1}: ${formatValue(v1)}`));
|
|
266
|
+
console.log(chalk_1.default.green(` + ${name2}: ${formatValue(v2)}`));
|
|
267
|
+
console.log();
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
if (!hasDiff) {
|
|
271
|
+
console.log(chalk_1.default.dim(' No differences found'));
|
|
272
|
+
}
|
|
273
|
+
}
|
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`);
|