rocketride 1.0.5 → 1.1.0

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.
Files changed (159) hide show
  1. package/README.md +4 -4
  2. package/dist/cjs/account.js +284 -0
  3. package/dist/cjs/account.js.map +1 -0
  4. package/dist/cjs/billing.js +171 -0
  5. package/dist/cjs/billing.js.map +1 -0
  6. package/dist/cjs/client.js +1208 -756
  7. package/dist/cjs/client.js.map +1 -1
  8. package/dist/cjs/constants.js +10 -1
  9. package/dist/cjs/constants.js.map +1 -1
  10. package/dist/cjs/core/DAPBase.js +4 -1
  11. package/dist/cjs/core/DAPBase.js.map +1 -1
  12. package/dist/cjs/core/DAPClient.js +121 -50
  13. package/dist/cjs/core/DAPClient.js.map +1 -1
  14. package/dist/cjs/core/TransportBase.js +0 -10
  15. package/dist/cjs/core/TransportBase.js.map +1 -1
  16. package/dist/cjs/core/TransportWebSocket.js +30 -19
  17. package/dist/cjs/core/TransportWebSocket.js.map +1 -1
  18. package/dist/cjs/database.js +121 -0
  19. package/dist/cjs/database.js.map +1 -0
  20. package/dist/cjs/index.js +4 -0
  21. package/dist/cjs/index.js.map +1 -1
  22. package/dist/cjs/schema/Question.js +2 -0
  23. package/dist/cjs/schema/Question.js.map +1 -1
  24. package/dist/cjs/types/account.js +26 -0
  25. package/dist/cjs/types/account.js.map +1 -0
  26. package/dist/cjs/types/billing.js +26 -0
  27. package/dist/cjs/types/billing.js.map +1 -0
  28. package/dist/cjs/types/client.js +14 -0
  29. package/dist/cjs/types/client.js.map +1 -1
  30. package/dist/cjs/types/cprofile.js +26 -0
  31. package/dist/cjs/types/cprofile.js.map +1 -0
  32. package/dist/cjs/types/dashboard.js +26 -0
  33. package/dist/cjs/types/dashboard.js.map +1 -0
  34. package/dist/cjs/types/events.js +5 -1
  35. package/dist/cjs/types/events.js.map +1 -1
  36. package/dist/cjs/types/index.js +5 -0
  37. package/dist/cjs/types/index.js.map +1 -1
  38. package/dist/cjs/types/service.js +85 -0
  39. package/dist/cjs/types/service.js.map +1 -0
  40. package/dist/cli/cli/rocketride.js +335 -113
  41. package/dist/cli/cli/rocketride.js.map +1 -1
  42. package/dist/cli/client/account.js +284 -0
  43. package/dist/cli/client/account.js.map +1 -0
  44. package/dist/cli/client/billing.js +171 -0
  45. package/dist/cli/client/billing.js.map +1 -0
  46. package/dist/cli/client/client.js +1208 -756
  47. package/dist/cli/client/client.js.map +1 -1
  48. package/dist/cli/client/constants.js +10 -1
  49. package/dist/cli/client/constants.js.map +1 -1
  50. package/dist/cli/client/core/DAPBase.js +4 -1
  51. package/dist/cli/client/core/DAPBase.js.map +1 -1
  52. package/dist/cli/client/core/DAPClient.js +121 -50
  53. package/dist/cli/client/core/DAPClient.js.map +1 -1
  54. package/dist/cli/client/core/TransportBase.js +0 -10
  55. package/dist/cli/client/core/TransportBase.js.map +1 -1
  56. package/dist/cli/client/core/TransportWebSocket.js +30 -19
  57. package/dist/cli/client/core/TransportWebSocket.js.map +1 -1
  58. package/dist/cli/client/database.js +121 -0
  59. package/dist/cli/client/database.js.map +1 -0
  60. package/dist/cli/client/index.js +4 -0
  61. package/dist/cli/client/index.js.map +1 -1
  62. package/dist/cli/client/schema/Question.js +2 -0
  63. package/dist/cli/client/schema/Question.js.map +1 -1
  64. package/dist/cli/client/types/account.js +26 -0
  65. package/dist/cli/client/types/account.js.map +1 -0
  66. package/dist/cli/client/types/billing.js +26 -0
  67. package/dist/cli/client/types/billing.js.map +1 -0
  68. package/dist/cli/client/types/client.js +14 -0
  69. package/dist/cli/client/types/client.js.map +1 -1
  70. package/dist/cli/client/types/cprofile.js +26 -0
  71. package/dist/cli/client/types/cprofile.js.map +1 -0
  72. package/dist/cli/client/types/dashboard.js +26 -0
  73. package/dist/cli/client/types/dashboard.js.map +1 -0
  74. package/dist/cli/client/types/events.js +5 -1
  75. package/dist/cli/client/types/events.js.map +1 -1
  76. package/dist/cli/client/types/index.js +5 -0
  77. package/dist/cli/client/types/index.js.map +1 -1
  78. package/dist/cli/client/types/service.js +85 -0
  79. package/dist/cli/client/types/service.js.map +1 -0
  80. package/dist/esm/account.js +280 -0
  81. package/dist/esm/account.js.map +1 -0
  82. package/dist/esm/billing.js +167 -0
  83. package/dist/esm/billing.js.map +1 -0
  84. package/dist/esm/client.js +1208 -756
  85. package/dist/esm/client.js.map +1 -1
  86. package/dist/esm/constants.js +9 -0
  87. package/dist/esm/constants.js.map +1 -1
  88. package/dist/esm/core/DAPBase.js +4 -1
  89. package/dist/esm/core/DAPBase.js.map +1 -1
  90. package/dist/esm/core/DAPClient.js +121 -50
  91. package/dist/esm/core/DAPClient.js.map +1 -1
  92. package/dist/esm/core/TransportBase.js +0 -10
  93. package/dist/esm/core/TransportBase.js.map +1 -1
  94. package/dist/esm/core/TransportWebSocket.js +30 -19
  95. package/dist/esm/core/TransportWebSocket.js.map +1 -1
  96. package/dist/esm/database.js +117 -0
  97. package/dist/esm/database.js.map +1 -0
  98. package/dist/esm/index.js +4 -0
  99. package/dist/esm/index.js.map +1 -1
  100. package/dist/esm/schema/Question.js +2 -0
  101. package/dist/esm/schema/Question.js.map +1 -1
  102. package/dist/esm/types/account.js +25 -0
  103. package/dist/esm/types/account.js.map +1 -0
  104. package/dist/esm/types/billing.js +25 -0
  105. package/dist/esm/types/billing.js.map +1 -0
  106. package/dist/esm/types/client.js +13 -1
  107. package/dist/esm/types/client.js.map +1 -1
  108. package/dist/esm/types/cprofile.js +25 -0
  109. package/dist/esm/types/cprofile.js.map +1 -0
  110. package/dist/esm/types/dashboard.js +25 -0
  111. package/dist/esm/types/dashboard.js.map +1 -0
  112. package/dist/esm/types/events.js +5 -1
  113. package/dist/esm/types/events.js.map +1 -1
  114. package/dist/esm/types/index.js +5 -0
  115. package/dist/esm/types/index.js.map +1 -1
  116. package/dist/esm/types/service.js +82 -0
  117. package/dist/esm/types/service.js.map +1 -0
  118. package/dist/types/account.d.ts +209 -0
  119. package/dist/types/account.d.ts.map +1 -0
  120. package/dist/types/billing.d.ts +135 -0
  121. package/dist/types/billing.d.ts.map +1 -0
  122. package/dist/types/client.d.ts +553 -285
  123. package/dist/types/client.d.ts.map +1 -1
  124. package/dist/types/constants.d.ts +9 -0
  125. package/dist/types/constants.d.ts.map +1 -1
  126. package/dist/types/core/DAPBase.d.ts.map +1 -1
  127. package/dist/types/core/DAPClient.d.ts +89 -7
  128. package/dist/types/core/DAPClient.d.ts.map +1 -1
  129. package/dist/types/core/TransportBase.d.ts +1 -11
  130. package/dist/types/core/TransportBase.d.ts.map +1 -1
  131. package/dist/types/core/TransportWebSocket.d.ts +14 -11
  132. package/dist/types/core/TransportWebSocket.d.ts.map +1 -1
  133. package/dist/types/database.d.ts +90 -0
  134. package/dist/types/database.d.ts.map +1 -0
  135. package/dist/types/index.d.ts +2 -0
  136. package/dist/types/index.d.ts.map +1 -1
  137. package/dist/types/schema/Question.d.ts +3 -1
  138. package/dist/types/schema/Question.d.ts.map +1 -1
  139. package/dist/types/types/account.d.ts +171 -0
  140. package/dist/types/types/account.d.ts.map +1 -0
  141. package/dist/types/types/billing.d.ts +115 -0
  142. package/dist/types/types/billing.d.ts.map +1 -0
  143. package/dist/types/types/client.d.ts +241 -1
  144. package/dist/types/types/client.d.ts.map +1 -1
  145. package/dist/types/types/cprofile.d.ts +67 -0
  146. package/dist/types/types/cprofile.d.ts.map +1 -0
  147. package/dist/types/types/dashboard.d.ts +198 -0
  148. package/dist/types/types/dashboard.d.ts.map +1 -0
  149. package/dist/types/types/events.d.ts +90 -95
  150. package/dist/types/types/events.d.ts.map +1 -1
  151. package/dist/types/types/index.d.ts +5 -0
  152. package/dist/types/types/index.d.ts.map +1 -1
  153. package/dist/types/types/pipeline.d.ts +10 -2
  154. package/dist/types/types/pipeline.d.ts.map +1 -1
  155. package/dist/types/types/service.d.ts +189 -0
  156. package/dist/types/types/service.d.ts.map +1 -0
  157. package/dist/types/types/task.d.ts +5 -1
  158. package/dist/types/types/task.d.ts.map +1 -1
  159. package/package.json +12 -7
