genbox 1.0.14 → 1.0.16
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/connect.js +7 -2
- package/dist/commands/create.js +101 -128
- package/dist/commands/init.js +636 -107
- package/dist/commands/profiles.js +49 -13
- package/dist/commands/scan.js +238 -3
- package/dist/commands/ssh-setup.js +34 -0
- package/dist/config-explainer.js +2 -1
- package/dist/config-loader.js +131 -41
- package/dist/index.js +3 -1
- package/dist/profile-resolver.js +35 -18
- package/package.json +1 -1
package/dist/config-loader.js
CHANGED
|
@@ -44,10 +44,42 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
44
44
|
})();
|
|
45
45
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
46
46
|
exports.configLoader = exports.ConfigLoader = void 0;
|
|
47
|
+
exports.isV4 = isV4;
|
|
48
|
+
exports.getProfileConnection = getProfileConnection;
|
|
49
|
+
exports.getAppDependencies = getAppDependencies;
|
|
50
|
+
exports.getInfrastructure = getInfrastructure;
|
|
47
51
|
const fs = __importStar(require("fs"));
|
|
48
52
|
const path = __importStar(require("path"));
|
|
49
53
|
const yaml = __importStar(require("js-yaml"));
|
|
50
54
|
const os = __importStar(require("os"));
|
|
55
|
+
const schema_v4_1 = require("./schema-v4");
|
|
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)
|
|
61
|
+
function getProfileConnection(profile) {
|
|
62
|
+
// v4 uses default_connection, v3 uses connect_to
|
|
63
|
+
return profile.default_connection || profile.connect_to;
|
|
64
|
+
}
|
|
65
|
+
// Helper to get app dependencies (handles both v3 and v4)
|
|
66
|
+
function getAppDependencies(app) {
|
|
67
|
+
// v4 uses connects_to, v3 uses requires
|
|
68
|
+
if (app.connects_to) {
|
|
69
|
+
return Object.keys(app.connects_to);
|
|
70
|
+
}
|
|
71
|
+
if (app.requires) {
|
|
72
|
+
return Object.keys(app.requires);
|
|
73
|
+
}
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
// Helper to get infrastructure/provides (handles both v3 and v4)
|
|
77
|
+
function getInfrastructure(config) {
|
|
78
|
+
if (isV4(config)) {
|
|
79
|
+
return config.provides;
|
|
80
|
+
}
|
|
81
|
+
return config.infrastructure;
|
|
82
|
+
}
|
|
51
83
|
const CONFIG_FILENAME = 'genbox.yaml';
|
|
52
84
|
const ENV_FILENAME = '.env.genbox';
|
|
53
85
|
const USER_CONFIG_DIR = path.join(os.homedir(), '.genbox');
|
|
@@ -189,17 +221,41 @@ class ConfigLoader {
|
|
|
189
221
|
}
|
|
190
222
|
/**
|
|
191
223
|
* Merge configurations from all sources
|
|
224
|
+
* Supports both v3 and v4 config formats
|
|
192
225
|
*/
|
|
193
226
|
mergeConfigs(sources, userConfig) {
|
|
194
|
-
//
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
}
|
|
227
|
+
// Detect version from first source that has it
|
|
228
|
+
let isVersion4 = false;
|
|
229
|
+
for (const source of sources) {
|
|
230
|
+
// Version can be number (v4) or string (v3)
|
|
231
|
+
if (source.config.version === 4) {
|
|
232
|
+
isVersion4 = true;
|
|
233
|
+
break;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
// Start with appropriate default structure
|
|
237
|
+
const merged = isVersion4
|
|
238
|
+
? {
|
|
239
|
+
version: 4,
|
|
240
|
+
project: {
|
|
241
|
+
name: 'unnamed',
|
|
242
|
+
structure: 'single-app',
|
|
243
|
+
},
|
|
244
|
+
apps: {},
|
|
245
|
+
strict: {
|
|
246
|
+
enabled: true,
|
|
247
|
+
allow_detect: true,
|
|
248
|
+
warnings_as_errors: false,
|
|
249
|
+
},
|
|
250
|
+
}
|
|
251
|
+
: {
|
|
252
|
+
version: '3.0',
|
|
253
|
+
project: {
|
|
254
|
+
name: 'unnamed',
|
|
255
|
+
structure: 'single-app',
|
|
256
|
+
},
|
|
257
|
+
apps: {},
|
|
258
|
+
};
|
|
203
259
|
// Merge sources in order (workspace first, then project)
|
|
204
260
|
for (const source of sources) {
|
|
205
261
|
this.deepMerge(merged, source.config);
|
|
@@ -244,11 +300,12 @@ class ConfigLoader {
|
|
|
244
300
|
}
|
|
245
301
|
}
|
|
246
302
|
/**
|
|
247
|
-
* Validate configuration
|
|
303
|
+
* Validate configuration (supports both v3 and v4)
|
|
248
304
|
*/
|
|
249
305
|
validate(config) {
|
|
250
306
|
const errors = [];
|
|
251
307
|
const warnings = [];
|
|
308
|
+
const configIsV4 = isV4(config);
|
|
252
309
|
// Required fields
|
|
253
310
|
if (!config.project?.name) {
|
|
254
311
|
errors.push({
|
|
@@ -264,6 +321,8 @@ class ConfigLoader {
|
|
|
264
321
|
severity: 'warning',
|
|
265
322
|
});
|
|
266
323
|
}
|
|
324
|
+
// Get infrastructure (v4: provides, v3: infrastructure)
|
|
325
|
+
const infra = getInfrastructure(config);
|
|
267
326
|
// Validate apps
|
|
268
327
|
for (const [name, app] of Object.entries(config.apps || {})) {
|
|
269
328
|
if (!app.path) {
|
|
@@ -280,18 +339,18 @@ class ConfigLoader {
|
|
|
280
339
|
severity: 'warning',
|
|
281
340
|
});
|
|
282
341
|
}
|
|
283
|
-
// Validate dependencies
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
}
|
|
342
|
+
// Validate dependencies (v4: connects_to, v3: requires)
|
|
343
|
+
const deps = getAppDependencies(app);
|
|
344
|
+
const depField = configIsV4 ? 'connects_to' : 'requires';
|
|
345
|
+
for (const dep of deps) {
|
|
346
|
+
const isAppDep = config.apps?.[dep];
|
|
347
|
+
const isInfraDep = infra?.[dep];
|
|
348
|
+
if (!isAppDep && !isInfraDep) {
|
|
349
|
+
warnings.push({
|
|
350
|
+
path: `apps.${name}.${depField}.${dep}`,
|
|
351
|
+
message: `App '${name}' ${configIsV4 ? 'connects to' : 'requires'} '${dep}' which is not defined`,
|
|
352
|
+
severity: 'warning',
|
|
353
|
+
});
|
|
295
354
|
}
|
|
296
355
|
}
|
|
297
356
|
}
|
|
@@ -315,11 +374,13 @@ class ConfigLoader {
|
|
|
315
374
|
severity: 'error',
|
|
316
375
|
});
|
|
317
376
|
}
|
|
318
|
-
// Check connect_to
|
|
319
|
-
|
|
377
|
+
// Check connection (v4: default_connection, v3: connect_to)
|
|
378
|
+
const connection = getProfileConnection(profile);
|
|
379
|
+
if (connection && !config.environments?.[connection]) {
|
|
380
|
+
const connField = configIsV4 ? 'default_connection' : 'connect_to';
|
|
320
381
|
warnings.push({
|
|
321
|
-
path: `profiles.${name}
|
|
322
|
-
message: `Profile '${name}' connects to undefined environment '${
|
|
382
|
+
path: `profiles.${name}.${connField}`,
|
|
383
|
+
message: `Profile '${name}' connects to undefined environment '${connection}'`,
|
|
323
384
|
severity: 'warning',
|
|
324
385
|
});
|
|
325
386
|
}
|
|
@@ -339,12 +400,24 @@ class ConfigLoader {
|
|
|
339
400
|
}
|
|
340
401
|
}
|
|
341
402
|
};
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
403
|
+
// v4 uses urls object, v3 uses separate mongodb/redis/rabbitmq objects
|
|
404
|
+
if (configIsV4) {
|
|
405
|
+
const v4Env = env;
|
|
406
|
+
if (v4Env.urls) {
|
|
407
|
+
for (const [key, url] of Object.entries(v4Env.urls)) {
|
|
408
|
+
checkValue(url, `environments.${name}.urls.${key}`);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
const v3Env = env;
|
|
414
|
+
if (v3Env.mongodb?.url)
|
|
415
|
+
checkValue(v3Env.mongodb.url, `environments.${name}.mongodb.url`);
|
|
416
|
+
if (v3Env.redis?.url)
|
|
417
|
+
checkValue(v3Env.redis.url, `environments.${name}.redis.url`);
|
|
418
|
+
if (v3Env.rabbitmq?.url)
|
|
419
|
+
checkValue(v3Env.rabbitmq.url, `environments.${name}.rabbitmq.url`);
|
|
420
|
+
}
|
|
348
421
|
}
|
|
349
422
|
return {
|
|
350
423
|
valid: errors.length === 0,
|
|
@@ -353,7 +426,7 @@ class ConfigLoader {
|
|
|
353
426
|
};
|
|
354
427
|
}
|
|
355
428
|
/**
|
|
356
|
-
* Get a specific profile, resolving extends
|
|
429
|
+
* Get a specific profile, resolving extends (supports both v3 and v4)
|
|
357
430
|
*/
|
|
358
431
|
getProfile(config, profileName) {
|
|
359
432
|
const profile = config.profiles?.[profileName];
|
|
@@ -368,7 +441,7 @@ class ConfigLoader {
|
|
|
368
441
|
return this.resolveProfile(config, profile);
|
|
369
442
|
}
|
|
370
443
|
/**
|
|
371
|
-
* Resolve profile inheritance (extends)
|
|
444
|
+
* Resolve profile inheritance (extends) - supports both v3 and v4
|
|
372
445
|
*/
|
|
373
446
|
resolveProfile(config, profile, userProfiles) {
|
|
374
447
|
if (!profile.extends) {
|
|
@@ -385,25 +458,40 @@ class ConfigLoader {
|
|
|
385
458
|
}
|
|
386
459
|
// Resolve parent first (recursive)
|
|
387
460
|
const resolvedParent = this.resolveProfile(config, parent, userProfiles);
|
|
388
|
-
// Merge parent into child
|
|
389
|
-
|
|
461
|
+
// Merge parent into child - handle both v3 and v4
|
|
462
|
+
const merged = {
|
|
390
463
|
...resolvedParent,
|
|
391
464
|
...profile,
|
|
392
465
|
// Arrays are replaced, not merged
|
|
393
466
|
apps: profile.apps || resolvedParent.apps,
|
|
394
467
|
// Objects are merged
|
|
395
|
-
infrastructure: {
|
|
396
|
-
...resolvedParent.infrastructure,
|
|
397
|
-
...profile.infrastructure,
|
|
398
|
-
},
|
|
399
468
|
env: {
|
|
400
469
|
...resolvedParent.env,
|
|
401
470
|
...profile.env,
|
|
402
471
|
},
|
|
403
472
|
};
|
|
473
|
+
// Handle v4 connections override
|
|
474
|
+
if (isV4(config)) {
|
|
475
|
+
const v4Profile = profile;
|
|
476
|
+
const v4Parent = resolvedParent;
|
|
477
|
+
merged.connections = {
|
|
478
|
+
...v4Parent.connections,
|
|
479
|
+
...v4Profile.connections,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
else {
|
|
483
|
+
// v3 infrastructure merge
|
|
484
|
+
const v3Profile = profile;
|
|
485
|
+
const v3Parent = resolvedParent;
|
|
486
|
+
merged.infrastructure = {
|
|
487
|
+
...v3Parent.infrastructure,
|
|
488
|
+
...v3Profile.infrastructure,
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
return merged;
|
|
404
492
|
}
|
|
405
493
|
/**
|
|
406
|
-
* List all available profiles
|
|
494
|
+
* List all available profiles (supports both v3 and v4)
|
|
407
495
|
*/
|
|
408
496
|
listProfiles(config) {
|
|
409
497
|
const profiles = [];
|
|
@@ -416,6 +504,7 @@ class ConfigLoader {
|
|
|
416
504
|
source: 'project',
|
|
417
505
|
apps: resolved?.apps || [],
|
|
418
506
|
size: resolved?.size,
|
|
507
|
+
connection: resolved ? getProfileConnection(resolved) : undefined,
|
|
419
508
|
});
|
|
420
509
|
}
|
|
421
510
|
// User profiles
|
|
@@ -432,6 +521,7 @@ class ConfigLoader {
|
|
|
432
521
|
source: 'user',
|
|
433
522
|
apps: resolved?.apps || [],
|
|
434
523
|
size: resolved?.size,
|
|
524
|
+
connection: resolved ? getProfileConnection(resolved) : undefined,
|
|
435
525
|
});
|
|
436
526
|
}
|
|
437
527
|
}
|
package/dist/index.js
CHANGED
|
@@ -29,6 +29,7 @@ const scan_1 = require("./commands/scan");
|
|
|
29
29
|
const resolve_1 = require("./commands/resolve");
|
|
30
30
|
const validate_1 = require("./commands/validate");
|
|
31
31
|
const migrate_1 = require("./commands/migrate");
|
|
32
|
+
const ssh_setup_1 = require("./commands/ssh-setup");
|
|
32
33
|
program
|
|
33
34
|
.addCommand(init_1.initCommand)
|
|
34
35
|
.addCommand(create_1.createCommand)
|
|
@@ -50,5 +51,6 @@ program
|
|
|
50
51
|
.addCommand(resolve_1.resolveCommand)
|
|
51
52
|
.addCommand(validate_1.validateCommand)
|
|
52
53
|
.addCommand(migrate_1.migrateCommand)
|
|
53
|
-
.addCommand(migrate_1.deprecationsCommand)
|
|
54
|
+
.addCommand(migrate_1.deprecationsCommand)
|
|
55
|
+
.addCommand(ssh_setup_1.sshSetupCommand);
|
|
54
56
|
program.parse(process.argv);
|
package/dist/profile-resolver.js
CHANGED
|
@@ -56,9 +56,11 @@ class ProfileResolver {
|
|
|
56
56
|
}
|
|
57
57
|
/**
|
|
58
58
|
* Resolve configuration from options (CLI flags, profile, interactive)
|
|
59
|
+
* Supports both v3 and v4 configs
|
|
59
60
|
*/
|
|
60
61
|
async resolve(config, options) {
|
|
61
62
|
const warnings = [];
|
|
63
|
+
const isV4Config = (0, config_loader_1.isV4)(config);
|
|
62
64
|
// Step 1: Get base profile (if specified)
|
|
63
65
|
let profile = {};
|
|
64
66
|
if (options.profile) {
|
|
@@ -107,13 +109,13 @@ class ProfileResolver {
|
|
|
107
109
|
size,
|
|
108
110
|
project: {
|
|
109
111
|
name: config.project.name,
|
|
110
|
-
structure: config.project.structure,
|
|
112
|
+
structure: (config.project.structure === '$detect' ? 'single-app' : config.project.structure),
|
|
111
113
|
},
|
|
112
114
|
apps,
|
|
113
115
|
infrastructure,
|
|
114
116
|
database,
|
|
115
117
|
repos: this.resolveRepos(config, apps),
|
|
116
|
-
env: this.resolveEnvVars(config, apps, infrastructure, database,
|
|
118
|
+
env: this.resolveEnvVars(config, apps, infrastructure, database, (0, config_loader_1.getProfileConnection)(profile)),
|
|
117
119
|
hooks: config.hooks || {},
|
|
118
120
|
profile: options.profile,
|
|
119
121
|
warnings,
|
|
@@ -150,8 +152,8 @@ class ProfileResolver {
|
|
|
150
152
|
const resolvedInfra = [];
|
|
151
153
|
const warnings = [];
|
|
152
154
|
const processedDeps = new Set();
|
|
153
|
-
// Default environment for dependencies
|
|
154
|
-
const defaultEnv = profile
|
|
155
|
+
// Default environment for dependencies (v3: connect_to, v4: default_connection)
|
|
156
|
+
const defaultEnv = (0, config_loader_1.getProfileConnection)(profile) || options.api;
|
|
155
157
|
for (const appName of selectedApps) {
|
|
156
158
|
const appConfig = config.apps[appName];
|
|
157
159
|
if (!appConfig) {
|
|
@@ -161,12 +163,13 @@ class ProfileResolver {
|
|
|
161
163
|
const resolvedApp = await this.resolveApp(config, appName, appConfig, selectedApps, defaultEnv, options, processedDeps);
|
|
162
164
|
resolvedApps.push(resolvedApp);
|
|
163
165
|
}
|
|
164
|
-
// Collect infrastructure needs
|
|
166
|
+
// Collect infrastructure needs (v3: infrastructure, v4: provides)
|
|
167
|
+
const infrastructure = (0, config_loader_1.getInfrastructure)(config);
|
|
165
168
|
for (const app of resolvedApps) {
|
|
166
169
|
for (const [depName, depConfig] of Object.entries(app.dependencies)) {
|
|
167
170
|
if (processedDeps.has(depName))
|
|
168
171
|
continue;
|
|
169
|
-
const infraConfig =
|
|
172
|
+
const infraConfig = infrastructure?.[depName];
|
|
170
173
|
if (infraConfig) {
|
|
171
174
|
resolvedInfra.push({
|
|
172
175
|
name: depName,
|
|
@@ -188,14 +191,19 @@ class ProfileResolver {
|
|
|
188
191
|
*/
|
|
189
192
|
async resolveApp(config, appName, appConfig, selectedApps, defaultEnv, options, processedDeps) {
|
|
190
193
|
const dependencies = {};
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
+
const infrastructure = (0, config_loader_1.getInfrastructure)(config);
|
|
195
|
+
// Process each dependency (v3: requires, v4: connects_to)
|
|
196
|
+
const appDeps = (0, config_loader_1.isV4)(config)
|
|
197
|
+
? Object.entries(appConfig.connects_to || {})
|
|
198
|
+
: Object.entries(appConfig.requires || {});
|
|
199
|
+
for (const [depName, requirement] of appDeps) {
|
|
200
|
+
// v3 uses 'none', v4 uses { required: false }
|
|
201
|
+
if (requirement === 'none' || (typeof requirement === 'object' && !requirement.required))
|
|
194
202
|
continue;
|
|
195
203
|
// Check if dependency is already in selected apps
|
|
196
204
|
const isLocalApp = selectedApps.includes(depName);
|
|
197
205
|
// Check if it's infrastructure
|
|
198
|
-
const isInfra =
|
|
206
|
+
const isInfra = infrastructure?.[depName];
|
|
199
207
|
if (isLocalApp) {
|
|
200
208
|
// Dependency is included locally
|
|
201
209
|
dependencies[depName] = { mode: 'local' };
|
|
@@ -210,12 +218,12 @@ class ProfileResolver {
|
|
|
210
218
|
url,
|
|
211
219
|
};
|
|
212
220
|
}
|
|
213
|
-
else if (requirement === 'required' && !options.yes) {
|
|
221
|
+
else if ((requirement === 'required' || (typeof requirement === 'object' && requirement.required)) && !options.yes) {
|
|
214
222
|
// Prompt user for resolution
|
|
215
223
|
const resolution = await this.promptDependencyResolution(config, appName, depName, isInfra ? 'infrastructure' : 'app');
|
|
216
224
|
dependencies[depName] = resolution;
|
|
217
225
|
}
|
|
218
|
-
else if (requirement === 'optional') {
|
|
226
|
+
else if (requirement === 'optional' || (typeof requirement === 'object' && !requirement.required)) {
|
|
219
227
|
// Skip optional dependencies if not explicitly included
|
|
220
228
|
continue;
|
|
221
229
|
}
|
|
@@ -325,13 +333,18 @@ class ProfileResolver {
|
|
|
325
333
|
source: profile.database.source,
|
|
326
334
|
};
|
|
327
335
|
}
|
|
328
|
-
// If connect_to is set, use remote
|
|
329
|
-
|
|
330
|
-
|
|
336
|
+
// If connect_to (v3) or default_connection (v4) is set, use remote
|
|
337
|
+
const profileConnection = (0, config_loader_1.getProfileConnection)(profile);
|
|
338
|
+
if (profileConnection) {
|
|
339
|
+
const envConfig = config.environments?.[profileConnection];
|
|
340
|
+
// v4 uses urls.mongodb, v3 uses mongodb.url
|
|
341
|
+
const mongoUrl = (0, config_loader_1.isV4)(config)
|
|
342
|
+
? envConfig?.urls?.mongodb
|
|
343
|
+
: envConfig?.mongodb?.url;
|
|
331
344
|
return {
|
|
332
345
|
mode: 'remote',
|
|
333
|
-
source:
|
|
334
|
-
url:
|
|
346
|
+
source: profileConnection,
|
|
347
|
+
url: mongoUrl,
|
|
335
348
|
};
|
|
336
349
|
}
|
|
337
350
|
// Interactive mode
|
|
@@ -385,10 +398,14 @@ class ProfileResolver {
|
|
|
385
398
|
else if (answer.startsWith('remote-')) {
|
|
386
399
|
const source = answer.replace('remote-', '');
|
|
387
400
|
const envConfig = config.environments?.[source];
|
|
401
|
+
// v4 uses urls.mongodb, v3 uses mongodb.url
|
|
402
|
+
const mongoUrl = (0, config_loader_1.isV4)(config)
|
|
403
|
+
? envConfig?.urls?.mongodb
|
|
404
|
+
: envConfig?.mongodb?.url;
|
|
388
405
|
return {
|
|
389
406
|
mode: 'remote',
|
|
390
407
|
source,
|
|
391
|
-
url:
|
|
408
|
+
url: mongoUrl,
|
|
392
409
|
};
|
|
393
410
|
}
|
|
394
411
|
return { mode: 'local' };
|