@rudderhq/cli 0.1.0-canary.17 → 0.1.0-canary.19

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/index.js CHANGED
@@ -10033,12 +10033,119 @@ import { constants as fsConstants, createWriteStream, mkdirSync as mkdirSync2, r
10033
10033
  import { access, chmod, copyFile, cp, mkdtemp, mkdir, readFile as readFile3, readdir as readdir2, rm, writeFile as writeFile2 } from "node:fs/promises";
10034
10034
  import { homedir, tmpdir } from "node:os";
10035
10035
  import path10 from "node:path";
10036
- import { Readable } from "node:stream";
10036
+ import { Readable, Transform } from "node:stream";
10037
10037
  import { pipeline } from "node:stream/promises";
10038
10038
  import { setTimeout as delay } from "node:timers/promises";
10039
10039
  import * as p13 from "@clack/prompts";
10040
10040
  import pc8 from "picocolors";
10041
10041
 
10042
+ // src/utils/progress.ts
10043
+ var BYTE_UNITS = ["B", "KB", "MB", "GB", "TB"];
10044
+ function formatBytes(bytes) {
10045
+ let value = Math.max(0, bytes);
10046
+ let unitIndex = 0;
10047
+ while (value >= 1024 && unitIndex < BYTE_UNITS.length - 1) {
10048
+ value /= 1024;
10049
+ unitIndex += 1;
10050
+ }
10051
+ if (unitIndex === 0) return `${Math.round(value)} ${BYTE_UNITS[unitIndex]}`;
10052
+ return `${value.toFixed(1)} ${BYTE_UNITS[unitIndex]}`;
10053
+ }
10054
+ function normalizeTotalBytes(totalBytes) {
10055
+ if (typeof totalBytes !== "number" || !Number.isFinite(totalBytes) || totalBytes <= 0) {
10056
+ return null;
10057
+ }
10058
+ return totalBytes;
10059
+ }
10060
+ function formatByteProgress(state) {
10061
+ const width = Math.max(4, state.width ?? 20);
10062
+ const receivedBytes = Math.max(0, state.receivedBytes);
10063
+ const totalBytes = normalizeTotalBytes(state.totalBytes);
10064
+ if (totalBytes === null) {
10065
+ return `[downloaded ${formatBytes(receivedBytes)}]`;
10066
+ }
10067
+ const ratio = Math.max(0, Math.min(1, receivedBytes / totalBytes));
10068
+ const filled = Math.round(ratio * width);
10069
+ const percent = Math.floor(ratio * 100);
10070
+ return `[${"#".repeat(filled)}${"-".repeat(width - filled)}] ${percent}% ${formatBytes(receivedBytes)}/${formatBytes(totalBytes)}`;
10071
+ }
10072
+ function progressSummary(receivedBytes, totalBytes) {
10073
+ const total = normalizeTotalBytes(totalBytes);
10074
+ if (total === null) return formatBytes(receivedBytes);
10075
+ return `${formatBytes(receivedBytes)}/${formatBytes(total)}`;
10076
+ }
10077
+ function createByteProgress(label, options = {}) {
10078
+ const stream = options.stream ?? process.stdout;
10079
+ const isTty = options.isTty ?? Boolean(stream.isTTY);
10080
+ const width = options.width ?? 20;
10081
+ const minIntervalMs = options.minIntervalMs ?? 80;
10082
+ const now = options.now ?? (() => Date.now());
10083
+ let started = false;
10084
+ let finished = false;
10085
+ let lastRenderAt = 0;
10086
+ let lastLineLength = 0;
10087
+ let latestReceivedBytes = 0;
10088
+ let latestTotalBytes = null;
10089
+ function render(receivedBytes, totalBytes, force = false) {
10090
+ if (finished) return;
10091
+ latestReceivedBytes = receivedBytes;
10092
+ latestTotalBytes = totalBytes;
10093
+ if (!isTty) return;
10094
+ const currentTime = now();
10095
+ const total = normalizeTotalBytes(totalBytes);
10096
+ const complete = total !== null && receivedBytes >= total;
10097
+ if (!force && currentTime - lastRenderAt < minIntervalMs && !complete) return;
10098
+ const line = `${label} ${formatByteProgress({ receivedBytes, totalBytes, width })}`;
10099
+ const padding = lastLineLength > line.length ? " ".repeat(lastLineLength - line.length) : "";
10100
+ stream.write(`\r${line}${padding}`);
10101
+ lastLineLength = line.length;
10102
+ lastRenderAt = currentTime;
10103
+ }
10104
+ function start(totalBytes) {
10105
+ if (started || finished) return;
10106
+ started = true;
10107
+ latestTotalBytes = totalBytes;
10108
+ if (isTty) {
10109
+ render(0, totalBytes, true);
10110
+ } else {
10111
+ stream.write(`${label}...
10112
+ `);
10113
+ }
10114
+ }
10115
+ function update(receivedBytes, totalBytes) {
10116
+ if (!started) start(totalBytes);
10117
+ render(receivedBytes, totalBytes, false);
10118
+ }
10119
+ function finish(receivedBytes = latestReceivedBytes, totalBytes = latestTotalBytes) {
10120
+ if (finished) return;
10121
+ if (!started) start(totalBytes);
10122
+ if (isTty) {
10123
+ render(receivedBytes, totalBytes, true);
10124
+ stream.write("\n");
10125
+ } else {
10126
+ stream.write(`${label} complete (${progressSummary(receivedBytes, totalBytes)}).
10127
+ `);
10128
+ }
10129
+ finished = true;
10130
+ }
10131
+ function fail() {
10132
+ if (finished) return;
10133
+ if (isTty && started) {
10134
+ stream.write("\n");
10135
+ } else if (!isTty && started) {
10136
+ stream.write(`${label} failed.
10137
+ `);
10138
+ }
10139
+ finished = true;
10140
+ }
10141
+ return {
10142
+ start,
10143
+ update,
10144
+ finish,
10145
+ fail
10146
+ };
10147
+ }
10148
+
10042
10149
  // src/version.ts
10043
10150
  import { existsSync as existsSync2, readFileSync } from "node:fs";
10044
10151
  import path9 from "node:path";
@@ -10263,7 +10370,13 @@ function buildGithubReleaseAsset(repo, tag, assetName) {
10263
10370
  browser_download_url: buildGithubReleaseAssetDownloadUrl(repo, tag, assetName)
10264
10371
  };
10265
10372
  }
