Dashboard › cli › Distillation
7f08f848-1b0f-45e7-827b-7c1bbcb530fd["lore_tm_v1_R2fN4G0pqU8jL2R6-dP30_gY253WaxzdmPsMLaw1mqM","lore_tm_v1_PLpkvGjIicve9GC63hobXAUjuhV6JY9dOOKMNn6dMtQ","lore_tm_v1_s_SJ0YGvBdHVymcYZGgxMTbzxi-Yt8_KBjqqUSGWmSM","lore_tm_v1_E2n5d4WBaq9PpX-ALTfMrRon_cf7mLA1W68qnQJnZXc","lore_tm_v1_wmDNI9JmLh3d99ZnXkIf9DyE-nhO4cUQS2Ec6FOdHAk","lore_tm_v1_ty9EN4PU8Dvlij-5OvTAEqY3SHxs1XK45Erh1dmwQtQ","lore_tm_v1_z_kfVdq8bEq_VZAV47u4whkIfRpODvtIfxJF93KYzto","lore_tm_v1_kYXstPZAwSTR3j4drw9de0Wa1o4u88oaFo0k94AP5aY"]
packages/cli/test/e2e/delta-upgrade.test.ts, packages/cli/test/lib/upgrade.test.ts, packages/cli/test/lib/binary.test.ts, packages/cli/test/lib/delta-upgrade.test.ts, packages/cli/test/lib/delta-upgrade.mocked.test.ts, and packages/cli/test/lib/ghcr.test.ts; stable assets use https://github.com/getsentry/cli/releases/download, while GHCR tests reference GHCR_REPO, manifests, tags, and blobs."toolkit-cli-workspace" in /package.json; pnpm-lock.yaml contains es-toolkit@1.49.0; packages/cli/test/helpers.ts documents and implements an unmatched-request fallback status of 404.packages/cli/src/lib/binary.ts defines KNOWN_CURL_DIRS = [".local/bin", "bin", ".sentry/bin"] and InstallationMethod values "curl" | "brew" | "npm" | "pnpm" | "bun" | "yarn" | "unknown"; parseInstallationMethod() lowercases input and accepts every value except "unknown", otherwise throwing an error listing valid methods.isMusl() in packages/cli/src/lib/binary.ts runs only on Linux and caches its result; it first checks /lib/ld-musl-${muslArch}.so.1, mapping x64 to x86_64 and other architectures to aarch64, then combines stdout and stderr from spawnSync("ldd", ["--version"]) and searches case-insensitively for musl; failure defaults to glibc.getPlatformBinaryName() in packages/cli/src/lib/binary.ts maps darwin to darwin, win32 to windows, all other platforms to linux, maps only arm64 explicitly and all other architectures to x64, adds -musl when isMusl() is true, and adds .exe on Windows, yielding sentry-<os>-<arch>[-musl][.exe].getBinaryDownloadUrl(version) returns https://github.com/getsentry/cli/releases/download/${version}/${getPlatformBinaryName()}; GITHUB_RELEASES_URL is https://api.github.com/repos/getsentry/cli/releases; isNightlyVersion(version) checks version.includes("-dev.").compareVersions(a, b) in packages/cli/src/lib/binary.ts uses compare from semver despite its comment referring to Bun.semver.order; it returns -1 | 0 | 1, and isDowngrade(current, target) returns true when compareVersions(current, target) === 1.getBinaryFilename() returns sentry.exe on win32 and sentry elsewhere; getBinaryPaths(installPath) derives ${installPath}.download, ${installPath}.old, and ${installPath}.lock.determineInstallDir(homeDir, env) priority is: 1. env.SENTRY_INSTALL_DIR; 2. existing ~/.local/bin already in PATH; 3. existing ~/bin already in PATH; 4. fallback ~/.sentry/bin.getGitHubHeaders() returns Accept: "application/vnd.github.v3+json" and "User-Agent": getUserAgent().fetchWithUpgradeError(url, init, serviceName) calls customFetch; it rethrows AbortError unchanged, maps TLS certificate errors through buildTlsErrorDetail() to UpgradeError("network_error", ...), and maps other failures to UpgradeError("network_error", "Failed to connect to ${serviceName}: ${msg}").replaceBinarySync(tempPath, installPath) is intentionally synchronous so replacement cannot be interrupted mid-sequence; Unix uses atomic renameSync(tempPath, installPath), while Windows tries installPath β ${installPath}.old, removes an existing .old and retries if needed, tolerates a missing current binary, then renames the temp binary into place.cleanupOldBinary(oldPath) asynchronously calls unlink(oldPath) and ignores failures; it intentionally does not clean .download files because another upgrade may be active, leaving that cleanup to the locked upgrade flow.acquireLock(lockPath) in packages/cli/src/lib/binary.ts first runs mkdirSync(dirname(lockPath), { recursive: true, mode: 0o755 }) outside its lock-contention try/catch, preventing directory errors such as EEXIST, ENOTDIR, or EACCES from being misinterpreted as an existing lock; this addresses failures documented as CLI-1E1 and CLI-1RV.acquireLock(lockPath) atomically writes the current PID using writeFileSync(lockPath, String(process.pid), { flag: "wx" }); non-EEXIST errors propagate, while EEXIST invokes handleExistingLock(lockPath).handleExistingLock(lockPath) retries acquisition if reading the lock races with deletion and yields ENOENT, rethrows other read errors, parses the lock content as a PID, and checks it with isProcessRunning().process.ppid is transferred to the current child PID so an upgrade parent can hand off to spawned setup --install; another active PID causes UpgradeError("execution_failed", "Another upgrade is already in progress"); a dead or invalid PID is treated as stale, removed, and acquisition is recursively retried.releaseLock(lockPath) synchronously removes the lock and ignores errors.installBinary(sourcePath, installDir) creates the install directory with mode 0o755, derives install/temp/lock paths, acquires the PID lock, and always releases it in finally.installBinary(), when the spawned childβs sourcePath is already the .download tempPath, unlinking and copying are skipped; path equality is tested through an internal asynchronous canonical() helper using realpath(), falling back to resolve() on ENOENT or other errors, with non-ENOENT fallback failures logged through logger.debug.sourcePath differs from tempPath, installBinary() removes a stale temp file while ignoring ENOENT and logging other failures, copies with copyFile(), applies mode 0o755 outside Windows, then calls replaceBinarySync(tempPath, installPath) and returns the final absolute install path.fetchLatestFromGitHub(), versionExists() for curl, npm, pnpm, bun, brew, and yarn, nightly versions such as 0.14.0-dev.1772661724, and fetchLatestNightlyVersion(); delta-upgrade tests cover fetchRecentReleases(), resolveStableChain(), and resolveStableDelta().packages/cli/src/lib/patch-cache.ts, packages/cli/src/lib/delta-upgrade.ts, packages/cli/src/lib/ghcr.ts, packages/cli/src/lib/init/tools/apply-patchset.ts, packages/cli/src/commands/cli/upgrade.ts, packages/cli/script/check-patches.ts, and tests packages/cli/test/lib/patch-cache.test.ts, bspatch.test.ts, bspatch.property.test.ts, delta-upgrade.test.ts, delta-upgrade.mocked.test.ts, packages/cli/test/e2e/delta-upgrade.test.ts, and packages/cli/test/commands/cli/upgrade.test.ts.packages/cli/package.json includes dev dependency "binpatch": "^0.3.1" and pnpm-lock.yaml resolves binpatch@0.3.1; packages/cli/src/lib/patch-cache.ts imports makeCache, PatchCache, and PatchChain, re-exports ChainMeta, PatchStepMeta, chainFileName, and patchFileName from binpatch.packages/cli/src/lib/delta-upgrade.ts is backed by binpatch and aliases extractStableChain, filterAndSortChainTags, and validateChainStep as binpatchExtractStableChain, binpatchFilterAndSortChainTags, and binpatchValidateChainStep; chain application deliberately uses the local validateChainStep() so its rich 3-reason result, including size-exceeded, survives adoption of binpatch, whose validator returns a less detailed result.packages/cli/test/lib/bspatch.test.ts, packages/cli/test/lib/bspatch.property.test.ts, and packages/cli/test/e2e/delta-upgrade.test.ts directly import from binpatch; the E2E test imports applyPatch and applyPatchChainInMemory, while the property test imports offtin and parsePatchHeader.packages/cli/src/lib/ghcr.ts defines GHCR_REQUEST_TIMEOUT = 10_000, GHCR_MAX_RETRIES = 1, and GHCR_BLOB_TIMEOUT = 30_000; retryable errors include names TimeoutError and AbortError, or messages containing timeout, econnreset, econnrefused, network, or fetch failed.buildSignal(timeout, externalSignal) combines AbortSignal.timeout(timeout) with an optional caller signal via AbortSignal.any(); isExternalAbort() recognizes caller cancellation only when the external signal is aborted and the error name is AbortError.fetchWithRetry() in packages/cli/src/lib/ghcr.ts performs up to 2 total attempts because its loop runs from attempt 0 through GHCR_MAX_RETRIES = 1; it immediately stops on external cancellation, retries only transient network/timeout exceptions, and finally throws UpgradeError("network_error", "${context}: ${lastError.message}").126ms to 30s; a short timeout plus one retry is intended to cap the worst case at approximately 20s rather than 30s.GHCR_REPO = "getsentry/cli", GHCR_TAG = "nightly", registry https://ghcr.io, and OCI media type application/vnd.oci.image.manifest.v1+json.OciLayer contains digest, mediaType, byte size, and optional string annotations; OciManifest contains schemaVersion, optional mediaType, optional config, layers, and optional annotations, with the nightly version stored under manifest annotation version.getAnonymousToken(signal?) requests https://ghcr.io/token?scope=repository:getsentry/cli:pull with only the CLI user agent; non-success responses become UpgradeError("network_error", "GHCR token exchange failed: HTTP ${status}"), and a missing JSON token becomes UpgradeError("network_error", "GHCR token exchange returned no token").fetchManifest(token, tag, signal?) requests https://ghcr.io/v2/getsentry/cli/manifests/${tag} with bearer authorization, the OCI manifest Accept header, and CLI user agent; a non-success response becomes UpgradeError("network_error", "Failed to fetch manifest for tag \"${tag}\": HTTP ${status}").fetchNightlyManifest(token) delegates to fetchManifest(token, "nightly") without exposing an abort signal; getNightlyVersion(manifest) returns manifest.annotations?.version or throws UpgradeError("network_error", "Nightly manifest has no version annotation").findLayerByFilename(manifest, filename) matches layer.annotations["org.opencontainers.image.title"]; absence throws UpgradeError("version_not_found", "No nightly build found for ${filename}").downloadNightlyBlob(token, digest, signal?) first requests https://ghcr.io/v2/getsentry/cli/blobs/${digest} with bearer authorization, redirect: "manual", user agent, and a 30_000 ms combined timeout signal; connection failures become UpgradeError("network_error", "Failed to connect to GHCR: ${msg}").downloadNightlyBlob() accepts a direct HTTP 200 response or manually handles 301, 302, 307, and 308; a redirect lacking Location throws UpgradeError("network_error", "GHCR blob redirect (${status}) had no Location header").Authorization, retains only the user agent, and uses the caller signal without AbortSignal.timeout; this avoids Azure HTTP 404 from mixed bearer and signed-query credentials and avoids imposing a 30s full-body timeout that would require sustained approximately 8 Mbps for a roughly 30 MB nightly binary.UpgradeError("network_error", "Failed to download from blob storage: ${msg}"), non-success responses become "Blob storage download failed: HTTP ${status}", and unrecognized initial GHCR statuses become "Unexpected GHCR blob response: HTTP ${status}".TAGS_PAGE_SIZE = 100 and endpoint /v2/getsentry/cli/tags/list?n=100, adding &last=${encodeURIComponent(lastTag)}; listTags(token, prefix?, signal?) accumulates optional prefix matches until an empty page or a page shorter than 100, then advances with tags.at(-1).downloadLayerBlob(token, digest, signal?) reuses downloadNightlyBlob() and returns response.arrayBuffer(); it is intended for fully buffered small patch payloads of approximately 50-500 KB.