genbox 1.0.11 → 1.0.13

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.
@@ -103,13 +103,15 @@ function detectAppGitRepos(apps, rootDir) {
103
103
  return repos;
104
104
  }
105
105
  /**
106
- * Find .env files in app directories
106
+ * Find .env files in app directories (including nested microservices)
107
107
  */
108
108
  function findAppEnvFiles(apps, rootDir) {
109
109
  const envFiles = [];
110
110
  const envPatterns = ['.env', '.env.local', '.env.development'];
111
111
  for (const app of apps) {
112
112
  const appDir = path_1.default.join(rootDir, app.path);
113
+ // Check for direct .env file in app directory
114
+ let foundDirectEnv = false;
113
115
  for (const pattern of envPatterns) {
114
116
  const envPath = path_1.default.join(appDir, pattern);
115
117
  if (fs_1.default.existsSync(envPath)) {
@@ -118,7 +120,35 @@ function findAppEnvFiles(apps, rootDir) {
118
120
  envFile: pattern,
119
121
  fullPath: envPath,
120
122
  });
121
- break; // Only take the first match per app
123
+ foundDirectEnv = true;
124
+ break;
125
+ }
126
+ }
127
+ // Check for nested microservices (e.g., api/apps/*)
128
+ const appsSubdir = path_1.default.join(appDir, 'apps');
129
+ if (fs_1.default.existsSync(appsSubdir) && fs_1.default.statSync(appsSubdir).isDirectory()) {
130
+ try {
131
+ const services = fs_1.default.readdirSync(appsSubdir);
132
+ for (const service of services) {
133
+ const serviceDir = path_1.default.join(appsSubdir, service);
134
+ if (!fs_1.default.statSync(serviceDir).isDirectory())
135
+ continue;
136
+ for (const pattern of envPatterns) {
137
+ const envPath = path_1.default.join(serviceDir, pattern);
138
+ if (fs_1.default.existsSync(envPath)) {
139
+ envFiles.push({
140
+ appName: `${app.name}/${service}`,
141
+ envFile: pattern,
142
+ fullPath: envPath,
143
+ isService: true,
144
+ });
145
+ break;
146
+ }
147
+ }
148
+ }
149
+ }
150
+ catch {
151
+ // Ignore errors reading subdirectories
122
152
  }
123
153
  }
124
154
  }
@@ -454,6 +484,20 @@ exports.initCommand = new commander_1.Command('init')
454
484
  });
455
485
  fs_1.default.writeFileSync(configPath, yamlContent);
456
486
  console.log(chalk_1.default.green(`\n✔ Configuration saved to ${CONFIG_FILENAME}`));
487
+ // Add API URLs from environments to envVarsToAdd
488
+ // Always add LOCAL_API_URL for local development
489
+ envVarsToAdd['LOCAL_API_URL'] = 'http://localhost:3050';
490
+ if (v3Config.environments) {
491
+ for (const [envName, envConfig] of Object.entries(v3Config.environments)) {
492
+ const apiUrl = envConfig.api?.api ||
493
+ envConfig.api?.url ||
494
+ envConfig.api?.gateway;
495
+ if (apiUrl) {
496
+ const varName = `${envName.toUpperCase()}_API_URL`;
497
+ envVarsToAdd[varName] = apiUrl;
498
+ }
499
+ }
500
+ }
457
501
  // Generate .env.genbox
458
502
  await setupEnvFile(projectName, v3Config, nonInteractive, scan, isMultiRepo, envVarsToAdd, overwriteExisting);
459
503
  // Show warnings
@@ -467,26 +511,35 @@ exports.initCommand = new commander_1.Command('init')
467
511
  // Show API URL guidance if environments are configured
