@rudderhq/cli 0.2.8-canary.1 → 0.2.8-canary.3

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
@@ -6449,6 +6449,7 @@ import { homedir, tmpdir } from "node:os";
6449
6449
  import path11 from "node:path";
6450
6450
  import { Readable, Transform } from "node:stream";
6451
6451
  import { pipeline } from "node:stream/promises";
6452
+ import { clearTimeout as clearTimeout2, setTimeout as setTimeout2 } from "node:timers";
6452
6453
  import { setTimeout as delay } from "node:timers/promises";
6453
6454
  import * as p13 from "@clack/prompts";
6454
6455
  import pc8 from "picocolors";
@@ -6693,7 +6694,7 @@ function compareStableSemver(a, b) {
6693
6694
  }
6694
6695
  async function fetchLatestCliVersion() {
6695
6696
  const controller = new AbortController();
6696
- const timeout = setTimeout(() => controller.abort(), 2e3);
6697
+ const timeout = setTimeout2(() => controller.abort(), 2e3);
6697
6698
  try {
6698
6699
  const response = await fetch(CLI_REGISTRY_LATEST_URL, {
6699
6700
  signal: controller.signal,
@@ -6705,7 +6706,7 @@ async function fetchLatestCliVersion() {
6705
6706
  } catch {
6706
6707
  return null;
6707
6708
  } finally {
6708
- clearTimeout(timeout);
6709
+ clearTimeout2(timeout);
6709
6710
  }
6710
6711
  }
6711
6712
  async function getCliUpdateNotice(currentVersion) {
@@ -6723,6 +6724,9 @@ function resolveDesktopReleaseTag(version) {
6723
6724
  `Desktop release lookup requires a release version like 0.1.0 or 0.1.0-canary.0. Received ${version}.`
6724
6725
  );
6725
6726
  }
6727
+ function isExactRuntimePackageSpec(version, packageSpec) {
6728
+ return version !== "latest" && packageSpec === resolveRuntimePackageSpec(version);
6729
+ }
6726
6730
  function resolveDesktopAssetTarget(platform = process.platform, arch = process.arch) {
6727
6731
  if (platform === "darwin") {
6728
6732
  if (arch !== "x64" && arch !== "arm64") {
@@ -6782,6 +6786,7 @@ function scoreDesktopAsset(asset, target) {
6782
6786
  const expectedExtension = target.extension.toLowerCase();
6783
6787
  if (!normalized.endsWith(expectedExtension.toLowerCase())) return -1;
6784
6788
  if (normalized.includes("blockmap") || normalized.includes("shasum")) return -1;
6789
+ if (normalized.includes("shell")) return -1;
6785
6790
  let score = 1;
6786
6791
  if (normalized.includes("rudder")) score += 2;
6787
6792
  if (normalized.includes(target.platform)) score += 4;
@@ -6808,18 +6813,77 @@ function selectDesktopAsset(assets, target) {
6808
6813
  const exactArch = equallyGood.find((item) => normalizeAssetName(item.asset.name).includes(target.arch));
6809
6814
  return exactArch?.asset ?? best.asset;
6810
6815
  }
6816
+ function selectDesktopShellAsset(assets, target) {
6817
+ if (!resolveDesktopShellAssetName("", target)) return null;
6818
+ const scored = assets.map((asset) => {
6819
+ const normalized = normalizeAssetName(asset.name);
6820
+ if (!normalized.endsWith(".zip")) return { asset, score: -1 };
6821
+ if (!normalized.includes("shell")) return { asset, score: -1 };
6822
+ if (!normalized.includes("rudder")) return { asset, score: -1 };
6823
+ if (!normalized.includes(target.platform)) return { asset, score: -1 };
6824
+ let score = 1;
6825
+ if (target.arch === "arm64" && normalized.includes("arm64")) score += 4;
6826
+ if (target.arch === "x64" && (normalized.includes("x64") || normalized.includes("amd64"))) score += 4;
6827
+ if (target.platform === "macos" && target.arch === "x64" && normalized.includes("arm64")) score -= 10;
6828
+ if (target.arch === "arm64" && normalized.includes("x64")) score -= 10;
6829
+ return { asset, score };
6830
+ }).filter((item) => item.score >= 0).sort((a, b) => b.score - a.score || a.asset.name.localeCompare(b.asset.name));
6831
+ return scored[0]?.asset ?? null;
6832
+ }
6833
+ function resolveDesktopAssetCandidates(options) {
6834
+ const candidates = [];
6835
+ const deterministicShellName = options.directReleaseVersion ? resolveDesktopShellAssetName(options.directReleaseVersion, options.target) : null;
6836
+ if (options.allowShellAssets !== false) {
6837
+ const shellAsset = selectDesktopShellAsset(options.releaseAssets, options.target) ?? (options.releaseAssets.length === 0 && deterministicShellName ? buildGithubReleaseAsset(options.repo, options.tag, deterministicShellName) : null);
6838
+ if (shellAsset) candidates.push({ asset: shellAsset, kind: "shell" });
6839
+ }
6840
+ const fullAsset = selectDesktopAsset(options.releaseAssets, options.target) ?? (options.directReleaseVersion ? buildGithubReleaseAsset(options.repo, options.tag, resolveDesktopAssetName(options.directReleaseVersion, options.target)) : null);
6841
+ if (fullAsset) candidates.push({ asset: fullAsset, kind: "full" });
6842
+ return candidates;
6843
+ }
6844
+ function selectChecksummedDesktopAssetCandidate(candidates, checksums) {
6845
+ const warnings = [];
6846
+ for (const candidate of candidates) {
6847
+ try {
6848
+ return {
6849
+ ...candidate,
6850
+ expectedChecksum: resolveAssetChecksum(checksums, candidate.asset.name),
6851
+ warnings
6852
+ };
6853
+ } catch (error) {
6854
+ if (candidate.kind === "shell") {
6855
+ warnings.push(
6856
+ `Layered Desktop shell asset is missing from ${DESKTOP_CHECKSUM_ASSET_NAME}; falling back to the full portable asset.`
6857
+ );
6858
+ continue;
6859
+ }
6860
+ throw error;
6861
+ }
6862
+ }
6863
+ throw new Error("No checksummed Rudder Desktop asset candidate is available.");
6864
+ }
6811
6865
  function selectChecksumAsset(assets) {
6812
6866
  return assets.find((asset) => asset.name.toLowerCase() === DESKTOP_CHECKSUM_ASSET_NAME.toLowerCase()) ?? null;
6813
6867
  }
6868
+ async function fetchWithTimeout(url, init, timeoutMs) {
6869
+ const controller = new AbortController();
6870
+ const timeout = setTimeout2(() => controller.abort(), timeoutMs);
6871
+ try {
6872
+ return await fetch(url, { ...init, signal: controller.signal });
6873
+ } finally {
6874
+ clearTimeout2(timeout);
6875
+ }
6876
+ }
6814
6877
  function githubApiHeaders() {
6815
6878
  return {
6816
6879
  Accept: "application/vnd.github+json",
6817
6880
  "User-Agent": "rudder-cli-installer"
6818
6881
  };
6819
6882
  }
6883
+ var GITHUB_API_TIMEOUT_MS = 15e3;
6820
6884
  async function fetchGithubRelease(repo, tag) {
6821
6885
  const endpoint = tag === "latest" ? `https://api.github.com/repos/${repo}/releases/latest` : `https://api.github.com/repos/${repo}/releases/tags/${encodeURIComponent(tag)}`;
6822
- const response = await fetch(endpoint, { headers: githubApiHeaders() });
6886
+ const response = await fetchWithTimeout(endpoint, { headers: githubApiHeaders() }, GITHUB_API_TIMEOUT_MS);
6823
6887
  if (!response.ok) {
6824
6888
  throw new Error(`GitHub Release ${tag} was not found in ${repo} (${response.status}).`);
6825
6889
  }
@@ -6838,6 +6902,11 @@ function resolveDesktopAssetName(version, target) {
6838
6902
  if (target.platform === "windows") return `${DESKTOP_APP_NAME}-${version}-windows-x64-portable.zip`;
6839
6903
  return `${DESKTOP_APP_NAME}-${version}-linux-x64.AppImage`;
6840
6904
  }
6905
+ function resolveDesktopShellAssetName(version, target) {
6906
+ if (target.platform === "macos") return `${DESKTOP_APP_NAME}-${version}-macos-${target.arch}-shell.zip`;
6907
+ if (target.platform === "windows") return `${DESKTOP_APP_NAME}-${version}-windows-x64-shell.zip`;
6908
+ return null;
6909
+ }
6841
6910
  function encodeReleaseTagForDownloadUrl(tag) {
6842
6911
  return tag.split("/").map((segment) => encodeURIComponent(segment)).join("/");
6843
6912
  }
@@ -6852,7 +6921,7 @@ function buildGithubReleaseAsset(repo, tag, assetName) {
6852
6921
  };
6853
6922
  }
6854
6923
  function uniqueAssetDownloadUrls(asset) {
6855
- const urls = [asset.url, asset.browser_download_url].filter((url) => Boolean(url));
6924
+ const urls = [asset.browser_download_url, asset.url].filter((url) => Boolean(url));
6856
6925
  return Array.from(new Set(urls));
6857
6926
  }
6858
6927
  function downloadHeadersForAssetUrl(asset, url) {
@@ -6880,13 +6949,16 @@ function contentLengthFromHeaders(headers) {
6880
6949
  async function downloadAsset(asset, outputDir, progressFactory = createByteProgress) {
6881
6950
  mkdirSync(outputDir, { recursive: true });
6882
6951
  const outputPath = path11.join(outputDir, path11.basename(asset.name));
6952
+ const ASSET_DOWNLOAD_TIMEOUT_MS = 6e5;
6883
6953
  let response = null;
6884
6954
  const failures = [];
6885
6955
  for (const url of uniqueAssetDownloadUrls(asset)) {
6886
6956
  try {
6887
- const candidate = await fetch(url, {
6888
- headers: downloadHeadersForAssetUrl(asset, url)
6889
- });
6957
+ const candidate = await fetchWithTimeout(
6958
+ url,
6959
+ { headers: downloadHeadersForAssetUrl(asset, url) },
6960
+ ASSET_DOWNLOAD_TIMEOUT_MS
6961
+ );
6890
6962
  if (candidate.ok && candidate.body) {
6891
6963
  response = candidate;
6892
6964
  break;
@@ -7323,13 +7395,14 @@ function launchDesktop(paths, target) {
7323
7395
  }
7324
7396
  spawn(paths.executablePath, [], { detached: true, stdio: "ignore" }).unref();
7325
7397
  }
7326
- async function writeInstallMetadata(paths, releaseTag, assetName, assetChecksum) {
7398
+ async function writeInstallMetadata(paths, releaseTag, assetName, assetChecksum, assetKind = "full") {
7327
7399
  mkdirSync(path11.dirname(paths.metadataPath), { recursive: true });
7328
7400
  const metadata = {
7329
7401
  version: 1,
7330
7402
  releaseTag,
7331
7403
  assetName,
7332
7404
  assetChecksum,
7405
+ assetKind,
7333
7406
  installedAt: (/* @__PURE__ */ new Date()).toISOString()
7334
7407
  };
7335
7408
  mkdirSync(paths.installRoot, { recursive: true });
@@ -7369,6 +7442,7 @@ async function startCommand(opts) {
7369
7442
  const version = opts.targetVersion?.trim() || opts.version?.trim() || resolveCurrentCliVersion();
7370
7443
  const dryRun = opts.dryRun === true;
7371
7444
  const desktopProgressJson = opts.desktopProgressJson === true;
7445
+ let runtimeSupportsShellAssets = false;
7372
7446
  if (desktopProgressJson) {
7373
7447
  process.stdout.on("error", (error) => {
7374
7448
  if (error.code !== "EPIPE") throw error;
@@ -7391,9 +7465,13 @@ async function startCommand(opts) {
7391
7465
  spinner3.start("Installing or reusing Rudder runtime...");
7392
7466
  try {
7393
7467
  const runtime = await ensureRuntimeInstalled({ version });
7468
+ runtimeSupportsShellAssets = isExactRuntimePackageSpec(version, runtime.packageSpec);
7394
7469
  spinner3.stop(
7395
7470
  runtime.status === "hit" ? `Rudder runtime cache hit at ${pc8.cyan(runtime.cacheDir)}.` : `Rudder runtime installed at ${pc8.cyan(runtime.cacheDir)}.`
7396
7471
  );
7472
+ if (!runtimeSupportsShellAssets && installDesktop) {
7473
+ p13.log.warn("Rudder runtime did not resolve to the exact Desktop version; the full portable Desktop asset will be used.");
7474
+ }
7397
7475
  } catch (error) {
7398
7476
  spinner3.stop(pc8.red("Rudder runtime installation failed."));
7399
7477
  if (error instanceof RuntimeInstallError && error.output) {
@@ -7466,15 +7544,31 @@ async function startCommand(opts) {
7466
7544
  if (!releaseTag) {
7467
7545
  throw new Error(`Unable to resolve Rudder Desktop release tag for ${repo}@${tag}.`);
7468
7546
  }
7469
- const asset = selectDesktopAsset(release?.assets ?? [], target) ?? (directReleaseVersion ? buildGithubReleaseAsset(repo, tag, resolveDesktopAssetName(directReleaseVersion, target)) : null);
7470
- if (!asset) {
7547
+ const assetCandidates = resolveDesktopAssetCandidates({
7548
+ releaseAssets: release?.assets ?? [],
7549
+ target,
7550
+ repo,
7551
+ tag,
7552
+ directReleaseVersion,
7553
+ allowShellAssets: runtimeSupportsShellAssets
7554
+ });
7555
+ if (assetCandidates.length === 0) {
7471
7556
  throw new Error(`No Rudder Desktop portable asset found for ${target.platform}/${target.arch} in ${repo}@${releaseTag}.`);
7472
7557
  }
7473
7558
  const checksumAsset = selectChecksumAsset(release?.assets ?? []) ?? (directReleaseVersion ? buildGithubReleaseAsset(repo, tag, DESKTOP_CHECKSUM_ASSET_NAME) : null);
7474
7559
  const checksums = await downloadChecksums(checksumAsset, outputDir, progressFactory);
7475
- const expectedChecksum = resolveAssetChecksum(checksums, asset.name);
7560
+ let selectedCandidate;
7561
+ try {
7562
+ selectedCandidate = selectChecksummedDesktopAssetCandidate(assetCandidates, checksums);
7563
+ } catch (error) {
7564
+ throw new Error(`No checksummed Rudder Desktop asset found for ${target.platform}/${target.arch} in ${repo}@${releaseTag}.`);
7565
+ }
7566
+ for (const warning of selectedCandidate.warnings) p13.log.warn(warning);
7567
+ let selectedAsset = selectedCandidate.asset;
7568
+ let selectedAssetKind = selectedCandidate.kind;
7569
+ let expectedChecksum = selectedCandidate.expectedChecksum;
7476
7570
  const metadata = await readInstallMetadata(installPaths.metadataPath);
7477
- if (isInstalledDesktopCurrent(metadata, releaseTag, asset.name, expectedChecksum) && await pathExists(installPaths.executablePath)) {
7571
+ if (isInstalledDesktopCurrent(metadata, releaseTag, selectedAsset.name, expectedChecksum) && await pathExists(installPaths.executablePath)) {
7478
7572
  p13.log.success(`Rudder Desktop is already installed at ${pc8.cyan(installPaths.appPath)}.`);
7479
7573
  await runStartPhase(
7480
7574
  "Refreshing Desktop launchers...",
@@ -7486,16 +7580,32 @@ async function startCommand(opts) {
7486
7580
  desktopProgressJson ? "preparing_restart" : null
7487
7581
  );
7488
7582
  } else {
7489
- const cachedAsset = await downloadDesktopAssetWithCache(asset, expectedChecksum, {
7490
- outputDir,
7491
- progressFactory
7492
- });
7583
+ let cachedAsset;
7584
+ try {
7585
+ cachedAsset = await downloadDesktopAssetWithCache(selectedAsset, expectedChecksum, {
7586
+ outputDir,
7587
+ progressFactory
7588
+ });
7589
+ } catch (error) {
7590
+ const fullCandidate = assetCandidates.find((candidate) => candidate.kind === "full");
7591
+ if (selectedAssetKind !== "shell" || !fullCandidate) throw error;
7592
+ p13.log.warn(
7593
+ `Layered Desktop shell asset download failed; falling back to the full portable asset. ${formatFetchError(error)}`
7594
+ );
7595
+ selectedAsset = fullCandidate.asset;
7596
+ selectedAssetKind = fullCandidate.kind;
7597
+ expectedChecksum = resolveAssetChecksum(checksums, selectedAsset.name);
7598
+ cachedAsset = await downloadDesktopAssetWithCache(selectedAsset, expectedChecksum, {
7599
+ outputDir,
7600
+ progressFactory
7601
+ });
7602
+ }
7493
7603
  if (cachedAsset.cacheStatus === "hit") {
7494
7604
  p13.log.success(`Desktop asset cache hit at ${pc8.cyan(cachedAsset.path)}.`);
7495
7605
  if (desktopProgressJson) {
7496
7606
  writeDesktopProgress({
7497
7607
  phase: "downloading_asset",
7498
- message: `Desktop asset cache hit for ${asset.name}.`,
7608
+ message: `Desktop asset cache hit for ${selectedAsset.name}.`,
7499
7609
  percent: 100
7500
7610
  });
7501
7611
  }
@@ -7539,7 +7649,7 @@ async function startCommand(opts) {
7539
7649
  },
7540
7650
  desktopProgressJson ? "preparing_restart" : null
7541
7651
  );
7542
- await writeInstallMetadata(installPaths, releaseTag, asset.name, checksum);
7652
+ await writeInstallMetadata(installPaths, releaseTag, selectedAsset.name, checksum, selectedAssetKind);
7543
7653
  }
7544
7654
  if (opts.open !== false) {
7545
7655
  await runStartPhase(