genbox 1.0.27 → 1.0.28
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.
- package/dist/commands/init.js +94 -111
- package/dist/commands/status.js +21 -9
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -1175,127 +1175,96 @@ async function setupGitAuth(gitInfo, projectName) {
|
|
|
1175
1175
|
* Setup staging/production environments (v4 format)
|
|
1176
1176
|
*/
|
|
1177
1177
|
async function setupEnvironments(scan, config, isMultiRepo = false, existingEnvValues = {}) {
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1178
|
+
// First ask which environments they want to configure
|
|
1179
|
+
const envChoice = await prompts.select({
|
|
1180
|
+
message: 'Which environments do you want to configure?',
|
|
1181
|
+
choices: [
|
|
1182
|
+
{ name: 'Staging only', value: 'staging', description: 'Connect to staging API' },
|
|
1183
|
+
{ name: 'Production only', value: 'production', description: 'Connect to production API' },
|
|
1184
|
+
{ name: 'Both staging and production', value: 'both', description: 'Configure both environments' },
|
|
1185
|
+
{ name: 'Skip for now', value: 'skip', description: 'No remote environments' },
|
|
1186
|
+
],
|
|
1187
|
+
default: 'staging',
|
|
1181
1188
|
});
|
|
1182
|
-
if (
|
|
1189
|
+
if (envChoice === 'skip') {
|
|
1183
1190
|
return undefined;
|
|
1184
1191
|
}
|
|
1185
1192
|
console.log('');
|
|
1186
1193
|
console.log(chalk_1.default.blue('=== Environment Setup ==='));
|
|
1187
1194
|
console.log(chalk_1.default.dim('These URLs will be used when connecting to external services.'));
|
|
1188
1195
|
console.log(chalk_1.default.dim('Actual secrets go in .env.genbox'));
|
|
1189
|
-
console.log('');
|
|
1190
1196
|
const environments = {};
|
|
1191
|
-
// Get existing
|
|
1197
|
+
// Get existing URLs if available
|
|
1192
1198
|
const existingStagingApiUrl = existingEnvValues['STAGING_API_URL'];
|
|
1193
1199
|
const existingProductionApiUrl = existingEnvValues['PRODUCTION_API_URL'] || existingEnvValues['PROD_API_URL'];
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
default:
|
|
1200
|
+
const configureStaging = envChoice === 'staging' || envChoice === 'both';
|
|
1201
|
+
const configureProduction = envChoice === 'production' || envChoice === 'both';
|
|
1202
|
+
// Configure staging if selected
|
|
1203
|
+
if (configureStaging) {
|
|
1204
|
+
console.log('');
|
|
1205
|
+
console.log(chalk_1.default.cyan('Staging Environment:'));
|
|
1206
|
+
if (isMultiRepo) {
|
|
1207
|
+
const backendApps = scan.apps.filter(a => a.type === 'backend' || a.type === 'api');
|
|
1208
|
+
if (backendApps.length > 0) {
|
|
1209
|
+
const urls = {};
|
|
1210
|
+
for (const app of backendApps) {
|
|
1211
|
+
const existingUrl = existingEnvValues[`STAGING_${app.name.toUpperCase()}_URL`] ||
|
|
1212
|
+
(app.name === 'api' ? existingStagingApiUrl : '');
|
|
1213
|
+
if (existingUrl) {
|
|
1214
|
+
console.log(chalk_1.default.dim(` Found existing value for ${app.name}: ${existingUrl}`));
|
|
1215
|
+
const useExisting = await prompts.confirm({
|
|
1216
|
+
message: ` Use existing ${app.name} staging URL?`,
|
|
1217
|
+
default: true,
|
|
1218
|
+
});
|
|
1219
|
+
if (useExisting) {
|
|
1220
|
+
urls[app.name] = existingUrl;
|
|
1221
|
+
continue;
|
|
1222
|
+
}
|
|
1223
|
+
}
|
|
1224
|
+
const url = await prompts.input({
|
|
1225
|
+
message: ` ${app.name} staging URL:`,
|
|
1226
|
+
default: '',
|
|
1209
1227
|
});
|
|
1210
|
-
if (
|
|
1211
|
-
urls[app.name] =
|
|
1212
|
-
continue;
|
|
1228
|
+
if (url) {
|
|
1229
|
+
urls[app.name] = url;
|
|
1213
1230
|
}
|
|
1214
1231
|
}
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
urls[app.name] = url;
|
|
1232
|
+
if (Object.keys(urls).length > 0) {
|
|
1233
|
+
environments.staging = {
|
|
1234
|
+
description: 'Staging environment',
|
|
1235
|
+
urls,
|
|
1236
|
+
};
|
|
1221
1237
|
}
|
|
1222
1238
|
}
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1239
|
+
else {
|
|
1240
|
+
const stagingApiUrl = await promptForApiUrl('staging', existingStagingApiUrl);
|
|
1241
|
+
if (stagingApiUrl) {
|
|
1242
|
+
environments.staging = {
|
|
1243
|
+
description: 'Staging environment',
|
|
1244
|
+
urls: { api: stagingApiUrl },
|
|
1245
|
+
};
|
|
1246
|
+
}
|
|
1228
1247
|
}
|
|
1229
1248
|
}
|
|
1230
1249
|
else {
|
|
1231
|
-
|
|
1232
|
-
let stagingApiUrl = '';
|
|
1233
|
-
if (existingStagingApiUrl) {
|
|
1234
|
-
console.log(chalk_1.default.dim(` Found existing value: ${existingStagingApiUrl}`));
|
|
1235
|
-
const useExisting = await prompts.confirm({
|
|
1236
|
-
message: 'Use existing staging API URL?',
|
|
1237
|
-
default: true,
|
|
1238
|
-
});
|
|
1239
|
-
if (useExisting) {
|
|
1240
|
-
stagingApiUrl = existingStagingApiUrl;
|
|
1241
|
-
}
|
|
1242
|
-
}
|
|
1243
|
-
if (!stagingApiUrl) {
|
|
1244
|
-
stagingApiUrl = await prompts.input({
|
|
1245
|
-
message: 'Staging API URL (leave empty to skip):',
|
|
1246
|
-
default: '',
|
|
1247
|
-
});
|
|
1248
|
-
}
|
|
1250
|
+
const stagingApiUrl = await promptForApiUrl('staging', existingStagingApiUrl);
|
|
1249
1251
|
if (stagingApiUrl) {
|
|
1250
1252
|
environments.staging = {
|
|
1251
1253
|
description: 'Staging environment',
|
|
1252
|
-
urls: {
|
|
1253
|
-
api: stagingApiUrl,
|
|
1254
|
-
},
|
|
1254
|
+
urls: { api: stagingApiUrl },
|
|
1255
1255
|
};
|
|
1256
1256
|
}
|
|
1257
1257
|
}
|
|
1258
1258
|
}
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
console.log(chalk_1.default.dim(` Found existing value: ${existingStagingApiUrl}`));
|
|
1264
|
-
const useExisting = await prompts.confirm({
|
|
1265
|
-
message: 'Use existing staging API URL?',
|
|
1266
|
-
default: true,
|
|
1267
|
-
});
|
|
1268
|
-
if (useExisting) {
|
|
1269
|
-
stagingApiUrl = existingStagingApiUrl;
|
|
1270
|
-
}
|
|
1271
|
-
}
|
|
1272
|
-
if (!stagingApiUrl) {
|
|
1273
|
-
stagingApiUrl = await prompts.input({
|
|
1274
|
-
message: 'Staging API URL (leave empty to skip):',
|
|
1275
|
-
default: '',
|
|
1276
|
-
});
|
|
1277
|
-
}
|
|
1278
|
-
if (stagingApiUrl) {
|
|
1279
|
-
environments.staging = {
|
|
1280
|
-
description: 'Staging environment',
|
|
1281
|
-
urls: {
|
|
1282
|
-
api: stagingApiUrl,
|
|
1283
|
-
},
|
|
1284
|
-
};
|
|
1285
|
-
}
|
|
1286
|
-
}
|
|
1287
|
-
const setupProd = await prompts.confirm({
|
|
1288
|
-
message: 'Also configure production environment?',
|
|
1289
|
-
default: false,
|
|
1290
|
-
});
|
|
1291
|
-
if (setupProd) {
|
|
1259
|
+
// Configure production if selected
|
|
1260
|
+
if (configureProduction) {
|
|
1261
|
+
console.log('');
|
|
1262
|
+
console.log(chalk_1.default.cyan('Production Environment:'));
|
|
1292
1263
|
if (isMultiRepo) {
|
|
1293
1264
|
const backendApps = scan.apps.filter(a => a.type === 'backend' || a.type === 'api');
|
|
1294
1265
|
if (backendApps.length > 0) {
|
|
1295
|
-
console.log(chalk_1.default.dim('Configure production API URLs for each backend service:'));
|
|
1296
1266
|
const prodUrls = {};
|
|
1297
1267
|
for (const app of backendApps) {
|
|
1298
|
-
// Check for existing app-specific URL or use general production URL for 'api' app
|
|
1299
1268
|
const existingUrl = existingEnvValues[`PRODUCTION_${app.name.toUpperCase()}_URL`] ||
|
|
1300
1269
|
existingEnvValues[`PROD_${app.name.toUpperCase()}_URL`] ||
|
|
1301
1270
|
(app.name === 'api' ? existingProductionApiUrl : '');
|
|
@@ -1329,31 +1298,26 @@ async function setupEnvironments(scan, config, isMultiRepo = false, existingEnvV
|
|
|
1329
1298
|
};
|
|
1330
1299
|
}
|
|
1331
1300
|
}
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1301
|
+
else {
|
|
1302
|
+
const prodApiUrl = await promptForApiUrl('production', existingProductionApiUrl);
|
|
1303
|
+
if (prodApiUrl) {
|
|
1304
|
+
environments.production = {
|
|
1305
|
+
description: 'Production (use with caution)',
|
|
1306
|
+
urls: { api: prodApiUrl },
|
|
1307
|
+
safety: {
|
|
1308
|
+
read_only: true,
|
|
1309
|
+
require_confirmation: true,
|
|
1310
|
+
},
|
|
1311
|
+
};
|
|
1343
1312
|
}
|
|
1344
1313
|
}
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
default: '',
|
|
1349
|
-
});
|
|
1350
|
-
}
|
|
1314
|
+
}
|
|
1315
|
+
else {
|
|
1316
|
+
const prodApiUrl = await promptForApiUrl('production', existingProductionApiUrl);
|
|
1351
1317
|
if (prodApiUrl) {
|
|
1352
1318
|
environments.production = {
|
|
1353
1319
|
description: 'Production (use with caution)',
|
|
1354
|
-
urls: {
|
|
1355
|
-
api: prodApiUrl,
|
|
1356
|
-
},
|
|
1320
|
+
urls: { api: prodApiUrl },
|
|
1357
1321
|
safety: {
|
|
1358
1322
|
read_only: true,
|
|
1359
1323
|
require_confirmation: true,
|
|
@@ -1364,6 +1328,25 @@ async function setupEnvironments(scan, config, isMultiRepo = false, existingEnvV
|
|
|
1364
1328
|
}
|
|
1365
1329
|
return Object.keys(environments).length > 0 ? environments : undefined;
|
|
1366
1330
|
}
|
|
1331
|
+
/**
|
|
1332
|
+
* Prompt for a single API URL with existing value support
|
|
1333
|
+
*/
|
|
1334
|
+
async function promptForApiUrl(envName, existingUrl) {
|
|
1335
|
+
if (existingUrl) {
|
|
1336
|
+
console.log(chalk_1.default.dim(` Found existing value: ${existingUrl}`));
|
|
1337
|
+
const useExisting = await prompts.confirm({
|
|
1338
|
+
message: ` Use existing ${envName} API URL?`,
|
|
1339
|
+
default: true,
|
|
1340
|
+
});
|
|
1341
|
+
if (useExisting) {
|
|
1342
|
+
return existingUrl;
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
return await prompts.input({
|
|
1346
|
+
message: ` ${envName.charAt(0).toUpperCase() + envName.slice(1)} API URL:`,
|
|
1347
|
+
default: '',
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1367
1350
|
/**
|
|
1368
1351
|
* Setup .env.genbox file with segregated app sections
|
|
1369
1352
|
*/
|
package/dist/commands/status.js
CHANGED
|
@@ -165,16 +165,28 @@ exports.statusCommand = new commander_1.Command('status')
|
|
|
165
165
|
}
|
|
166
166
|
}
|
|
167
167
|
else if (status.includes('done')) {
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
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
|
+
}
|
|
176
188
|
}
|
|
177
|
-
console.log(chalk_1.default.green(`[SUCCESS] Cloud-init completed!${
|
|
189
|
+
console.log(chalk_1.default.green(`[SUCCESS] Cloud-init completed!${completionTimeFormatted ? ` (${completionTimeFormatted})` : ''}`));
|
|
178
190
|
console.log('');
|
|
179
191
|
// Show Database Stats - only if configured
|
|
180
192
|
let dbContainer = '';
|