468
512
  if (v3Config.environments && Object.keys(v3Config.environments).length > 0) {
469
513
  console.log('');
470
- console.log(chalk_1.default.yellow('Important: Update API URLs in .env.genbox for remote environments'));
514
+ console.log(chalk_1.default.blue('=== API URL Configuration ==='));
515
+ console.log(chalk_1.default.dim('The following API URLs were added to .env.genbox:'));
471
516
  console.log('');
517
+ console.log(chalk_1.default.dim(' LOCAL_API_URL=http://localhost:3050'));
472
518
  for (const [envName, envConfig] of Object.entries(v3Config.environments)) {
473
519
  const apiUrl = envConfig.api?.api ||
474
520
  envConfig.api?.url ||
475
521
  envConfig.api?.gateway;
476
522
  if (apiUrl) {
477
- console.log(chalk_1.default.dim(` For ${envName} profiles (connect_to: ${envName}):`));
478
- console.log(chalk_1.default.cyan(` VITE_API_BASE_URL="${apiUrl}"`));
479
- console.log(chalk_1.default.cyan(` VITE_AUTH_SERVICE_URL="${apiUrl}"`));
480
- console.log(chalk_1.default.cyan(` NEXT_PUBLIC_API_BASE_URL="${apiUrl}"`));
481
- console.log('');
523
+ const varName = `${envName.toUpperCase()}_API_URL`;
524
+ console.log(chalk_1.default.dim(` ${varName}=${apiUrl}`));
482
525
  }
483
526
  }
527
+ console.log('');
528
+ console.log(chalk_1.default.yellow('To use dynamic API URLs:'));
529
+ console.log(chalk_1.default.dim(' Use ${API_URL} in your app env vars, e.g.:'));
530
+ console.log(chalk_1.default.cyan(' VITE_API_BASE_URL=${API_URL}'));
531
+ console.log(chalk_1.default.cyan(' NEXT_PUBLIC_API_URL=${API_URL}'));
532
+ console.log('');
533
+ console.log(chalk_1.default.dim(' At create time, ${API_URL} expands based on profile:'));
534
+ console.log(chalk_1.default.dim(' • connect_to: staging → uses STAGING_API_URL'));
535
+ console.log(chalk_1.default.dim(' • connect_to: production → uses PRODUCTION_API_URL'));
536
+ console.log(chalk_1.default.dim(' • local/no connect_to → uses LOCAL_API_URL'));
484
537
  }
485
538
  // Next steps
486
539
  console.log('');
487
540
  console.log(chalk_1.default.bold('Next steps:'));
488
541
  console.log(chalk_1.default.dim(` 1. Review and edit ${CONFIG_FILENAME}`));
489
- console.log(chalk_1.default.dim(` 2. Update API URLs in ${ENV_FILENAME} for staging/production`));
542
+ console.log(chalk_1.default.dim(` 2. Update ${ENV_FILENAME} to use API URL variables where needed`));
490
543
  console.log(chalk_1.default.dim(` 3. Run 'genbox profiles' to see available profiles`));
491
544
  console.log(chalk_1.default.dim(` 4. Run 'genbox create <name> --profile <profile>' to create an environment`));
492
545
  }
@@ -764,7 +817,7 @@ async function setupEnvironments(scan, config, isMultiRepo = false) {
764
817
  return Object.keys(environments).length > 0 ? environments : undefined;
765
818
  }
766
819
  /**
767
- * Setup .env.genbox file
820
+ * Setup .env.genbox file with segregated app sections
768
821
  */
769
822
  async function setupEnvFile(projectName, config, nonInteractive = false, scan, isMultiRepo = false, extraEnvVars = {}, overwriteExisting = false) {
770
823
  const envPath = path_1.default.join(process.cwd(), ENV_FILENAME);
@@ -778,66 +831,84 @@ async function setupEnvFile(projectName, config, nonInteractive = false, scan, i
778
831
  return;
779
832
  }
780
833
  }
834
+ // Build segregated content with GLOBAL section first
835
+ let segregatedContent = `# Genbox Environment Variables
836
+ # Project: ${projectName}
837
+ # DO NOT COMMIT THIS FILE
838
+ #
839
+ # This file uses segregated sections for each app/service.
840
+ # At 'genbox create' time, only GLOBAL + selected app sections are used.
841
+ # Use \${API_URL} for dynamic API URLs based on profile's connect_to setting.
842
+
843
+ # === GLOBAL ===
844
+ # These variables are always included regardless of which apps are selected
845
+
846
+ `;
847
+ // Add global env vars
848
+ for (const [key, value] of Object.entries(extraEnvVars)) {
849
+ segregatedContent += `${key}=${value}\n`;
850
+ }
851
+ // Add GIT authentication placeholder if not already added
852
+ if (!extraEnvVars['GIT_TOKEN']) {
853
+ segregatedContent += `# GIT_TOKEN=ghp_xxxxxxxxxxxx\n`;
854
+ }
855
+ segregatedContent += `
856
+ # Database URLs (used by profiles with database mode)
857
+ # STAGING_MONGODB_URL=mongodb+srv://user:password@staging.mongodb.net
858
+ # PROD_MONGODB_URL=mongodb+srv://readonly:password@prod.mongodb.net
859
+
860
+ `;
781
861
  // For multi-repo: find env files in app directories
