genbox 1.0.29 → 1.0.31

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.
@@ -618,6 +618,14 @@ exports.initCommand = new commander_1.Command('init')
618
618
  const v4Config = convertV2ToV4(generated.config, scan);
619
619
  // Update project name
620
620
  v4Config.project.name = projectName;
621
+ // Environment configuration - do this BEFORE profiles so profiles can reference environments
622
+ // (skip only in non-interactive mode, always show for --from-scan since environments are required)
623
+ if (!nonInteractive) {
624
+ const envConfig = await setupEnvironments(scan, v4Config, isMultiRepoStructure, existingEnvValues);
625
+ if (envConfig) {
626
+ v4Config.environments = envConfig;
627
+ }
628
+ }
621
629
  // Ask about profiles (skip prompt when using --from-scan)
622
630
  let createProfiles = true;
623
631
  if (!nonInteractive && !options.fromScan) {
@@ -840,14 +848,6 @@ exports.initCommand = new commander_1.Command('init')
840
848
  };
841
849
  }
842
850
  }
843
- // Environment configuration (skip only in non-interactive mode)
844
- // For --from-scan, we still want to prompt for environments since they're required for genbox to work
845
- if (!nonInteractive) {
846
- const envConfig = await setupEnvironments(scan, v4Config, isMultiRepo, existingEnvValues);
847
- if (envConfig) {
848
- v4Config.environments = envConfig;
849
- }
850
- }
851
851
  // Script selection - always show multi-select UI (skip in non-interactive mode and --from-scan)
852
852
  if (!nonInteractive && !options.fromScan) {
853
853
  // Scan for scripts
@@ -935,32 +935,6 @@ exports.initCommand = new commander_1.Command('init')
935
935
  console.log(chalk_1.default.dim(` - ${warning}`));
936
936
  }
937
937
  }
938
- // Show API URL guidance if environments are configured
939
- if (v4Config.environments && Object.keys(v4Config.environments).length > 0) {
940
- console.log('');
941
- console.log(chalk_1.default.blue('=== API URL Configuration ==='));
942
- console.log(chalk_1.default.dim('The following API URLs were added to .env.genbox:'));
943
- console.log('');
944
- console.log(chalk_1.default.dim(' LOCAL_API_URL=http://localhost:3050'));
945
- for (const [envName, envConfig] of Object.entries(v4Config.environments)) {
946
- // v4 format: urls.api contains the API URL
947
- const apiUrl = envConfig.urls?.api || envConfig.urls?.gateway;
948
- if (apiUrl) {
949
- const varName = `${envName.toUpperCase()}_API_URL`;
950
- console.log(chalk_1.default.dim(` ${varName}=${apiUrl}`));
951
- }
952
- }
953
- console.log('');
954
- console.log(chalk_1.default.yellow('To use dynamic API URLs:'));
955
- console.log(chalk_1.default.dim(' Use ${API_URL} in your app env vars, e.g.:'));
956
- console.log(chalk_1.default.cyan(' VITE_API_BASE_URL=${API_URL}'));
957
- console.log(chalk_1.default.cyan(' NEXT_PUBLIC_API_URL=${API_URL}'));
958
- console.log('');
959
- console.log(chalk_1.default.dim(' At create time, ${API_URL} expands based on profile:'));
960
- console.log(chalk_1.default.dim(' • default_connection: staging → uses STAGING_API_URL'));
961
- console.log(chalk_1.default.dim(' • default_connection: production → uses PRODUCTION_API_URL'));
962
- console.log(chalk_1.default.dim(' • local/no default_connection → uses LOCAL_API_URL'));
963
- }
964
938
  // CORS Configuration Warning
965
939
  const hasBackendApps = scan.apps.some(a => a.type === 'backend' || a.type === 'api');
966
940
  const hasFrontendApps = scan.apps.some(a => a.type === 'frontend');
@@ -79,6 +79,72 @@ function sshExec(ip, keyPath, command, timeoutSecs = 10) {
79
79
  return '';
80
80
  }
81
81
  }
