Dashboard › cli › Distillation
335bbde0-abbb-4da8-b55a-6673c8fcb13b["lore_tm_v1_wvFqo0OPXgbROCpnpr2HU63QtDJz3xbP8BkfaUMaimY","lore_tm_v1_Hk_GsitqLZkTVYcv4Vuj1YM8uynFgfsu9igGaZHE9jc","lore_tm_v1_HjHFq4SXTt8NrJFll64ifmfOA8M_Lk9AFVJDmKfvayI","lore_tm_v1_zMcpzqOwtLFFAFx4VmpA7zASU7GL0cciWYlKwO8Z0kg","lore_tm_v1__U1m-zuzJkSGZCRMm01tNGaERnFEKFw_RgzCo2WvrCw","lore_tm_v1_9rx1miiR7070A9JjpAu0x0aXUbl3VrRFuQFjU7dtqz8","lore_tm_v1_V3Xrn07v37mht8LegM8s20qykyAgkctfXogMKZabwBA","lore_tm_v1_axtKawTDnomsWXVgk9BXYOaUrzzKZ1qI8DOF3rtyVgw"]
packages/cli/test/lib/upgrade.test.ts directly covers fetchLatestFromGitHub(), fetchLatestFromNpm(), fetchLatestNightlyVersion(), fetchLatestVersion(), versionExists(), and getBinaryDownloadUrl(). fetchLatestVersion() cases include methods "curl", "npm", "pnpm", "bun", "yarn", "brew", and "unknown", plus nightly cases for "curl" and "npm".packages/cli/test/lib/upgrade.test.ts has versionExists() cases for stable versions "1.0.0" and "99.99.99" across curl/npm/pnpm/bun/brew/yarn, plus nightly versions "0.14.0-dev.1772661724" and "0.14.0-dev.9999999999".packages/cli/test/lib/ghcr.test.ts covers getAnonymousToken(), downloadNightlyBlob(), and fetchManifest(), asserting URLs derived from GHCR_REPO, including https://ghcr.io/token?scope=repository:${GHCR_REPO}:pull, /v2/${GHCR_REPO}/manifests/patch-0.13.0, /v2/${GHCR_REPO}/tags/list, and /v2/${GHCR_REPO}/blobs/sha256:abc123."https://github.com/getsentry/cli/releases/download" in packages/cli/test/e2e/delta-upgrade.test.ts line 64 and expected URL literals in packages/cli/test/lib/upgrade.test.ts and packages/cli/test/lib/binary.test.ts.packages/cli/src/lib/version-check.ts imports delta-prefetch helpers from ./delta-upgrade.js, cleanupPatchCache from ./patch-cache.js, and fetchLatestFromGitHub / fetchLatestNightlyVersion from ./upgrade.js; packages/cli/src/lib/upgrade.ts imports binary utilities from ./binary.js, attemptDeltaUpgrade and DeltaResult from ./delta-upgrade.js, GHCR helpers from ./ghcr.js, and clearPatchCache from ./patch-cache.js.packages/cli/src/lib/upgrade.ts re-exports InstallationMethod and parseInstallationMethod from ./binary.js; packages/cli/src/lib/db/install-info.ts imports InstallationMethod directly from ../binary.js, avoiding an upgrade.ts dependency.packages/cli/src/lib/telemetry.ts imports isMusl from ./binary.js; packages/cli/src/lib/release-notes.ts imports binary utilities and GitHubRelease from ./delta-upgrade.js; packages/cli/src/commands/cli/upgrade.ts imports from ../../lib/binary.js, ../../lib/release-notes.js, and ../../lib/upgrade.js; packages/cli/src/commands/cli/setup.ts imports from ../../lib/binary.js.packages/cli/src/cli.ts dynamically imports startCleanupOldBinary from ./lib/upgrade.js at line 179 and version-check functionality from ./lib/version-check.js around line 186.packages/cli/src/lib/binary.ts is 529 lines and exports KNOWN_CURL_DIRS = [".local/bin", "bin", ".sentry/bin"].isDowngrade(current, target) in packages/cli/src/lib/binary.ts returns compareVersions(current, target) === 1.getBinaryFilename() returns "sentry.exe" on Win32 and "sentry" elsewhere.getBinaryPaths(installPath) returns exact sibling paths: installPath, tempPath: \${installPath}.download`, oldPath: `${installPath}.old`, and lockPath: `${installPath}.lock``.determineInstallDir(homeDir, env) chooses the curl install directory in this order: 1. env.SENTRY_INSTALL_DIR when present; 2. join(homeDir, ".local", "bin") if the directory exists and is in PATH; 3. join(homeDir, "bin") under the same conditions; 4. fallback join(homeDir, ".sentry", "bin"). PATH is split using Nodeβs platform-specific delimiter.getGitHubHeaders() returns Accept: "application/vnd.github.v3+json" and "User-Agent": getUserAgent().fetchWithUpgradeError(url, init, serviceName) calls customFetch(url, init), rethrows errors named "AbortError" unchanged, maps TLS certificate errors detected by isTlsCertError() to new UpgradeError("network_error", buildTlsErrorDetail(error)), and maps other failures to new UpgradeError("network_error", \Failed to connect to ${serviceName}: ${stringifyUnknown(error)}`)`.replaceBinarySync(tempPath, installPath) is intentionally synchronous so its rename sequence cannot be interrupted. On Unix it atomically executes renameSync(tempPath, installPath). On Windows it first tries renameSync(installPath, \${installPath}.old`); if that fails it attempts to unlink the old .old` file and retry the rename, tolerating a missing current binary, then renames the temp file into place.cleanupOldBinary(oldPath) asynchronously calls unlink(oldPath) and ignores errors. It intentionally does not clean .download files because another upgrade may be active; .download cleanup occurs inside the locked upgrade flow.acquireLock(lockPath) first calls mkdirSync(dirname(lockPath), { recursive: true, mode: 0o755 }), then atomically creates the lock with writeFileSync(lockPath, String(process.pid), { flag: "wx" }). Creating the parent outside the lock-acquisition try/catch preserves genuine EEXIST, ENOTDIR, and EACCES directory errors rather than misclassifying them as lock contention.acquireLock() fixes crashes such as ENOENT ... open '.../sentry.lock' for fresh ~/.sentry/bin, npm-to-nightly migration, or a purged directory referenced by stale SENTRY_INSTALL_DIR; comments associate these cases with CLI-1E1 and CLI-1RV.EEXIST, acquireLock() rethrows it. For EEXIST, handleExistingLock(lockPath) reads and trims the lock PID; an ENOENT while reading is treated as a race and recursively retries acquisition, while other read errors such as EACCES are rethrown.handleExistingLock() permits parent-to-child takeover when existingPid === process.ppid by replacing the lock contents with the childβs PID. This supports upgrade spawning setup --install while retaining exclusion against concurrent upgrades. Any other live owner causes UpgradeError("execution_failed", "Another upgrade is already in progress").unlinkSync(lockPath) and acquisition is retried recursively. ENOENT during removal is tolerated as a race; other unlink failures are rethrown. releaseLock(lockPath) tries unlinkSync() and ignores all errors.installBinary(sourcePath, installDir) creates installDir recursively with mode 0o755, derives installPath using getBinaryFilename(), derives .download and .lock paths through getBinaryPaths(), acquires the PID lock, installs inside try, and always releases the lock in finally; it returns the absolute installPath.installBinary() canonicalizes source and temp paths with asynchronous realpath(). On ENOENT it falls back to resolve(); on other failures it logs "realpath failed, falling back to resolve()" and also uses resolve(). This handles OS-canonicalized process.execPath versus symlinked paths such as macOS /tmp β /private/tmp.sourcePath equals canonical tempPath, installBinary() skips unlinking and copying because an upgrade-spawned setup --install child may already be executing from the .download file. Otherwise it removes a leftover temp file, ignores ENOENT, logs other cleanup failures as "Failed to clean up temp file", copies with copyFile(sourcePath, tempPath), and applies mode 0o755 on non-Windows systems.installBinary() calls replaceBinarySync(tempPath, installPath) for atomic replacement and Windows .old handling.packages/cli/test/lib/upgrade.test.ts, packages/cli/test/commands/cli/upgrade.test.ts, packages/cli/test/lib/delta-upgrade.test.ts, packages/cli/test/lib/delta-upgrade.mocked.test.ts, and packages/cli/test/e2e/delta-upgrade.test.ts.packages/cli/test/lib/install-script.test.ts, packages/cli/test/lib/db/install-info.test.ts, packages/cli/test/commands/cli/uninstall.test.ts, packages/cli/install, packages/cli/src/lib/db/install-info.ts, and packages/cli/src/commands/cli/uninstall.ts.