@@ -141,7 +141,7 @@ class Box {
141
141
  }
142
142
  boxTop() {
143
143
  const titlePart = ` ${this.title} `;
144
- const remainingWidth = (this.width - 3) - titlePart.length;
144
+ const remainingWidth = this.width - 3 - titlePart.length;
145
145
  return CHR_TL + CHR_HORIZ + titlePart + CHR_HORIZ.repeat(Math.max(0, remainingWidth)) + CHR_TR;
146
146
  }
147
147
  boxMiddle(content) {
@@ -328,23 +328,16 @@ class StatusMonitor extends BoxMonitor {
328
328
  3: ['Online', ANSI_GREEN],
329
329
  4: ['Stopping', ANSI_YELLOW],
330
330
  5: ['Offline', ANSI_GRAY],
331
- 6: ['Offline', ANSI_GRAY]
331
+ 6: ['Offline', ANSI_GRAY],
332
332
  };
333
333
  return stateMap[state] || ['Unknown', ANSI_RESET];
334
334
  }
335
335
  hasCountData(status) {
336
- return ((Number(status.totalSize) || 0) > 0 ||
337
- (Number(status.totalCount) || 0) > 0 ||
338
- (Number(status.completedSize) || 0) > 0 ||
339
- (Number(status.completedCount) || 0) > 0 ||
340
- (Number(status.failedSize) || 0) > 0 ||
341
- (Number(status.failedCount) || 0) > 0 ||
342
- (Number(status.rateSize) || 0) > 0 ||
343
- (Number(status.rateCount) || 0) > 0);
336
+ return (Number(status.totalSize) || 0) > 0 || (Number(status.totalCount) || 0) > 0 || (Number(status.completedSize) || 0) > 0 || (Number(status.completedCount) || 0) > 0 || (Number(status.failedSize) || 0) > 0 || (Number(status.failedCount) || 0) > 0 || (Number(status.rateSize) || 0) > 0 || (Number(status.rateCount) || 0) > 0;
344
337
  }
