genbox 1.0.31 → 1.0.33

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.
@@ -621,10 +621,12 @@ exports.initCommand = new commander_1.Command('init')
621
621
  // Environment configuration - do this BEFORE profiles so profiles can reference environments
622
622
  // (skip only in non-interactive mode, always show for --from-scan since environments are required)
623
623
  if (!nonInteractive) {
624
- const envConfig = await setupEnvironments(scan, v4Config, isMultiRepoStructure, existingEnvValues);
625
- if (envConfig) {
626
- v4Config.environments = envConfig;
624
+ const envResult = await setupEnvironments(scan, v4Config, isMultiRepoStructure, existingEnvValues);
625
+ if (envResult.environments) {
626
+ v4Config.environments = envResult.environments;
627
627
  }
628
+ // Add collected database URLs to envVarsToAdd (for .env.genbox)
629
+ Object.assign(envVarsToAdd, envResult.databaseUrls);
628
630
  }
629
631
  // Ask about profiles (skip prompt when using --from-scan)
630
632
  let createProfiles = true;
@@ -1170,6 +1172,7 @@ async function setupGitAuth(gitInfo, projectName) {
1170
1172
  * Setup staging/production environments (v4 format)
1171
1173
  */
1172
1174
  async function setupEnvironments(scan, config, isMultiRepo = false, existingEnvValues = {}) {
1175
+ const databaseUrls = {};
1173
1176
  // First ask which environments they want to configure
1174
1177
  const envChoice = await prompts.select({
1175
1178
  message: 'Which environments do you want to configure?',
@@ -1182,16 +1185,18 @@ async function setupEnvironments(scan, config, isMultiRepo = false, existingEnvV
1182
1185
  default: 'staging',
1183
1186
  });
1184
1187
  if (envChoice === 'skip') {
1185
- return undefined;
1188
+ return { environments: undefined, databaseUrls };
1186
1189
  }
1187
1190
  console.log('');
1188
1191
  console.log(chalk_1.default.blue('=== Environment Setup ==='));
1189
1192
  console.log(chalk_1.default.dim('These URLs will be used when connecting to external services.'));
1190
- console.log(chalk_1.default.dim('Actual secrets go in .env.genbox'));
1193
+ console.log(chalk_1.default.dim('Database URLs are stored in .env.genbox for database copy operations.'));
1191
1194
  const environments = {};
1192
1195
  // Get existing URLs if available
1193
1196
  const existingStagingApiUrl = existingEnvValues['STAGING_API_URL'];
1194
1197
  const existingProductionApiUrl = existingEnvValues['PRODUCTION_API_URL'] || existingEnvValues['PROD_API_URL'];
1198
+ const existingStagingMongoUrl = existingEnvValues['STAGING_MONGODB_URL'];
1199
+ const existingProdMongoUrl = existingEnvValues['PROD_MONGODB_URL'] || existingEnvValues['PRODUCTION_MONGODB_URL'];
1195
1200
  const configureStaging = envChoice === 'staging' || envChoice === 'both';
1196
1201
  const configureProduction = envChoice === 'production' || envChoice === 'both';
1197
1202
  // Configure staging if selected
@@ -1250,6 +1255,36 @@ async function setupEnvironments(scan, config, isMultiRepo = false, existingEnvV
1250
1255
  };
1251
1256
  }
1252
1257
  }
1258
+ // Prompt for staging database URL (for database copy operations)
1259
+ console.log('');
1260
+ console.log(chalk_1.default.dim(' Database URL (for "Copy from staging" database mode):'));
1261
+ console.log(chalk_1.default.dim(' Format: mongodb+srv://user:password@staging.mongodb.net/dbname'));
1262
+ if (existingStagingMongoUrl) {
1263
+ console.log(chalk_1.default.dim(` Found existing value: ${existingStagingMongoUrl.substring(0, 50)}...`));
1264
+ const useExisting = await prompts.confirm({
1265
+ message: ' Use existing staging MongoDB URL?',
1266
+ default: true,
1267
+ });
1268
+ if (useExisting) {
1269
+ databaseUrls['STAGING_MONGODB_URL'] = existingStagingMongoUrl;
1270
+ }
1271
+ else {
1272
+ const mongoUrl = await prompts.input({
1273
+ message: ' Staging MongoDB URL:',
1274
+ });
1275
+ if (mongoUrl) {
1276
+ databaseUrls['STAGING_MONGODB_URL'] = mongoUrl;
1277
+ }
1278
+ }
1279
+ }
1280
+ else {
1281
+ const mongoUrl = await prompts.input({
1282
+ message: ' Staging MongoDB URL (optional, press Enter to skip):',
1283
+ });
1284
+ if (mongoUrl) {
1285
+ databaseUrls['STAGING_MONGODB_URL'] = mongoUrl;
1286
+ }
1287
+ }
1253
1288
  }
1254
1289
  // Configure production if selected
1255
1290
  if (configureProduction) {
@@ -1320,8 +1355,41 @@ async function setupEnvironments(scan, config, isMultiRepo = false, existingEnvV
1320
1355
  };
1321
1356
  }
1322
1357
  }
