genbox 1.0.65 → 1.0.67

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.
@@ -147,7 +147,34 @@ exports.dbSyncCommand
147
147
  if (snapshotChoice === 'existing') {
148
148
  snapshotId = existingSnapshot._id;
149
149
  snapshotS3Key = existingSnapshot.s3Key;
150
- console.log(chalk_1.default.green(` ✓ Using existing snapshot`));
150
+ // Download the existing snapshot from S3
151
+ const downloadSpinner = (0, ora_1.default)('Downloading existing snapshot...').start();
152
+ try {
153
+ const downloadUrlResponse = await (0, api_1.getSnapshotDownloadUrl)(snapshotId);
154
+ const downloadResult = await (0, db_utils_1.downloadSnapshotFromS3)(downloadUrlResponse.downloadUrl, {
155
+ onProgress: (msg) => downloadSpinner.text = msg,
156
+ });
157
+ if (!downloadResult.success) {
158
+ downloadSpinner.fail(chalk_1.default.red('Failed to download snapshot'));
159
+ console.log(chalk_1.default.dim(` Error: ${downloadResult.error}`));
160
+ console.log(chalk_1.default.dim(' Creating fresh snapshot instead...'));
161
+ // Reset to create fresh
162
+ snapshotId = undefined;
163
+ snapshotS3Key = undefined;
164
+ }
165
+ else {
166
+ downloadSpinner.succeed(chalk_1.default.green('Snapshot downloaded'));
167
+ localDumpPath = downloadResult.dumpPath;
168
+ }
169
+ }
170
+ catch (error) {
171
+ downloadSpinner.fail(chalk_1.default.red('Failed to download snapshot'));
172
+ console.log(chalk_1.default.dim(` Error: ${error.message}`));
173
+ console.log(chalk_1.default.dim(' Creating fresh snapshot instead...'));
174
+ // Reset to create fresh
175
+ snapshotId = undefined;
176
+ snapshotS3Key = undefined;
177
+ }
151
178
  }
152
179
  }
153
180
  }
@@ -200,10 +227,7 @@ exports.dbSyncCommand
200
227
  uploadSpinner.succeed(chalk_1.default.green('Database snapshot uploaded'));
201
228
  snapshotId = snapshotResult.snapshotId;
202
229
  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
- }
230
+ // Don't cleanup yet - we still need the file for SCP to genbox
207
231
  }
208
232
  else {
209
233
  uploadSpinner.fail(chalk_1.default.red('Database snapshot upload failed'));
@@ -215,29 +239,57 @@ exports.dbSyncCommand
215
239
  }
216
240
  }
217
241
  }
