genbox 1.0.67 → 1.0.68

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.
@@ -137,7 +137,7 @@ async function scpUpload(localPath, ip, remotePath, keyPath) {
137
137
  });
138
138
  }
139
139
  async function runSoftRebuild(options) {
140
- const { genbox, resolved, config, keyPath, envFiles, snapshotId, snapshotS3Key, gitToken, onStep, onLog } = options;
140
+ const { genbox, resolved, config, keyPath, envFiles, snapshotId, snapshotS3Key, gitToken, hardMode, onStep, onLog } = options;
141
141
  const ip = genbox.ipAddress;
142
142
  const log = (line, type) => {
143
143
  if (onLog) {
@@ -150,9 +150,16 @@ async function runSoftRebuild(options) {
150
150
  log('Stopping PM2 processes...', 'dim');
151
151
  await sshExec(ip, keyPath, 'source ~/.nvm/nvm.sh 2>/dev/null; pm2 kill 2>/dev/null || true', 30);
152
152
  // Find and stop docker compose in repo directories
153
+ // In hard mode, use -v flag to remove volumes
154
+ const dockerDownFlags = hardMode ? 'down -v' : 'down';
153
155
  for (const repo of resolved.repos) {
154
- log(`Stopping Docker Compose in ${repo.path}...`, 'dim');
155
- await sshExec(ip, keyPath, `cd ${repo.path} 2>/dev/null && docker compose down 2>/dev/null || true`, 60);
156
+ log(`Stopping Docker Compose in ${repo.path}${hardMode ? ' (removing volumes)' : ''}...`, 'dim');
157
+ await sshExec(ip, keyPath, `cd ${repo.path} 2>/dev/null && docker compose ${dockerDownFlags} 2>/dev/null || true`, 60);
158
+ }
159
+ // In hard mode, also prune docker volumes
160
+ if (hardMode) {
161
+ log('Pruning unused Docker volumes...', 'dim');
162
+ await sshExec(ip, keyPath, 'docker volume prune -f 2>/dev/null || true', 30);
156
163
  }
157
164
  // Step 2: Clean up repo directories
158
165
  onStep?.('Cleaning up repositories...');
@@ -272,8 +279,9 @@ async function runSoftRebuild(options) {
272
279
  }
273
280
  }
274
281
  }
275
- // Step 7: Restore database if snapshot provided
276
- if (snapshotId && snapshotS3Key) {
282
+ // Step 7: Restore database if snapshot provided (only in hard mode)
283
+ // In soft mode (default), database is preserved
284
+ if (hardMode && snapshotId && snapshotS3Key) {
277
285
  onStep?.('Restoring database...');
278
286
  log('Fetching database snapshot...', 'info');
279
287
  try {
@@ -377,107 +385,9 @@ async function runSoftRebuild(options) {
377
385
  return { success: false, error: error.message };
378
386
  }
379
387
  }
380
- async function rebuildGenbox(id, payload) {
381
- return (0, api_1.fetchApi)(`/genboxes/${id}/rebuild`, {
382
- method: 'POST',
383
- body: JSON.stringify(payload),
384
- });
385
- }
386
- /**
387
- * Build rebuild payload from resolved config
388
- */
389
- function buildRebuildPayload(resolved, config, publicKey, privateKey, configLoader) {
390
- const envVars = configLoader.loadEnvVars(process.cwd());
391
- // Build services map
392
- const services = {};
393
- for (const app of resolved.apps) {
394
- if (app.port) {
395
- services[app.name] = { port: app.port, healthcheck: app.healthcheck };
396
- }
397
- }
398
- // Build files bundle
399
- const files = [];
400
- const envFilesToMove = [];
401
- // Process .env.genbox
402
- const envGenboxPath = path.join(process.cwd(), '.env.genbox');
403
- if (fs.existsSync(envGenboxPath)) {
404
- const rawEnvContent = fs.readFileSync(envGenboxPath, 'utf-8');
405
- const sections = (0, utils_1.parseEnvGenboxSections)(rawEnvContent);
406
- const globalSection = sections.get('GLOBAL') || '';
407
- const envVarsFromFile = (0, utils_1.parseEnvVarsFromSection)(globalSection);
408
- let connectTo;
409
- if (resolved.profile && config.profiles?.[resolved.profile]) {
410
- const profile = config.profiles[resolved.profile];
411
- connectTo = (0, config_loader_1.getProfileConnection)(profile);
412
- }
413
- const serviceUrlMap = (0, utils_1.buildServiceUrlMap)(envVarsFromFile, connectTo);
414
- if (connectTo && Object.keys(serviceUrlMap).length > 0) {
415
- console.log(chalk_1.default.dim(` Using ${connectTo} URLs for variable expansion`));
416
- }
417
- for (const app of resolved.apps) {
418
- const appPath = config.apps[app.name]?.path || app.name;
419
- const repoPath = resolved.repos.find(r => r.name === app.name)?.path ||
420
- (resolved.repos[0]?.path ? `${resolved.repos[0].path}/${appPath}` : `/home/dev/${config.project.name}/${appPath}`);
421
- const servicesSections = Array.from(sections.keys()).filter(s => s.startsWith(`${app.name}/`));
422
- if (servicesSections.length > 0) {
423
- for (const serviceSectionName of servicesSections) {
424
- const serviceName = serviceSectionName.split('/')[1];
425
- const serviceEnvContent = (0, utils_1.buildAppEnvContent)(sections, serviceSectionName, serviceUrlMap);
426
- const stagingName = `${app.name}-${serviceName}.env`;
427
- const targetPath = `${repoPath}/apps/${serviceName}/.env`;
428
- files.push({
429
- path: `/home/dev/.env-staging/${stagingName}`,
430
- content: serviceEnvContent,
431
- permissions: '0644',
432
- });
433
- envFilesToMove.push({ stagingName, targetPath });
434
- }
435
- }
436
- else {
437
- const appEnvContent = (0, utils_1.buildAppEnvContent)(sections, app.name, serviceUrlMap);
438
- files.push({
439
- path: `/home/dev/.env-staging/${app.name}.env`,
440
- content: appEnvContent,
441
- permissions: '0644',
442
- });
443
- envFilesToMove.push({
444
- stagingName: `${app.name}.env`,
445
- targetPath: `${repoPath}/.env`,
446
- });
447
- }
448
- }
449
- }
450
- // NOTE: Setup script generation removed from CLI.
451
- // The API generates a comprehensive setup script with clone commands at /root/setup-genbox.sh
452
- // and copies it to /home/dev/setup-genbox.sh via runcmd. This avoids having two conflicting
453
- // scripts in write_files where the CLI version lacked clone commands.
454
- // The envFilesToMove variable is kept as API needs this info for .env file movement.
455
- const postDetails = [];
456
- // Build repos
457
- const repos = {};
458
- for (const repo of resolved.repos) {
459
- repos[repo.name] = {
460
- url: repo.url,
461
- path: repo.path,
462
- branch: repo.branch,
463
- newBranch: repo.newBranch,
464
- sourceBranch: repo.sourceBranch,
465
- };
466
- }
467
- // Get local git config for commits
468
- const gitConfig = (0, utils_1.getGitConfig)();
469
- return {
470
- publicKey,
471
- services,
472
- files,
473
- postDetails,
474
- repos,
475
- privateKey,
476
- gitToken: envVars.GIT_TOKEN,
477
- gitUserName: gitConfig.userName,
478
- gitUserEmail: gitConfig.userEmail,
479
- };
480
- }
388
+ // ============================================================================
389
+ // End of Soft Rebuild
390
+ // ============================================================================
481
391
  exports.rebuildCommand = new commander_1.Command('rebuild')
482
392
  .description('Rebuild an existing Genbox environment with updated configuration')
483
393
  .argument('[name]', 'Name of the Genbox to rebuild (optional - will prompt if not provided)')
@@ -490,7 +400,7 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
490
400
  .option('--db-source <source>', 'Database source for copy mode: staging, production')
491
401
  .option('--db-dump <path>', 'Path to existing database dump file')
492
402
  .option('-y, --yes', 'Skip interactive prompts')
493
- .option('--hard', 'Full rebuild (reinstall OS) instead of soft rebuild')
403
+ .option('--hard', 'Wipe docker volumes and restore database from snapshot')
494
404
  .action(async (name, options) => {
495
405
  try {
496
406
  // Select genbox (interactive if no name provided)
@@ -628,13 +538,19 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
628
538
  if (!options.yes) {
629
539
  console.log('');
630
540
  if (options.hard) {
631
- console.log(chalk_1.default.yellow('Warning: Hard rebuild will reinstall the OS and rerun setup.'));
632
- console.log(chalk_1.default.yellow('All unsaved work on the server will be lost.'));
541
+ console.log(chalk_1.default.blue('Hard rebuild will:'));
542
+ console.log(chalk_1.default.dim(' Stop services (PM2, Docker Compose)'));
543
+ console.log(chalk_1.default.dim(' • Delete and re-clone repositories'));
544
+ console.log(chalk_1.default.dim(' • Wipe Docker volumes (fresh containers)'));
545
+ console.log(chalk_1.default.dim(' • Restore database from snapshot'));
546
+ console.log(chalk_1.default.dim(' • Reinstall dependencies and restart services'));
547
+ console.log(chalk_1.default.yellow('Note: All local data (volumes, DB) will be reset.'));
633
548
  }
634
549
  else {
635
- console.log(chalk_1.default.blue('Soft rebuild will:'));
550
+ console.log(chalk_1.default.blue('Rebuild will:'));
636
551
  console.log(chalk_1.default.dim(' • Stop services (PM2, Docker Compose)'));
637
552
  console.log(chalk_1.default.dim(' • Delete and re-clone repositories'));
553
+ console.log(chalk_1.default.dim(' • Preserve Docker volumes and database'));
638
554
  console.log(chalk_1.default.dim(' • Reinstall dependencies and restart services'));
639
555
  console.log(chalk_1.default.yellow('Note: Uncommitted changes in repos will be lost.'));
640
556
  }
@@ -813,188 +729,149 @@ exports.rebuildCommand = new commander_1.Command('rebuild')
813
729
  }
814
730
  }
815
731
  // ================================================================
816
- // SOFT REBUILD (default)
732
+ // REBUILD VIA SSH
817
733
  // ================================================================
818
- if (!options.hard) {
819
- // Verify server is accessible
820
- if (!genbox.ipAddress) {
821
- console.log(chalk_1.default.red('Error: Genbox has no IP address. It may still be provisioning.'));
822
- console.log(chalk_1.default.dim(' Use --hard for a full rebuild, or wait for provisioning to complete.'));
823
- return;
824
- }
825
- // Get SSH key path
826
- let sshKeyPath;
827
- try {
828
- sshKeyPath = (0, utils_1.getPrivateSshKeyPath)();
829
- }
830
- catch (error) {
831
- console.log(chalk_1.default.red(error.message));
832
- return;
833
- }
834
- // Test SSH connection
835
- console.log('');
836
- console.log(chalk_1.default.dim('Testing SSH connection...'));
837
- const testResult = sshExec(genbox.ipAddress, sshKeyPath, 'echo ok', 10);
838
- if (!testResult.success || testResult.output !== 'ok') {
839
- console.log(chalk_1.default.red('Error: Cannot connect to genbox via SSH.'));
840
- console.log(chalk_1.default.dim(` ${testResult.error || 'Connection failed'}`));
841
- console.log(chalk_1.default.dim(' Use --hard for a full rebuild if the server is unresponsive.'));
842
- return;
843
- }
844
- console.log(chalk_1.default.green('✓ SSH connection verified'));
845
- // Build env files for soft rebuild
846
- const envFilesForSoftRebuild = [];
847
- const envGenboxPath = path.join(process.cwd(), '.env.genbox');
848
- if (fs.existsSync(envGenboxPath)) {
849
- const rawEnvContent = fs.readFileSync(envGenboxPath, 'utf-8');
850
- const sections = (0, utils_1.parseEnvGenboxSections)(rawEnvContent);
851
- const globalSection = sections.get('GLOBAL') || '';
852
- const envVarsFromFile = {};
853
- for (const line of globalSection.split('\n')) {
854
- const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
855
- if (match) {
856
- let value = match[2].trim();
857
- if ((value.startsWith('"') && value.endsWith('"')) ||
858
- (value.startsWith("'") && value.endsWith("'"))) {
859
- value = value.slice(1, -1);
860
- }
861
- envVarsFromFile[match[1]] = value;
734
+ // Verify server is accessible
735
+ if (!genbox.ipAddress) {
736
+ console.log(chalk_1.default.red('Error: Genbox has no IP address. It may still be provisioning.'));
737
+ console.log(chalk_1.default.dim(' Wait for provisioning to complete before rebuilding.'));
738
+ return;
739
+ }
740
+ // Get SSH key path
741
+ let sshKeyPath;
742
+ try {
743
+ sshKeyPath = (0, utils_1.getPrivateSshKeyPath)();
744
+ }
745
+ catch (error) {
746
+ console.log(chalk_1.default.red(error.message));
747
+ return;
748
+ }
749
+ // Test SSH connection
750
+ console.log('');
751
+ console.log(chalk_1.default.dim('Testing SSH connection...'));
752
+ const testResult = sshExec(genbox.ipAddress, sshKeyPath, 'echo ok', 10);
753
+ if (!testResult.success || testResult.output !== 'ok') {
754
+ console.log(chalk_1.default.red('Error: Cannot connect to genbox via SSH.'));
755
+ console.log(chalk_1.default.dim(` ${testResult.error || 'Connection failed'}`));
756
+ console.log(chalk_1.default.dim(' Check if the server is running with: genbox status'));
757
+ return;
758
+ }
759
+ console.log(chalk_1.default.green('✓ SSH connection verified'));
760
+ // Build env files for soft rebuild
761
+ const envFilesForSoftRebuild = [];
762
+ const envGenboxPath = path.join(process.cwd(), '.env.genbox');
763
+ if (fs.existsSync(envGenboxPath)) {
764
+ const rawEnvContent = fs.readFileSync(envGenboxPath, 'utf-8');
765
+ const sections = (0, utils_1.parseEnvGenboxSections)(rawEnvContent);
766
+ const globalSection = sections.get('GLOBAL') || '';
767
+ const envVarsFromFile = {};
768
+ for (const line of globalSection.split('\n')) {
769
+ const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
770
+ if (match) {
771
+ let value = match[2].trim();
772
+ if ((value.startsWith('"') && value.endsWith('"')) ||
773
+ (value.startsWith("'") && value.endsWith("'"))) {
774
+ value = value.slice(1, -1);
862
775
  }
776
+ envVarsFromFile[match[1]] = value;
863
777
  }
864
- let connectTo;
865
- if (resolved.profile && config.profiles?.[resolved.profile]) {
866
- const profile = config.profiles[resolved.profile];
867
- connectTo = (0, config_loader_1.getProfileConnection)(profile);
868
- }
869
- const serviceUrlMap = (0, utils_1.buildServiceUrlMap)(envVarsFromFile, connectTo);
870
- for (const app of resolved.apps) {
871
- const appConfig = config.apps[app.name];
872
- const appPath = appConfig?.path || app.name;
873
- const repoPath = resolved.repos.find(r => r.name === app.name)?.path ||
874
- (resolved.repos[0]?.path ? `${resolved.repos[0].path}/${appPath}` : `/home/dev/${config.project.name}/${appPath}`);
875
- const servicesSections = Array.from(sections.keys()).filter(s => s.startsWith(`${app.name}/`));
876
- if (servicesSections.length > 0) {
877
- for (const serviceSectionName of servicesSections) {
878
- const serviceName = serviceSectionName.split('/')[1];
879
- const serviceEnvContent = (0, utils_1.buildAppEnvContent)(sections, serviceSectionName, serviceUrlMap);
880
- envFilesForSoftRebuild.push({
881
- stagingName: `${app.name}-${serviceName}.env`,
882
- remotePath: `${repoPath}/apps/${serviceName}/.env`,
883
- content: serviceEnvContent,
884
- });
885
- }
886
- }
887
- else {
888
- const appEnvContent = (0, utils_1.buildAppEnvContent)(sections, app.name, serviceUrlMap);
778
+ }
779
+ let connectTo;
780
+ if (resolved.profile && config.profiles?.[resolved.profile]) {
781
+ const profile = config.profiles[resolved.profile];
782
+ connectTo = (0, config_loader_1.getProfileConnection)(profile);
783
+ }
784
+ const serviceUrlMap = (0, utils_1.buildServiceUrlMap)(envVarsFromFile, connectTo);
785
+ for (const app of resolved.apps) {
786
+ const appConfig = config.apps[app.name];
787
+ const appPath = appConfig?.path || app.name;
788
+ const repoPath = resolved.repos.find(r => r.name === app.name)?.path ||
789
+ (resolved.repos[0]?.path ? `${resolved.repos[0].path}/${appPath}` : `/home/dev/${config.project.name}/${appPath}`);
790
+ const servicesSections = Array.from(sections.keys()).filter(s => s.startsWith(`${app.name}/`));
791
+ if (servicesSections.length > 0) {
792
+ for (const serviceSectionName of servicesSections) {
793
+ const serviceName = serviceSectionName.split('/')[1];
794
+ const serviceEnvContent = (0, utils_1.buildAppEnvContent)(sections, serviceSectionName, serviceUrlMap);
889
795
  envFilesForSoftRebuild.push({
890
- stagingName: `${app.name}.env`,
891
- remotePath: `${repoPath}/.env`,
892
- content: appEnvContent,
796
+ stagingName: `${app.name}-${serviceName}.env`,
797
+ remotePath: `${repoPath}/apps/${serviceName}/.env`,
798
+ content: serviceEnvContent,
893
799
  });
894
800
  }
895
801
  }
802
+ else {
803
+ const appEnvContent = (0, utils_1.buildAppEnvContent)(sections, app.name, serviceUrlMap);
804
+ envFilesForSoftRebuild.push({
805
+ stagingName: `${app.name}.env`,
806
+ remotePath: `${repoPath}/.env`,
807
+ content: appEnvContent,
808
+ });
809
+ }
896
810
  }
897
- // Get git token from env
898
- const envVars = configLoader.loadEnvVars(process.cwd());
899
- // Run soft rebuild
900
- console.log('');
901
- console.log(chalk_1.default.blue('=== Starting Soft Rebuild ==='));
902
- console.log('');
903
- let currentStep = '';
904
- const rebuildResult = await runSoftRebuild({
905
- genbox,
906
- resolved,
907
- config,
908
- keyPath: sshKeyPath,
909
- envFiles: envFilesForSoftRebuild,
910
- snapshotId,
911
- snapshotS3Key,
912
- gitToken: envVars.GIT_TOKEN,
913
- onStep: (step) => {
914
- currentStep = step;
915
- console.log(chalk_1.default.blue(`\n=== ${step} ===`));
916
- },
917
- onLog: (line, type) => {
918
- switch (type) {
919
- case 'error':
920
- console.log(chalk_1.default.red(` ${line}`));
921
- break;
922
- case 'success':
923
- console.log(chalk_1.default.green(` ${line}`));
924
- break;
925
- case 'info':
926
- console.log(chalk_1.default.cyan(` ${line}`));
927
- break;
928
- case 'dim':
929
- default:
930
- console.log(chalk_1.default.dim(` ${line}`));
931
- break;
932
- }
933
- },
934
- });
811
+ }
812
+ // Get git token from env
813
+ const envVars = configLoader.loadEnvVars(process.cwd());
814
+ // Run soft rebuild
815
+ console.log('');
816
+ console.log(chalk_1.default.blue(`=== Starting ${options.hard ? 'Hard' : 'Soft'} Rebuild ===`));
817
+ console.log('');
818
+ let currentStep = '';
819
+ const rebuildResult = await runSoftRebuild({
820
+ genbox,
821
+ resolved,
822
+ config,
823
+ keyPath: sshKeyPath,
824
+ envFiles: envFilesForSoftRebuild,
825
+ snapshotId,
826
+ snapshotS3Key,
827
+ gitToken: envVars.GIT_TOKEN,
828
+ hardMode: options.hard, // Pass hard mode flag
829
+ onStep: (step) => {
830
+ currentStep = step;
831
+ console.log(chalk_1.default.blue(`\n=== ${step} ===`));
832
+ },
833
+ onLog: (line, type) => {
834
+ switch (type) {
835
+ case 'error':
836
+ console.log(chalk_1.default.red(` ${line}`));
837
+ break;
838
+ case 'success':
839
+ console.log(chalk_1.default.green(` ${line}`));
840
+ break;
841
+ case 'info':
842
+ console.log(chalk_1.default.cyan(` ${line}`));
843
+ break;
844
+ case 'dim':
845
+ default:
846
+ console.log(chalk_1.default.dim(` ${line}`));
847
+ break;
848
+ }
849
+ },
850
+ });
851
+ console.log('');
852
+ if (rebuildResult.success) {
853
+ console.log(chalk_1.default.green(`✓ ${options.hard ? 'Hard' : 'Soft'} rebuild completed successfully!`));
935
854
  console.log('');
936
- if (rebuildResult.success) {
937
- console.log(chalk_1.default.green(`✓ Soft rebuild completed successfully!`));
938
- console.log('');
939
- console.log(`Run ${chalk_1.default.cyan(`genbox status ${selectedName}`)} to check service status.`);
940
- }
941
- else {
942
- console.log(chalk_1.default.red(`✗ Soft rebuild failed: ${rebuildResult.error}`));
943
- console.log(chalk_1.default.dim(` Failed during: ${currentStep}`));
944
- console.log(chalk_1.default.dim(` Use --hard for a full OS reinstall if needed.`));
945
- }
946
- // Notify API about the rebuild (for tracking)
947
- try {
948
- await (0, api_1.fetchApi)(`/genboxes/${genbox._id}/soft-rebuild-completed`, {
949
- method: 'POST',
950
- body: JSON.stringify({
951
- success: rebuildResult.success,
952
- branch: resolved.repos[0]?.branch,
953
- newBranch: resolved.repos[0]?.newBranch,
954
- sourceBranch: resolved.repos[0]?.sourceBranch,
955
- }),
956
- });
957
- }
958
- catch {
959
- // Silently ignore API notification failures
960
- }
961
- return;
855
+ console.log(`Run ${chalk_1.default.cyan(`genbox status ${selectedName}`)} to check service status.`);
962
856
  }
963
- // ================================================================
964
- // HARD REBUILD (--hard flag)
965
- // ================================================================
966
- // Build payload
967
- const payload = buildRebuildPayload(resolved, config, publicKey, privateKeyContent, configLoader);
968
- // Add database info to payload if we have a snapshot
969
- if (snapshotId && snapshotS3Key) {
970
- payload.database = {
971
- mode: 'snapshot',
972
- source: dbSource,
973
- snapshotId,
974
- s3Key: snapshotS3Key,
975
- };
976
- }
977
- else if (dbMode === 'local' || dbMode === 'fresh') {
978
- payload.database = { mode: 'local' };
979
- }
980
- // Execute hard rebuild via API
981
- const rebuildSpinner = (0, ora_1.default)(`Hard rebuilding Genbox '${selectedName}'...`).start();
857
+ else {
858
+ console.log(chalk_1.default.red(`✗ Rebuild failed: ${rebuildResult.error}`));
859
+ console.log(chalk_1.default.dim(` Failed during: ${currentStep}`));
860
+ }
861
+ // Notify API about the rebuild (for tracking)
982
862
  try {
983
- await rebuildGenbox(genbox._id, payload);
984
- rebuildSpinner.succeed(chalk_1.default.green(`Genbox '${selectedName}' hard rebuild initiated!`));
985
- console.log('');
986
- console.log(chalk_1.default.dim('Server is rebuilding. This may take a few minutes.'));
987
- console.log(chalk_1.default.dim('SSH connection will be temporarily unavailable.'));
988
- console.log('');
989
- console.log(`Run ${chalk_1.default.cyan(`genbox status ${selectedName}`)} to check progress.`);
863
+ await (0, api_1.fetchApi)(`/genboxes/${genbox._id}/soft-rebuild-completed`, {
864
+ method: 'POST',
865
+ body: JSON.stringify({
866
+ success: rebuildResult.success,
867
+ branch: resolved.repos[0]?.branch,
868
+ newBranch: resolved.repos[0]?.newBranch,
869
+ sourceBranch: resolved.repos[0]?.sourceBranch,
870
+ }),
871
+ });
990
872
  }
991
- catch (error) {
992
- rebuildSpinner.fail(chalk_1.default.red(`Failed to rebuild: ${error.message}`));
993
- if (error instanceof api_1.AuthenticationError) {
994
- console.log('');
995
- console.log(chalk_1.default.yellow(' Please authenticate first:'));
996
- console.log(chalk_1.default.cyan(' $ genbox login'));
997
- }
873
+ catch {
874
+ // Silently ignore API notification failures
998
875
  }
999
876
  }
1000
877
  catch (error) {
package/dist/db-utils.js CHANGED
@@ -286,23 +286,26 @@ async function runRemoteMongoRestoreDynamic(ipAddress, options = {}) {
286
286
  return new Promise((resolve) => {
287
287
  options.onProgress?.('Detecting MongoDB and restoring database...');
288
288
  // The restore command - detects MongoDB port dynamically
289
+ // Note: Uses sed/awk instead of grep -P for portability (grep -P not available on Alpine)
289
290
  const restoreCmd = `
290
291
  set -e
291
292
 
292
293
  # Detect MongoDB port from running docker containers
293
294
  # 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
+ # Use sed instead of grep -P for portability
296
+ MONGO_PORT=$(docker ps --format '{{.Ports}}' | sed -n 's/.*:\\([0-9]*\\)->27017.*/\\1/p' | head -1)
295
297
 
296
298
  if [ -z "$MONGO_PORT" ]; then
297
299
  echo "ERROR: Could not detect MongoDB port from running containers"
298
- docker ps
300
+ echo "Running containers:"
301
+ docker ps --format '{{.Names}}: {{.Ports}}'
299
302
  exit 1
300
303
  fi
301
304
 
302
305
  echo "MongoDB detected on port: $MONGO_PORT"
303
306
 
304
307
  # Wait for MongoDB to be responsive
305
- for i in {1..30}; do
308
+ for i in $(seq 1 30); do
306
309
  if mongosh --quiet --host localhost --port $MONGO_PORT --eval "db.runCommand({ping:1})" 2>/dev/null; then
307
310
  echo "MongoDB is ready"
308
311
  break
@@ -331,6 +334,7 @@ fi
331
334
  const proc = (0, child_process_1.spawn)('ssh', [
332
335
  '-o', 'StrictHostKeyChecking=no',
333
336
  '-o', 'UserKnownHostsFile=/dev/null',
337
+ '-o', 'LogLevel=ERROR',
334
338
  '-o', 'ConnectTimeout=30',
335
339
  `dev@${ipAddress}`,
336
340
  'bash', '-c', `'${restoreCmd.replace(/'/g, "'\\''")}'`,
@@ -351,13 +355,35 @@ fi
351
355
  stderr += data.toString();
352
356
  });
353
357
  proc.on('close', (code) => {
358
+ // Filter out SSH warnings from stderr
359
+ const filteredStderr = stderr
360
+ .split('\n')
361
+ .filter(line => !line.includes('Permanently added') && !line.includes('Warning:'))
362
+ .join('\n')
363
+ .trim();
354
364
  if (code === 0) {
355
365
  resolve({ success: true });
356
366
  }
357
367
  else {
368
+ // Include both stdout and filtered stderr for better error diagnosis
369
+ // stdout often contains the actual error messages from the script
370
+ let errorMsg = '';
371
+ if (filteredStderr) {
372
+ errorMsg = filteredStderr;
373
+ }
374
+ if (stdout) {
375
+ // Look for ERROR lines in stdout
376
+ const errorLines = stdout.split('\n').filter(line => line.includes('ERROR') || line.includes('Error') || line.includes('error'));
377
+ if (errorLines.length > 0) {
378
+ errorMsg = errorLines.join('; ');
379
+ }
380
+ else if (!errorMsg) {
381
+ errorMsg = stdout.trim().split('\n').slice(-3).join('\n'); // Last 3 lines
382
+ }
383
+ }
358
384
  resolve({
359
385
  success: false,
360
- error: stderr || stdout || 'Restore failed',
386
+ error: errorMsg || `Restore failed with exit code ${code}`,
361
387
  });
362
388
  }
363
389
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genbox",
3
- "version": "1.0.67",
3
+ "version": "1.0.68",
4
4
  "description": "Genbox CLI - AI-Powered Development Environments",
5
5
  "main": "dist/index.js",
6
6
  "bin": {