1358
+ // Prompt for production database URL (for database copy operations)
1359
+ console.log('');
1360
+ console.log(chalk_1.default.dim(' Database URL (for "Copy from production" database mode):'));
1361
+ console.log(chalk_1.default.dim(' Format: mongodb+srv://readonly:password@prod.mongodb.net/dbname'));
1362
+ if (existingProdMongoUrl) {
1363
+ console.log(chalk_1.default.dim(` Found existing value: ${existingProdMongoUrl.substring(0, 50)}...`));
1364
+ const useExisting = await prompts.confirm({
1365
+ message: ' Use existing production MongoDB URL?',
1366
+ default: true,
1367
+ });
1368
+ if (useExisting) {
1369
+ databaseUrls['PROD_MONGODB_URL'] = existingProdMongoUrl;
1370
+ }
1371
+ else {
1372
+ const mongoUrl = await prompts.input({
1373
+ message: ' Production MongoDB URL:',
1374
+ });
1375
+ if (mongoUrl) {
1376
+ databaseUrls['PROD_MONGODB_URL'] = mongoUrl;
1377
+ }
1378
+ }
1379
+ }
1380
+ else {
1381
+ const mongoUrl = await prompts.input({
1382
+ message: ' Production MongoDB URL (optional, press Enter to skip):',
1383
+ });
1384
+ if (mongoUrl) {
1385
+ databaseUrls['PROD_MONGODB_URL'] = mongoUrl;
1386
+ }
1387
+ }
1323
1388
  }
1324
- return Object.keys(environments).length > 0 ? environments : undefined;
1389
+ return {
1390
+ environments: Object.keys(environments).length > 0 ? environments : undefined,
1391
+ databaseUrls,
1392
+ };
1325
1393
  }