10266
- async function downloadAsset(asset, outputDir) {
10373
+ function contentLengthFromHeaders(headers) {
10374
+ const raw = headers.get("content-length");
10375
+ if (!raw) return null;
10376
+ const value = Number(raw);
10377
+ return Number.isFinite(value) && value > 0 ? value : null;
10378
+ }
10379
+ async function downloadAsset(asset, outputDir, progressFactory = createByteProgress) {
10267
10380
  mkdirSync2(outputDir, { recursive: true });
10268
10381
  const outputPath = path10.join(outputDir, path10.basename(asset.name));
10269
10382
  const response = await fetch(asset.browser_download_url, {
@@ -10272,7 +10385,24 @@ async function downloadAsset(asset, outputDir) {
10272
10385
  if (!response.ok || !response.body) {
10273
10386
  throw new Error(`Failed to download ${asset.name} from ${asset.browser_download_url} (${response.status}).`);
10274
10387
  }
10275
- await pipeline(Readable.fromWeb(response.body), createWriteStream(outputPath));
10388
+ const totalBytes = contentLengthFromHeaders(response.headers);
10389
+ const progress = progressFactory(`Downloading ${asset.name}`);
10390
+ let receivedBytes = 0;
10391
+ const monitor = new Transform({
10392
+ transform(chunk, _encoding, callback) {
10393
+ receivedBytes += typeof chunk === "string" ? Buffer.byteLength(chunk) : chunk.length;
10394
+ progress.update(receivedBytes, totalBytes);
10395
+ callback(null, chunk);
10396
+ }
10397
+ });
10398
+ progress.start(totalBytes);
10399
+ try {
10400
+ await pipeline(Readable.fromWeb(response.body), monitor, createWriteStream(outputPath));
10401
+ progress.finish(receivedBytes, totalBytes);
10402
+ } catch (error) {
10403
+ progress.fail();
10404
+ throw error;
10405
+ }
10276
10406
  return outputPath;
10277
10407
  }
10278
10408
  function checksumForFile(filePath) {
@@ -10303,11 +10433,11 @@ function assertChecksumMatch(filePath, expected) {
10303
10433
  }
10304
10434
  return actual;
10305
10435
  }
10306
- async function downloadChecksums(checksumAsset, outputDir) {
10436
+ async function downloadChecksums(checksumAsset, outputDir, progressFactory = createByteProgress) {
10307
10437
  if (!checksumAsset) {
10308
10438
  throw new Error("Desktop release is missing SHASUMS256.txt.");
10309
10439
  }
10310
- const checksumPath = await downloadAsset(checksumAsset, outputDir);
10440
+ const checksumPath = await downloadAsset(checksumAsset, outputDir, progressFactory);
10311
10441
  return parseChecksumFile(readFileSync2(checksumPath, "utf8"));
10312
10442
  }
10313
10443
  async function pathExists(targetPath) {
@@ -10571,6 +10701,18 @@ async function writeInstallMetadata(paths, releaseTag, assetName, assetChecksum)
10571
10701
  await writeFile2(paths.metadataPath, `${JSON.stringify(metadata, null, 2)}
10572
10702
  `, "utf8");
10573
10703
  }
10704
+ async function runStartPhase(message, successMessage, task) {
10705
+ const spinner5 = p13.spinner();
10706
+ spinner5.start(message);
10707
+ try {
10708
+ const result = await task();
10709
+ spinner5.stop(successMessage);
10710
+ return result;
10711
+ } catch (error) {
10712
+ spinner5.stop(pc8.red(`${message} failed.`));
10713
+ throw error;
10714
+ }
10715
+ }
10574
10716
  async function startCommand(opts) {
10575
10717
  const installCli = opts.cli !== false;
10576
10718
  const installDesktop = opts.desktop !== false;
@@ -10596,12 +10738,21 @@ async function startCommand(opts) {
10596
10738
  p13.log.message(`[dry-run] ${command}`);
10597
10739
  } else {
10598
10740
  p13.log.message(pc8.dim(`Running: ${command}`));
10599
- const result = installPersistentCli({ installSpec });
10741
+ const spinner5 = p13.spinner();
10742
+ spinner5.start("Installing persistent CLI...");
10743
+ let result;
10744
+ try {
10745
+ result = installPersistentCli({ installSpec });
10746
+ } catch (error) {
10747
+ spinner5.stop(pc8.red("Persistent CLI installation failed."));
10748
+ throw error;
10749
+ }
10600
10750
  if (!result.ok) {
10751
+ spinner5.stop(pc8.red("Persistent CLI installation failed."));
10601
10752
  if (result.output) p13.log.message(pc8.dim(result.output));
10602
10753
  throw new Error(`Persistent CLI installation failed. Re-run manually: ${result.command}`);
10603
10754
  }
10604
- p13.log.success(`${pc8.cyan("rudder")} CLI installed.`);
10755
+ spinner5.stop(`${pc8.cyan("rudder")} CLI installed.`);
10605
10756
  }
10606
10757
  }
10607
10758
  if (installDesktop) {
@@ -10620,7 +10771,12 @@ async function startCommand(opts) {
10620
10771
  return;
10621
10772
  }
10622
10773
  const directReleaseVersion = resolveDesktopReleaseVersion(tag);
10623
- const release = directReleaseVersion ? null : await fetchGithubRelease(repo, tag);
10774
+ const progressFactory = createByteProgress;
10775
+ const release = directReleaseVersion ? null : await runStartPhase(
10776
+ "Resolving Desktop release...",
10777
+ "Desktop release resolved.",
10778
+ () => fetchGithubRelease(repo, tag)
10779
+ );
10624
10780
  const releaseTag = directReleaseVersion ? tag : release?.tag_name;
10625
10781
  if (!releaseTag) {
10626
10782
  throw new Error(`Unable to resolve Rudder Desktop release tag for ${repo}@${tag}.`);
@@ -10630,28 +10786,52 @@ async function startCommand(opts) {
10630
10786
  throw new Error(`No Rudder Desktop portable asset found for ${target.platform}/${target.arch} in ${repo}@${releaseTag}.`);
10631
10787
  }
10632
10788
  const checksumAsset = directReleaseVersion ? buildGithubReleaseAsset(repo, tag, DESKTOP_CHECKSUM_ASSET_NAME) : selectChecksumAsset(release?.assets ?? []);
10633
- const checksums = await downloadChecksums(checksumAsset, outputDir);
10789
+ const checksums = await downloadChecksums(checksumAsset, outputDir, progressFactory);
10634
10790
  const expectedChecksum = resolveAssetChecksum(checksums, asset.name);
10635
10791
  const metadata = await readInstallMetadata(installPaths.metadataPath);
10636
10792
  if (isInstalledDesktopCurrent(metadata, releaseTag, asset.name, expectedChecksum) && await pathExists(installPaths.executablePath)) {
10637
10793
  p13.log.success(`Rudder Desktop is already installed at ${pc8.cyan(installPaths.appPath)}.`);
10638
- await removeMacQuarantine(installPaths, target);
10639
- await createPlatformLaunchers(installPaths, target);
10794
+ await runStartPhase(
10795
+ "Refreshing Desktop launchers...",
10796
+ "Desktop launchers ready.",
10797
+ async () => {
10798
+ await removeMacQuarantine(installPaths, target);
10799
+ await createPlatformLaunchers(installPaths, target);
10800
+ }
10801
+ );
10640
10802
  } else {
10641
- const installerPath = await downloadAsset(asset, outputDir);
10642
- const checksum = assertChecksumMatch(installerPath, expectedChecksum);
10643
- p13.log.success(`Downloaded and verified ${pc8.cyan(path10.basename(installerPath))}`);
10644
- p13.log.message("Replacing existing Rudder Desktop if needed.");
10645
- await prepareForDesktopReplace(installPaths, target);
10646
- await installPortableDesktop(installerPath, installPaths, target);
10647
- await removeMacQuarantine(installPaths, target);
10648
- await createPlatformLaunchers(installPaths, target);
10803
+ const installerPath = await downloadAsset(asset, outputDir, progressFactory);
10804
+ const checksum = await runStartPhase(
10805
+ "Verifying Desktop checksum...",
10806
+ `Verified ${pc8.cyan(path10.basename(installerPath))}.`,
10807
+ () => assertChecksumMatch(installerPath, expectedChecksum)
10808
+ );
10809
+ await runStartPhase(
10810
+ "Replacing existing Rudder Desktop if needed...",
10811
+ "Existing Desktop install is ready for replacement.",
10812
+ () => prepareForDesktopReplace(installPaths, target)
10813
+ );
10814
+ await runStartPhase(
10815
+ "Installing portable Desktop app...",
10816
+ `Installed Rudder Desktop to ${pc8.cyan(installPaths.appPath)}.`,
10817
+ () => installPortableDesktop(installerPath, installPaths, target)
10818
+ );
10819
+ await runStartPhase(
10820
+ "Preparing Desktop launchers...",
10821
+ "Desktop launchers ready.",
10822
+ async () => {
10823
+ await removeMacQuarantine(installPaths, target);
10824
+ await createPlatformLaunchers(installPaths, target);
10825
+ }
10826
+ );
10649
10827
  await writeInstallMetadata(installPaths, releaseTag, asset.name, checksum);
10650
- p13.log.success(`Installed Rudder Desktop to ${pc8.cyan(installPaths.appPath)}.`);
10651
10828
  }
10652
10829
  if (opts.open !== false) {
10653
- launchDesktop(installPaths, target);
10654
- p13.log.success("Rudder Desktop launched.");
10830
+ await runStartPhase(
10831
+ "Launching Rudder Desktop...",
10832
+ "Rudder Desktop launched.",
10833
+ () => launchDesktop(installPaths, target)
10834
+ );
10655
10835
  }
10656
10836
  }
10657
10837
  p13.outro(pc8.green("Rudder start complete."));
@@ -12657,8 +12837,8 @@ async function dbBackupCommand(opts) {
12657
12837
  p15.log.message(pc20.dim(`Connection source: ${connection.source}`));
12658
12838
  p15.log.message(pc20.dim(`Backup dir: ${backupDir}`));
12659
12839
  p15.log.message(pc20.dim(`Retention: ${retentionDays} day(s)`));
12660
- const spinner4 = p15.spinner();
12661
- spinner4.start("Creating database backup...");
12840
+ const spinner5 = p15.spinner();
12841
+ spinner5.start("Creating database backup...");
12662
12842
  try {
12663
12843
  const result = await runDatabaseBackup({
12664
12844
  connectionString: connection.value,
@@ -12666,7 +12846,7 @@ async function dbBackupCommand(opts) {
12666
12846
  retentionDays,
12667
12847
  filenamePrefix
12668
12848
  });
12669
- spinner4.stop(`Backup saved: ${formatDatabaseBackupResult(result)}`);
12849
+ spinner5.stop(`Backup saved: ${formatDatabaseBackupResult(result)}`);
12670
12850
  if (opts.json) {
12671
12851
  console.log(
12672
12852
  JSON.stringify(
@@ -12685,7 +12865,7 @@ async function dbBackupCommand(opts) {
12685
12865
  }
12686
12866
  p15.outro(pc20.green("Backup completed."));
12687
12867
  } catch (err) {
12688
- spinner4.stop(pc20.red("Backup failed."));
12868
+ spinner5.stop(pc20.red("Backup failed."));
12689
12869
  throw err;
12690
12870
  }
12691
12871
  }
@@ -16925,8 +17105,8 @@ async function runWorktreeInit(opts) {
16925
17105
  `Cannot seed worktree database because source config was not found at ${sourceConfigPath}. Use --no-seed or provide --from-config.`
16926
17106
  );
16927
17107
  }
16928
- const spinner4 = p17.spinner();
16929
- spinner4.start(`Seeding isolated worktree database from source instance (${seedMode})...`);
17108
+ const spinner5 = p17.spinner();
17109
+ spinner5.start(`Seeding isolated worktree database from source instance (${seedMode})...`);
16930
17110
  try {
16931
17111
  const seeded = await seedWorktreeDatabase({
16932
17112
  sourceConfigPath,
@@ -16938,9 +17118,9 @@ async function runWorktreeInit(opts) {
16938
17118
  });
16939
17119
  seedSummary = seeded.backupSummary;
16940
17120
  reboundWorkspaceSummary = seeded.reboundWorkspaces;
16941
- spinner4.stop(`Seeded isolated worktree database (${seedMode}).`);
17121
+ spinner5.stop(`Seeded isolated worktree database (${seedMode}).`);
16942
17122
  } catch (error) {
16943
- spinner4.stop(pc23.red("Failed to seed worktree database."));
17123
+ spinner5.stop(pc23.red("Failed to seed worktree database."));
16944
17124
  throw error;
16945
17125
  }
16946
17126
  }
@@ -17006,16 +17186,16 @@ async function worktreeMakeCommand(nameArg, opts) {
17006
17186
  branchExists: !startPoint && localBranchExists(sourceCwd, name),
17007
17187
  startPoint
17008
17188
  });
17009
- const spinner4 = p17.spinner();
17010
- spinner4.start(`Creating git worktree at ${targetPath}...`);
17189
+ const spinner5 = p17.spinner();
17190
+ spinner5.start(`Creating git worktree at ${targetPath}...`);
17011
17191
  try {
17012
17192
  execFileSync2("git", worktreeArgs, {
17013
17193
  cwd: sourceCwd,
17014
17194
  stdio: ["ignore", "pipe", "pipe"]
17015
17195
  });
17016
- spinner4.stop(`Created git worktree at ${targetPath}.`);
17196
+ spinner5.stop(`Created git worktree at ${targetPath}.`);
17017
17197
  } catch (error) {
17018
- spinner4.stop(pc23.red("Failed to create git worktree."));
17198
+ spinner5.stop(pc23.red("Failed to create git worktree."));
17019
17199
  throw new Error(extractExecSyncErrorMessage(error) ?? String(error));
17020
17200
  }
17021
17201
  const installSpinner = p17.spinner();
@@ -17183,9 +17363,9 @@ async function worktreeCleanupCommand(nameArg, opts) {
17183
17363
  }
17184
17364
  if (linkedWorktree) {
17185
17365
  const worktreeDirExists = existsSync3(linkedWorktree.worktree);
17186
- const spinner4 = p17.spinner();
17366
+ const spinner5 = p17.spinner();
17187
17367
  if (worktreeDirExists) {
17188
- spinner4.start(`Removing git worktree at ${linkedWorktree.worktree}...`);
17368
+ spinner5.start(`Removing git worktree at ${linkedWorktree.worktree}...`);
17189
17369
  try {
17190
17370
  const removeArgs = ["worktree", "remove", linkedWorktree.worktree];
17191
17371
  if (opts.force) removeArgs.push("--force");
@@ -17193,18 +17373,18 @@ async function worktreeCleanupCommand(nameArg, opts) {
17193
17373
  cwd: sourceCwd,
17194
17374
  stdio: ["ignore", "pipe", "pipe"]
17195
17375
  });
17196
- spinner4.stop(`Removed git worktree at ${linkedWorktree.worktree}.`);
17376
+ spinner5.stop(`Removed git worktree at ${linkedWorktree.worktree}.`);
17197
17377
  } catch (error) {
17198
- spinner4.stop(pc23.yellow(`Could not remove worktree cleanly, will prune instead.`));
17378
+ spinner5.stop(pc23.yellow(`Could not remove worktree cleanly, will prune instead.`));
17199
17379
  p17.log.warning(extractExecSyncErrorMessage(error) ?? String(error));
17200
17380
  }
17201
17381
  } else {
17202
- spinner4.start("Pruning stale worktree entry...");
17382
+ spinner5.start("Pruning stale worktree entry...");
17203
17383
  execFileSync2("git", ["worktree", "prune"], {
17204
17384
  cwd: sourceCwd,
17205
17385
  stdio: ["ignore", "pipe", "pipe"]
17206
17386
  });
17207
- spinner4.stop("Pruned stale worktree entry.");
17387
+ spinner5.stop("Pruned stale worktree entry.");
17208
17388
  }
17209
17389
  } else {
17210
17390
  execFileSync2("git", ["worktree", "prune"], {
@@ -17213,31 +17393,31 @@ async function worktreeCleanupCommand(nameArg, opts) {
17213
17393
  });
17214
17394
  }
17215
17395
  if (existsSync3(targetPath)) {
17216
- const spinner4 = p17.spinner();
17217
- spinner4.start(`Removing worktree directory ${targetPath}...`);
17396
+ const spinner5 = p17.spinner();
17397
+ spinner5.start(`Removing worktree directory ${targetPath}...`);
17218
17398
  rmSync(targetPath, { recursive: true, force: true });
17219
- spinner4.stop(`Removed worktree directory ${targetPath}.`);
17399
+ spinner5.stop(`Removed worktree directory ${targetPath}.`);
17220
17400
  }
17221
17401
  if (localBranchExists(sourceCwd, name)) {
17222
- const spinner4 = p17.spinner();
17223
- spinner4.start(`Deleting local branch "${name}"...`);
17402
+ const spinner5 = p17.spinner();
17403
+ spinner5.start(`Deleting local branch "${name}"...`);
17224
17404
  try {
17225
17405
  const deleteFlag = opts.force ? "-D" : "-d";
17226
17406
  execFileSync2("git", ["branch", deleteFlag, name], {
17227
17407
  cwd: sourceCwd,
17228
17408
  stdio: ["ignore", "pipe", "pipe"]
17229
17409
  });
17230
- spinner4.stop(`Deleted local branch "${name}".`);
17410
+ spinner5.stop(`Deleted local branch "${name}".`);
17231
17411
  } catch (error) {
17232
- spinner4.stop(pc23.yellow(`Could not delete branch "${name}".`));
17412
+ spinner5.stop(pc23.yellow(`Could not delete branch "${name}".`));
17233
17413
  p17.log.warning(extractExecSyncErrorMessage(error) ?? String(error));
17234
17414
  }
17235
17415
  }
17236
17416
  if (existsSync3(instanceRoot)) {
17237
- const spinner4 = p17.spinner();
17238
- spinner4.start(`Removing instance data at ${instanceRoot}...`);
17417
+ const spinner5 = p17.spinner();
17418
+ spinner5.start(`Removing instance data at ${instanceRoot}...`);
17239
17419
  rmSync(instanceRoot, { recursive: true, force: true });
17240
- spinner4.stop(`Removed instance data at ${instanceRoot}.`);
17420
+ spinner5.stop(`Removed instance data at ${instanceRoot}.`);
17241
17421
  }
17242
17422
  p17.outro(pc23.green("Cleanup complete."));
17243
17423
  }