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.
@@ -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
- // Start with default structure
195
- const merged = {
196
- version: '3.0',
197
- project: {
198
- name: 'unnamed',
199
- structure: 'single-app',
200
- },
201
- apps: {},
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 reference existing apps/infrastructure
284
- if (app.requires) {
285
- for (const dep of Object.keys(app.requires)) {
286
- const isApp = config.apps?.[dep];
287
- const isInfra = config.infrastructure?.[dep];
288
- if (!isApp && !isInfra) {
289
- warnings.push({
290
- path: `apps.${name}.requires.${dep}`,
291
- message: `App '${name}' requires '${dep}' which is not defined`,
292
- severity: 'warning',
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
- if (profile.connect_to && !config.environments?.[profile.connect_to]) {
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}.connect_to`,
322
- message: `Profile '${name}' connects to undefined environment '${profile.connect_to}'`,
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
- if (env.mongodb?.url)
343
- checkValue(env.mongodb.url, `environments.${name}.mongodb.url`);
344
- if (env.redis?.url)
345
- checkValue(env.redis.url, `environments.${name}.redis.url`);
346
- if (env.rabbitmq?.url)
347
- checkValue(env.rabbitmq.url, `environments.${name}.rabbitmq.url`);
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
- return {
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);
@@ -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, profile.connect_to),
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.connect_to || options.api;
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 = config.infrastructure?.[depName];
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
- // Process each dependency
192
- for (const [depName, requirement] of Object.entries(appConfig.requires || {})) {
193
- if (requirement === 'none')
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 = config.infrastructure?.[depName];
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
- if (profile.connect_to) {
330
- const envConfig = config.environments?.[profile.connect_to];
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: profile.connect_to,
334
- url: envConfig?.mongodb?.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: envConfig?.mongodb?.url,
408
+ url: mongoUrl,
392
409
  };
393
410
  }
394
411
  return { mode: 'local' };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genbox",
3
- "version": "1.0.14",
3
+ "version": "1.0.16",
4
4
  "description": "Genbox CLI - AI-Powered Development Environments",
5
5
  "main": "dist/index.js",
6
6
  "bin": {