345
338
  hasMetricsData(status) {
346
339
  const metrics = (status.metrics || {});
347
- return Object.values(metrics).some(value => typeof value === 'number' && value > 0);
340
+ return Object.values(metrics).some((value) => typeof value === 'number' && value > 0);
348
341
  }
349
342
  onEvent(message) {
350
343
  const eventType = message.event || '';
@@ -390,7 +383,7 @@ class StatusMonitor extends BoxMonitor {
390
383
  if (startTime > 0) {
391
384
  const startStr = new Date(startTime * 1000).toLocaleString();
392
385
  lines.push(`Started: ${startStr}`);
393
- const endTime = status.completed ? (Number(status.endTime) || 0) : undefined;
386
+ const endTime = status.completed ? Number(status.endTime) || 0 : undefined;
394
387
  const duration = this.formatDuration(startTime, endTime);
395
388
  lines.push(`Elapsed: ${duration}`);
396
389
  }
@@ -399,7 +392,7 @@ class StatusMonitor extends BoxMonitor {
399
392
  const dataTypes = [
400
393
  ['total', 'Total'],
401
394
  ['completed', 'Completed'],
402
- ['failed', 'Failed']
395
+ ['failed', 'Failed'],
403
396
  ];
404
397
  for (const [keyBase, label] of dataTypes) {
405
398
  const count = Number(status[`${keyBase}Count`]) || 0;
@@ -424,7 +417,7 @@ class StatusMonitor extends BoxMonitor {
424
417
  const metrics = (status.metrics || {});
425
418
  for (const [key, value] of Object.entries(metrics)) {
426
419
  if (typeof value === 'number' && value > 0) {
427
- const label = key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase());
420
+ const label = key.replace(/_/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
428
421
  lines.push(`${label}: ${value}`);
429
422
  }
430
423
  }
@@ -442,8 +435,7 @@ class StatusMonitor extends BoxMonitor {
442
435
  const errType = parts[0].trim();
443
436
  const message = parts[1].replace(/`/g, '').trim();
444
437
  const fileInfo = parts[2].trim();
445
- const filename = fileInfo.includes('\\') || fileInfo.includes('/') ?
446
- path.basename(fileInfo) : fileInfo;
438
+ const filename = fileInfo.includes('\\') || fileInfo.includes('/') ? path.basename(fileInfo) : fileInfo;
447
439
  lines.push(`${color}${errType}${ANSI_RESET}: ${message}`);
448
440
  if (filename) {
449
441
  lines.push(` -> ${filename}`);
@@ -459,7 +451,7 @@ class StatusMonitor extends BoxMonitor {
459
451
  if (!items || items.length === 0) {
460
452
  return [];
461
453
  }
462
- return items.slice(-5).map(item => `• ${item}`);
454
+ return items.slice(-5).map((item) => `• ${item}`);
463
455
  }
464
456
  displayConnecting(url, attempt) {
465
457
  this.clear();
@@ -480,7 +472,7 @@ class UploadProgressMonitor extends BoxMonitor {
480
472
  this.setCommandStatus('Preparing upload...');
481
473
  }
482
474
  createProgressBar(percent, width = 30) {
483
- const filledLength = Math.floor(width * percent / 100);
475
+ const filledLength = Math.floor((width * percent) / 100);
484
476
  const bar = CHR_BLOCK.repeat(filledLength) + CHR_LIGHT_BLOCK.repeat(width - filledLength);
485
477
  return `[${bar}] ${percent.toFixed(1).padStart(5)}%`;
486
478
  }
@@ -516,7 +508,7 @@ class UploadProgressMonitor extends BoxMonitor {
516
508
  filepath,
517
509
  action,
518
510
  bytes_sent: bytesSent,
519
- file_size: fileSize
511
+ file_size: fileSize,
520
512
  });
521
513
  }
522
514
  else if (action === 'close') {
@@ -526,7 +518,7 @@ class UploadProgressMonitor extends BoxMonitor {
526
518
  ...existing,
527
519
  action,
528
520
  bytes_sent: bytesSent,
529
- file_size: fileSize
521
+ file_size: fileSize,
530
522
  });
531
523
  }
532
524
  }
@@ -535,7 +527,7 @@ class UploadProgressMonitor extends BoxMonitor {
535
527
  this.completedUploads.set(filename, {
536
528
  filepath,
537
529
  action,
538
- file_size: fileSize
530
+ file_size: fileSize,
539
531
  });
540
532
  }
541
533
  else if (action === 'error') {
@@ -545,7 +537,7 @@ class UploadProgressMonitor extends BoxMonitor {
545
537
  filepath,
546
538
  action,
547
539
  file_size: fileSize,
548
- error: errorMessage
540
+ error: errorMessage,
549
541
  });
550
542
  }
551
543
  this.draw();
@@ -569,7 +561,7 @@ class UploadProgressMonitor extends BoxMonitor {
569
561
  filepath,
570
562
  action,
571
563
  bytes_sent: bytesSent,
572
- file_size: fileSize
564
+ file_size: fileSize,
573
565
  });
574
566
  }
575
567
  else if (action === 'close') {
@@ -579,7 +571,7 @@ class UploadProgressMonitor extends BoxMonitor {
579
571
  ...existing,
580
572
  action,
581
573
  bytes_sent: bytesSent,
582
- file_size: fileSize
574
+ file_size: fileSize,
583
575
  });
584
576
  }
585
577
  }
@@ -588,7 +580,7 @@ class UploadProgressMonitor extends BoxMonitor {
588
580
  this.completedUploads.set(filename, {
589
581
  filepath,
590
582
  action,
591
- file_size: fileSize
583
+ file_size: fileSize,
592
584
  });
593
585
  }
594
586
  else if (action === 'error') {
@@ -598,7 +590,7 @@ class UploadProgressMonitor extends BoxMonitor {
598
590
  filepath,
599
591
  action,
600
592
  file_size: fileSize,
601
- error: errorMessage
593
+ error: errorMessage,
602
594
  });
603
595
  }
604
596
  if (this.cli.isCancelled()) {
@@ -625,7 +617,7 @@ class UploadProgressMonitor extends BoxMonitor {
625
617
  const phase = action === 'write' ? 'Writing ' : action === 'close' ? 'Finalize' : ' ';
626
618
  const bytesSent = Number(data.bytes_sent) || 0;
627
619
  const fileSize = Number(data.file_size) || 1;
628
- const percent = fileSize > 0 ? (bytesSent / fileSize * 100) : 0;
620
+ const percent = fileSize > 0 ? (bytesSent / fileSize) * 100 : 0;
629
621
  const progressBar = this.createProgressBar(percent, 12);
630
622
  const sizeInfo = `${this.formatSize(bytesSent)}/${this.formatSize(fileSize)}`;
631
623
  uploadLines.push(`${displayName} ${phase} ${progressBar} ${sizeInfo}`);
@@ -645,8 +637,7 @@ class UploadProgressMonitor extends BoxMonitor {
645
637
  if (this.failedUploads.size > 0) {
646
638
  summaryLines.push(`Failed: ${this.failedUploads.size} files`);
647
639
  }
648
- const totalBytes = Array.from(this.completedUploads.values())
649
- .reduce((sum, data) => sum + (Number(data.file_size) || 0), 0);
640
+ const totalBytes = Array.from(this.completedUploads.values()).reduce((sum, data) => sum + (Number(data.file_size) || 0), 0);
650
641
  summaryLines.push(`Total size: ${this.formatSize(totalBytes)}`);
651
642
  this.addBox('Upload Summary', summaryLines);
652
643
  }
@@ -658,8 +649,7 @@ class UploadProgressMonitor extends BoxMonitor {
658
649
  for (const [filename, data] of failedEntries.slice(-displayCount)) {
659
650
  const displayName = this.truncateFilename(filename, 25);
660
651
  const errorStr = String(data.error || '');
661
- const errorMsg = errorStr.length > 30 ?
662
- `${errorStr.substring(0, 30)}...` : errorStr;
652
+ const errorMsg = errorStr.length > 30 ? `${errorStr.substring(0, 30)}...` : errorStr;
663
653
  failedLines.push(`${ANSI_RED}${CHR_CROSS}${ANSI_RESET} ${displayName} - ${errorMsg}`);
664
654
  }
665
655
  if (failedEntries.length > 5) {
@@ -697,7 +687,7 @@ class RocketRideCLI {
697
687
  total_bytes: 0,
698
688
  successful_uploads: 0,
699
689
  failed_uploads: 0,
700
- upload_times: []
690
+ upload_times: [],
701
691
  };
702
692
  this.uri = '';
703
693
  this.connected = false;
@@ -711,25 +701,52 @@ class RocketRideCLI {
711
701
  isCancelled() {
712
702
  return this.cancelled;
713
703
  }
704
+ isShuttingDown() {
705
+ return this.signalShutdownPromise !== undefined;
706
+ }
707
+ // Resolves only when the signal handler calls process.exit, so any
708
+ // command/run/main flow that awaits this will stand down and let the
709
+ // signal handler own the exit code.
710
+ awaitShutdown() {
711
+ return this.signalShutdownPromise ?? new Promise(() => { });
712
+ }
714
713
  setupSignalHandlers() {
715
- // TODO: Enable proper signal handling
716
- // const signalHandler = () => {
717
- // this.cancel();
718
- // };
719
- // process.on('SIGINT', signalHandler);
720
- // process.on('SIGTERM', signalHandler);
714
+ const FORCE_EXIT_TIMEOUT_MS = 5000;
715
+ const signalHandler = async (signal) => {
716
+ const exitCode = 128 + (signal === 'SIGINT' ? 2 : 15);
717
+ if (this.signalShutdownPromise) {
718
+ // Second signal: force exit immediately
719
+ process.exit(exitCode);
720
+ }
721
+ // Park a promise that never resolves; other flows await it to
722
+ // stand down while the signal handler drives the exit.
723
+ this.signalShutdownPromise = new Promise(() => { });
724
+ this.cancel();
725
+ // Force exit if cleanup hangs
726
+ const forceExitTimer = setTimeout(() => {
727
+ console.error(`\nCleanup timed out after ${FORCE_EXIT_TIMEOUT_MS}ms, forcing exit`);
728
+ process.exit(exitCode);
729
+ }, FORCE_EXIT_TIMEOUT_MS);
730
+ try {
731
+ await this.cleanupClient();
732
+ }
733
+ catch {
734
+ // Ignore cleanup errors during signal handling
735
+ }
736
+ finally {
737
+ clearTimeout(forceExitTimer);
738
+ }
739
+ process.exit(exitCode);
740
+ };
741
+ process.on('SIGINT', () => signalHandler('SIGINT'));
742
+ process.on('SIGTERM', () => signalHandler('SIGTERM'));
721
743
  }
722
744
  createProgram() {
723
745
  const program = new commander_1.Command();
724
- program
725
- .name('rocketride')
726
- .description('RocketRide Unified Pipeline and File Management CLI')
727
- .version('1.3.0');
746
+ program.name('rocketride').description('RocketRide Unified Pipeline and File Management CLI').version('1.3.0');
728
747
  // Common options
729
748
  const addCommonOptions = (cmd) => {
730
- return cmd
731
- .option('--uri <uri>', 'RocketRide server URI (can use ROCKETRIDE_URI env var)', process.env.ROCKETRIDE_URI || constants_1.CONST_DEFAULT_WEB_LOCAL)
732
- .option('--apikey <key>', 'API key for RocketRide server authentication (can use ROCKETRIDE_APIKEY in .env or env var)', process.env.ROCKETRIDE_APIKEY);
749
+ return cmd.option('--uri <uri>', 'RocketRide server URI (can use ROCKETRIDE_URI env var)', process.env.ROCKETRIDE_URI || constants_1.CONST_DEFAULT_WEB_LOCAL).option('--apikey <key>', 'API key for RocketRide server authentication (can use ROCKETRIDE_APIKEY in .env or env var)', process.env.ROCKETRIDE_APIKEY);
733
750
  };
734
751
  // Start command
735
752
  const startCmd = program
@@ -749,16 +766,20 @@ class RocketRideCLI {
749
766
  command: 'start',
750
767
  ...options,
751
768
  pipeline: options.pipeline,
752
- threads: parseInt(options.threads)
769
+ threads: parseInt(options.threads),
753
770
  };
754
771
  this.uri = options.uri;
755
772
  try {
756
773
  const exitCode = await this.cmdStart();
757
- process.exit(exitCode);
774
+ if (!this.isCancelled()) {
775
+ process.exit(exitCode);
776
+ }
758
777
  }
759
778
  finally {
760
- this.cancel();
761
- await this.cleanupClient();
779
+ if (!this.isCancelled()) {
780
+ this.cancel();
781
+ await this.cleanupClient();
782
+ }
762
783
  }
763
784
  });
764
785
  addCommonOptions(startCmd);
@@ -784,16 +805,20 @@ class RocketRideCLI {
784
805
  files,
785
806
  threads: parseInt(options.threads),
786
807
  max_concurrent: parseInt(options.maxConcurrent || '5'),
787
- pipeline_args: options.args
808
+ pipeline_args: options.args,
788
809
  };
789
810
  this.uri = options.uri;
790
811
  try {
791
812
  const exitCode = await this.cmdUpload();
792
- process.exit(exitCode);
813
+ if (!this.isCancelled()) {
814
+ process.exit(exitCode);
815
+ }
793
816
  }
794
817
  finally {
795
- this.cancel();
796
- await this.cleanupClient();
818
+ if (!this.isCancelled()) {
819
+ this.cancel();
820
+ await this.cleanupClient();
821
+ }
797
822
  }
798
823
  });
799
824
  addCommonOptions(uploadCmd);
@@ -815,11 +840,15 @@ class RocketRideCLI {
815
840
  this.uri = options.uri;
816
841
  try {
817
842
  const exitCode = await this.cmdStatus();
818
- process.exit(exitCode);
843
+ if (!this.isCancelled()) {
844
+ process.exit(exitCode);
845
+ }
819
846
  }
820
847
  finally {
821
- this.cancel();
822
- await this.cleanupClient();
848
+ if (!this.isCancelled()) {
849
+ this.cancel();
850
+ await this.cleanupClient();
851
+ }
823
852
  }
824
853
  });
825
854
  addCommonOptions(statusCmd);
@@ -841,14 +870,213 @@ class RocketRideCLI {
841
870
  this.uri = options.uri;
842
871
  try {
843
872
  const exitCode = await this.cmdStop();
844
- process.exit(exitCode);
873
+ if (!this.isCancelled()) {
874
+ process.exit(exitCode);
875
+ }
876
+ }
877
+ finally {
878
+ if (!this.isCancelled()) {
879
+ this.cancel();
880
+ await this.cleanupClient();
881
+ }
882
+ }
883
+ });
884
+ addCommonOptions(stopCmd);
885
+ // Store command with file system subcommands
886
+ const storeCmd = program.command('store').description('File store operations');
887
+ // store dir
888
+ const storeDirCmd = storeCmd
889
+ .command('dir [path]')
890
+ .description('List directory contents')
891
+ .action(async (path, options) => {
892
+ this.args = { command: 'store', subcommand: 'dir', path: path || '', ...options };
893
+ this.uri = options.uri;
894
+ try {
895
+ const client = await this.createAndConnectClient();
896
+ const result = await client.fsListDir(path || '');
897
+ const entries = result.entries || [];
898
+ if (entries.length === 0) {
899
+ const stat = path ? await client.fsStat(path) : { exists: true, type: 'dir' };
900
+ if (stat.exists && stat.type === 'dir') {
901
+ console.log(` ${(0).toLocaleString().padStart(8)} File(s) ${(0).toLocaleString().padStart(14)} bytes`);
902
+ console.log(` ${(0).toLocaleString().padStart(8)} Dir(s)`);
903
+ }
904
+ else {
905
+ console.log('File Not Found');
906
+ }
907
+ }
908
+ else {
909
+ let totalSize = 0;
910
+ let fileCount = 0;
911
+ let dirCount = 0;
912
+ for (const e of entries) {
913
+ let dateStr = ' ';
914
+ if (e.modified) {
915
+ const d = new Date(e.modified * 1000);
916
+ const mm = String(d.getUTCMonth() + 1).padStart(2, '0');
917
+ const dd = String(d.getUTCDate()).padStart(2, '0');
918
+ const yyyy = d.getUTCFullYear();
919
+ let hh = d.getUTCHours();
920
+ const min = String(d.getUTCMinutes()).padStart(2, '0');
921
+ const ampm = hh >= 12 ? 'PM' : 'AM';
922
+ hh = hh % 12 || 12;
923
+ dateStr = `${mm}/${dd}/${yyyy} ${String(hh).padStart(2, '0')}:${min} ${ampm}`;
924
+ }
925
+ if (e.type === 'dir') {
926
+ console.log(`${dateStr} <DIR> ${e.name}`);
927
+ dirCount++;
928
+ }
929
+ else {
930
+ const size = e.size ?? 0;
931
+ totalSize += size;
932
+ console.log(`${dateStr} ${size.toLocaleString().padStart(14)} ${e.name}`);
933
+ fileCount++;
934
+ }
935
+ }
936
+ console.log(` ${fileCount.toLocaleString().padStart(8)} File(s) ${totalSize.toLocaleString().padStart(14)} bytes`);
937
+ console.log(` ${dirCount.toLocaleString().padStart(8)} Dir(s)`);
938
+ }
939
+ process.exit(0);
845
940
  }
846
941
  finally {
847
942
  this.cancel();
848
943
  await this.cleanupClient();
849
944
  }
850
945
  });
851
- addCommonOptions(stopCmd);
946
+ addCommonOptions(storeDirCmd);
947
+ // store type
948
+ const storeTypeCmd = storeCmd
949
+ .command('type <path>')
950
+ .description('Display file contents')
951
+ .action(async (path, options) => {
952
+ this.args = { command: 'store', subcommand: 'type', path, ...options };
953
+ this.uri = options.uri;
954
+ try {
955
+ const client = await this.createAndConnectClient();
956
+ const text = await client.fsReadString(path);
957
+ process.stdout.write(text);
958
+ process.exit(0);
959
+ }
960
+ finally {
961
+ this.cancel();
962
+ await this.cleanupClient();
963
+ }
964
+ });
965
+ addCommonOptions(storeTypeCmd);
966
+ // store write
967
+ const storeWriteCmd = storeCmd
968
+ .command('write <path>')
969
+ .description('Write a file')
970
+ .option('--file <localFile>', 'Local file to upload')
971
+ .option('--content <text>', 'Inline text content')
972
+ .action(async (path, options) => {
973
+ this.args = { command: 'store', subcommand: 'write', path, ...options };
974
+ this.uri = options.uri;
975
+ try {
976
+ const client = await this.createAndConnectClient();
977
+ if (options.file) {
978
+ const nodeFs = await Promise.resolve().then(() => __importStar(require('fs')));
979
+ const { handle } = await client.fsOpen(path, 'w');
980
+ try {
981
+ const stream = nodeFs.createReadStream(options.file);
982
+ for await (const chunk of stream) {
983
+ await client.fsWrite(handle, chunk);
984
+ }
985
+ await client.fsClose(handle, 'w');
986
+ }
987
+ catch (err) {
988
+ try {
989
+ await client.fsClose(handle, 'w');
990
+ }
991
+ catch {
992
+ /* best-effort */
993
+ }
994
+ throw err;
995
+ }
996
+ }
997
+ else if (options.content !== undefined) {
998
+ await client.fsWriteString(path, options.content);
999
+ }
1000
+ else {
1001
+ console.error('Error: Either --file or --content is required');
1002
+ process.exit(1);
1003
+ }
1004
+ console.log(`Written: ${path}`);
1005
+ process.exit(0);
1006
+ }
1007
+ finally {
1008
+ this.cancel();
1009
+ await this.cleanupClient();
1010
+ }
1011
+ });
1012
+ addCommonOptions(storeWriteCmd);
1013
+ // store rm
1014
+ const storeRmCmd = storeCmd
1015
+ .command('rm <path>')
1016
+ .description('Delete a file')
1017
+ .action(async (path, options) => {
1018
+ this.args = { command: 'store', subcommand: 'rm', path, ...options };
1019
+ this.uri = options.uri;
1020
+ try {
1021
+ const client = await this.createAndConnectClient();
1022
+ await client.fsDelete(path);
1023
+ console.log(`Deleted: ${path}`);
1024
+ process.exit(0);
1025
+ }
1026
+ finally {
1027
+ this.cancel();
1028
+ await this.cleanupClient();
1029
+ }
1030
+ });
1031
+ addCommonOptions(storeRmCmd);
1032
+ // store mkdir
1033
+ const storeMkdirCmd = storeCmd
1034
+ .command('mkdir <path>')
1035
+ .description('Create a directory')
1036
+ .action(async (path, options) => {
1037
+ this.args = { command: 'store', subcommand: 'mkdir', path, ...options };
1038
+ this.uri = options.uri;
1039
+ try {
1040
+ const client = await this.createAndConnectClient();
1041
+ await client.fsMkdir(path);
1042
+ console.log(`Created: ${path}/`);
1043
+ process.exit(0);
1044
+ }
1045
+ finally {
1046
+ this.cancel();
1047
+ await this.cleanupClient();
1048
+ }
1049
+ });
1050
+ addCommonOptions(storeMkdirCmd);
1051
+ // store stat
1052
+ const storeStatCmd = storeCmd
1053
+ .command('stat <path>')
1054
+ .description('Get file/directory metadata')
1055
+ .action(async (path, options) => {
1056
+ this.args = { command: 'store', subcommand: 'stat', path, ...options };
1057
+ this.uri = options.uri;
1058
+ try {
1059
+ const client = await this.createAndConnectClient();
1060
+ const result = await client.fsStat(path);
1061
+ if (!result.exists) {
1062
+ console.log(`${path}: not found`);
1063
+ }
1064
+ else {
1065
+ const details = [];
1066
+ if (result.size !== undefined)
1067
+ details.push(`size: ${result.size.toLocaleString()}`);
1068
+ if (result.modified)
1069
+ details.push(`modified: ${new Date(result.modified * 1000).toISOString()}`);
1070
+ console.log(`${path}: ${result.type}${details.length ? ` (${details.join(', ')})` : ''}`);
1071
+ }
1072
+ process.exit(0);
1073
+ }
1074
+ finally {
1075
+ this.cancel();
1076
+ await this.cleanupClient();
1077
+ }
1078
+ });
1079
+ addCommonOptions(storeStatCmd);
852
1080
  return program;
853
1081
  }
854
1082
  async handleEvent(message) {
@@ -862,7 +1090,7 @@ class RocketRideCLI {
862
1090
  auth: this.args.apikey,
863
1091
  onEvent: this.handleEvent.bind(this),
864
1092
  onConnected,
865
- onDisconnected
1093
+ onDisconnected,
866
1094
  });
867
1095
  await this.client.connect();
868
1096
  return this.client;
@@ -874,7 +1102,7 @@ class RocketRideCLI {
874
1102
  const arguments_ = { subscribe };
875
1103
  const monitorRequest = this.client.buildRequest('rrext_monitor', {
876
1104
  token,
877
- arguments: arguments_
1105
+ arguments: arguments_,
878
1106
  });
879
1107
  const monitorResponse = await this.client.request(monitorRequest);
880
1108
  return !this.client.didFail(monitorResponse);
@@ -884,17 +1112,17 @@ class RocketRideCLI {
884
1112
  }
885
1113
  }
886
1114
  async cleanupClient() {
887
- if (this.client) {
888
- try {
889
- await this.client.disconnect();
890
- }
891
- catch {
892
- // Ignore cleanup errors
893
- }
894
- finally {
895
- this.client = undefined;
896
- }
1115
+ if (this._cleanupPromise) {
1116
+ return this._cleanupPromise;
1117
+ }
1118
+ const client = this.client;
1119
+ if (!client) {
1120
+ return;
897
1121
  }
1122
+ this.client = undefined;
1123
+ this._cleanupPromise = client.disconnect().catch(() => { });
1124
+ await this._cleanupPromise;
1125
+ this._cleanupPromise = undefined;
898
1126
  }
899
1127
  loadPipelineConfig(pipelineFile) {
900
1128
  if (!fs.existsSync(pipelineFile) || !fs.statSync(pipelineFile).isFile()) {
@@ -933,15 +1161,9 @@ class RocketRideCLI {
933
1161
  pipeline: pipelineData,
934
1162
  threads: this.args.threads,
935
1163
  token: this.args.token,
936
- args: this.args.pipeline_args || []
1164
+ args: this.args.pipeline_args || [],
937
1165
  });
938
- const executionLines = [
939
- 'Pipeline execution started successfully',
940
- `Task token: ${taskToken}`,
941
- '',
942
- 'Use the following command to monitor status:',
943
- `rocketride status --token ${taskToken} --apikey ${this.args.apikey}`
944
- ];
1166
+ const executionLines = ['Pipeline execution started successfully', `Task token: ${taskToken}`, '', 'Use the following command to monitor status:', `rocketride status --token ${taskToken} --apikey ${this.args.apikey}`];
945
1167
  this.monitor.setCommandStatus(executionLines);
946
1168
  this.monitor.draw();
947
1169
  return 0;
@@ -973,7 +1195,7 @@ class RocketRideCLI {
973
1195
  total_bytes: 0,
974
1196
  successful_uploads: 0,
975
1197
  failed_uploads: 0,
976
- upload_times: []
1198
+ upload_times: [],
977
1199
  };
978
1200
  let pipelineConfig;
979
1201
  let taskToken;
@@ -1022,7 +1244,7 @@ class RocketRideCLI {
1022
1244
  this.monitor.addBox('File Validation Errors', validationErrorLines);
1023
1245
  this.monitor.draw();
1024
1246
  // Wait briefly to show errors
1025
- await new Promise(resolve => setTimeout(resolve, 3000));
1247
+ await new Promise((resolve) => setTimeout(resolve, 3000));
1026
1248
  }
1027
1249
  if (validFiles.length === 0) {
1028
1250
  this.monitor.setCommandStatus('File validation failed');
@@ -1041,7 +1263,7 @@ class RocketRideCLI {
1041
1263
  pipeline: pipelineConfig,
1042
1264
  threads: this.args.threads,
1043
1265
  token: 'UPLOAD_TASK',
1044
- args: this.args.pipeline_args || []
1266
+ args: this.args.pipeline_args || [],
1045
1267
  });
1046
1268
  taskToken = result.token;
1047
1269
  }
@@ -1050,18 +1272,18 @@ class RocketRideCLI {
1050
1272
  this.monitor.draw();
1051
1273
  const startTime = Date.now();
1052
1274
  // Convert file paths to File objects for sendFiles
1053
- const fileObjects = validFiles.map(filePath => {
1275
+ const fileObjects = validFiles.map((filePath) => {
1054
1276
  const stats = fs.statSync(filePath);
1055
1277
  const content = fs.readFileSync(filePath);
1056
1278
  return {
1057
1279
  file: new File([content], path.basename(filePath), {
1058
1280
  type: 'application/octet-stream',
1059
- lastModified: stats.mtimeMs
1281
+ lastModified: stats.mtimeMs,
1060
1282
  }),
1061
1283
  objinfo: {
1062
1284
  filepath: filePath,
1063
- size: stats.size
1064
- }
1285
+ size: stats.size,
1286
+ },
1065
1287
  };
1066
1288
  });
1067
1289
  // Upload files - progress events come through event subscription
@@ -1073,11 +1295,7 @@ class RocketRideCLI {
1073
1295
  // Cleanup pipeline if we created it
1074
1296
  if (shouldManagePipeline && taskToken) {
1075
1297
  try {
1076
- this.monitor.setCommandStatus([
1077
- 'Upload completed successfully',
1078
- 'Cleaning up...',
1079
- 'Terminating pipeline...'
1080
- ]);
1298
+ this.monitor.setCommandStatus(['Upload completed successfully', 'Cleaning up...', 'Terminating pipeline...']);
1081
1299
  this.monitor.draw();
1082
1300
  await this.client.terminate(taskToken);
1083
1301
  // Re-show results after cleanup
@@ -1121,13 +1339,13 @@ class RocketRideCLI {
1121
1339
  }
1122
1340
  else if (stat.isDirectory()) {
1123
1341
  const dirFiles = glob.sync(path.join(fullPath, '**/*'), { nodir: true });
1124
- files.push(...dirFiles.map(f => path.resolve(f)));
1342
+ files.push(...dirFiles.map((f) => path.resolve(f)));
1125
1343
  }
1126
1344
  }
1127
1345
  catch {
1128
1346
  // Try glob pattern
1129
1347
  const matches = glob.sync(pattern, { nodir: true });
1130
- files.push(...matches.map(f => path.resolve(f)));
1348
+ files.push(...matches.map((f) => path.resolve(f)));
1131
1349
  }
1132
1350
  }
1133
1351
  // Remove duplicates
@@ -1169,13 +1387,13 @@ class RocketRideCLI {
1169
1387
  successfulFiles.push({
1170
1388
  name: filename,
1171
1389
  size: result.file_size || 0,
1172
- time: result.upload_time || 0
1390
+ time: result.upload_time || 0,
1173
1391
  });
1174
1392
  }
1175
1393
  else {
1176
1394
  failedFiles.push({
1177
1395
  name: filename,
1178
- error: result.error || 'Unknown error'
1396
+ error: result.error || 'Unknown error',
1179
1397
  });
1180
1398
  this.uploadStats.failed_uploads++;
1181
1399
  }
@@ -1185,10 +1403,7 @@ class RocketRideCLI {
1185
1403
  const successful = this.uploadStats.successful_uploads;
1186
1404
  const failed = this.uploadStats.failed_uploads;
1187
1405
  const totalBytes = this.uploadStats.total_bytes;
1188
- const summaryLines = [
1189
- `Total files processed: ${successful + failed}`,
1190
- `Successful uploads: ${ANSI_GREEN}${successful}${ANSI_RESET}`
1191
- ];
1406
+ const summaryLines = [`Total files processed: ${successful + failed}`, `Successful uploads: ${ANSI_GREEN}${successful}${ANSI_RESET}`];
1192
1407
  if (failed > 0) {
1193
1408
  summaryLines.push(`Failed uploads: ${ANSI_RED}${failed}${ANSI_RESET}`);
1194
1409
  }
@@ -1222,10 +1437,8 @@ class RocketRideCLI {
1222
1437
  if (failedFiles.length > 0) {
1223
1438
  const failureLines = [];
1224
1439
  for (const failedFile of failedFiles.slice(0, 10)) {
1225
- const filename = failedFile.name.length > 25 ?
1226
- `${failedFile.name.substring(0, 25)}...` : failedFile.name;
1227
- const errorMsg = failedFile.error.length > 40 ?
1228
- `${failedFile.error.substring(0, 40)}...` : failedFile.error;
1440
+ const filename = failedFile.name.length > 25 ? `${failedFile.name.substring(0, 25)}...` : failedFile.name;
1441
+ const errorMsg = failedFile.error.length > 40 ? `${failedFile.error.substring(0, 40)}...` : failedFile.error;
1229
1442
  failureLines.push(`${ANSI_RED}${CHR_CROSS}${ANSI_RESET} ${filename} - ${errorMsg}`);
1230
1443
  }
1231
1444
  if (failedFiles.length > 10) {
@@ -1237,8 +1450,7 @@ class RocketRideCLI {
1237
1450
  if (successfulFiles.length > 0) {
1238
1451
  const successLines = [];
1239
1452
  for (const successFile of successfulFiles.slice(-5)) {
1240
- const truncatedName = successFile.name.length > 35 ?
1241
- `${successFile.name.substring(0, 35)}...` : successFile.name;
1453
+ const truncatedName = successFile.name.length > 35 ? `${successFile.name.substring(0, 35)}...` : successFile.name;
1242
1454
  const sizeStr = this.monitor.formatSize(successFile.size);
1243
1455
  const timeStr = `${successFile.time.toFixed(1)}s`;
1244
1456
  successLines.push(`${ANSI_GREEN}${CHR_CHECK}${ANSI_RESET} ${truncatedName} (${sizeStr}, ${timeStr})`);
@@ -1276,11 +1488,11 @@ class RocketRideCLI {
1276
1488
  }
1277
1489
  catch {
1278
1490
  this.attempt++;
1279
- await new Promise(resolve => setTimeout(resolve, 5000));
1491
+ await new Promise((resolve) => setTimeout(resolve, 5000));
1280
1492
  continue;
1281
1493
  }
1282
1494
  }
1283
- await new Promise(resolve => setTimeout(resolve, 1000));
1495
+ await new Promise((resolve) => setTimeout(resolve, 1000));
1284
1496
  }
1285
1497
  return 0;
1286
1498
  }
@@ -1313,11 +1525,7 @@ class RocketRideCLI {
1313
1525
  this.monitor.setCommandStatus(`Terminating task: ${this.args.token}`);
1314
1526
  this.monitor.draw();
1315
1527
  await this.client.terminate(this.args.token);
1316
- const stopLines = [
1317
- `Task ${this.args.token} terminated successfully`,
1318
- '',
1319
- 'The task has been stopped and resources cleaned up.'
1320
- ];
1528
+ const stopLines = [`Task ${this.args.token} terminated successfully`, '', 'The task has been stopped and resources cleaned up.'];
1321
1529
  this.monitor.setCommandStatus(stopLines);
1322
1530
  this.monitor.draw();
1323
1531
  return 0;
@@ -1340,9 +1548,16 @@ class RocketRideCLI {
1340
1548
  // Parse command line arguments - commander will handle command routing
1341
1549
  try {
1342
1550
  await program.parseAsync(process.argv);
1551
+ if (this.isShuttingDown()) {
1552
+ // Signal handler owns the exit; park until it calls process.exit.
1553
+ await this.awaitShutdown();
1554
+ }
1343
1555
  return 0; // If we get here, a command was executed successfully
1344
1556
  }
1345
1557
  catch (error) {
1558
+ if (this.isShuttingDown()) {
1559
+ await this.awaitShutdown();
1560
+ }
1346
1561
  if (error instanceof Error && error.message.includes('interrupted')) {
1347
1562
  console.log('\nOperation interrupted by user');
1348
1563
  return 1;
@@ -1369,12 +1584,19 @@ function formatError(e) {
1369
1584
  return `${e.constructor.name}: ${e.message}`;
1370
1585
  }
1371
1586
  async function main() {
1587
+ const cli = new RocketRideCLI();
1372
1588
  try {
1373
- const cli = new RocketRideCLI();
1374
1589
  const exitCode = await cli.run();
1590
+ if (cli.isShuttingDown()) {
1591
+ // Signal handler owns the exit code; never race it to process.exit.
1592
+ await cli.awaitShutdown();
1593
+ }
1375
1594
  process.exit(exitCode);
1376
1595
  }
1377
1596
  catch (error) {
1597
+ if (cli.isShuttingDown()) {
1598
+ await cli.awaitShutdown();
1599
+ }
1378
1600
  if (error instanceof Error && error.message.includes('interrupted')) {
1379
1601
  console.log('\n\nOperation interrupted by user');
1380
1602
  }
@@ -1386,7 +1608,7 @@ async function main() {
1386
1608
  }
1387
1609
  // Entry point when script is run directly
1388
1610
  if (require.main === module) {
1389
- main().catch(error => {
1611
+ main().catch((error) => {
1390
1612
  console.error('Fatal error:', error);
1391
1613
  process.exit(1);
1392
1614
  });