82
+ function formatDuration(secs) {
83
+ const mins = Math.floor(secs / 60);
84
+ const remainingSecs = secs % 60;
85
+ return `${mins}m ${remainingSecs}s`;
86
+ }
87
+ function getTimingBreakdown(ip, keyPath) {
88
+ const timing = {
89
+ sshReady: null,
90
+ cloudInit: null,
91
+ total: null,
92
+ };
93
+ // Get total system uptime
94
+ const uptime = sshExec(ip, keyPath, "cat /proc/uptime 2>/dev/null | cut -d' ' -f1 | cut -d'.' -f1");
95
+ if (uptime) {
96
+ timing.total = parseInt(uptime, 10);
97
+ }
98
+ // Get SSH ready time (sshd service start time relative to boot)
99
+ // ActiveEnterTimestampMonotonic gives microseconds since boot when service became active
100
+ const sshdTimestamp = sshExec(ip, keyPath, "systemctl show sshd --property=ActiveEnterTimestampMonotonic 2>/dev/null | cut -d'=' -f2");
101
+ if (sshdTimestamp && sshdTimestamp.trim()) {
102
+ const microseconds = parseInt(sshdTimestamp.trim(), 10);
103
+ if (!isNaN(microseconds) && microseconds > 0) {
104
+ timing.sshReady = Math.round(microseconds / 1000000); // Convert to seconds
105
+ }
106
+ }
107
+ // Get cloud-init execution time from logs
108
+ const cloudInitTime = sshExec(ip, keyPath, "sudo grep -oP 'Setup Complete in \\K[0-9]+m [0-9]+s' /var/log/cloud-init-output.log 2>/dev/null | tail -1");
109
+ if (cloudInitTime && cloudInitTime.trim()) {
110
+ // Parse "Xm Ys" format
111
+ const match = cloudInitTime.trim().match(/(\d+)m\s*(\d+)s/);
112
+ if (match) {
113
+ timing.cloudInit = parseInt(match[1], 10) * 60 + parseInt(match[2], 10);
114
+ }
115
+ }
116
+ // Fallback: calculate cloud-init time from result.json if not in logs
117
+ if (timing.cloudInit === null && timing.sshReady !== null && timing.total !== null) {
118
+ // Get cloud-init finish timestamp
119
+ const resultTimestamp = sshExec(ip, keyPath, "stat -c %Y /run/cloud-init/result.json 2>/dev/null");
120
+ const bootTimestamp = sshExec(ip, keyPath, "date -d \"$(uptime -s)\" +%s 2>/dev/null");
121
+ if (resultTimestamp && bootTimestamp) {
122
+ const resultTime = parseInt(resultTimestamp.trim(), 10);
123
+ const bootTime = parseInt(bootTimestamp.trim(), 10);
124
+ if (!isNaN(resultTime) && !isNaN(bootTime) && resultTime > bootTime) {
125
+ // Cloud-init time = (finish time - boot time) - ssh ready time
126
+ const totalFromBoot = resultTime - bootTime;
127
+ timing.cloudInit = Math.max(0, totalFromBoot - timing.sshReady);
128
+ }
129
+ }
130
+ }
131
+ return timing;
132
+ }
133
+ function displayTimingBreakdown(timing) {
134
+ if (timing.sshReady !== null || timing.cloudInit !== null || timing.total !== null) {
135
+ console.log(chalk_1.default.blue('[INFO] === Timing Breakdown ==='));
136
+ if (timing.sshReady !== null) {
137
+ console.log(` SSH Ready: ${formatDuration(timing.sshReady)}`);
138
+ }
139
+ if (timing.cloudInit !== null) {
140
+ console.log(` Cloud-init: ${formatDuration(timing.cloudInit)}`);
141
+ }
142
+ if (timing.total !== null) {
143
+ console.log(` ${chalk_1.default.bold('Total: ' + formatDuration(timing.total))}`);
144
+ }
145
+ console.log('');
146
+ }
147
+ }
82
148
  exports.statusCommand = new commander_1.Command('status')
83
149
  .description('Check cloud-init progress and service status of a Genbox')
84
150
  .argument('[name]', 'Name of the Genbox (optional - will prompt if not provided)')
@@ -145,18 +211,24 @@ exports.statusCommand = new commander_1.Command('status')
145
211
  return;
146
212
  }