218
- // Trigger restore on genbox
219
- if (!snapshotId || !snapshotS3Key) {
220
- console.log(chalk_1.default.red('No snapshot available to restore'));
242
+ // Restore directly via SCP/SSH (user's own SSH key, more reliable than API async job)
243
+ if (!localDumpPath) {
244
+ console.log(chalk_1.default.red('No dump file available to restore'));
221
245
  return;
222
246
  }
223
- const restoreSpinner = (0, ora_1.default)('Restoring database on genbox...').start();
247
+ const ipAddress = genbox.ipAddress;
248
+ if (!ipAddress) {
249
+ console.log(chalk_1.default.red('Genbox has no IP address'));
250
+ return;
251
+ }
252
+ const restoreSpinner = (0, ora_1.default)('Uploading dump to genbox...').start();
224
253
  try {
225
- await (0, api_1.fetchApi)(`/genboxes/${genboxId}/db/restore`, {
226
- method: 'POST',
227
- body: JSON.stringify({
228
- snapshotId,
229
- s3Key: snapshotS3Key,
230
- }),
254
+ // SCP dump file to genbox
255
+ const uploadResult = await (0, db_utils_1.uploadDumpToGenbox)(localDumpPath, ipAddress, {
256
+ onProgress: (msg) => restoreSpinner.text = msg,
231
257
  });
258
+ if (!uploadResult.success) {
259
+ restoreSpinner.fail(chalk_1.default.red('Upload failed'));
260
+ console.error(chalk_1.default.red(` Error: ${uploadResult.error}`));
261
+ if (localDumpPath && !options.dump) {
262
+ (0, db_utils_1.cleanupDump)(localDumpPath);
263
+ }
264
+ return;
265
+ }
266
+ restoreSpinner.text = 'Restoring database...';
267
+ // SSH to run mongorestore with dynamic port detection
268
+ const restoreResult = await (0, db_utils_1.runRemoteMongoRestoreDynamic)(ipAddress, {
269
+ onProgress: (msg) => restoreSpinner.text = msg,
270
+ });
271
+ if (!restoreResult.success) {
272
+ restoreSpinner.fail(chalk_1.default.red('Restore failed'));
273
+ console.error(chalk_1.default.red(` Error: ${restoreResult.error}`));
274
+ if (localDumpPath && !options.dump) {
275
+ (0, db_utils_1.cleanupDump)(localDumpPath);
276
+ }
277
+ return;
278
+ }
232
279
  restoreSpinner.succeed(chalk_1.default.green('Database sync completed!'));
233
280
  console.log('');
234
281
  console.log(chalk_1.default.dim(` Database has been restored from ${source} snapshot.`));
282
+ // Cleanup local dump file now that we're done
283
+ if (localDumpPath && !options.dump) {
284
+ (0, db_utils_1.cleanupDump)(localDumpPath);
285
+ }
235
286
  }
236
287
  catch (error) {
237
288
  restoreSpinner.fail(chalk_1.default.red('Database restore failed'));
238
289
  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'));
290
+ // Cleanup on error too
291
+ if (localDumpPath && !options.dump) {
292
+ (0, db_utils_1.cleanupDump)(localDumpPath);
241
293
  }
242
294
  }
243
295
  }
@@ -562,9 +562,10 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
562
562
  // For branch: CLI options override, then stored newBranch (if it was created), then stored branch
563
563
  const effectiveBranch = options.branch || storedNewBranch || storedBranch;
564
564
  // For new branch creation: use CLI options OR stored values (to re-create branch if first attempt failed)
565
- // If storedNewBranch exists with storedSourceBranch, the branch may not have been created yet
566
- const effectiveNewBranch = newBranchName || (storedNewBranch && storedSourceBranch ? storedNewBranch : undefined);
567
- const effectiveSourceBranch = options.fromBranch || (storedNewBranch && storedSourceBranch ? storedSourceBranch : undefined);
565
+ // If storedNewBranch exists, we need to re-create it - default sourceBranch to 'main' if not stored
566
+ const defaultSourceBranch = config.defaults?.branch || 'main';
567
+ const effectiveNewBranch = newBranchName || storedNewBranch;
568
+ const effectiveSourceBranch = options.fromBranch || (storedNewBranch ? (storedSourceBranch || defaultSourceBranch) : undefined);
568
569
  // For database: use CLI option, or stored database config (convert 'snapshot' to 'copy')
569
570
  const storedDbMode = genbox.database?.mode === 'snapshot' ? 'copy' : genbox.database?.mode;
570
571
  const effectiveDbMode = options.db || storedDbMode;
package/dist/db-utils.js CHANGED
@@ -47,9 +47,11 @@ exports.getMongoDumpInstallInstructions = getMongoDumpInstallInstructions;
47
47
  exports.runLocalMongoDump = runLocalMongoDump;
48
48
  exports.uploadDumpToGenbox = uploadDumpToGenbox;
49
49
  exports.runRemoteMongoRestore = runRemoteMongoRestore;
50
+ exports.runRemoteMongoRestoreDynamic = runRemoteMongoRestoreDynamic;
50
51
  exports.cleanupDump = cleanupDump;
51
52
  exports.formatBytes = formatBytes;
52
53
  exports.waitForSshAccess = waitForSshAccess;
54
+ exports.downloadSnapshotFromS3 = downloadSnapshotFromS3;
53
55
  exports.uploadDumpToS3 = uploadDumpToS3;
54
56
  exports.createAndUploadSnapshot = createAndUploadSnapshot;
55
57
  const child_process_1 = require("child_process");
@@ -276,6 +278,94 @@ async function runRemoteMongoRestore(ipAddress, dbName, options = {}) {
276
278
  });
277
279
  });
278
280
  }