782
862
  if (isMultiRepo && scan) {
783
863
  const appEnvFiles = findAppEnvFiles(scan.apps, process.cwd());
784
864
  if (appEnvFiles.length > 0 && !nonInteractive) {
785
865
  console.log('');
786
866
  console.log(chalk_1.default.blue('=== Environment Files ==='));
787
- console.log(chalk_1.default.dim(`Found .env files in ${appEnvFiles.length} app directories`));
867
+ // Group by app type
868
+ const directApps = appEnvFiles.filter(e => !e.isService);
869
+ const serviceApps = appEnvFiles.filter(e => e.isService);
870
+ if (directApps.length > 0) {
871
+ console.log(chalk_1.default.dim(`Found ${directApps.length} app env files`));
872
+ }
873
+ if (serviceApps.length > 0) {
874
+ console.log(chalk_1.default.dim(`Found ${serviceApps.length} microservice env files`));
875
+ }
788
876
  const envChoices = appEnvFiles.map(env => ({
789
- name: `${env.appName}/${env.envFile}`,
877
+ name: env.isService ? `${env.appName} (service)` : env.appName,
790
878
  value: env.fullPath,
791
879
  checked: true,
792
880
  }));
793
881
  const selectedEnvFiles = await prompts.checkbox({
794
- message: 'Select .env files to merge into .env.genbox:',
882
+ message: 'Select .env files to include in .env.genbox:',
795
883
  choices: envChoices,
796
884
  });
797
885
  if (selectedEnvFiles.length > 0) {
798
- let mergedContent = `# Genbox Environment Variables
799
- # Merged from: ${selectedEnvFiles.map(f => path_1.default.relative(process.cwd(), f)).join(', ')}
800
- # DO NOT COMMIT THIS FILE
801
- #
802
- # Add staging/production URLs:
803
- # STAGING_MONGODB_URL=mongodb+srv://...
804
- # STAGING_REDIS_URL=redis://...
805
- # PROD_MONGODB_URL=mongodb+srv://...
806
- #
807
- # Git authentication:
808
- # GIT_TOKEN=ghp_xxxxxxxxxxxx
809
-
810
- `;
811
886
  for (const envFilePath of selectedEnvFiles) {
812
887
  const appInfo = appEnvFiles.find(e => e.fullPath === envFilePath);
813
- const content = fs_1.default.readFileSync(envFilePath, 'utf8');
814
- mergedContent += `\n# === ${appInfo?.appName || path_1.default.dirname(envFilePath)} ===\n`;
815
- mergedContent += content;
816
- mergedContent += '\n';
888
+ if (!appInfo)
889
+ continue;
890
+ const content = fs_1.default.readFileSync(envFilePath, 'utf8').trim();
891
+ // Add section header and content
892
+ segregatedContent += `# === ${appInfo.appName} ===\n`;
893
+ segregatedContent += content;
894
+ segregatedContent += '\n\n';
817
895
  }
818
- fs_1.default.writeFileSync(envPath, mergedContent);
819
- console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} from ${selectedEnvFiles.length} app env files`));
820
896
  }
821
897
  }
822
898
  else if (appEnvFiles.length > 0 && nonInteractive) {
823
899
  // Non-interactive: merge all env files
824
- let mergedContent = `# Genbox Environment Variables
825
- # Merged from app directories
826
- # DO NOT COMMIT THIS FILE
827
-
828
- `;
829
900
  for (const envFile of appEnvFiles) {
830
- const content = fs_1.default.readFileSync(envFile.fullPath, 'utf8');
831
- mergedContent += `\n# === ${envFile.appName} ===\n`;
832
- mergedContent += content;
833
- mergedContent += '\n';
901
+ const content = fs_1.default.readFileSync(envFile.fullPath, 'utf8').trim();
902
+ segregatedContent += `# === ${envFile.appName} ===\n`;
903
+ segregatedContent += content;
904
+ segregatedContent += '\n\n';
834
905
  }
835
- fs_1.default.writeFileSync(envPath, mergedContent);
836
- console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} from ${appEnvFiles.length} app env files`));
837
906
  }
838
907
  }
839
- // If no env file created yet, check for root .env
840
- if (!fs_1.default.existsSync(envPath)) {
908
+ // If no app env files found, check for root .env
909
+ const hasAppSections = segregatedContent.includes('# === ') &&
910
+ !segregatedContent.endsWith('# === GLOBAL ===\n');
911
+ if (!hasAppSections) {
841
912
  const existingEnvFiles = ['.env.local', '.env', '.env.development'];
842
913
  let existingEnvPath;
843
914
  for (const envFile of existingEnvFiles) {
@@ -849,51 +920,28 @@ async function setupEnvFile(projectName, config, nonInteractive = false, scan, i
849
920
  }
850
921
  if (existingEnvPath) {
851
922
  const copyExisting = nonInteractive ? true : await prompts.confirm({
852
- message: `Found ${path_1.default.basename(existingEnvPath)}. Copy to ${ENV_FILENAME}?`,
923
+ message: `Found ${path_1.default.basename(existingEnvPath)}. Include in ${ENV_FILENAME}?`,
853
924
  default: true,
854
925
  });
855
926
  if (copyExisting) {
856
- const content = fs_1.default.readFileSync(existingEnvPath, 'utf8');
857
- const header = `# Genbox Environment Variables
858
- # Generated from ${path_1.default.basename(existingEnvPath)}
859
- # DO NOT COMMIT THIS FILE
860
- #
861
- # Add staging/production URLs:
862
- # STAGING_MONGODB_URL=mongodb+srv://...
863
- # STAGING_REDIS_URL=redis://...
864
- # PROD_MONGODB_URL=mongodb+srv://...
865
- #
866
- # Git authentication:
867
- # GIT_TOKEN=ghp_xxxxxxxxxxxx
868
-
869
- `;
870
- fs_1.default.writeFileSync(envPath, header + content);
871
- console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} from ${path_1.default.basename(existingEnvPath)}`));
872
- }
873
- }
874
- else {
875
- const createEnv = nonInteractive ? true : await prompts.confirm({
876
- message: `Create ${ENV_FILENAME} template?`,
877
- default: true,
878
- });
879
- if (createEnv) {
880
- const template = generateEnvTemplate(projectName, config);
881
- fs_1.default.writeFileSync(envPath, template);
882
- console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} template`));
927
+ const content = fs_1.default.readFileSync(existingEnvPath, 'utf8').trim();
928
+ segregatedContent += `# === root ===\n`;
929
+ segregatedContent += `# From ${path_1.default.basename(existingEnvPath)}\n`;
930
+ segregatedContent += content;
931
+ segregatedContent += '\n\n';
883
932
  }