147
213
  if (status.includes('running')) {
148
- // Get elapsed time since server boot
149
- const uptime = sshExec(target.ipAddress, keyPath, "cat /proc/uptime 2>/dev/null | cut -d' ' -f1 | cut -d'.' -f1");
150
- if (uptime) {
151
- const uptimeSecs = parseInt(uptime, 10);
152
- const mins = Math.floor(uptimeSecs / 60);
153
- const secs = uptimeSecs % 60;
154
- console.log(chalk_1.default.yellow(`[WARN] Cloud-init is still running... (elapsed: ${mins}m ${secs}s)`));
214
+ // Get timing breakdown
215
+ const timing = getTimingBreakdown(target.ipAddress, keyPath);
216
+ if (timing.total !== null) {
217
+ console.log(chalk_1.default.yellow(`[WARN] Cloud-init is still running... (elapsed: ${formatDuration(timing.total)})`));
155
218
  }
156
219
  else {
157
220
  console.log(chalk_1.default.yellow('[WARN] Cloud-init is still running...'));
158
221
  }
159
222
  console.log('');
223
+ // Show timing breakdown so far
224
+ if (timing.sshReady !== null) {
225
+ console.log(chalk_1.default.blue('[INFO] === Timing So Far ==='));
226
+ console.log(` SSH Ready: ${formatDuration(timing.sshReady)}`);
227
+ if (timing.total !== null) {
228
+ console.log(` Elapsed: ${formatDuration(timing.total)}`);
229
+ }
230
+ console.log('');
231
+ }
160
232
  console.log(chalk_1.default.blue('[INFO] Latest setup progress:'));
161
233
  // Tail cloud-init output log for progress
162
234
  const logOutput = sshExec(target.ipAddress, keyPath, "sudo tail -30 /var/log/cloud-init-output.log 2>/dev/null | grep -E '^===|^#|Progress:|DONE|error|Error|failed|Failed' || sudo tail -20 /var/log/cloud-init-output.log", 15);
@@ -165,29 +237,12 @@ exports.statusCommand = new commander_1.Command('status')
165
237
  }
166
238
  }
167
239
  else if (status.includes('done')) {
168
- // Try to get actual cloud-init completion time from logs first
169
- let completionTimeFormatted = '';
170
- // First, try to extract "Setup Complete in Xm Ys" from cloud-init logs
171
- const setupTime = sshExec(target.ipAddress, keyPath, "sudo grep -oP 'Setup Complete in \\K[0-9]+m [0-9]+s' /var/log/cloud-init-output.log 2>/dev/null | tail -1");
172
- if (setupTime && setupTime.trim()) {
173
- completionTimeFormatted = setupTime.trim();
174
- }
175
- else {
176
- // Fallback: Calculate from cloud-init result.json timestamp vs boot time
177
- // Get boot timestamp and cloud-init finish timestamp
178
- const timestamps = sshExec(target.ipAddress, keyPath, "echo $(date -d \"$(uptime -s)\" +%s) $(stat -c %Y /run/cloud-init/result.json 2>/dev/null || echo 0)");
179
- if (timestamps && timestamps.trim()) {
180
- const [bootTime, finishTime] = timestamps.trim().split(' ').map(Number);
181
- if (bootTime && finishTime && finishTime > bootTime) {
182
- const elapsedSecs = finishTime - bootTime;
183
- const mins = Math.floor(elapsedSecs / 60);
184
- const secs = elapsedSecs % 60;
185
- completionTimeFormatted = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
186
- }
187
- }
188
- }
189
- console.log(chalk_1.default.green(`[SUCCESS] Cloud-init completed!${completionTimeFormatted ? ` (${completionTimeFormatted})` : ''}`));
240
+ // Get timing breakdown
241
+ const timing = getTimingBreakdown(target.ipAddress, keyPath);
242
+ console.log(chalk_1.default.green(`[SUCCESS] Cloud-init completed!${timing.total !== null ? ` (${formatDuration(timing.total)})` : ''}`));
190
243
  console.log('');
244
+ // Show detailed timing breakdown
245
+ displayTimingBreakdown(timing);
191
246
  // Show Database Stats - only if configured
192
247
  let dbContainer = '';
193
248
  let dbName = '';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genbox",
3
- "version": "1.0.29",
3
+ "version": "1.0.31",
4
4
  "description": "Genbox CLI - AI-Powered Development Environments",
5
5
  "main": "dist/index.js",
6
6
  "bin": {