281
+ /**
282
+ * Run mongorestore on genbox via SSH with dynamic port detection
283
+ * This version doesn't hardcode container names or ports - it detects them dynamically
284
+ */
285
+ async function runRemoteMongoRestoreDynamic(ipAddress, options = {}) {
286
+ return new Promise((resolve) => {
287
+ options.onProgress?.('Detecting MongoDB and restoring database...');
288
+ // The restore command - detects MongoDB port dynamically
289
+ const restoreCmd = `
290
+ set -e
291
+
292
+ # Detect MongoDB port from running docker containers
293
+ # Different projects use different port mappings (e.g., 27037, 27117, etc.)
294
+ MONGO_PORT=$(docker ps --format '{{.Ports}}' | grep -oP '\\d+(?=->27017)' | head -1)
295
+
296
+ if [ -z "$MONGO_PORT" ]; then
297
+ echo "ERROR: Could not detect MongoDB port from running containers"
298
+ docker ps
299
+ exit 1
300
+ fi
301
+
302
+ echo "MongoDB detected on port: $MONGO_PORT"
303
+
304
+ # Wait for MongoDB to be responsive
305
+ for i in {1..30}; do
306
+ if mongosh --quiet --host localhost --port $MONGO_PORT --eval "db.runCommand({ping:1})" 2>/dev/null; then
307
+ echo "MongoDB is ready"
308
+ break
309
+ fi
310
+ if [ $i -eq 30 ]; then
311
+ echo "ERROR: MongoDB not responding after 30 attempts"
312
+ exit 1
313
+ fi
314
+ echo "Waiting for MongoDB... ($i/30)"
315
+ sleep 2
316
+ done
317
+
318
+ # Restore the database from the uploaded dump
319
+ if [ -f /home/dev/.db-dump.gz ]; then
320
+ echo "Restoring database..."
321
+ # The dump is a raw mongodump --archive file (gzipped), not a tar archive
322
+ # Don't use --db flag with --archive as it causes issues
323
+ mongorestore --host localhost:$MONGO_PORT --drop --gzip --archive=/home/dev/.db-dump.gz
324
+ rm -f /home/dev/.db-dump.gz
325
+ echo "Database restored successfully"
326
+ else
327
+ echo "Error: Dump file not found at /home/dev/.db-dump.gz"
328
+ exit 1
329
+ fi
330
+ `;
331
+ const proc = (0, child_process_1.spawn)('ssh', [
332
+ '-o', 'StrictHostKeyChecking=no',
333
+ '-o', 'UserKnownHostsFile=/dev/null',
334
+ '-o', 'ConnectTimeout=30',
335
+ `dev@${ipAddress}`,
336
+ 'bash', '-c', `'${restoreCmd.replace(/'/g, "'\\''")}'`,
337
+ ], {
338
+ stdio: ['ignore', 'pipe', 'pipe'],
339
+ });
340
+ let stdout = '';
341
+ let stderr = '';
342
+ proc.stdout?.on('data', (data) => {
343
+ const line = data.toString();
344
+ stdout += line;
345
+ // Report progress
346
+ if (line.includes('MongoDB detected') || line.includes('Restoring') || line.includes('restored') || line.includes('Waiting')) {
347
+ options.onProgress?.(line.trim());
348
+ }
349
+ });
350
+ proc.stderr?.on('data', (data) => {
351
+ stderr += data.toString();
352
+ });
353
+ proc.on('close', (code) => {
354
+ if (code === 0) {
355
+ resolve({ success: true });
356
+ }
357
+ else {
358
+ resolve({
359
+ success: false,
360
+ error: stderr || stdout || 'Restore failed',
361
+ });
362
+ }
363
+ });
364
+ proc.on('error', (err) => {
365
+ resolve({ success: false, error: `Failed to run ssh: ${err.message}` });
366
+ });
367
+ });
368
+ }
279
369
  /**
280
370
  * Clean up temporary dump files
281
371
  */
@@ -324,6 +414,36 @@ async function waitForSshAccess(ipAddress, maxWaitSeconds = 300, onProgress) {
324
414
  }
325
415
  return false;
326
416
  }
417
+ /**
418
+ * Download a snapshot from S3 using pre-signed URL
419
+ */
420
+ async function downloadSnapshotFromS3(downloadUrl, options = {}) {
421
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'genbox-dbdump-'));
422
+ const dumpPath = path.join(tempDir, 'dump.gz');
423
+ options.onProgress?.('Downloading snapshot from cloud...');
424
+ try {
425
+ const response = await fetch(downloadUrl);
426
+ if (!response.ok) {
427
+ fs.rmSync(tempDir, { recursive: true, force: true });
428
+ return {
429
+ success: false,
430
+ error: `Download failed: ${response.status} ${response.statusText}`,
431
+ };
432
+ }
433
+ const buffer = await response.arrayBuffer();
434
+ fs.writeFileSync(dumpPath, Buffer.from(buffer));
435
+ const stats = fs.statSync(dumpPath);
436
+ options.onProgress?.(`Downloaded snapshot (${formatBytes(stats.size)})`);
437
+ return { success: true, dumpPath };
438
+ }
439
+ catch (error) {
440
+ fs.rmSync(tempDir, { recursive: true, force: true });
441
+ return {
442
+ success: false,
443
+ error: `Download failed: ${error.message}`,
444
+ };
445
+ }
446
+ }
327
447
  /**
328
448
  * Upload dump file to S3 using pre-signed URL
329
449
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genbox",
3
- "version": "1.0.65",
3
+ "version": "1.0.67",
4
4
  "description": "Genbox CLI - AI-Powered Development Environments",
5
5
  "main": "dist/index.js",
6
6
  "bin": {