genbox 1.0.51 → 1.0.53
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/create.js +113 -58
- package/dist/commands/init.js +1 -1
- package/dist/commands/profiles.js +1 -1
- package/dist/profile-resolver.js +29 -12
- package/package.json +1 -1
package/dist/commands/create.js
CHANGED
|
@@ -439,69 +439,124 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
439
439
|
resolved.database.url &&
|
|
440
440
|
!options.dbCopyRemote;
|
|
441
441
|
if (needsLocalDbCopy) {
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
442
|
+
const snapshotSource = (resolved.database.source === 'staging' ? 'staging' :
|
|
443
|
+
resolved.database.source === 'production' ? 'production' : 'local');
|
|
444
|
+
// Check for existing snapshot if project is synced
|
|
445
|
+
let useExistingSnapshot = false;
|
|
446
|
+
let existingSnapshot = null;
|
|
447
|
+
if (projectCache?._id && !options.dbDump) {
|
|
448
|
+
try {
|
|
449
|
+
existingSnapshot = await (0, api_1.getLatestSnapshot)(projectCache._id, snapshotSource);
|
|
450
|
+
if (existingSnapshot && existingSnapshot.status === 'ready') {
|
|
451
|
+
const snapshotAge = Date.now() - new Date(existingSnapshot.createdAt).getTime();
|
|
452
|
+
const hoursAgo = Math.floor(snapshotAge / (1000 * 60 * 60));
|
|
453
|
+
const timeAgoStr = hoursAgo < 1 ? 'less than an hour ago' :
|
|
454
|
+
hoursAgo === 1 ? '1 hour ago' :
|
|
455
|
+
hoursAgo < 24 ? `${hoursAgo} hours ago` :
|
|
456
|
+
`${Math.floor(hoursAgo / 24)} days ago`;
|
|
457
|
+
console.log('');
|
|
458
|
+
console.log(chalk_1.default.blue('=== Database Copy ==='));
|
|
459
|
+
console.log(chalk_1.default.dim(` Source: ${resolved.database.source}`));
|
|
460
|
+
if (!options.yes) {
|
|
461
|
+
const snapshotChoice = await prompts.select({
|
|
462
|
+
message: 'Database snapshot:',
|
|
463
|
+
choices: [
|
|
464
|
+
{
|
|
465
|
+
name: `Use existing snapshot (${timeAgoStr}, ${(0, db_utils_1.formatBytes)(existingSnapshot.sizeBytes)})`,
|
|
466
|
+
value: 'existing',
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
name: 'Create fresh snapshot (dump now)',
|
|
470
|
+
value: 'fresh',
|
|
471
|
+
},
|
|
472
|
+
],
|
|
473
|
+
});
|
|
474
|
+
useExistingSnapshot = snapshotChoice === 'existing';
|
|
475
|
+
}
|
|
476
|
+
else {
|
|
477
|
+
// In non-interactive mode, use existing if less than 24 hours old
|
|
478
|
+
if (hoursAgo < 24) {
|
|
479
|
+
useExistingSnapshot = true;
|
|
480
|
+
console.log(chalk_1.default.dim(` Using existing snapshot from ${timeAgoStr}`));
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
if (useExistingSnapshot) {
|
|
484
|
+
snapshotId = existingSnapshot._id;
|
|
485
|
+
snapshotS3Key = existingSnapshot.s3Key;
|
|
486
|
+
console.log(chalk_1.default.green(` ✓ Using existing snapshot`));
|
|
487
|
+
}
|
|
488
|
+
}
|
|
462
489
|
}
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
console.log(chalk_1.default.blue('=== Database Copy ==='));
|
|
466
|
-
console.log(chalk_1.default.dim(` Source: ${resolved.database.source}`));
|
|
467
|
-
console.log(chalk_1.default.dim(` URL: ${dbUrl.replace(/\/\/[^:]+:[^@]+@/, '//***:***@')}`));
|
|
468
|
-
const dumpSpinner = (0, ora_1.default)('Creating database dump...').start();
|
|
469
|
-
const dumpResult = await (0, db_utils_1.runLocalMongoDump)(dbUrl, {
|
|
470
|
-
onProgress: (msg) => dumpSpinner.text = msg,
|
|
471
|
-
});
|
|
472
|
-
if (!dumpResult.success) {
|
|
473
|
-
dumpSpinner.fail(chalk_1.default.red('Database dump failed'));
|
|
474
|
-
console.log(chalk_1.default.red(` ${dumpResult.error}`));
|
|
475
|
-
console.log('');
|
|
476
|
-
console.log(chalk_1.default.dim('You can:'));
|
|
477
|
-
console.log(chalk_1.default.dim(' • Fix the database connection and try again'));
|
|
478
|
-
console.log(chalk_1.default.dim(' • Use --db local to start with an empty database'));
|
|
479
|
-
console.log(chalk_1.default.dim(' • Use --db-dump <path> to provide an existing dump'));
|
|
480
|
-
return;
|
|
490
|
+
catch {
|
|
491
|
+
// Silently continue if we can't fetch snapshots
|
|
481
492
|
}
|
|
482
|
-
dumpSpinner.succeed(chalk_1.default.green(`Database dump created (${(0, db_utils_1.formatBytes)(dumpResult.sizeBytes || 0)})`));
|
|
483
|
-
localDumpPath = dumpResult.dumpPath;
|
|
484
493
|
}
|
|
485
|
-
//
|
|
486
|
-
if (
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
uploadSpinner.succeed(chalk_1.default.green('Database snapshot uploaded'));
|
|
496
|
-
snapshotId = snapshotResult.snapshotId;
|
|
497
|
-
snapshotS3Key = snapshotResult.s3Key;
|
|
498
|
-
// Cleanup local dump since it's now in S3
|
|
499
|
-
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
500
|
-
localDumpPath = undefined;
|
|
494
|
+
// If not using existing snapshot, create a new one
|
|
495
|
+
if (!useExistingSnapshot) {
|
|
496
|
+
// Check for user-provided dump file
|
|
497
|
+
if (options.dbDump) {
|
|
498
|
+
if (!fs.existsSync(options.dbDump)) {
|
|
499
|
+
console.log(chalk_1.default.red(`Database dump file not found: ${options.dbDump}`));
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
localDumpPath = options.dbDump;
|
|
503
|
+
console.log(chalk_1.default.dim(` Using provided dump file: ${options.dbDump}`));
|
|
501
504
|
}
|
|
502
505
|
else {
|
|
503
|
-
|
|
504
|
-
|
|
506
|
+
// Need to run mongodump locally
|
|
507
|
+
if (!(0, db_utils_1.isMongoDumpAvailable)()) {
|
|
508
|
+
console.log(chalk_1.default.red('mongodump not found. Required for database copy.'));
|
|
509
|
+
console.log('');
|
|
510
|
+
console.log((0, db_utils_1.getMongoDumpInstallInstructions)());
|
|
511
|
+
console.log('');
|
|
512
|
+
console.log(chalk_1.default.dim('Alternatively:'));
|
|
513
|
+
console.log(chalk_1.default.dim(' • Use --db-dump <path> to provide an existing dump file'));
|
|
514
|
+
console.log(chalk_1.default.dim(' • Use --db-copy-remote if your database is publicly accessible'));
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const dbUrl = resolved.database.url;
|
|
518
|
+
if (!existingSnapshot) {
|
|
519
|
+
console.log('');
|
|
520
|
+
console.log(chalk_1.default.blue('=== Database Copy ==='));
|
|
521
|
+
console.log(chalk_1.default.dim(` Source: ${resolved.database.source}`));
|
|
522
|
+
}
|
|
523
|
+
console.log(chalk_1.default.dim(` URL: ${dbUrl.replace(/\/\/[^:]+:[^@]+@/, '//***:***@')}`));
|
|
524
|
+
const dumpSpinner = (0, ora_1.default)('Creating database dump...').start();
|
|
525
|
+
const dumpResult = await (0, db_utils_1.runLocalMongoDump)(dbUrl, {
|
|
526
|
+
onProgress: (msg) => dumpSpinner.text = msg,
|
|
527
|
+
});
|
|
528
|
+
if (!dumpResult.success) {
|
|
529
|
+
dumpSpinner.fail(chalk_1.default.red('Database dump failed'));
|
|
530
|
+
console.log(chalk_1.default.red(` ${dumpResult.error}`));
|
|
531
|
+
console.log('');
|
|
532
|
+
console.log(chalk_1.default.dim('You can:'));
|
|
533
|
+
console.log(chalk_1.default.dim(' • Fix the database connection and try again'));
|
|
534
|
+
console.log(chalk_1.default.dim(' • Use --db local to start with an empty database'));
|
|
535
|
+
console.log(chalk_1.default.dim(' • Use --db-dump <path> to provide an existing dump'));
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
dumpSpinner.succeed(chalk_1.default.green(`Database dump created (${(0, db_utils_1.formatBytes)(dumpResult.sizeBytes || 0)})`));
|
|
539
|
+
localDumpPath = dumpResult.dumpPath;
|
|
540
|
+
}
|
|
541
|
+
// Upload to S3 if we have a project ID
|
|
542
|
+
if (localDumpPath && projectCache?._id) {
|
|
543
|
+
const uploadSpinner = (0, ora_1.default)('Uploading database snapshot...').start();
|
|
544
|
+
const snapshotResult = await (0, db_utils_1.createAndUploadSnapshot)(localDumpPath, projectCache._id, snapshotSource, {
|
|
545
|
+
sourceUrl: resolved.database.url?.replace(/\/\/[^:]+:[^@]+@/, '//***:***@'),
|
|
546
|
+
onProgress: (msg) => uploadSpinner.text = msg,
|
|
547
|
+
});
|
|
548
|
+
if (snapshotResult.success) {
|
|
549
|
+
uploadSpinner.succeed(chalk_1.default.green('Database snapshot uploaded'));
|
|
550
|
+
snapshotId = snapshotResult.snapshotId;
|
|
551
|
+
snapshotS3Key = snapshotResult.s3Key;
|
|
552
|
+
// Cleanup local dump since it's now in S3
|
|
553
|
+
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
554
|
+
localDumpPath = undefined;
|
|
555
|
+
}
|
|
556
|
+
else {
|
|
557
|
+
uploadSpinner.warn(chalk_1.default.yellow(`Snapshot upload failed: ${snapshotResult.error}`));
|
|
558
|
+
console.log(chalk_1.default.dim(' Will fall back to direct SCP upload after genbox creation'));
|
|
559
|
+
}
|
|
505
560
|
}
|
|
506
561
|
}
|
|
507
562
|
}
|
package/dist/commands/init.js
CHANGED
|
@@ -945,7 +945,7 @@ async function editSingleProfile(name, profile, detected, environments) {
|
|
|
945
945
|
const dbMode = await prompts.select({
|
|
946
946
|
message: 'Database mode:',
|
|
947
947
|
choices: [
|
|
948
|
-
{ name: '
|
|
948
|
+
{ name: 'fresh (empty database)', value: 'local' },
|
|
949
949
|
{ name: 'copy (copy from remote)', value: 'copy' },
|
|
950
950
|
{ name: 'none (no database)', value: 'none' },
|
|
951
951
|
],
|
|
@@ -255,7 +255,7 @@ exports.profilesCommand
|
|
|
255
255
|
message: 'Database mode:',
|
|
256
256
|
choices: [
|
|
257
257
|
{ name: 'None', value: 'none' },
|
|
258
|
-
{ name: '
|
|
258
|
+
{ name: 'Fresh (empty)', value: 'local' },
|
|
259
259
|
{ name: 'Copy from staging', value: 'copy-staging' },
|
|
260
260
|
{ name: 'Connect to staging', value: 'remote-staging' },
|
|
261
261
|
],
|
package/dist/profile-resolver.js
CHANGED
|
@@ -321,10 +321,19 @@ class ProfileResolver {
|
|
|
321
321
|
* Resolve database mode
|
|
322
322
|
*/
|
|
323
323
|
async resolveDatabaseMode(config, options, profile) {
|
|
324
|
-
//
|
|
324
|
+
// Load env vars from .env.genbox (where init stores MongoDB URLs)
|
|
325
|
+
const envVars = this.configLoader.loadEnvVars(process.cwd());
|
|
326
|
+
// Helper to get MongoDB URL from .env.genbox
|
|
327
|
+
// Matches what init saves: PROD_MONGODB_URL, STAGING_MONGODB_URL
|
|
325
328
|
const getMongoUrl = (source) => {
|
|
326
|
-
|
|
327
|
-
|
|
329
|
+
if (source === 'production') {
|
|
330
|
+
return envVars['PROD_MONGODB_URL'] || envVars['PRODUCTION_MONGODB_URL'];
|
|
331
|
+
}
|
|
332
|
+
else if (source === 'staging') {
|
|
333
|
+
return envVars['STAGING_MONGODB_URL'];
|
|
334
|
+
}
|
|
335
|
+
// Custom environments: {NAME}_MONGODB_URL
|
|
336
|
+
return envVars[`${source.toUpperCase()}_MONGODB_URL`];
|
|
328
337
|
};
|
|
329
338
|
// CLI flag takes precedence
|
|
330
339
|
if (options.db) {
|
|
@@ -374,15 +383,27 @@ class ProfileResolver {
|
|
|
374
383
|
*/
|
|
375
384
|
async selectDatabaseModeInteractive(config) {
|
|
376
385
|
console.log(chalk_1.default.cyan('\n🗄️ Database Configuration:\n'));
|
|
386
|
+
// Load env vars from .env.genbox (where init stores MongoDB URLs)
|
|
387
|
+
const envVars = this.configLoader.loadEnvVars(process.cwd());
|
|
388
|
+
// Helper to get MongoDB URL from .env.genbox
|
|
389
|
+
const getMongoUrl = (source) => {
|
|
390
|
+
if (source === 'production') {
|
|
391
|
+
return envVars['PROD_MONGODB_URL'] || envVars['PRODUCTION_MONGODB_URL'];
|
|
392
|
+
}
|
|
393
|
+
else if (source === 'staging') {
|
|
394
|
+
return envVars['STAGING_MONGODB_URL'];
|
|
395
|
+
}
|
|
396
|
+
return envVars[`${source.toUpperCase()}_MONGODB_URL`];
|
|
397
|
+
};
|
|
377
398
|
const modeChoices = [
|
|
378
399
|
{ name: 'None (no database)', value: 'none' },
|
|
379
|
-
{ name: '
|
|
380
|
-
{ name: 'Copy from staging
|
|
400
|
+
{ name: 'Fresh (empty database)', value: 'local' },
|
|
401
|
+
{ name: 'Copy from staging', value: 'copy-staging' },
|
|
381
402
|
];
|
|
382
403
|
// Add production copy if available
|
|
383
404
|
if (config.environments?.production) {
|
|
384
405
|
modeChoices.push({
|
|
385
|
-
name: 'Copy from production
|
|
406
|
+
name: 'Copy from production',
|
|
386
407
|
value: 'copy-production',
|
|
387
408
|
});
|
|
388
409
|
}
|
|
@@ -405,18 +426,14 @@ class ProfileResolver {
|
|
|
405
426
|
}
|
|
406
427
|
else if (answer.startsWith('copy-')) {
|
|
407
428
|
const source = answer.replace('copy-', '');
|
|
408
|
-
|
|
409
|
-
const mongoUrl = envConfig?.urls?.mongodb;
|
|
410
|
-
return { mode: 'copy', source, url: mongoUrl };
|
|
429
|
+
return { mode: 'copy', source, url: getMongoUrl(source) };
|
|
411
430
|
}
|
|
412
431
|
else if (answer.startsWith('remote-')) {
|
|
413
432
|
const source = answer.replace('remote-', '');
|
|
414
|
-
const envConfig = config.environments?.[source];
|
|
415
|
-
const mongoUrl = envConfig?.urls?.mongodb;
|
|
416
433
|
return {
|
|
417
434
|
mode: 'remote',
|
|
418
435
|
source,
|
|
419
|
-
url:
|
|
436
|
+
url: getMongoUrl(source),
|
|
420
437
|
};
|
|
421
438
|
}
|
|
422
439
|
return { mode: 'local' };
|