genbox 1.0.53 → 1.0.55
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 +17 -65
- package/dist/commands/db-sync.js +228 -187
- package/dist/commands/init.js +2 -1
- package/dist/commands/rebuild.js +147 -0
- package/dist/profile-resolver.js +4 -2
- package/package.json +1 -1
package/dist/commands/create.js
CHANGED
|
@@ -236,7 +236,7 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
236
236
|
.option('-a, --apps <apps>', 'Comma-separated list of apps to include')
|
|
237
237
|
.option('--add-apps <apps>', 'Add apps to the profile')
|
|
238
238
|
.option('--api <mode>', 'API mode: local, staging, production')
|
|
239
|
-
.option('--db <mode>', 'Database mode: none,
|
|
239
|
+
.option('--db <mode>', 'Database mode: none, fresh, copy, remote')
|
|
240
240
|
.option('--db-source <source>', 'Database source: staging, production')
|
|
241
241
|
.option('--db-dump <path>', 'Use existing mongodump file instead of creating one')
|
|
242
242
|
.option('--db-copy-remote', 'Copy database on the server (requires publicly accessible DB)')
|
|
@@ -550,14 +550,27 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
550
550
|
snapshotId = snapshotResult.snapshotId;
|
|
551
551
|
snapshotS3Key = snapshotResult.s3Key;
|
|
552
552
|
// Cleanup local dump since it's now in S3
|
|
553
|
-
|
|
553
|
+
if (localDumpPath)
|
|
554
|
+
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
554
555
|
localDumpPath = undefined;
|
|
555
556
|
}
|
|
556
557
|
else {
|
|
557
|
-
uploadSpinner.
|
|
558
|
-
console.log(chalk_1.default.dim(
|
|
558
|
+
uploadSpinner.fail(chalk_1.default.red('Database snapshot upload failed'));
|
|
559
|
+
console.log(chalk_1.default.dim(` Error: ${snapshotResult.error}`));
|
|
560
|
+
console.log(chalk_1.default.dim(' Try again later or use --db fresh to start with empty database.'));
|
|
561
|
+
if (localDumpPath)
|
|
562
|
+
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
563
|
+
return;
|
|
559
564
|
}
|
|
560
565
|
}
|
|
566
|
+
else {
|
|
567
|
+
// No project synced - can't upload to S3
|
|
568
|
+
console.log(chalk_1.default.red('Project not synced - cannot upload database snapshot.'));
|
|
569
|
+
console.log(chalk_1.default.dim(' Run `genbox init` first to sync your project, then try again.'));
|
|
570
|
+
if (localDumpPath)
|
|
571
|
+
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
561
574
|
}
|
|
562
575
|
}
|
|
563
576
|
// Build payload
|
|
@@ -572,14 +585,6 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
572
585
|
s3Key: snapshotS3Key,
|
|
573
586
|
};
|
|
574
587
|
}
|
|
575
|
-
else if (localDumpPath) {
|
|
576
|
-
// Fall back to SCP upload mode (no project or S3 upload failed)
|
|
577
|
-
payloadResolved.database = {
|
|
578
|
-
...resolved.database,
|
|
579
|
-
url: undefined,
|
|
580
|
-
mode: 'copy-local',
|
|
581
|
-
};
|
|
582
|
-
}
|
|
583
588
|
const payload = buildPayload(payloadResolved, config, publicKey, privateKeyContent, configLoader);
|
|
584
589
|
// Create genbox
|
|
585
590
|
const spinner = (0, ora_1.default)(`Creating Genbox '${name}'...`).start();
|
|
@@ -600,59 +605,6 @@ exports.createCommand = new commander_1.Command('create')
|
|
|
600
605
|
}
|
|
601
606
|
// Display results
|
|
602
607
|
displayGenboxInfo(genbox, resolved);
|
|
603
|
-
// Handle database upload if we have a local dump
|
|
604
|
-
if (localDumpPath && genbox.ipAddress) {
|
|
605
|
-
console.log('');
|
|
606
|
-
console.log(chalk_1.default.blue('=== Database Restore ==='));
|
|
607
|
-
// Wait for SSH access
|
|
608
|
-
const sshSpinner = (0, ora_1.default)('Waiting for SSH access...').start();
|
|
609
|
-
const sshReady = await (0, db_utils_1.waitForSshAccess)(genbox.ipAddress, 300, (msg) => {
|
|
610
|
-
sshSpinner.text = msg;
|
|
611
|
-
});
|
|
612
|
-
if (!sshReady) {
|
|
613
|
-
sshSpinner.fail(chalk_1.default.yellow('SSH not ready - database will need manual restore'));
|
|
614
|
-
console.log(chalk_1.default.dim(' Run `genbox db sync` after the server is ready'));
|
|
615
|
-
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
616
|
-
}
|
|
617
|
-
else {
|
|
618
|
-
sshSpinner.succeed('SSH connected');
|
|
619
|
-
// Upload dump
|
|
620
|
-
const uploadSpinner = (0, ora_1.default)('Uploading database dump...').start();
|
|
621
|
-
const uploadResult = await (0, db_utils_1.uploadDumpToGenbox)(localDumpPath, genbox.ipAddress, {
|
|
622
|
-
onProgress: (msg) => uploadSpinner.text = msg,
|
|
623
|
-
});
|
|
624
|
-
if (!uploadResult.success) {
|
|
625
|
-
uploadSpinner.fail(chalk_1.default.yellow('Upload failed - database will need manual restore'));
|
|
626
|
-
console.log(chalk_1.default.dim(` ${uploadResult.error}`));
|
|
627
|
-
console.log(chalk_1.default.dim(' Run `genbox db sync` after the server is ready'));
|
|
628
|
-
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
629
|
-
}
|
|
630
|
-
else {
|
|
631
|
-
uploadSpinner.succeed('Dump uploaded');
|
|
632
|
-
// Trigger restore
|
|
633
|
-
const restoreSpinner = (0, ora_1.default)('Restoring database...').start();
|
|
634
|
-
const restoreResult = await (0, db_utils_1.runRemoteMongoRestore)(genbox.ipAddress, config.project.name, { onProgress: (msg) => restoreSpinner.text = msg });
|
|
635
|
-
if (!restoreResult.success) {
|
|
636
|
-
restoreSpinner.fail(chalk_1.default.yellow('Restore failed'));
|
|
637
|
-
console.log(chalk_1.default.dim(` ${restoreResult.error}`));
|
|
638
|
-
console.log(chalk_1.default.dim(' The dump file is on the server - you can restore manually'));
|
|
639
|
-
}
|
|
640
|
-
else {
|
|
641
|
-
restoreSpinner.succeed(chalk_1.default.green('Database restored successfully!'));
|
|
642
|
-
}
|
|
643
|
-
// Cleanup local dump
|
|
644
|
-
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
else if (localDumpPath) {
|
|
649
|
-
// No IP yet - inform user to run db sync later
|
|
650
|
-
console.log('');
|
|
651
|
-
console.log(chalk_1.default.yellow('Database dump created but IP not yet assigned.'));
|
|
652
|
-
console.log(chalk_1.default.dim(' Run `genbox db sync` once the server is ready to restore the database.'));
|
|
653
|
-
// Keep the dump file - user might want to use it
|
|
654
|
-
console.log(chalk_1.default.dim(` Dump file: ${localDumpPath}`));
|
|
655
|
-
}
|
|
656
608
|
// Inform user about server provisioning
|
|
657
609
|
console.log('');
|
|
658
610
|
console.log(chalk_1.default.dim('Server is provisioning. Run `genbox connect` once ready.'));
|
package/dist/commands/db-sync.js
CHANGED
|
@@ -41,31 +41,25 @@ const commander_1 = require("commander");
|
|
|
41
41
|
const prompts = __importStar(require("@inquirer/prompts"));
|
|
42
42
|
const chalk_1 = __importDefault(require("chalk"));
|
|
43
43
|
const ora_1 = __importDefault(require("ora"));
|
|
44
|
+
const fs = __importStar(require("fs"));
|
|
44
45
|
const config_loader_1 = require("../config-loader");
|
|
45
46
|
const api_1 = require("../api");
|
|
46
47
|
const genbox_selector_1 = require("../genbox-selector");
|
|
47
|
-
|
|
48
|
-
return (0, api_1.fetchApi)('/genboxes/db', {
|
|
49
|
-
method: 'POST',
|
|
50
|
-
body: JSON.stringify(payload),
|
|
51
|
-
});
|
|
52
|
-
}
|
|
48
|
+
const db_utils_1 = require("../db-utils");
|
|
53
49
|
exports.dbSyncCommand = new commander_1.Command('db')
|
|
54
50
|
.description('Database operations for genbox environments');
|
|
55
51
|
// Subcommand: db sync
|
|
56
52
|
exports.dbSyncCommand
|
|
57
53
|
.command('sync [genbox]')
|
|
58
54
|
.description('Sync database from staging/production to a genbox')
|
|
59
|
-
.option('-s, --source <source>', 'Source environment: staging, production', '
|
|
60
|
-
.option('--
|
|
61
|
-
.option('--exclude <collections>', 'Comma-separated list of collections to exclude')
|
|
55
|
+
.option('-s, --source <source>', 'Source environment: staging, production', 'production')
|
|
56
|
+
.option('--dump <path>', 'Path to existing database dump file')
|
|
62
57
|
.option('-f, --force', 'Skip confirmation prompt')
|
|
63
|
-
.option('--
|
|
58
|
+
.option('--fresh', 'Create new snapshot even if recent one exists')
|
|
64
59
|
.action(async (genboxName, options) => {
|
|
65
60
|
try {
|
|
66
61
|
const configLoader = new config_loader_1.ConfigLoader();
|
|
67
62
|
const loadResult = await configLoader.load();
|
|
68
|
-
// Get genbox
|
|
69
63
|
// Select genbox
|
|
70
64
|
const result = await (0, genbox_selector_1.selectGenbox)(genboxName);
|
|
71
65
|
if (!result.genbox) {
|
|
@@ -74,21 +68,24 @@ exports.dbSyncCommand
|
|
|
74
68
|
}
|
|
75
69
|
return;
|
|
76
70
|
}
|
|
77
|
-
const genbox = result.genbox
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
if (
|
|
82
|
-
console.log(chalk_1.default.
|
|
83
|
-
console.log(chalk_1.default.dim('
|
|
84
|
-
for (const env of Object.keys(config.environments || {})) {
|
|
85
|
-
console.log(chalk_1.default.dim(` - ${env}`));
|
|
86
|
-
}
|
|
71
|
+
const genbox = result.genbox;
|
|
72
|
+
const genboxId = genbox._id;
|
|
73
|
+
// Check project ID
|
|
74
|
+
const projectId = genbox.project;
|
|
75
|
+
if (!projectId) {
|
|
76
|
+
console.log(chalk_1.default.red('Project not synced - cannot sync database.'));
|
|
77
|
+
console.log(chalk_1.default.dim(' Run `genbox init` first to sync your project.'));
|
|
87
78
|
return;
|
|
88
79
|
}
|
|
89
|
-
|
|
80
|
+
const config = loadResult.config;
|
|
81
|
+
const source = options.source;
|
|
82
|
+
// Get source URL from .env.genbox
|
|
90
83
|
const envVars = configLoader.loadEnvVars(process.cwd());
|
|
91
|
-
const sourceUrlVar = source === 'production'
|
|
84
|
+
const sourceUrlVar = source === 'production'
|
|
85
|
+
? 'PROD_MONGODB_URL'
|
|
86
|
+
: source === 'staging'
|
|
87
|
+
? 'STAGING_MONGODB_URL'
|
|
88
|
+
: `${source.toUpperCase()}_MONGODB_URL`;
|
|
92
89
|
const sourceUrl = envVars[sourceUrlVar];
|
|
93
90
|
if (!sourceUrl) {
|
|
94
91
|
console.log(chalk_1.default.red(`${sourceUrlVar} not found in .env.genbox`));
|
|
@@ -99,34 +96,21 @@ exports.dbSyncCommand
|
|
|
99
96
|
console.log('');
|
|
100
97
|
console.log(chalk_1.default.bold('Database Sync:'));
|
|
101
98
|
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
102
|
-
console.log(` ${chalk_1.default.bold('Genbox:')} ${genbox}`);
|
|
99
|
+
console.log(` ${chalk_1.default.bold('Genbox:')} ${genbox.name}`);
|
|
103
100
|
console.log(` ${chalk_1.default.bold('Source:')} ${source}`);
|
|
104
|
-
console.log(` ${chalk_1.default.bold('
|
|
105
|
-
if (options.collections) {
|
|
106
|
-
console.log(` ${chalk_1.default.bold('Collections:')} ${options.collections}`);
|
|
107
|
-
}
|
|
108
|
-
if (options.exclude) {
|
|
109
|
-
console.log(` ${chalk_1.default.bold('Exclude:')} ${options.exclude}`);
|
|
110
|
-
}
|
|
101
|
+
console.log(` ${chalk_1.default.bold('URL:')} ${sourceUrl.replace(/\/\/[^:]+:[^@]+@/, '//***:***@')}`);
|
|
111
102
|
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
112
103
|
// Warning for production
|
|
113
104
|
if (source === 'production') {
|
|
114
105
|
console.log('');
|
|
115
106
|
console.log(chalk_1.default.yellow('⚠ WARNING: Syncing from production database'));
|
|
116
107
|
}
|
|
117
|
-
// Dry run mode
|
|
118
|
-
if (options.dryRun) {
|
|
119
|
-
console.log('');
|
|
120
|
-
console.log(chalk_1.default.yellow('Dry run mode - no changes made'));
|
|
121
|
-
console.log(chalk_1.default.dim('Remove --dry-run to perform the sync'));
|
|
122
|
-
return;
|
|
123
|
-
}
|
|
124
108
|
// Confirmation
|
|
125
109
|
if (!options.force) {
|
|
126
110
|
console.log('');
|
|
127
111
|
console.log(chalk_1.default.yellow('⚠ This will replace all data in the genbox database!'));
|
|
128
112
|
const confirm = await prompts.confirm({
|
|
129
|
-
message: `Sync database from ${source} to ${genbox}?`,
|
|
113
|
+
message: `Sync database from ${source} to ${genbox.name}?`,
|
|
130
114
|
default: false,
|
|
131
115
|
});
|
|
132
116
|
if (!confirm) {
|
|
@@ -134,147 +118,191 @@ exports.dbSyncCommand
|
|
|
134
118
|
return;
|
|
135
119
|
}
|
|
136
120
|
}
|
|
137
|
-
//
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
121
|
+
// Map source to snapshot source type
|
|
122
|
+
const snapshotSource = source === 'production' ? 'production' : source === 'staging' ? 'staging' : 'local';
|
|
123
|
+
let snapshotId;
|
|
124
|
+
let snapshotS3Key;
|
|
125
|
+
let localDumpPath;
|
|
126
|
+
// Check for existing snapshots (unless --fresh specified)
|
|
127
|
+
if (!options.fresh && !options.dump) {
|
|
128
|
+
try {
|
|
129
|
+
const existingSnapshot = await (0, api_1.getLatestSnapshot)(projectId, snapshotSource);
|
|
130
|
+
if (existingSnapshot) {
|
|
131
|
+
const snapshotAge = Date.now() - new Date(existingSnapshot.createdAt).getTime();
|
|
132
|
+
const hoursAgo = Math.floor(snapshotAge / (1000 * 60 * 60));
|
|
133
|
+
const timeAgoStr = hoursAgo < 1 ? 'less than an hour ago' : `${hoursAgo} hours ago`;
|
|
134
|
+
const snapshotChoice = await prompts.select({
|
|
135
|
+
message: `Found existing ${snapshotSource} snapshot (${timeAgoStr}):`,
|
|
136
|
+
choices: [
|
|
137
|
+
{
|
|
138
|
+
name: `Use existing snapshot (${timeAgoStr}, ${(0, db_utils_1.formatBytes)(existingSnapshot.sizeBytes)})`,
|
|
139
|
+
value: 'existing',
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
name: 'Create fresh snapshot (dump now)',
|
|
143
|
+
value: 'fresh',
|
|
144
|
+
},
|
|
145
|
+
],
|
|
146
|
+
});
|
|
147
|
+
if (snapshotChoice === 'existing') {
|
|
148
|
+
snapshotId = existingSnapshot._id;
|
|
149
|
+
snapshotS3Key = existingSnapshot.s3Key;
|
|
150
|
+
console.log(chalk_1.default.green(` ✓ Using existing snapshot`));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
157
153
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
spinner.fail(chalk_1.default.red('Sync failed'));
|
|
161
|
-
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
162
|
-
if (error instanceof api_1.AuthenticationError) {
|
|
163
|
-
console.log(chalk_1.default.yellow('\nRun: genbox login'));
|
|
154
|
+
catch {
|
|
155
|
+
// Silently continue if we can't fetch snapshots
|
|
164
156
|
}
|
|
165
157
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
exports.dbSyncCommand
|
|
177
|
-
.command('status [genbox]')
|
|
178
|
-
.description('Show database status for a genbox')
|
|
179
|
-
.action(async (genboxName) => {
|
|
180
|
-
try {
|
|
181
|
-
const configLoader = new config_loader_1.ConfigLoader();
|
|
182
|
-
const loadResult = await configLoader.load();
|
|
183
|
-
// Select genbox
|
|
184
|
-
const selectResult = await (0, genbox_selector_1.selectGenbox)(genboxName);
|
|
185
|
-
if (!selectResult.genbox) {
|
|
186
|
-
if (!selectResult.cancelled) {
|
|
187
|
-
console.log(chalk_1.default.yellow('No genbox selected'));
|
|
158
|
+
// Create new snapshot if needed
|
|
159
|
+
if (!snapshotId) {
|
|
160
|
+
// Check for user-provided dump file
|
|
161
|
+
if (options.dump) {
|
|
162
|
+
if (!fs.existsSync(options.dump)) {
|
|
163
|
+
console.log(chalk_1.default.red(`Database dump file not found: ${options.dump}`));
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
localDumpPath = options.dump;
|
|
167
|
+
console.log(chalk_1.default.dim(` Using provided dump file: ${options.dump}`));
|
|
188
168
|
}
|
|
169
|
+
else {
|
|
170
|
+
// Need to run mongodump locally
|
|
171
|
+
if (!(0, db_utils_1.isMongoDumpAvailable)()) {
|
|
172
|
+
console.log(chalk_1.default.red('mongodump not found. Required for database sync.'));
|
|
173
|
+
console.log('');
|
|
174
|
+
console.log((0, db_utils_1.getMongoDumpInstallInstructions)());
|
|
175
|
+
console.log('');
|
|
176
|
+
console.log(chalk_1.default.dim('Alternatively:'));
|
|
177
|
+
console.log(chalk_1.default.dim(' • Use --dump <path> to provide an existing dump file'));
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const dumpSpinner = (0, ora_1.default)('Creating database dump...').start();
|
|
181
|
+
const dumpResult = await (0, db_utils_1.runLocalMongoDump)(sourceUrl, {
|
|
182
|
+
onProgress: (msg) => dumpSpinner.text = msg,
|
|
183
|
+
});
|
|
184
|
+
if (!dumpResult.success) {
|
|
185
|
+
dumpSpinner.fail(chalk_1.default.red('Database dump failed'));
|
|
186
|
+
console.log(chalk_1.default.red(` ${dumpResult.error}`));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
dumpSpinner.succeed(chalk_1.default.green(`Database dump created (${(0, db_utils_1.formatBytes)(dumpResult.sizeBytes || 0)})`));
|
|
190
|
+
localDumpPath = dumpResult.dumpPath;
|
|
191
|
+
}
|
|
192
|
+
// Upload to S3
|
|
193
|
+
if (localDumpPath) {
|
|
194
|
+
const uploadSpinner = (0, ora_1.default)('Uploading database snapshot...').start();
|
|
195
|
+
const snapshotResult = await (0, db_utils_1.createAndUploadSnapshot)(localDumpPath, projectId, snapshotSource, {
|
|
196
|
+
sourceUrl: sourceUrl.replace(/\/\/[^:]+:[^@]+@/, '//***:***@'),
|
|
197
|
+
onProgress: (msg) => uploadSpinner.text = msg,
|
|
198
|
+
});
|
|
199
|
+
if (snapshotResult.success) {
|
|
200
|
+
uploadSpinner.succeed(chalk_1.default.green('Database snapshot uploaded'));
|
|
201
|
+
snapshotId = snapshotResult.snapshotId;
|
|
202
|
+
snapshotS3Key = snapshotResult.s3Key;
|
|
203
|
+
// Only cleanup if we created the dump (not user-provided)
|
|
204
|
+
if (!options.dump) {
|
|
205
|
+
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
else {
|
|
209
|
+
uploadSpinner.fail(chalk_1.default.red('Database snapshot upload failed'));
|
|
210
|
+
console.log(chalk_1.default.dim(` Error: ${snapshotResult.error}`));
|
|
211
|
+
if (!options.dump) {
|
|
212
|
+
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
// Trigger restore on genbox
|
|
219
|
+
if (!snapshotId || !snapshotS3Key) {
|
|
220
|
+
console.log(chalk_1.default.red('No snapshot available to restore'));
|
|
189
221
|
return;
|
|
190
222
|
}
|
|
191
|
-
const
|
|
192
|
-
const spinner = (0, ora_1.default)('Fetching database status...').start();
|
|
223
|
+
const restoreSpinner = (0, ora_1.default)('Restoring database on genbox...').start();
|
|
193
224
|
try {
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
225
|
+
await (0, api_1.fetchApi)(`/genboxes/${genboxId}/db/restore`, {
|
|
226
|
+
method: 'POST',
|
|
227
|
+
body: JSON.stringify({
|
|
228
|
+
snapshotId,
|
|
229
|
+
s3Key: snapshotS3Key,
|
|
230
|
+
}),
|
|
197
231
|
});
|
|
198
|
-
|
|
232
|
+
restoreSpinner.succeed(chalk_1.default.green('Database sync completed!'));
|
|
199
233
|
console.log('');
|
|
200
|
-
console.log(chalk_1.default.
|
|
201
|
-
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
202
|
-
if (result.status === 'connected') {
|
|
203
|
-
console.log(` ${chalk_1.default.bold('Status:')} ${chalk_1.default.green('Connected')}`);
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
console.log(` ${chalk_1.default.bold('Status:')} ${chalk_1.default.red('Disconnected')}`);
|
|
207
|
-
}
|
|
208
|
-
if (result.mode) {
|
|
209
|
-
console.log(` ${chalk_1.default.bold('Mode:')} ${result.mode}`);
|
|
210
|
-
}
|
|
211
|
-
if (result.source) {
|
|
212
|
-
console.log(` ${chalk_1.default.bold('Source:')} ${result.source}`);
|
|
213
|
-
}
|
|
214
|
-
if (result.database) {
|
|
215
|
-
console.log(` ${chalk_1.default.bold('Database:')} ${result.database}`);
|
|
216
|
-
}
|
|
217
|
-
if (result.collections) {
|
|
218
|
-
console.log(` ${chalk_1.default.bold('Collections:')} ${result.collections}`);
|
|
219
|
-
}
|
|
220
|
-
if (result.size) {
|
|
221
|
-
console.log(` ${chalk_1.default.bold('Size:')} ${result.size}`);
|
|
222
|
-
}
|
|
223
|
-
if (result.lastSync) {
|
|
224
|
-
console.log(` ${chalk_1.default.bold('Last Sync:')} ${result.lastSync}`);
|
|
225
|
-
}
|
|
226
|
-
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
234
|
+
console.log(chalk_1.default.dim(` Database has been restored from ${source} snapshot.`));
|
|
227
235
|
}
|
|
228
236
|
catch (error) {
|
|
229
|
-
|
|
230
|
-
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
237
|
+
restoreSpinner.fail(chalk_1.default.red('Database restore failed'));
|
|
238
|
+
console.error(chalk_1.default.red(` Error: ${error.message}`));
|
|
239
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
240
|
+
console.log(chalk_1.default.yellow('\nRun: genbox login'));
|
|
241
|
+
}
|
|
231
242
|
}
|
|
232
243
|
}
|
|
233
244
|
catch (error) {
|
|
245
|
+
if (error.name === 'ExitPromptError') {
|
|
246
|
+
console.log(chalk_1.default.dim('\nCancelled.'));
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
234
249
|
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
235
250
|
}
|
|
236
251
|
});
|
|
237
|
-
// Subcommand: db restore
|
|
252
|
+
// Subcommand: db restore (from existing snapshot)
|
|
238
253
|
exports.dbSyncCommand
|
|
239
254
|
.command('restore [genbox]')
|
|
240
|
-
.description('Restore database from
|
|
241
|
-
.option('-
|
|
242
|
-
.option('--
|
|
255
|
+
.description('Restore database from an existing snapshot')
|
|
256
|
+
.option('-s, --source <source>', 'Snapshot source: staging, production', 'production')
|
|
257
|
+
.option('--snapshot <id>', 'Specific snapshot ID to restore')
|
|
258
|
+
.option('-f, --force', 'Skip confirmation prompt')
|
|
243
259
|
.action(async (genboxName, options) => {
|
|
244
260
|
try {
|
|
245
|
-
const configLoader = new config_loader_1.ConfigLoader();
|
|
246
|
-
const loadResult = await configLoader.load();
|
|
247
261
|
// Select genbox
|
|
248
|
-
const
|
|
249
|
-
if (!
|
|
250
|
-
if (!
|
|
262
|
+
const result = await (0, genbox_selector_1.selectGenbox)(genboxName);
|
|
263
|
+
if (!result.genbox) {
|
|
264
|
+
if (!result.cancelled) {
|
|
251
265
|
console.log(chalk_1.default.yellow('No genbox selected'));
|
|
252
266
|
}
|
|
253
267
|
return;
|
|
254
268
|
}
|
|
255
|
-
const genbox =
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
269
|
+
const genbox = result.genbox;
|
|
270
|
+
const genboxId = genbox._id;
|
|
271
|
+
// Check project ID
|
|
272
|
+
const projectId = genbox.project;
|
|
273
|
+
if (!projectId) {
|
|
274
|
+
console.log(chalk_1.default.red('Project not synced - cannot restore database.'));
|
|
275
|
+
console.log(chalk_1.default.dim(' Run `genbox init` first to sync your project.'));
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
const source = options.source;
|
|
279
|
+
const snapshotSource = source === 'production' ? 'production' : source === 'staging' ? 'staging' : 'local';
|
|
280
|
+
let snapshotId = options.snapshot;
|
|
281
|
+
let snapshotS3Key;
|
|
282
|
+
// Find snapshot if not specified
|
|
283
|
+
if (!snapshotId) {
|
|
259
284
|
try {
|
|
260
|
-
const
|
|
261
|
-
if (!
|
|
262
|
-
console.log(chalk_1.default.yellow(
|
|
285
|
+
const existingSnapshot = await (0, api_1.getLatestSnapshot)(projectId, snapshotSource);
|
|
286
|
+
if (!existingSnapshot) {
|
|
287
|
+
console.log(chalk_1.default.yellow(`No ${source} snapshots found for this project.`));
|
|
288
|
+
console.log(chalk_1.default.dim(' Use `genbox db sync` to create a snapshot first.'));
|
|
263
289
|
return;
|
|
264
290
|
}
|
|
291
|
+
const snapshotAge = Date.now() - new Date(existingSnapshot.createdAt).getTime();
|
|
292
|
+
const hoursAgo = Math.floor(snapshotAge / (1000 * 60 * 60));
|
|
293
|
+
const timeAgoStr = hoursAgo < 1 ? 'less than an hour ago' : `${hoursAgo} hours ago`;
|
|
265
294
|
console.log('');
|
|
266
|
-
console.log(chalk_1.default.bold('Available
|
|
295
|
+
console.log(chalk_1.default.bold('Available Snapshot:'));
|
|
267
296
|
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
}
|
|
297
|
+
console.log(` ${chalk_1.default.bold('Source:')} ${source}`);
|
|
298
|
+
console.log(` ${chalk_1.default.bold('Created:')} ${timeAgoStr}`);
|
|
299
|
+
console.log(` ${chalk_1.default.bold('Size:')} ${(0, db_utils_1.formatBytes)(existingSnapshot.sizeBytes)}`);
|
|
271
300
|
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
return;
|
|
301
|
+
snapshotId = existingSnapshot._id;
|
|
302
|
+
snapshotS3Key = existingSnapshot.s3Key;
|
|
275
303
|
}
|
|
276
304
|
catch (error) {
|
|
277
|
-
console.
|
|
305
|
+
console.log(chalk_1.default.red(`Failed to fetch snapshots: ${error.message}`));
|
|
278
306
|
return;
|
|
279
307
|
}
|
|
280
308
|
}
|
|
@@ -283,7 +311,7 @@ exports.dbSyncCommand
|
|
|
283
311
|
console.log('');
|
|
284
312
|
console.log(chalk_1.default.yellow('⚠ This will replace all data in the genbox database!'));
|
|
285
313
|
const confirm = await prompts.confirm({
|
|
286
|
-
message: `Restore database
|
|
314
|
+
message: `Restore database on ${genbox.name}?`,
|
|
287
315
|
default: false,
|
|
288
316
|
});
|
|
289
317
|
if (!confirm) {
|
|
@@ -291,26 +319,24 @@ exports.dbSyncCommand
|
|
|
291
319
|
return;
|
|
292
320
|
}
|
|
293
321
|
}
|
|
294
|
-
//
|
|
295
|
-
const
|
|
322
|
+
// Trigger restore
|
|
323
|
+
const restoreSpinner = (0, ora_1.default)('Restoring database...').start();
|
|
296
324
|
try {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
325
|
+
await (0, api_1.fetchApi)(`/genboxes/${genboxId}/db/restore`, {
|
|
326
|
+
method: 'POST',
|
|
327
|
+
body: JSON.stringify({
|
|
328
|
+
snapshotId,
|
|
329
|
+
s3Key: snapshotS3Key,
|
|
330
|
+
}),
|
|
301
331
|
});
|
|
302
|
-
|
|
303
|
-
if (result.stats) {
|
|
304
|
-
console.log('');
|
|
305
|
-
console.log(chalk_1.default.bold('Restore Statistics:'));
|
|
306
|
-
console.log(` Collections: ${result.stats.collections || 0}`);
|
|
307
|
-
console.log(` Documents: ${result.stats.documents || 0}`);
|
|
308
|
-
console.log(` Duration: ${result.stats.duration || 'N/A'}`);
|
|
309
|
-
}
|
|
332
|
+
restoreSpinner.succeed(chalk_1.default.green('Database restored successfully!'));
|
|
310
333
|
}
|
|
311
334
|
catch (error) {
|
|
312
|
-
|
|
313
|
-
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
335
|
+
restoreSpinner.fail(chalk_1.default.red('Restore failed'));
|
|
336
|
+
console.error(chalk_1.default.red(` Error: ${error.message}`));
|
|
337
|
+
if (error instanceof api_1.AuthenticationError) {
|
|
338
|
+
console.log(chalk_1.default.yellow('\nRun: genbox login'));
|
|
339
|
+
}
|
|
314
340
|
}
|
|
315
341
|
}
|
|
316
342
|
catch (error) {
|
|
@@ -321,41 +347,56 @@ exports.dbSyncCommand
|
|
|
321
347
|
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
322
348
|
}
|
|
323
349
|
});
|
|
324
|
-
// Subcommand: db
|
|
350
|
+
// Subcommand: db snapshots (list available snapshots)
|
|
325
351
|
exports.dbSyncCommand
|
|
326
|
-
.command('
|
|
327
|
-
.description('
|
|
328
|
-
.
|
|
329
|
-
.action(async (genboxName, options) => {
|
|
352
|
+
.command('snapshots')
|
|
353
|
+
.description('List available database snapshots for current project')
|
|
354
|
+
.action(async () => {
|
|
330
355
|
try {
|
|
331
356
|
const configLoader = new config_loader_1.ConfigLoader();
|
|
332
357
|
const loadResult = await configLoader.load();
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
if (!selectResult.cancelled) {
|
|
337
|
-
console.log(chalk_1.default.yellow('No genbox selected'));
|
|
338
|
-
}
|
|
358
|
+
if (!loadResult.config) {
|
|
359
|
+
console.log(chalk_1.default.red('Not a genbox project'));
|
|
360
|
+
console.log(chalk_1.default.dim('Run "genbox init" to initialize a project.'));
|
|
339
361
|
return;
|
|
340
362
|
}
|
|
341
|
-
|
|
342
|
-
const
|
|
363
|
+
// Get project ID
|
|
364
|
+
const config = loadResult.config;
|
|
365
|
+
const projectName = config.project?.name;
|
|
366
|
+
if (!projectName) {
|
|
367
|
+
console.log(chalk_1.default.red('Project name not found in genbox.yaml'));
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
// Fetch project to get ID
|
|
371
|
+
const spinner = (0, ora_1.default)('Fetching snapshots...').start();
|
|
343
372
|
try {
|
|
344
|
-
const
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
if (result.path) {
|
|
350
|
-
console.log(` Location: ${result.path}`);
|
|
373
|
+
const project = await (0, api_1.fetchApi)(`/projects/by-name/${projectName}`);
|
|
374
|
+
if (!project?._id) {
|
|
375
|
+
spinner.fail(chalk_1.default.yellow('Project not synced'));
|
|
376
|
+
console.log(chalk_1.default.dim(' Run `genbox init` to sync your project.'));
|
|
377
|
+
return;
|
|
351
378
|
}
|
|
352
|
-
|
|
353
|
-
|
|
379
|
+
const snapshots = await (0, api_1.fetchApi)(`/snapshots/project/${project._id}`);
|
|
380
|
+
spinner.stop();
|
|
381
|
+
if (!snapshots || snapshots.length === 0) {
|
|
382
|
+
console.log(chalk_1.default.yellow('No snapshots found for this project.'));
|
|
383
|
+
console.log(chalk_1.default.dim(' Use `genbox db sync` or `genbox create --db copy` to create snapshots.'));
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
console.log('');
|
|
387
|
+
console.log(chalk_1.default.bold('Database Snapshots:'));
|
|
388
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
389
|
+
for (const snapshot of snapshots) {
|
|
390
|
+
const age = Date.now() - new Date(snapshot.createdAt).getTime();
|
|
391
|
+
const hoursAgo = Math.floor(age / (1000 * 60 * 60));
|
|
392
|
+
const timeAgoStr = hoursAgo < 1 ? '<1h ago' : hoursAgo < 24 ? `${hoursAgo}h ago` : `${Math.floor(hoursAgo / 24)}d ago`;
|
|
393
|
+
console.log(` ${chalk_1.default.cyan(snapshot.source.padEnd(12))} ${(0, db_utils_1.formatBytes)(snapshot.sizeBytes).padEnd(10)} ${chalk_1.default.dim(timeAgoStr)}`);
|
|
354
394
|
}
|
|
395
|
+
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
355
396
|
}
|
|
356
397
|
catch (error) {
|
|
357
|
-
spinner.fail(chalk_1.default.red('
|
|
358
|
-
console.error(chalk_1.default.red(`Error: ${error.message}`));
|
|
398
|
+
spinner.fail(chalk_1.default.red('Failed to fetch snapshots'));
|
|
399
|
+
console.error(chalk_1.default.red(` Error: ${error.message}`));
|
|
359
400
|
}
|
|
360
401
|
}
|
|
361
402
|
catch (error) {
|
package/dist/commands/init.js
CHANGED
|
@@ -858,7 +858,8 @@ async function setupProfiles(detected, environments) {
|
|
|
858
858
|
console.log(chalk_1.default.dim(` Connection: ${profile.default_connection}`));
|
|
859
859
|
}
|
|
860
860
|
if (profile.database) {
|
|
861
|
-
|
|
861
|
+
const dbModeDisplay = profile.database.mode === 'local' ? 'fresh' : profile.database.mode;
|
|
862
|
+
console.log(chalk_1.default.dim(` Database: ${dbModeDisplay}${profile.database.source ? ` from ${profile.database.source}` : ''}`));
|
|
862
863
|
}
|
|
863
864
|
console.log('');
|
|
864
865
|
}
|
package/dist/commands/rebuild.js
CHANGED
|
@@ -49,6 +49,7 @@ const profile_resolver_1 = require("../profile-resolver");
|
|
|
49
49
|
const api_1 = require("../api");
|
|
50
50
|
const genbox_selector_1 = require("../genbox-selector");
|
|
51
51
|
const schema_v4_1 = require("../schema-v4");
|
|
52
|
+
const db_utils_1 = require("../db-utils");
|
|
52
53
|
function getPublicSshKey() {
|
|
53
54
|
const home = os.homedir();
|
|
54
55
|
const potentialKeys = [
|
|
@@ -419,6 +420,9 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
419
420
|
.option('-b, --branch <branch>', 'Git branch to checkout')
|
|
420
421
|
.option('-n, --new-branch <name>', 'Create a new branch with this name')
|
|
421
422
|
.option('-f, --from-branch <branch>', 'Source branch to create new branch from')
|
|
423
|
+
.option('--db <mode>', 'Database mode: fresh, copy, none')
|
|
424
|
+
.option('--db-source <source>', 'Database source for copy mode: staging, production')
|
|
425
|
+
.option('--db-dump <path>', 'Path to existing database dump file')
|
|
422
426
|
.option('-y, --yes', 'Skip interactive prompts')
|
|
423
427
|
.action(async (name, options) => {
|
|
424
428
|
try {
|
|
@@ -493,6 +497,10 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
493
497
|
// For new branch creation: only use CLI options (user must explicitly request new branch on rebuild)
|
|
494
498
|
const effectiveNewBranch = newBranchName;
|
|
495
499
|
const effectiveSourceBranch = options.fromBranch;
|
|
500
|
+
// For database: use CLI option, or stored database config (convert 'snapshot' to 'copy')
|
|
501
|
+
const storedDbMode = genbox.database?.mode === 'snapshot' ? 'copy' : genbox.database?.mode;
|
|
502
|
+
const effectiveDbMode = options.db || storedDbMode;
|
|
503
|
+
const effectiveDbSource = options.dbSource || genbox.database?.source;
|
|
496
504
|
// Build options for resolving - use stored config, skip interactive prompts
|
|
497
505
|
const createOptions = {
|
|
498
506
|
name: selectedName,
|
|
@@ -501,6 +509,8 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
501
509
|
branch: effectiveBranch,
|
|
502
510
|
newBranch: effectiveNewBranch,
|
|
503
511
|
sourceBranch: effectiveSourceBranch,
|
|
512
|
+
db: effectiveDbMode,
|
|
513
|
+
dbSource: effectiveDbSource,
|
|
504
514
|
// Skip interactive prompts if we have stored config
|
|
505
515
|
yes: options.yes || !!(storedProfile || storedApps),
|
|
506
516
|
};
|
|
@@ -538,6 +548,12 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
538
548
|
console.log(` - ${repo.name}: ${branchInfo}`);
|
|
539
549
|
}
|
|
540
550
|
}
|
|
551
|
+
// Display database info
|
|
552
|
+
if (effectiveDbMode && effectiveDbMode !== 'none') {
|
|
553
|
+
console.log('');
|
|
554
|
+
const dbModeDisplay = effectiveDbMode === 'local' ? 'fresh' : effectiveDbMode;
|
|
555
|
+
console.log(` ${chalk_1.default.bold('Database:')} ${dbModeDisplay}${effectiveDbSource ? ` (from ${effectiveDbSource})` : ''}`);
|
|
556
|
+
}
|
|
541
557
|
console.log(chalk_1.default.dim('───────────────────────────────────────────────'));
|
|
542
558
|
// Confirm rebuild
|
|
543
559
|
if (!options.yes) {
|
|
@@ -573,8 +589,139 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
|
|
|
573
589
|
}
|
|
574
590
|
}
|
|
575
591
|
}
|
|
592
|
+
// Handle database copy if requested
|
|
593
|
+
let snapshotId;
|
|
594
|
+
let snapshotS3Key;
|
|
595
|
+
let localDumpPath;
|
|
596
|
+
// Use the effective database mode/source from createOptions
|
|
597
|
+
const dbMode = effectiveDbMode || 'none';
|
|
598
|
+
const dbSource = effectiveDbSource || 'production';
|
|
599
|
+
if (dbMode === 'copy' && resolved.database?.url) {
|
|
600
|
+
// Get project ID for S3 upload
|
|
601
|
+
const projectId = genbox.project;
|
|
602
|
+
if (!projectId) {
|
|
603
|
+
console.log(chalk_1.default.red('Project not synced - cannot upload database snapshot.'));
|
|
604
|
+
console.log(chalk_1.default.dim(' Run `genbox init` first to sync your project.'));
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
// Map source to snapshot source type
|
|
608
|
+
const snapshotSource = dbSource === 'production' ? 'production' : dbSource === 'staging' ? 'staging' : 'local';
|
|
609
|
+
// Check for existing snapshots
|
|
610
|
+
let useExistingSnapshot = false;
|
|
611
|
+
try {
|
|
612
|
+
const existingSnapshot = await (0, api_1.getLatestSnapshot)(projectId, snapshotSource);
|
|
613
|
+
if (existingSnapshot) {
|
|
614
|
+
const snapshotAge = Date.now() - new Date(existingSnapshot.createdAt).getTime();
|
|
615
|
+
const hoursAgo = Math.floor(snapshotAge / (1000 * 60 * 60));
|
|
616
|
+
const timeAgoStr = hoursAgo < 1 ? 'less than an hour ago' : `${hoursAgo} hours ago`;
|
|
617
|
+
if (!options.yes) {
|
|
618
|
+
const snapshotChoice = await prompts.select({
|
|
619
|
+
message: `Found existing ${snapshotSource} snapshot (${timeAgoStr}):`,
|
|
620
|
+
choices: [
|
|
621
|
+
{
|
|
622
|
+
name: `Use existing snapshot (${timeAgoStr}, ${(0, db_utils_1.formatBytes)(existingSnapshot.sizeBytes)})`,
|
|
623
|
+
value: 'existing',
|
|
624
|
+
},
|
|
625
|
+
{
|
|
626
|
+
name: 'Create fresh snapshot (dump now)',
|
|
627
|
+
value: 'fresh',
|
|
628
|
+
},
|
|
629
|
+
],
|
|
630
|
+
});
|
|
631
|
+
useExistingSnapshot = snapshotChoice === 'existing';
|
|
632
|
+
}
|
|
633
|
+
else if (hoursAgo < 24) {
|
|
634
|
+
useExistingSnapshot = true;
|
|
635
|
+
console.log(chalk_1.default.dim(` Using existing snapshot from ${timeAgoStr}`));
|
|
636
|
+
}
|
|
637
|
+
if (useExistingSnapshot) {
|
|
638
|
+
snapshotId = existingSnapshot._id;
|
|
639
|
+
snapshotS3Key = existingSnapshot.s3Key;
|
|
640
|
+
console.log(chalk_1.default.green(` ✓ Using existing snapshot`));
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
catch {
|
|
645
|
+
// Silently continue if we can't fetch snapshots
|
|
646
|
+
}
|
|
647
|
+
// If not using existing snapshot, create a new one
|
|
648
|
+
if (!useExistingSnapshot) {
|
|
649
|
+
// Check for user-provided dump file
|
|
650
|
+
if (options.dbDump) {
|
|
651
|
+
if (!fs.existsSync(options.dbDump)) {
|
|
652
|
+
console.log(chalk_1.default.red(`Database dump file not found: ${options.dbDump}`));
|
|
653
|
+
return;
|
|
654
|
+
}
|
|
655
|
+
localDumpPath = options.dbDump;
|
|
656
|
+
console.log(chalk_1.default.dim(` Using provided dump file: ${options.dbDump}`));
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
// Need to run mongodump locally
|
|
660
|
+
if (!(0, db_utils_1.isMongoDumpAvailable)()) {
|
|
661
|
+
console.log(chalk_1.default.red('mongodump not found. Required for database copy.'));
|
|
662
|
+
console.log('');
|
|
663
|
+
console.log((0, db_utils_1.getMongoDumpInstallInstructions)());
|
|
664
|
+
console.log('');
|
|
665
|
+
console.log(chalk_1.default.dim('Alternatively:'));
|
|
666
|
+
console.log(chalk_1.default.dim(' • Use --db-dump <path> to provide an existing dump file'));
|
|
667
|
+
console.log(chalk_1.default.dim(' • Use --db fresh to start with empty database'));
|
|
668
|
+
return;
|
|
669
|
+
}
|
|
670
|
+
const dbUrl = resolved.database.url;
|
|
671
|
+
console.log('');
|
|
672
|
+
console.log(chalk_1.default.blue('=== Database Copy ==='));
|
|
673
|
+
console.log(chalk_1.default.dim(` Source: ${dbSource}`));
|
|
674
|
+
console.log(chalk_1.default.dim(` URL: ${dbUrl.replace(/\/\/[^:]+:[^@]+@/, '//***:***@')}`));
|
|
675
|
+
const dumpSpinner = (0, ora_1.default)('Creating database dump...').start();
|
|
676
|
+
const dumpResult = await (0, db_utils_1.runLocalMongoDump)(dbUrl, {
|
|
677
|
+
onProgress: (msg) => dumpSpinner.text = msg,
|
|
678
|
+
});
|
|
679
|
+
if (!dumpResult.success) {
|
|
680
|
+
dumpSpinner.fail(chalk_1.default.red('Database dump failed'));
|
|
681
|
+
console.log(chalk_1.default.red(` ${dumpResult.error}`));
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
dumpSpinner.succeed(chalk_1.default.green(`Database dump created (${(0, db_utils_1.formatBytes)(dumpResult.sizeBytes || 0)})`));
|
|
685
|
+
localDumpPath = dumpResult.dumpPath;
|
|
686
|
+
}
|
|
687
|
+
// Upload to S3
|
|
688
|
+
if (localDumpPath) {
|
|
689
|
+
const uploadSpinner = (0, ora_1.default)('Uploading database snapshot...').start();
|
|
690
|
+
const snapshotResult = await (0, db_utils_1.createAndUploadSnapshot)(localDumpPath, projectId, snapshotSource, {
|
|
691
|
+
sourceUrl: resolved.database.url?.replace(/\/\/[^:]+:[^@]+@/, '//***:***@'),
|
|
692
|
+
onProgress: (msg) => uploadSpinner.text = msg,
|
|
693
|
+
});
|
|
694
|
+
if (snapshotResult.success) {
|
|
695
|
+
uploadSpinner.succeed(chalk_1.default.green('Database snapshot uploaded'));
|
|
696
|
+
snapshotId = snapshotResult.snapshotId;
|
|
697
|
+
snapshotS3Key = snapshotResult.s3Key;
|
|
698
|
+
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
699
|
+
localDumpPath = undefined;
|
|
700
|
+
}
|
|
701
|
+
else {
|
|
702
|
+
uploadSpinner.fail(chalk_1.default.red('Database snapshot upload failed'));
|
|
703
|
+
console.log(chalk_1.default.dim(` Error: ${snapshotResult.error}`));
|
|
704
|
+
console.log(chalk_1.default.dim(' Try again later or use --db fresh to start with empty database.'));
|
|
705
|
+
(0, db_utils_1.cleanupDump)(localDumpPath);
|
|
706
|
+
return;
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
576
711
|
// Build payload
|
|
577
712
|
const payload = buildRebuildPayload(resolved, config, publicKey, privateKeyContent, configLoader);
|
|
713
|
+
// Add database info to payload if we have a snapshot
|
|
714
|
+
if (snapshotId && snapshotS3Key) {
|
|
715
|
+
payload.database = {
|
|
716
|
+
mode: 'snapshot',
|
|
717
|
+
source: dbSource,
|
|
718
|
+
snapshotId,
|
|
719
|
+
s3Key: snapshotS3Key,
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
else if (dbMode === 'local' || dbMode === 'fresh') {
|
|
723
|
+
payload.database = { mode: 'local' };
|
|
724
|
+
}
|
|
578
725
|
// Execute rebuild
|
|
579
726
|
const rebuildSpinner = (0, ora_1.default)(`Rebuilding Genbox '${selectedName}'...`).start();
|
|
580
727
|
try {
|
package/dist/profile-resolver.js
CHANGED
|
@@ -337,11 +337,13 @@ class ProfileResolver {
|
|
|
337
337
|
};
|
|
338
338
|
// CLI flag takes precedence
|
|
339
339
|
if (options.db) {
|
|
340
|
+
// Normalize 'fresh' to 'local' (fresh is the UI name, local is internal)
|
|
341
|
+
const dbMode = options.db === 'fresh' ? 'local' : options.db;
|
|
340
342
|
const source = options.dbSource || profile.database?.source || 'staging';
|
|
341
343
|
return {
|
|
342
|
-
mode:
|
|
344
|
+
mode: dbMode,
|
|
343
345
|
source,
|
|
344
|
-
url:
|
|
346
|
+
url: dbMode === 'copy' ? getMongoUrl(source) : undefined,
|
|
345
347
|
};
|
|
346
348
|
}
|
|
347
349
|
// Profile setting
|