genbox 1.0.14 → 1.0.15

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,6 +44,7 @@ const yaml = __importStar(require("js-yaml"));
44
44
  const fs = __importStar(require("fs"));
45
45
  const path = __importStar(require("path"));
46
46
  const config_loader_1 = require("../config-loader");
47
+ const schema_v4_1 = require("../schema-v4");
47
48
  exports.profilesCommand = new commander_1.Command('profiles')
48
49
  .description('List and manage profiles')
49
50
  .option('--json', 'Output as JSON')
@@ -51,9 +52,15 @@ exports.profilesCommand = new commander_1.Command('profiles')
51
52
  try {
52
53
  const configLoader = new config_loader_1.ConfigLoader();
53
54
  const loadResult = await configLoader.load();
54
- if (!loadResult.config || loadResult.config.version !== '3.0') {
55
- console.log(chalk_1.default.yellow('Profiles require genbox.yaml v3.0'));
56
- console.log(chalk_1.default.dim('Run "genbox init" to create a v3 configuration'));
55
+ if (!loadResult.config) {
56
+ console.log(chalk_1.default.yellow('No genbox.yaml found'));
57
+ console.log(chalk_1.default.dim('Run "genbox init" to create a configuration'));
58
+ return;
59
+ }
60
+ const version = (0, schema_v4_1.getConfigVersion)(loadResult.config);
61
+ if (version === 'unknown') {
62
+ console.log(chalk_1.default.yellow('Unknown config version'));
63
+ console.log(chalk_1.default.dim('Run "genbox init" to create a v4 configuration'));
57
64
  return;
58
65
  }
59
66
  const config = loadResult.config;
@@ -105,8 +112,13 @@ exports.profilesCommand
105
112
  try {
106
113
  const configLoader = new config_loader_1.ConfigLoader();
107
114
  const loadResult = await configLoader.load();
108
- if (!loadResult.config || loadResult.config.version !== '3.0') {
109
- console.log(chalk_1.default.yellow('Profiles require genbox.yaml v3.0'));
115
+ if (!loadResult.config) {
116
+ console.log(chalk_1.default.yellow('No genbox.yaml found'));
117
+ return;
118
+ }
119
+ const configVersion = (0, schema_v4_1.getConfigVersion)(loadResult.config);
120
+ if (configVersion === 'unknown') {
121
+ console.log(chalk_1.default.yellow('Unknown config version'));
110
122
  return;
111
123
  }
112
124
  const config = loadResult.config;
@@ -137,8 +149,10 @@ exports.profilesCommand
137
149
  }
138
150
  }
139
151
  }
140
- if (profile.connect_to) {
141
- console.log(` ${chalk_1.default.bold('Connect to:')} ${profile.connect_to} environment`);
152
+ // Show connection (v4: default_connection, v3: connect_to)
153
+ const connection = (0, config_loader_1.getProfileConnection)(profile);
154
+ if (connection) {
155
+ console.log(` ${chalk_1.default.bold('Connect to:')} ${connection} environment`);
142
156
  }
143
157
  if (profile.database) {
144
158
  console.log(` ${chalk_1.default.bold('Database:')}`);
@@ -147,9 +161,21 @@ exports.profilesCommand
147
161
  console.log(` Source: ${profile.database.source}`);
148
162
  }
149
163
  }
150
- if (profile.infrastructure) {
164
+ // v4 uses connections, v3 uses infrastructure
165
+ const v4Profile = profile;
166
+ const v3Profile = profile;
167
+ if (v4Profile.connections) {
168
+ console.log(` ${chalk_1.default.bold('Connections:')}`);
169
+ for (const [appName, appConns] of Object.entries(v4Profile.connections)) {
170
+ console.log(` ${appName}:`);
171
+ for (const [targetName, mode] of Object.entries(appConns)) {
172
+ console.log(` ${targetName}: ${mode}`);
173
+ }
174
+ }
175
+ }
176
+ else if (v3Profile.infrastructure) {
151
177
  console.log(` ${chalk_1.default.bold('Infrastructure:')}`);
152
- for (const [name, mode] of Object.entries(profile.infrastructure)) {
178
+ for (const [name, mode] of Object.entries(v3Profile.infrastructure)) {
153
179
  console.log(` ${name}: ${mode}`);
154
180
  }
155
181
  }
@@ -169,8 +195,13 @@ exports.profilesCommand
169
195
  try {
170
196
  const configLoader = new config_loader_1.ConfigLoader();
171
197
  const loadResult = await configLoader.load();
172
- if (!loadResult.config || loadResult.config.version !== '3.0') {
173
- console.log(chalk_1.default.yellow('Profiles require genbox.yaml v3.0'));
198
+ if (!loadResult.config) {
199
+ console.log(chalk_1.default.yellow('No genbox.yaml found'));
200
+ return;
201
+ }
202
+ const configVersion = (0, schema_v4_1.getConfigVersion)(loadResult.config);
203
+ if (configVersion === 'unknown') {
204
+ console.log(chalk_1.default.yellow('Unknown config version'));
174
205
  return;
175
206
  }
176
207
  const config = loadResult.config;
@@ -286,8 +317,13 @@ exports.profilesCommand
286
317
  try {
287
318
  const configLoader = new config_loader_1.ConfigLoader();
288
319
  const loadResult = await configLoader.load();
289
- if (!loadResult.config || loadResult.config.version !== '3.0') {
290
- console.log(chalk_1.default.yellow('Profiles require genbox.yaml v3.0'));
320
+ if (!loadResult.config) {
321
+ console.log(chalk_1.default.yellow('No genbox.yaml found'));
322
+ return;
323
+ }
324
+ const configVersion = (0, schema_v4_1.getConfigVersion)(loadResult.config);
325
+ if (configVersion === 'unknown') {
326
+ console.log(chalk_1.default.yellow('Unknown config version'));
291
327
  return;
292
328
  }
293
329
  const config = loadResult.config;
@@ -56,9 +56,53 @@ const chalk_1 = __importDefault(require("chalk"));
56
56
  const fs = __importStar(require("fs"));
57
57
  const path = __importStar(require("path"));
58
58
  const yaml = __importStar(require("js-yaml"));
59
+ const child_process_1 = require("child_process");
59
60
  const scanner_1 = require("../scanner");
60
61
  // eslint-disable-next-line @typescript-eslint/no-var-requires
61
62
  const { version } = require('../../package.json');
63
+ /**
64
+ * Detect git repository info for a specific directory
65
+ */
66
+ function detectGitForDirectory(dir) {
67
+ const gitDir = path.join(dir, '.git');
68
+ if (!fs.existsSync(gitDir))
69
+ return undefined;
70
+ try {
71
+ const remote = (0, child_process_1.execSync)('git remote get-url origin', {
72
+ cwd: dir,
73
+ stdio: 'pipe',
74
+ encoding: 'utf8',
75
+ }).trim();
76
+ if (!remote)
77
+ return undefined;
78
+ const isSSH = remote.startsWith('git@') || remote.startsWith('ssh://');
79
+ let provider = 'other';
80
+ if (remote.includes('github.com'))
81
+ provider = 'github';
82
+ else if (remote.includes('gitlab.com'))
83
+ provider = 'gitlab';
84
+ else if (remote.includes('bitbucket.org'))
85
+ provider = 'bitbucket';
86
+ let branch = 'main';
87
+ try {
88
+ branch = (0, child_process_1.execSync)('git rev-parse --abbrev-ref HEAD', {
89
+ cwd: dir,
90
+ stdio: 'pipe',
91
+ encoding: 'utf8',
92
+ }).trim();
93
+ }
94
+ catch { }
95
+ return {
96
+ remote,
97
+ type: isSSH ? 'ssh' : 'https',
98
+ provider,
99
+ branch,
100
+ };
101
+ }
102
+ catch {
103
+ return undefined;
104
+ }
105
+ }
62
106
  exports.scanCommand = new commander_1.Command('scan')
63
107
  .description('Analyze project structure and output detected configuration')
64
108
  .option('--stdout', 'Output to stdout instead of .genbox/detected.yaml')
@@ -117,10 +161,17 @@ function convertScanToDetected(scan, root) {
117
161
  })),
118
162
  apps: {},
119
163
  };
120
- // Convert apps
164
+ // Convert apps (detect git info for each app in multi-repo workspaces)
165
+ const isMultiRepo = scan.structure.type === 'hybrid';
121
166
  for (const app of scan.apps) {
122
167
  // Map scanner AppType to DetectedApp type
123
168
  const mappedType = mapAppType(app.type);
169
+ // Detect git info for this app (for multi-repo workspaces)
170
+ let appGit;
171
+ if (isMultiRepo) {
172
+ const appDir = path.join(root, app.path);
173
+ appGit = detectGitForDirectory(appDir);
174
+ }
124
175
  detected.apps[app.name] = {
125
176
  path: app.path,
126
177
  type: mappedType,
@@ -135,6 +186,7 @@ function convertScanToDetected(scan, root) {
135
186
  start: app.scripts.start,
136
187
  } : undefined,
137
188
  dependencies: app.dependencies,
189
+ git: appGit,
138
190
  };
139
191
  }
140
192
  // Convert infrastructure
@@ -341,11 +393,21 @@ function showSummary(detected) {
341
393
  console.log(` ${chalk_1.default.cyan(infra.name)}: ${infra.type} (${infra.image})`);
342
394
  }
343
395
  }
344
- // Git
396
+ // Git (root level)
345
397
  if (detected.git?.remote) {
346
398
  console.log(`\n Git: ${detected.git.provider || 'git'} (${detected.git.type})`);
347
399
  console.log(chalk_1.default.dim(` Branch: ${detected.git.branch || 'unknown'}`));
348
400
  }
401
+ // Per-app git repos (for multi-repo workspaces)
402
+ const appsWithGit = Object.entries(detected.apps).filter(([, app]) => app.git);
403
+ if (appsWithGit.length > 0) {
404
+ console.log(`\n App Repositories (${appsWithGit.length}):`);
405
+ for (const [name, app] of appsWithGit) {
406
+ const git = app.git;
407
+ console.log(` ${chalk_1.default.cyan(name)}: ${git.provider} (${git.type})`);
408
+ console.log(chalk_1.default.dim(` ${git.remote}`));
409
+ }
410
+ }
349
411
  console.log(chalk_1.default.bold('\n📝 Next steps:\n'));
350
412
  console.log(' 1. Review the detected configuration in .genbox/detected.yaml');
351
413
  console.log(' 2. Run ' + chalk_1.default.cyan('genbox init --from-scan') + ' to create genbox.yaml');
@@ -0,0 +1,34 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.sshSetupCommand = void 0;
4
+ const commander_1 = require("commander");
5
+ const api_1 = require("../api");
6
+ const ssh_config_1 = require("../ssh-config");
7
+ /**
8
+ * Internal command to poll for IP and add SSH config in background
9
+ * Called by: genbox ssh-setup <genboxId> <name>
10
+ */
11
+ exports.sshSetupCommand = new commander_1.Command('ssh-setup')
12
+ .argument('<genboxId>', 'Genbox ID')
13
+ .argument('<name>', 'Genbox name')
14
+ .option('--max-attempts <n>', 'Max polling attempts', '90')
15
+ .option('--delay <ms>', 'Delay between attempts in ms', '2000')
16
+ .action(async (genboxId, name, options) => {
17
+ const maxAttempts = parseInt(options.maxAttempts, 10);
18
+ const delayMs = parseInt(options.delay, 10);
19
+ for (let i = 0; i < maxAttempts; i++) {
20
+ try {
21
+ const genbox = await (0, api_1.fetchApi)(`/genboxes/${genboxId}`);
22
+ if (genbox.ipAddress) {
23
+ (0, ssh_config_1.addSshConfigEntry)({ name, ipAddress: genbox.ipAddress });
24
+ process.exit(0);
25
+ }
26
+ }
27
+ catch {
28
+ // Ignore errors during polling
29
+ }
30
+ await new Promise(resolve => setTimeout(resolve, delayMs));
31
+ }
32
+ // Timed out - exit silently
33
+ process.exit(0);
34
+ });
@@ -200,7 +200,8 @@ class ConfigExplainer {
200
200
  if (!config)
201
201
  return warnings;
202
202
  // Check each app for implicit type/port/framework
203
- for (const [appName, appConfig] of Object.entries(config.apps || {})) {
203
+ for (const [appName, rawAppConfig] of Object.entries(config.apps || {})) {
204
+ const appConfig = rawAppConfig;
204
205
  // Check type - was it inferred?
205
206
  if (appConfig.type) {
206
207
  const typeExplanation = this.explain(`apps.${appName}.type`);
@@ -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);