1326
1394
  /**
1327
1395
  * Prompt for a single API URL with existing value support
@@ -1378,12 +1446,19 @@ async function setupEnvFile(projectName, config, nonInteractive = false, scan, i
1378
1446
  if (!extraEnvVars['GIT_TOKEN']) {
1379
1447
  segregatedContent += `# GIT_TOKEN=ghp_xxxxxxxxxxxx\n`;
1380
1448
  }
1381
- segregatedContent += `
1382
- # Database URLs (used by profiles with database mode)
1383
- # STAGING_MONGODB_URL=mongodb+srv://user:password@staging.mongodb.net
1384
- # PROD_MONGODB_URL=mongodb+srv://readonly:password@prod.mongodb.net
1385
-
1386
- `;
1449
+ // Add database URL section (only show templates for missing URLs)
1450
+ const hasStagingMongo = !!extraEnvVars['STAGING_MONGODB_URL'];
1451
+ const hasProdMongo = !!extraEnvVars['PROD_MONGODB_URL'];
1452
+ if (!hasStagingMongo || !hasProdMongo) {
1453
+ segregatedContent += `\n# Database URLs (used by profiles with database mode)\n`;
1454
+ if (!hasStagingMongo) {
1455
+ segregatedContent += `# STAGING_MONGODB_URL=mongodb+srv://user:password@staging.mongodb.net\n`;
1456
+ }
1457
+ if (!hasProdMongo) {
1458
+ segregatedContent += `# PROD_MONGODB_URL=mongodb+srv://readonly:password@prod.mongodb.net\n`;
1459
+ }
1460
+ }
1461
+ segregatedContent += '\n';
1387
1462
  // For multi-repo: find env files in app directories
1388
1463
  if (isMultiRepo && scan) {
1389
1464
  const appEnvFiles = findAppEnvFiles(scan.apps, process.cwd());
@@ -1770,11 +1845,24 @@ function convertDetectedToScan(detected) {
1770
1845
  }));
1771
1846
  // Convert infrastructure to compose analysis
1772
1847
  let compose = null;
1773
- if (detected.infrastructure && detected.infrastructure.length > 0) {
1848
+ const hasInfra = detected.infrastructure && detected.infrastructure.length > 0;
1849
+ const hasDockerServices = detected.docker_services && detected.docker_services.length > 0;
1850
+ if (hasInfra || hasDockerServices) {
1774
1851
  compose = {
1775
1852
  files: ['docker-compose.yml'],
1776
- applications: [],
1777
- databases: detected.infrastructure
1853
+ applications: (detected.docker_services || []).map(svc => ({
1854
+ name: svc.name,
1855
+ image: svc.image,
1856
+ build: svc.build_context ? {
1857
+ context: svc.build_context,
1858
+ dockerfile: svc.dockerfile,
1859
+ } : undefined,
1860
+ ports: svc.port ? [{ host: svc.port, container: svc.port }] : [],
1861
+ environment: {},
1862
+ dependsOn: svc.depends_on || [],
1863
+ volumes: [],
1864
+ })),
1865
+ databases: (detected.infrastructure || [])
1778
1866
  .filter(i => i.type === 'database')
1779
1867
  .map(i => ({
1780
1868
  name: i.name,
@@ -1784,7 +1872,7 @@ function convertDetectedToScan(detected) {
1784
1872
  dependsOn: [],
1785
1873
  volumes: [],
1786
1874
  })),
1787
- caches: detected.infrastructure
1875
+ caches: (detected.infrastructure || [])
1788
1876
  .filter(i => i.type === 'cache')
1789
1877
  .map(i => ({
1790
1878
  name: i.name,
@@ -1794,7 +1882,7 @@ function convertDetectedToScan(detected) {
1794
1882
  dependsOn: [],
1795
1883
  volumes: [],
1796
1884
  })),
1797
- queues: detected.infrastructure
1885
+ queues: (detected.infrastructure || [])
1798
1886
  .filter(i => i.type === 'queue')
1799
1887
  .map(i => ({
1800
1888
  name: i.name,
@@ -239,6 +239,30 @@ async function interactiveSelection(detected) {
239
239
  // Filter scripts to only selected ones
240
240
  result.scripts = detected.scripts.filter(s => selectedScripts.includes(s.path));
241
241
  }
242
+ // === Docker Services Selection ===
243
+ if (detected.docker_services && detected.docker_services.length > 0) {
244
+ console.log('');
245
+ console.log(chalk_1.default.blue('=== Detected Docker Services ===\n'));
246
+ for (const svc of detected.docker_services) {
247
+ const portInfo = svc.port ? ` port:${svc.port}` : '';
248
+ console.log(` ${chalk_1.default.cyan(svc.name)}${portInfo}`);
249
+ if (svc.build_context) {
250
+ console.log(chalk_1.default.dim(` build: ${svc.dockerfile || 'Dockerfile'}`));
251
+ }
252
+ }
253
+ console.log();
254
+ const dockerChoices = detected.docker_services.map(svc => ({
255
+ name: `${svc.name}${svc.port ? ` (port ${svc.port})` : ''}`,
256
+ value: svc.name,
257
+ checked: true, // Default: include all
258
+ }));
259
+ const selectedDocker = await prompts.checkbox({
260
+ message: 'Select Docker services to include:',
261
+ choices: dockerChoices,
262
+ });
263
+ // Filter docker services to only selected ones
264
+ result.docker_services = detected.docker_services.filter(svc => selectedDocker.includes(svc.name));
265
+ }
242
266
  // === Infrastructure Selection ===
243
267
  if (detected.infrastructure && detected.infrastructure.length > 0) {
244
268
  console.log('');
@@ -482,6 +506,18 @@ function convertScanToDetected(scan, root) {
482
506
  source: 'docker-compose.yml',
483
507
  });
484
508
  }
509
+ // Save Docker application services (services with build context)
510
+ if (scan.compose.applications && scan.compose.applications.length > 0) {
511
+ detected.docker_services = scan.compose.applications.map(app => ({
512
+ name: app.name,
513
+ build_context: app.build?.context,
514
+ dockerfile: app.build?.dockerfile,
515
+ image: app.image,
516
+ port: app.ports?.[0]?.host,
517
+ depends_on: app.dependsOn?.length ? app.dependsOn : undefined,
518
+ source: 'docker-compose.yml',
519
+ }));
520
+ }
485
521
  }
486
522
  // Git info
487
523
  if (scan.git) {
@@ -649,6 +685,17 @@ function showSummary(detected) {
649
685
  console.log(` ${parts.join(' ')}`);
650
686
  }
651
687
  }
688
+ // Docker Services (applications with build context)
689
+ if (detected.docker_services && detected.docker_services.length > 0) {
690
+ console.log(`\n Docker Services (${detected.docker_services.length}):`);
691
+ for (const svc of detected.docker_services) {
692
+ const portInfo = svc.port ? ` port:${svc.port}` : '';
693
+ console.log(` ${chalk_1.default.cyan(svc.name)}${portInfo}`);
694
+ if (svc.build_context) {
695
+ console.log(chalk_1.default.dim(` build: ${svc.build_context}`));
696
+ }
697
+ }
698
+ }
652
699
  // Infrastructure
653
700
  if (detected.infrastructure && detected.infrastructure.length > 0) {
654
701
  console.log(`\n Infrastructure (${detected.infrastructure.length}):`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genbox",
3
- "version": "1.0.31",
3
+ "version": "1.0.33",
4
4
  "description": "Genbox CLI - AI-Powered Development Environments",
5
5
  "main": "dist/index.js",
6
6
  "bin": {