884
933
  }
885
934
  }
886
- // Append extra env vars (like GIT_TOKEN) to the file
887
- if (Object.keys(extraEnvVars).length > 0 && fs_1.default.existsSync(envPath)) {
888
- let content = fs_1.default.readFileSync(envPath, 'utf8');
889
- // Add extra env vars section
890
- let extraSection = '\n# === Added by genbox init ===\n';
891
- for (const [key, value] of Object.entries(extraEnvVars)) {
892
- // Remove any existing commented placeholder
893
- content = content.replace(new RegExp(`^#\\s*${key}=.*$`, 'gm'), '');
894
- extraSection += `${key}=${value}\n`;
895
- }
896
- fs_1.default.writeFileSync(envPath, content.trim() + '\n' + extraSection);
935
+ // Add END marker
936
+ segregatedContent += `# === END ===\n`;
937
+ // Write the file
938
+ fs_1.default.writeFileSync(envPath, segregatedContent);
939
+ const sectionCount = (segregatedContent.match(/# === [^=]+ ===/g) || []).length - 2; // Exclude GLOBAL and END
940
+ if (sectionCount > 0) {
941
+ console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME} with ${sectionCount} app section(s)`));
942
+ }
943
+ else {
944
+ console.log(chalk_1.default.green(`✔ Created ${ENV_FILENAME}`));
897
945
  }
898
946
  // Add to .gitignore
899
947
  const gitignorePath = path_1.default.join(process.cwd(), '.gitignore');
@@ -915,6 +963,19 @@ function generateEnvTemplate(projectName, config) {
915
963
  '# DO NOT COMMIT THIS FILE',
916
964
  '',
917
965
  '# ============================================',
966
+ '# API URL CONFIGURATION',
967
+ '# ============================================',
968
+ '# Use ${API_URL} in your app env vars (e.g., VITE_API_BASE_URL=${API_URL})',
969
+ '# At create time, ${API_URL} expands based on profile:',
970
+ '# - connect_to: staging → uses STAGING_API_URL',
971
+ '# - connect_to: production → uses PRODUCTION_API_URL',
972
+ '# - local/no connect_to → uses LOCAL_API_URL',
973
+ '',
974
+ 'LOCAL_API_URL=http://localhost:3050',
975
+ 'STAGING_API_URL=https://api.staging.example.com',
976
+ '# PRODUCTION_API_URL=https://api.example.com',
977
+ '',
978
+ '# ============================================',
918
979
  '# STAGING ENVIRONMENT',
919
980
  '# ============================================',
920
981
  '',
@@ -957,6 +1018,15 @@ function generateEnvTemplate(projectName, config) {
957
1018
  'STRIPE_SECRET_KEY=sk_test_xxx',
958
1019
  'STRIPE_WEBHOOK_SECRET=whsec_xxx',
959
1020
  '',
1021
+ '# ============================================',
1022
+ '# APPLICATION ENV VARS',
1023
+ '# ============================================',
1024
+ '# Use ${API_URL} for dynamic API URLs',
1025
+ '',
1026
+ '# Example:',
1027
+ '# VITE_API_BASE_URL=${API_URL}',
1028
+ '# NEXT_PUBLIC_API_URL=${API_URL}',
1029
+ '',
960
1030
  ];
961
1031
  return lines.join('\n');
962
1032
  }
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ /**
3
+ * Migrate Command
4
+ *
5
+ * Migrates genbox.yaml from v3 to v4 format:
6
+ * - Shows what changes will be made
7
+ * - Creates backup of original
8
+ * - Applies migration
9
+ *
10
+ * Usage:
11
+ * genbox migrate # Interactive migration
12
+ * genbox migrate --dry-run # Show changes without applying
13
+ * genbox migrate --yes # Skip confirmation
14
+ */
15
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
16
+ if (k2 === undefined) k2 = k;
17
+ var desc = Object.getOwnPropertyDescriptor(m, k);
18
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
19
+ desc = { enumerable: true, get: function() { return m[k]; } };
20
+ }
21
+ Object.defineProperty(o, k2, desc);
22
+ }) : (function(o, m, k, k2) {
23
+ if (k2 === undefined) k2 = k;
24
+ o[k2] = m[k];
25
+ }));
26
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
27
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
28
+ }) : function(o, v) {
29
+ o["default"] = v;
30
+ });
31
+ var __importStar = (this && this.__importStar) || (function () {
32
+ var ownKeys = function(o) {
33
+ ownKeys = Object.getOwnPropertyNames || function (o) {
34
+ var ar = [];
35
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
36
+ return ar;
37
+ };
38
+ return ownKeys(o);
39
+ };
40
+ return function (mod) {
41
+ if (mod && mod.__esModule) return mod;
42
+ var result = {};
43
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
44
+ __setModuleDefault(result, mod);
45
+ return result;
46
+ };
47
+ })();
48
+ var __importDefault = (this && this.__importDefault) || function (mod) {
49
+ return (mod && mod.__esModule) ? mod : { "default": mod };
50
+ };
51
+ Object.defineProperty(exports, "__esModule", { value: true });
52
+ exports.deprecationsCommand = exports.migrateCommand = void 0;
53
+ const commander_1 = require("commander");
54
+ const chalk_1 = __importDefault(require("chalk"));
55
+ const fs = __importStar(require("fs"));
56
+ const yaml = __importStar(require("js-yaml"));
57
+ const prompts_1 = require("@inquirer/prompts");
58
+ const config_loader_1 = require("../config-loader");
59
+ const migration_1 = require("../migration");
60
+ exports.migrateCommand = new commander_1.Command('migrate')
61
+ .description('Migrate genbox.yaml from v3 to v4 format')
62
+ .option('--dry-run', 'Show changes without applying')
63
+ .option('-y, --yes', 'Skip confirmation prompts')
64
+ .option('--no-backup', 'Skip creating backup file')
65
+ .option('--json', 'Output migration plan as JSON')
66
+ .action(async (options) => {
67
+ const cwd = process.cwd();
68
+ console.log(chalk_1.default.cyan('\n🔄 Genbox Configuration Migration\n'));
69
+ try {
70
+ // Load current configuration
71
+ const configLoader = new config_loader_1.ConfigLoader();
72
+ const loadResult = await configLoader.load(cwd);
73
+ if (!loadResult.found || !loadResult.config) {
74
+ console.log(chalk_1.default.red('No genbox.yaml found. Nothing to migrate.'));
75
+ process.exit(1);
76
+ }
77
+ const config = loadResult.config;
78
+ // Check if migration is needed
79
+ if (!(0, migration_1.needsMigration)(config)) {
80
+ console.log(chalk_1.default.green('✓ Configuration is already at v4. No migration needed.'));
81
+ return;
82
+ }
83
+ // Show deprecation warnings
84
+ const deprecations = (0, migration_1.checkDeprecations)(config);
85
+ if (deprecations.length > 0) {
86
+ console.log(chalk_1.default.yellow.bold(`Found ${deprecations.length} deprecated patterns:\n`));
87
+ for (const dep of deprecations) {
88
+ console.log(chalk_1.default.yellow(` • ${dep.path}: ${dep.message}`));
89
+ console.log(chalk_1.default.dim(` → ${dep.suggestion}`));
90
+ }
91
+ console.log();
92
+ }
93
+ // Get migration summary
94
+ const summary = (0, migration_1.getMigrationSummary)(config);
95
+ console.log(chalk_1.default.bold('Migration Summary:\n'));
96
+ console.log(` Fields to migrate: ${summary.fieldsToMigrate}`);
97
+ console.log(` Deprecated patterns: ${summary.deprecatedPatterns}`);
98
+ if (summary.estimatedChanges.length > 0) {
99
+ console.log(chalk_1.default.bold('\n Changes to be made:'));
100
+ for (const change of summary.estimatedChanges) {
101
+ console.log(` • ${change}`);
102
+ }
103
+ }
104
+ // Perform migration
105
+ const result = (0, migration_1.migrateV3ToV4)(config);
106
+ if (options.json) {
107
+ console.log(JSON.stringify({
108
+ summary,
109
+ changes: result.changes,
110
+ warnings: result.warnings,
111
+ config: result.config,
112
+ }, null, 2));
113
+ return;
114
+ }
115
+ // Show detailed changes
116
+ console.log(chalk_1.default.bold('\n📝 Detailed Changes:\n'));
117
+ for (const change of result.changes) {
118
+ const icon = change.type === 'add' ? '+' :
119
+ change.type === 'remove' ? '-' :
120
+ change.type === 'rename' ? '→' : '~';
121
+ const color = change.type === 'add' ? chalk_1.default.green :
122
+ change.type === 'remove' ? chalk_1.default.red :
123
+ chalk_1.default.yellow;
124
+ console.log(color(` ${icon} ${change.path}: ${change.description}`));
125
+ }
126
+ // Show warnings
127
+ if (result.warnings.length > 0) {
128
+ console.log(chalk_1.default.yellow.bold('\n⚠️ Warnings:\n'));
129
+ for (const warning of result.warnings) {
130
+ console.log(chalk_1.default.yellow(` • ${warning}`));
131
+ }
132
+ }
133
+ // Dry run stops here
134
+ if (options.dryRun) {
135
+ console.log(chalk_1.default.bold('\n📄 Migrated Configuration (preview):\n'));
136
+ console.log(chalk_1.default.dim('---'));
137
+ console.log(yaml.dump(result.config, { lineWidth: 120, noRefs: true }));
138
+ console.log(chalk_1.default.dim('---'));
139
+ console.log(chalk_1.default.cyan('\nThis was a dry run. No changes were made.'));
140
+ console.log('Run ' + chalk_1.default.bold('genbox migrate') + ' (without --dry-run) to apply changes.');
141
+ return;
142
+ }
143
+ // Confirm migration
144
+ if (!options.yes) {
145
+ console.log();
146
+ const confirmed = await (0, prompts_1.confirm)({
147
+ message: 'Apply these changes to genbox.yaml?',
148
+ default: true,
149
+ });
150
+ if (!confirmed) {
151
+ console.log(chalk_1.default.yellow('Migration cancelled.'));
152
+ return;
153
+ }
154
+ }
155
+ // Find the config file path
156
+ const projectSource = loadResult.sources.find(s => s.type === 'project');
157
+ if (!projectSource) {
158
+ console.log(chalk_1.default.red('Could not find project config file path.'));
159
+ process.exit(1);
160
+ }
161
+ const configPath = projectSource.path;
162
+ // Create backup
163
+ if (options.backup !== false) {
164
+ const backupPath = configPath.replace('.yaml', '.v3.yaml.bak');
165
+ fs.copyFileSync(configPath, backupPath);
166
+ console.log(chalk_1.default.dim(`\nBackup created: ${backupPath}`));
167
+ }
168
+ // Write migrated config
169
+ const migratedContent = yaml.dump(result.config, {
170
+ lineWidth: 120,
171
+ noRefs: true,
172
+ quotingType: '"',
173
+ forceQuotes: false,
174
+ });
175
+ // Add header comment
176
+ const header = `# Genbox Configuration v4
177
+ # Migrated from v3 on ${new Date().toISOString()}
178
+ # See: https://genbox.dev/docs/config-v4
179
+
180
+ `;
181
+ fs.writeFileSync(configPath, header + migratedContent);
182
+ console.log(chalk_1.default.green.bold('\n✓ Migration complete!'));
183
+ console.log(`\n Updated: ${configPath}`);
184
+ // Next steps
185
+ console.log(chalk_1.default.bold('\n📝 Next steps:\n'));
186
+ console.log(' 1. Review the migrated configuration');
187
+ console.log(' 2. Run ' + chalk_1.default.cyan('genbox validate') + ' to check for issues');
188
+ console.log(' 3. Run ' + chalk_1.default.cyan('genbox scan') + ' to update detected.yaml');
189
+ console.log(' 4. Run ' + chalk_1.default.cyan('genbox resolve') + ' to verify resolution');
190
+ console.log();
191
+ // Show v4 benefits
192
+ console.log(chalk_1.default.bold('🎉 v4 Benefits:\n'));
193
+ console.log(' • Explicit connections with ' + chalk_1.default.cyan('connects_to'));
194
+ console.log(' • Clear infrastructure with ' + chalk_1.default.cyan('provides'));
195
+ console.log(' • Opt-in detection with ' + chalk_1.default.cyan('$detect') + ' markers');
196
+ console.log(' • Strict mode for predictable behavior');
197
+ console.log();
198
+ }
199
+ catch (error) {
200
+ console.error(chalk_1.default.red('Migration failed:'), error);
201
+ process.exit(1);
202
+ }
203
+ });
204
+ /**
205
+ * Show deprecation warnings subcommand
206
+ */
207
+ exports.deprecationsCommand = new commander_1.Command('deprecations')
208
+ .description('Check for deprecated patterns in genbox.yaml')
209
+ .option('--json', 'Output as JSON')
210
+ .action(async (options) => {
211
+ const cwd = process.cwd();
212
+ try {
213
+ const configLoader = new config_loader_1.ConfigLoader();
214
+ const loadResult = await configLoader.load(cwd);
215
+ if (!loadResult.found || !loadResult.config) {
216
+ console.log(chalk_1.default.red('No genbox.yaml found.'));
217
+ process.exit(1);
218
+ }
219
+ const deprecations = (0, migration_1.checkDeprecations)(loadResult.config);
220
+ if (options.json) {
221
+ console.log(JSON.stringify(deprecations, null, 2));
222
+ return;
223
+ }
224
+ if (deprecations.length === 0) {
225
+ console.log(chalk_1.default.green('✓ No deprecated patterns found.'));
226
+ return;
227
+ }
228
+ console.log(chalk_1.default.yellow.bold(`\nFound ${deprecations.length} deprecated pattern(s):\n`));
229
+ for (const dep of deprecations) {
230
+ const icon = dep.severity === 'error' ? '✗' : '⚠';
231
+ const color = dep.severity === 'error' ? chalk_1.default.red : chalk_1.default.yellow;
232
+ console.log(color(`${icon} ${dep.path}`));
233
+ console.log(` ${dep.message}`);
234
+ console.log(chalk_1.default.dim(` → ${dep.suggestion}`));
235
+ if (dep.autoMigrate) {
236
+ console.log(chalk_1.default.dim(' (can be auto-migrated)'));
237
+ }
238
+ console.log();
239
+ }
240
+ console.log(chalk_1.default.bold('Run ' + chalk_1.default.cyan('genbox migrate') + ' to fix these issues.\n'));
241
+ }
242
+ catch (error) {
243
+ console.error(chalk_1.default.red('Check failed:'), error);
244
+ process.exit(1);
245
+ }
246
+ });