Dashboard › cli › Distillation
fec31d4e-6344-4d8b-bc58-599a6bc6caaf["lore_tm_v1_F5bCAIJ1Oa0vRau1DSN1pVc9aBUl7P2hsiGM-c9PoOY","lore_tm_v1_HbgF9EBljdan4EHhR4jojHhR4tdkJ9UDJykwQ8nEuY0","lore_tm_v1_-FE9jVBINEnx3Jkx0DhvRuOI595hrq2M9-jm4VKH-UQ","lore_tm_v1_zw6Turo2zlUP3lXLOKAqYLfLPm6aZ87hWxr_RuI2cU0","lore_tm_v1_bKFXIXOaR23LgdyWtO_mqS-n1J2mq6M-kvAQgV3pvcM"]
packages/cli/src/lib/delta-upgrade.ts, a 648-line sentry-cli adapter around binpatch that preserves the existing public API while injecting Sentry tracing, custom CA fetch, logging, cache location, version comparison, upgrade-source configuration, and CLI progress rendering.getPrimaryUpgradeSource() returning PRIMARY_UPGRADE_SOURCE or throwing No primary upgrade source is configured.InstrumentHook implementation as (name, fn) => withTracing(name, "http.client", fn).packages/cli/src/lib/delta-upgrade.ts: keys use patch-chain:${fromVersion}-${toVersion}; load() creates a cache.get span with cache.key, cache.hit, and, on hits, cache.item_size; save() creates a cache.put span with cache.key and summed patch sizes; cleanup() and clear() delegate directly.getPatchCache() creating an instrumented cache at join(getConfigDir(), "patch-cache") via makeCache().githubReleaseSource() with releasesUrl: getGitHubReleasesUrl(source), binaryName: getPlatformBinaryName(), userAgent: \sentry-cli/${CLI_VERSION}`, fetch: customFetch, and instrument`.ghcrSource() with registry https://ghcr.io, repo: source.ghcrRepo, the platform binary name, target tags formatted as nightly-${version}, compareVersions, userAgent: \sentry-cli/${CLI_VERSION}`, customFetch`, and tracing instrumentation.canAttemptDelta(targetVersion) rejecting delta upgrades when CLI_VERSION === "0.0.0-dev", when current and target versions belong to different stable/nightly channels, or when isDowngrade(CLI_VERSION, targetVersion) is true.packages/cli/src/lib/delta-upgrade.ts: fetchRecentReleases() requests ${getGitHubReleasesUrl(source)}?per_page=12, returns [] for non-OK responses, malformed non-array JSON, or exceptions, and logs debug details; downloadStablePatch() returns patch bytes only for an OK response and otherwise returns null, logging exceptions.extractStableChain() adapting binpatch’s classified result back to the preserved public shape by returning null when "failure" in result; filterAndSortChainTags() delegates to binpatchFilterAndSortChainTags(..., compareVersions).validateChainStep() that preserves rich failures: version-mismatch with expected/actual values, missing-layer with layerName, and size-exceeded with layerSize and budget; it checks getPatchFromVersion(manifest) first, delegates successful validation to binpatchValidateChainStep(), and distinguishes missing layer from exceeded size by finding the OCI layer annotation org.opencontainers.image.title.resolveNightlyChain() using OciClient against https://ghcr.io, filtering tags between current and target versions, rejecting zero tags or more than MAX_NIGHTLY_CHAIN_DEPTH, fetching manifests concurrently, and returning null on manifest-fetch failure.resolveNightlyChain() intentionally using the local validateChainStep() instead of binpatch’s validator so telemetry retains version-mismatch | missing-layer | size-exceeded; binpatch’s result is coarser: "malformed" | "over_budget".opts.fullGzSize * SIZE_THRESHOLD_RATIO - totalSize; validation failures stamp the active Sentry span’s telemetry_reason with result.failure.reason.{ fromVersion, toVersion } steps, collecting layer digests, deriving the final target hash via getPatchTargetSha256(manifest, binaryName), rejecting a final-version mismatch or absent target hash with telemetry_reason="version-mismatch", downloading all blobs concurrently, and returning patches, recomputed totalSize, expectedSha256, and steps.applyPatchChain() wrapped in an apply-patches / upgrade.delta.apply tracing span with patches.count and patches.total_bytes; it calls applyPatchChainInMemory() and throws SHA-256 mismatch after patching: got ${sha256}, expected ${chain.expectedSha256} on verification failure.makeProgressHandler(): apply-phase totals aggregate every hop’s newSize, which can display 930 MB for a 310 MB installation, so apply uses { format: "pct" }; pre-apply "download"/"read" phases retain { format: "bytes" }.makeProgressHandler() creating a fresh bar when a byte event introduces a new phase, labeling apply as "Applying patch(es)" and other phases as "Processing patch(es)", converting cumulative event.written to increments with event.written - previousWritten, and calling progress?.done() on "done" events.telemetry() and attemptDeltaUpgrade(): _source.current captures "cache", "network", or "offline_miss" so an apply failure after successful resolution can still stamp delta.source on the active error span; previously the catch path silently omitted this attribute.telemetry().onResolved() setting delta.source and logging the source, patch count, and formatted total size; onOfflineMiss() sets source to "offline_miss"; onUnavailable(reason) sets telemetry_reason.resolveDelta() calling resolveAndApply() with currentVersion: CLI_VERSION, the selected source, old/destination paths, getPatchCache(), offline mode, makeProgressHandler(setMessage), and telemetry, then returning both the delta result and captured source.resolveStableDelta() and resolveNightlyDelta() preserving their public call shapes while selecting stableSource(source) and nightlySource(source) respectively and discarding the internal captured-source field.attemptDeltaUpgrade() selecting channel "nightly" or "stable", tracing upgrade.delta, setting delta.from_version and delta.to_version, and recording successful delta.patch_bytes and delta.chain_length span attributes plus upgrade.delta.patch_bytes and upgrade.delta.chain_length Sentry distributions with { channel }.delta.result="unavailable" and span status code 1; caught failures are captured by Sentry at warning level with delta.from_version, delta.to_version, and delta.channel tags, logged as Delta upgrade failed (${message}), falling back to full download, marked with status code 2, delta.result="error", and delta.error, then converted to null.prefetch() exits when delta is ineligible or the signal is aborted, resolves a chain from CLI_VERSION, requires chain.steps, rechecks abortion, and saves through getPatchCache(); prefetchNightlyPatches() and prefetchStablePatches() provide channel-specific wrappers.getsentry/toolkit and legacy getsentry/cli: tests reference toolkit release tags such as cli@0.50.0, toolkit tagPrefix: "cli@", legacy CLI tags without that prefix, and both GitHub/GHCR repositories.<binaryName>, <binaryName>.gz, and <binaryName>.patch; the binary digest supplies expected SHA-256, gzip size supplies the ratio budget, and patches form the chain.extractSha256() accepting case-insensitive sha256:<hex> through /^sha256:([0-9a-f]+)$/i and returning lowercase hex, while getStableTargetSha256() locates the exact binary asset name before extracting its digest.StableChainInfo or StableChainFailure, with failures exactly "no_patches" | "malformed_chain" | "too_long" | "over_budget".extractStableChain() classifying absent/misordered current or target releases as "no_patches", depth above MAX_STABLE_CHAIN_DEPTH as "too_long", absent target binary SHA-256 or any in-range <binaryName>.patch asset as "malformed_chain", and cumulative patch asset size above fullGzSize * SIZE_THRESHOLD_RATIO as "over_budget".steps are generated from currentVersion through reversed releases as { fromVersion: prevVersion, toVersion: release.tag_name }.githubReleaseSource() fetching MAX_STABLE_CHAIN_DEPTH + 2 releases with GitHub API headers, optionally wrapping each request with instrument("fetch-releases", ...) or instrument("download-patch", ...); request exceptions and non-OK responses produce null."network"; missing target release reports "no_patches"; an existing target release missing ${binaryName}.gz reports "malformed_chain"; classified extraction failures are reported unchanged.Promise.all(); an advertised asset that cannot be downloaded is classified "network" rather than a malformed publish, while successful downloads become PatchLink entries with actual data.byteLength and a recomputed total size.