genbox 1.0.47 → 1.0.49
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/api.js +62 -0
- package/dist/commands/create.js +189 -9
- package/dist/commands/db-sync.js +1 -1
- package/dist/commands/help.js +15 -13
- package/dist/commands/init.js +103 -1
- package/dist/commands/migrate.js +18 -215
- package/dist/commands/profiles.js +5 -11
- package/dist/commands/push.js +70 -32
- package/dist/commands/rebuild.js +3 -8
- package/dist/commands/validate.js +13 -13
- package/dist/config-explainer.js +1 -1
- package/dist/config-loader.js +74 -150
- package/dist/db-utils.js +416 -0
- package/dist/index.js +1 -1
- package/dist/profile-resolver.js +35 -34
- package/dist/schema-v4.js +35 -35
- package/dist/strict-mode.js +57 -126
- package/package.json +3 -2
- package/dist/migration.js +0 -335
- package/dist/schema-v3.js +0 -48
package/dist/config-loader.js
CHANGED
|
@@ -4,10 +4,7 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Loads genbox.yaml from the CURRENT directory only.
|
|
6
6
|
* No parent directory lookup - each project is self-contained.
|
|
7
|
-
*
|
|
8
|
-
* Additional sources (merged with project config):
|
|
9
|
-
* - User profiles (~/.genbox/profiles.yaml)
|
|
10
|
-
* - User defaults (~/.genbox/config.json)
|
|
7
|
+
* Only supports v4 configuration format.
|
|
11
8
|
*/
|
|
12
9
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
13
10
|
if (k2 === undefined) k2 = k;
|
|
@@ -44,7 +41,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
44
41
|
})();
|
|
45
42
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
43
|
exports.configLoader = exports.ConfigLoader = void 0;
|
|
47
|
-
exports.isV4 = isV4;
|
|
48
44
|
exports.getProfileConnection = getProfileConnection;
|
|
49
45
|
exports.getAppDependencies = getAppDependencies;
|
|
50
46
|
exports.getInfrastructure = getInfrastructure;
|
|
@@ -52,33 +48,20 @@ const fs = __importStar(require("fs"));
|
|
|
52
48
|
const path = __importStar(require("path"));
|
|
53
49
|
const yaml = __importStar(require("js-yaml"));
|
|
54
50
|
const os = __importStar(require("os"));
|
|
55
|
-
|
|
56
|
-
// Helper to check if config is v4
|
|
57
|
-
function isV4(config) {
|
|
58
|
-
return (0, schema_v4_1.getConfigVersion)(config) === 4;
|
|
59
|
-
}
|
|
60
|
-
// Helper to get profile connection (handles both v3 and v4)
|
|
51
|
+
// Helper to get profile connection
|
|
61
52
|
function getProfileConnection(profile) {
|
|
62
|
-
|
|
63
|
-
return profile.default_connection || profile.connect_to;
|
|
53
|
+
return profile.default_connection;
|
|
64
54
|
}
|
|
65
|
-
// Helper to get app dependencies
|
|
55
|
+
// Helper to get app dependencies
|
|
66
56
|
function getAppDependencies(app) {
|
|
67
|
-
// v4 uses connects_to, v3 uses requires
|
|
68
57
|
if (app.connects_to) {
|
|
69
58
|
return Object.keys(app.connects_to);
|
|
70
59
|
}
|
|
71
|
-
if (app.requires) {
|
|
72
|
-
return Object.keys(app.requires);
|
|
73
|
-
}
|
|
74
60
|
return [];
|
|
75
61
|
}
|
|
76
|
-
// Helper to get infrastructure/provides
|
|
62
|
+
// Helper to get infrastructure/provides
|
|
77
63
|
function getInfrastructure(config) {
|
|
78
|
-
|
|
79
|
-
return config.provides;
|
|
80
|
-
}
|
|
81
|
-
return config.infrastructure;
|
|
64
|
+
return config.provides;
|
|
82
65
|
}
|
|
83
66
|
const CONFIG_FILENAME = 'genbox.yaml';
|
|
84
67
|
const ENV_FILENAME = '.env.genbox';
|
|
@@ -102,16 +85,18 @@ class ConfigLoader {
|
|
|
102
85
|
// Merge configurations
|
|
103
86
|
const mergedConfig = this.mergeConfigs(sources, userConfig);
|
|
104
87
|
// Validate
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
88
|
+
if (mergedConfig) {
|
|
89
|
+
const validation = this.validate(mergedConfig);
|
|
90
|
+
if (!validation.valid) {
|
|
91
|
+
warnings.push(...validation.errors.map(e => e.message));
|
|
92
|
+
}
|
|
93
|
+
warnings.push(...validation.warnings.map(w => w.message));
|
|
108
94
|
}
|
|
109
|
-
warnings.push(...validation.warnings.map(w => w.message));
|
|
110
95
|
// Root is current directory
|
|
111
96
|
const root = projectConfig?.path ? path.dirname(projectConfig.path) : cwd;
|
|
112
97
|
return {
|
|
113
98
|
found: sources.length > 0,
|
|
114
|
-
config:
|
|
99
|
+
config: mergedConfig,
|
|
115
100
|
sources,
|
|
116
101
|
warnings,
|
|
117
102
|
isWorkspace: false,
|
|
@@ -129,6 +114,12 @@ class ConfigLoader {
|
|
|
129
114
|
try {
|
|
130
115
|
const content = fs.readFileSync(configPath, 'utf8');
|
|
131
116
|
const config = yaml.load(content);
|
|
117
|
+
// Validate it's v4
|
|
118
|
+
if (config.version !== 4) {
|
|
119
|
+
console.warn(`Warning: Config version ${config.version} is not supported. Only v4 is supported.`);
|
|
120
|
+
console.warn('Run "genbox init" to create a new v4 configuration.');
|
|
121
|
+
return null;
|
|
122
|
+
}
|
|
132
123
|
return {
|
|
133
124
|
type: 'project',
|
|
134
125
|
path: configPath,
|
|
@@ -173,43 +164,27 @@ class ConfigLoader {
|
|
|
173
164
|
}
|
|
174
165
|
}
|
|
175
166
|
/**
|
|
176
|
-
* Merge configurations from all sources
|
|
177
|
-
* Supports both v3 and v4 config formats
|
|
167
|
+
* Merge configurations from all sources (v4 only)
|
|
178
168
|
*/
|
|
179
169
|
mergeConfigs(sources, userConfig) {
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
for (const source of sources) {
|
|
183
|
-
// Version can be number (v4) or string (v3)
|
|
184
|
-
if (source.config.version === 4) {
|
|
185
|
-
isVersion4 = true;
|
|
186
|
-
break;
|
|
187
|
-
}
|
|
170
|
+
if (sources.length === 0) {
|
|
171
|
+
return null;
|
|
188
172
|
}
|
|
189
|
-
// Start with
|
|
190
|
-
const merged =
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
: {
|
|
205
|
-
version: '3.0',
|
|
206
|
-
project: {
|
|
207
|
-
name: 'unnamed',
|
|
208
|
-
structure: 'single-app',
|
|
209
|
-
},
|
|
210
|
-
apps: {},
|
|
211
|
-
};
|
|
212
|
-
// Merge sources in order (workspace first, then project)
|
|
173
|
+
// Start with v4 default structure
|
|
174
|
+
const merged = {
|
|
175
|
+
version: 4,
|
|
176
|
+
project: {
|
|
177
|
+
name: 'unnamed',
|
|
178
|
+
structure: 'single-app',
|
|
179
|
+
},
|
|
180
|
+
apps: {},
|
|
181
|
+
strict: {
|
|
182
|
+
enabled: true,
|
|
183
|
+
allow_detect: true,
|
|
184
|
+
warnings_as_errors: false,
|
|
185
|
+
},
|
|
186
|
+
};
|
|
187
|
+
// Merge sources in order
|
|
213
188
|
for (const source of sources) {
|
|
214
189
|
this.deepMerge(merged, source.config);
|
|
215
190
|
}
|
|
@@ -232,10 +207,6 @@ class ConfigLoader {
|
|
|
232
207
|
}
|
|
233
208
|
/**
|
|
234
209
|
* Deep merge objects
|
|
235
|
-
*
|
|
236
|
-
* Special handling:
|
|
237
|
-
* - 'version' field is never overwritten (prevents v4 -> v1 downgrades)
|
|
238
|
-
* - Empty objects don't overwrite non-empty objects
|
|
239
210
|
*/
|
|
240
211
|
deepMerge(target, source) {
|
|
241
212
|
for (const key of Object.keys(source)) {
|
|
@@ -243,7 +214,7 @@ class ConfigLoader {
|
|
|
243
214
|
const targetValue = target[key];
|
|
244
215
|
if (sourceValue === undefined)
|
|
245
216
|
continue;
|
|
246
|
-
// Never overwrite version field
|
|
217
|
+
// Never overwrite version field
|
|
247
218
|
if (key === 'version' && targetValue !== undefined)
|
|
248
219
|
continue;
|
|
249
220
|
// Don't overwrite non-empty objects with empty objects
|
|
@@ -255,7 +226,6 @@ class ConfigLoader {
|
|
|
255
226
|
targetValue !== null &&
|
|
256
227
|
!Array.isArray(targetValue) &&
|
|
257
228
|
Object.keys(targetValue).length > 0) {
|
|
258
|
-
// Skip - don't let empty {} overwrite populated object
|
|
259
229
|
continue;
|
|
260
230
|
}
|
|
261
231
|
if (typeof sourceValue === 'object' &&
|
|
@@ -272,12 +242,19 @@ class ConfigLoader {
|
|
|
272
242
|
}
|
|
273
243
|
}
|
|
274
244
|
/**
|
|
275
|
-
* Validate configuration (
|
|
245
|
+
* Validate configuration (v4 only)
|
|
276
246
|
*/
|
|
277
247
|
validate(config) {
|
|
278
248
|
const errors = [];
|
|
279
249
|
const warnings = [];
|
|
280
|
-
|
|
250
|
+
// Check version
|
|
251
|
+
if (config.version !== 4) {
|
|
252
|
+
errors.push({
|
|
253
|
+
path: 'version',
|
|
254
|
+
message: `Config version ${config.version} is not supported. Only v4 is supported.`,
|
|
255
|
+
severity: 'error',
|
|
256
|
+
});
|
|
257
|
+
}
|
|
281
258
|
// Required fields
|
|
282
259
|
if (!config.project?.name) {
|
|
283
260
|
errors.push({
|
|
@@ -293,8 +270,7 @@ class ConfigLoader {
|
|
|
293
270
|
severity: 'warning',
|
|
294
271
|
});
|
|
295
272
|
}
|
|
296
|
-
|
|
297
|
-
const infra = getInfrastructure(config);
|
|
273
|
+
const infra = config.provides;
|
|
298
274
|
// Validate apps
|
|
299
275
|
for (const [name, app] of Object.entries(config.apps || {})) {
|
|
300
276
|
if (!app.path) {
|
|
@@ -311,16 +287,15 @@ class ConfigLoader {
|
|
|
311
287
|
severity: 'warning',
|
|
312
288
|
});
|
|
313
289
|
}
|
|
314
|
-
// Validate dependencies
|
|
290
|
+
// Validate dependencies
|
|
315
291
|
const deps = getAppDependencies(app);
|
|
316
|
-
const depField = configIsV4 ? 'connects_to' : 'requires';
|
|
317
292
|
for (const dep of deps) {
|
|
318
293
|
const isAppDep = config.apps?.[dep];
|
|
319
294
|
const isInfraDep = infra?.[dep];
|
|
320
295
|
if (!isAppDep && !isInfraDep) {
|
|
321
296
|
warnings.push({
|
|
322
|
-
path: `apps.${name}.${
|
|
323
|
-
message: `App '${name}'
|
|
297
|
+
path: `apps.${name}.connects_to.${dep}`,
|
|
298
|
+
message: `App '${name}' connects to '${dep}' which is not defined`,
|
|
324
299
|
severity: 'warning',
|
|
325
300
|
});
|
|
326
301
|
}
|
|
@@ -328,7 +303,6 @@ class ConfigLoader {
|
|
|
328
303
|
}
|
|
329
304
|
// Validate profiles
|
|
330
305
|
for (const [name, profile] of Object.entries(config.profiles || {})) {
|
|
331
|
-
// Check apps exist
|
|
332
306
|
for (const appName of profile.apps || []) {
|
|
333
307
|
if (!config.apps?.[appName]) {
|
|
334
308
|
errors.push({
|
|
@@ -338,7 +312,6 @@ class ConfigLoader {
|
|
|
338
312
|
});
|
|
339
313
|
}
|
|
340
314
|
}
|
|
341
|
-
// Check extends
|
|
342
315
|
if (profile.extends && !config.profiles?.[profile.extends]) {
|
|
343
316
|
errors.push({
|
|
344
317
|
path: `profiles.${name}.extends`,
|
|
@@ -346,12 +319,10 @@ class ConfigLoader {
|
|
|
346
319
|
severity: 'error',
|
|
347
320
|
});
|
|
348
321
|
}
|
|
349
|
-
|
|
350
|
-
const connection = getProfileConnection(profile);
|
|
322
|
+
const connection = profile.default_connection;
|
|
351
323
|
if (connection && !config.environments?.[connection]) {
|
|
352
|
-
const connField = configIsV4 ? 'default_connection' : 'connect_to';
|
|
353
324
|
warnings.push({
|
|
354
|
-
path: `profiles.${name}
|
|
325
|
+
path: `profiles.${name}.default_connection`,
|
|
355
326
|
message: `Profile '${name}' connects to undefined environment '${connection}'`,
|
|
356
327
|
severity: 'warning',
|
|
357
328
|
});
|
|
@@ -359,37 +330,20 @@ class ConfigLoader {
|
|
|
359
330
|
}
|
|
360
331
|
// Validate environments
|
|
361
332
|
for (const [name, env] of Object.entries(config.environments || {})) {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
}
|
|
374
|
-
};
|
|
375
|
-
// v4 uses urls object, v3 uses separate mongodb/redis/rabbitmq objects
|
|
376
|
-
if (configIsV4) {
|
|
377
|
-
const v4Env = env;
|
|
378
|
-
if (v4Env.urls) {
|
|
379
|
-
for (const [key, url] of Object.entries(v4Env.urls)) {
|
|
380
|
-
checkValue(url, `environments.${name}.urls.${key}`);
|
|
333
|
+
if (env.urls) {
|
|
334
|
+
for (const [key, url] of Object.entries(env.urls)) {
|
|
335
|
+
if (typeof url === 'string' && url.includes('${')) {
|
|
336
|
+
const match = url.match(/\$\{([^}]+)\}/);
|
|
337
|
+
if (match) {
|
|
338
|
+
warnings.push({
|
|
339
|
+
path: `environments.${name}.urls.${key}`,
|
|
340
|
+
message: `Environment '${name}' has variable reference '${match[1]}' - ensure it's defined in .env.genbox`,
|
|
341
|
+
severity: 'warning',
|
|
342
|
+
});
|
|
343
|
+
}
|
|
381
344
|
}
|
|
382
345
|
}
|
|
383
346
|
}
|
|
384
|
-
else {
|
|
385
|
-
const v3Env = env;
|
|
386
|
-
if (v3Env.mongodb?.url)
|
|
387
|
-
checkValue(v3Env.mongodb.url, `environments.${name}.mongodb.url`);
|
|
388
|
-
if (v3Env.redis?.url)
|
|
389
|
-
checkValue(v3Env.redis.url, `environments.${name}.redis.url`);
|
|
390
|
-
if (v3Env.rabbitmq?.url)
|
|
391
|
-
checkValue(v3Env.rabbitmq.url, `environments.${name}.rabbitmq.url`);
|
|
392
|
-
}
|
|
393
347
|
}
|
|
394
348
|
return {
|
|
395
349
|
valid: errors.length === 0,
|
|
@@ -398,12 +352,11 @@ class ConfigLoader {
|
|
|
398
352
|
};
|
|
399
353
|
}
|
|
400
354
|
/**
|
|
401
|
-
* Get a specific profile, resolving extends
|
|
355
|
+
* Get a specific profile, resolving extends
|
|
402
356
|
*/
|
|
403
357
|
getProfile(config, profileName) {
|
|
404
358
|
const profile = config.profiles?.[profileName];
|
|
405
359
|
if (!profile) {
|
|
406
|
-
// Check user profiles
|
|
407
360
|
const userProfiles = this.loadUserProfiles();
|
|
408
361
|
if (userProfiles?.profiles?.[profileName]) {
|
|
409
362
|
return this.resolveProfile(config, userProfiles.profiles[profileName], userProfiles);
|
|
@@ -413,13 +366,12 @@ class ConfigLoader {
|
|
|
413
366
|
return this.resolveProfile(config, profile);
|
|
414
367
|
}
|
|
415
368
|
/**
|
|
416
|
-
* Resolve profile inheritance (extends)
|
|
369
|
+
* Resolve profile inheritance (extends)
|
|
417
370
|
*/
|
|
418
371
|
resolveProfile(config, profile, userProfiles) {
|
|
419
372
|
if (!profile.extends) {
|
|
420
373
|
return profile;
|
|
421
374
|
}
|
|
422
|
-
// Find parent profile
|
|
423
375
|
let parent = config.profiles?.[profile.extends];
|
|
424
376
|
if (!parent && userProfiles) {
|
|
425
377
|
parent = userProfiles.profiles?.[profile.extends];
|
|
@@ -428,47 +380,26 @@ class ConfigLoader {
|
|
|
428
380
|
console.warn(`Warning: Profile extends '${profile.extends}' which doesn't exist`);
|
|
429
381
|
return profile;
|
|
430
382
|
}
|
|
431
|
-
// Resolve parent first (recursive)
|
|
432
383
|
const resolvedParent = this.resolveProfile(config, parent, userProfiles);
|
|
433
|
-
|
|
434
|
-
const merged = {
|
|
384
|
+
return {
|
|
435
385
|
...resolvedParent,
|
|
436
386
|
...profile,
|
|
437
|
-
// Arrays are replaced, not merged
|
|
438
387
|
apps: profile.apps || resolvedParent.apps,
|
|
439
|
-
// Objects are merged
|
|
440
388
|
env: {
|
|
441
389
|
...resolvedParent.env,
|
|
442
390
|
...profile.env,
|
|
443
391
|
},
|
|
392
|
+
connections: {
|
|
393
|
+
...resolvedParent.connections,
|
|
394
|
+
...profile.connections,
|
|
395
|
+
},
|
|
444
396
|
};
|
|
445
|
-
// Handle v4 connections override
|
|
446
|
-
if (isV4(config)) {
|
|
447
|
-
const v4Profile = profile;
|
|
448
|
-
const v4Parent = resolvedParent;
|
|
449
|
-
merged.connections = {
|
|
450
|
-
...v4Parent.connections,
|
|
451
|
-
...v4Profile.connections,
|
|
452
|
-
};
|
|
453
|
-
}
|
|
454
|
-
else {
|
|
455
|
-
// v3 infrastructure merge
|
|
456
|
-
const v3Profile = profile;
|
|
457
|
-
const v3Parent = resolvedParent;
|
|
458
|
-
merged.infrastructure = {
|
|
459
|
-
...v3Parent.infrastructure,
|
|
460
|
-
...v3Profile.infrastructure,
|
|
461
|
-
};
|
|
462
|
-
}
|
|
463
|
-
return merged;
|
|
464
397
|
}
|
|
465
398
|
/**
|
|
466
|
-
* List all available profiles
|
|
399
|
+
* List all available profiles
|
|
467
400
|
*/
|
|
468
401
|
listProfiles(config) {
|
|
469
402
|
const profiles = [];
|
|
470
|
-
// Project profiles only (user/global profiles removed - they don't make sense
|
|
471
|
-
// because profiles reference project-specific app names)
|
|
472
403
|
for (const [name, profile] of Object.entries(config.profiles || {})) {
|
|
473
404
|
const resolved = this.getProfile(config, name);
|
|
474
405
|
profiles.push({
|
|
@@ -477,7 +408,7 @@ class ConfigLoader {
|
|
|
477
408
|
source: 'project',
|
|
478
409
|
apps: resolved?.apps || [],
|
|
479
410
|
size: resolved?.size,
|
|
480
|
-
connection: resolved
|
|
411
|
+
connection: resolved?.default_connection,
|
|
481
412
|
});
|
|
482
413
|
}
|
|
483
414
|
return profiles;
|
|
@@ -486,11 +417,9 @@ class ConfigLoader {
|
|
|
486
417
|
* Save user profile
|
|
487
418
|
*/
|
|
488
419
|
saveUserProfile(name, profile) {
|
|
489
|
-
// Ensure directory exists
|
|
490
420
|
if (!fs.existsSync(USER_CONFIG_DIR)) {
|
|
491
421
|
fs.mkdirSync(USER_CONFIG_DIR, { recursive: true });
|
|
492
422
|
}
|
|
493
|
-
// Load existing profiles
|
|
494
423
|
let profiles = { profiles: {} };
|
|
495
424
|
if (fs.existsSync(USER_PROFILES_FILE)) {
|
|
496
425
|
try {
|
|
@@ -499,9 +428,7 @@ class ConfigLoader {
|
|
|
499
428
|
}
|
|
500
429
|
catch { }
|
|
501
430
|
}
|
|
502
|
-
// Add/update profile
|
|
503
431
|
profiles.profiles[name] = profile;
|
|
504
|
-
// Save
|
|
505
432
|
const content = yaml.dump(profiles, { lineWidth: 120 });
|
|
506
433
|
fs.writeFileSync(USER_PROFILES_FILE, content);
|
|
507
434
|
}
|
|
@@ -510,10 +437,9 @@ class ConfigLoader {
|
|
|
510
437
|
*/
|
|
511
438
|
loadEnvVars(root) {
|
|
512
439
|
const vars = {};
|
|
513
|
-
// Load from multiple locations
|
|
514
440
|
const envPaths = [
|
|
515
441
|
path.join(root, ENV_FILENAME),
|
|
516
|
-
path.join(path.dirname(root), ENV_FILENAME),
|
|
442
|
+
path.join(path.dirname(root), ENV_FILENAME),
|
|
517
443
|
];
|
|
518
444
|
for (const envPath of envPaths) {
|
|
519
445
|
if (fs.existsSync(envPath)) {
|
|
@@ -532,13 +458,11 @@ class ConfigLoader {
|
|
|
532
458
|
const lines = content.split('\n');
|
|
533
459
|
for (const line of lines) {
|
|
534
460
|
const trimmed = line.trim();
|
|
535
|
-
// Skip comments and empty lines
|
|
536
461
|
if (!trimmed || trimmed.startsWith('#'))
|
|
537
462
|
continue;
|
|
538
463
|
const match = trimmed.match(/^([A-Za-z_][A-Za-z0-9_]*)\s*=\s*(.*)$/);
|
|
539
464
|
if (match) {
|
|
540
465
|
let [, key, value] = match;
|
|
541
|
-
// Remove quotes
|
|
542
466
|
if ((value.startsWith('"') && value.endsWith('"')) ||
|
|
543
467
|
(value.startsWith("'") && value.endsWith("'"))) {
|
|
544
468
|
value = value.slice(1, -1);
|