Dashboard › cli › Session 056Pj9ojvE8B
056Pj9ojvE8BBbIVCREAD-ONLY merge-gate review of getsentry/cli PR #1569. Review immutable Git objects only in repo /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade. Exact base: ec83887a16f780f32fba4b7d710bad262dba3a22. Exact head: 9525f72c70fabbb23ec21d13cf86add58f6287cc. Never modify files or VCS and never use the worktree file contents as evidence; use git show/diff against exact SHAs. Inspect every changed source and test file. Verify merge base, trees, complete diff, changed paths, and diff --check. Focus on behavioral regressions in dual-source upgrade resolution: genuine-404-only fallback, source affinity, GitHub pagination, cancellation/body error typing, strict npm and GHCR response validation, canonical Toolkit versus legacy tags, pinned stable/nightly validation, package-manager no-spawn failures, channel/offline behavior, redirect auth isolation, delta final SHA/cleanup, and end-to-end nightly blob/setup coverage. The source-neutral patch cache is an accepted contract because dual-published artifacts are byte-identical and cached output is final-SHA verified; report it only if you prove a defect that survives both guarantees. Your response MUST be non-empty. Findings first, ordered by severity, classified PASS/CONCERN/MUST-FIX/BLOCKED, with exact HEAD file:line evidence. For each defect, give a deterministic regression and concrete fix. If no defect exists, state PASS and residual risks. If any tool fails, state BLOCKED with the exact command/error. End the final line exactly MERGE or DO-NOT-MERGE.
commit commit ec83887a16f780f32fba4b7d710bad262dba3a22 9525f72c70fabbb23ec21d13cf86add58f6287cc ec83887a16f780f32fba4b7d710bad262dba3a22
commit=ec83887a16f780f32fba4b7d710bad262dba3a22 tree=110d6530e57ca4fbfb2a3f98e52013d2668b5b5e parents=e7beceb6ba09b64dc277ae1732df04e65500062b author=sentry[bot] <39604003+sentry[bot]@users.noreply.github.com> author_date=2026-09-09T19:01:12+01:00 committer=GitHub <noreply@github.com> commit_date=2026-09-09T19:01:12+01:00 subject=fix(event): Prevent immediate retry of rate-limited org in event view fallback (#1566) commit=9525f72c70fabbb23ec21d13cf86add58f6287cc tree=22f49363a13e32c36dcd9e1ca95916809c2dc271 parents=a84012184c79c2566c7466aa6beda5f767199218 author=Burak Yigit Kaya <byk@sentry.io> author_date=2026-09-10T10:43:42+00:00 committer=Burak Yigit Kaya <byk@sentry.io> commit_date=2026-09-10T10:43:42+00:00 subject=fix(cli): harden upgrade metadata validation
M packages/cli/src/commands/cli/upgrade.ts M packages/cli/src/lib/binary.ts M packages/cli/src/lib/delta-upgrade.ts M packages/cli/src/lib/errors.ts M packages/cli/src/lib/ghcr.ts M packages/cli/src/lib/release-notes.ts M packages/cli/src/lib/upgrade.ts M packages/cli/src/lib/version-check.ts M packages/cli/test/commands/cli.test.ts M packages/cli/test/commands/cli/upgrade.test.ts M packages/cli/test/lib/binary.test.ts M packages/cli/test/lib/delta-upgrade.mocked.test.ts M packages/cli/test/lib/delta-upgrade.test.ts M packages/cli/test/lib/ghcr.test.ts M packages/cli/test/lib/release-notes.test.ts M packages/cli/test/lib/upgrade.test.ts M packages/cli/test/lib/version-check.test.ts packages/cli/src/commands/cli/upgrade.ts | 180 +++-- packages/cli/src/lib/binary.ts | 191 +++++- packages/cli/src/lib/delta-upgrade.ts | 170 ++++- packages/cli/src/lib/errors.ts | 8 + packages/cli/src/lib/ghcr.ts | ...
diff_sha256=4950719273dc17d5a9e061b8cfe70c62131fe3299dc1799d9c09bb522b112193 -
diff --git a/packages/cli/src/commands/cli/upgrade.ts b/packages/cli/src/commands/cli/upgrade.ts index 8d8282368..b3039d86b 100644 --- a/packages/cli/src/commands/cli/upgrade.ts +++ b/packages/cli/src/commands/cli/upgrade.ts @@ -1,414 +1,465 @@ /** * sentry cli upgrade * * Self-update the Sentry CLI to the latest or a specific version. * After upgrading, spawns the NEW binary with `cli setup` to update * completions, agent skills, and record installation metadata. * * Supports two release channels: * - stable (default): tracks the latest GitHub release * - nightly: tracks the rolling nightly prerelease built from main * * The channel can be set via --channel or by passing "nightly"/"stable" * as the version argument. The choice is persisted in the local database * so that subsequent bare `sentry cli upgrade` calls use the same channel. ...
diff --git a/packages/cli/src/lib/binary.ts b/packages/cli/src/lib/binary.ts
index 75582612a..e64f5059e 100644
--- a/packages/cli/src/lib/binary.ts
+++ b/packages/cli/src/lib/binary.ts
@@ -1,450 +1,621 @@
/**
* Binary Management
*
* Shared utilities for installing, replacing, and managing the CLI binary.
* Used by both `setup --install` (fresh installs) and `upgrade` (self-updates).
*/
import { spawnSync } from "node:child_process";
import {
existsSync,
mkdirSync,
readFileSync,
renameSync,
unlinkSync,
writeFileSync,
} from "node:fs";
import { chmod, copyFile, mkdir, realpath, unlink } from "node:fs/promises";
import { delimiter, dirname, isAbsolute, join, resolve, sep } from "node:path";
import { compare as semverCompare } from "semver";
import { getUserAgent } from "./constants.js";
import {
buildTlsErrorDetail,
customFetch,
isTlsCertError,
} from "./custom-ca.js";
-import { stringifyUnknown, UpgradeError } from "./errors.js";
+import ...
diff --git a/packages/cli/src/lib/delta-upgrade.ts b/packages/cli/src/lib/delta-upgrade.ts
index ec709b8c8..204875dce 100644
--- a/packages/cli/src/lib/delta-upgrade.ts
+++ b/packages/cli/src/lib/delta-upgrade.ts
@@ -1,388 +1,481 @@
/** Delta upgrade discovery and application backed by binpatch. */
import { join } from "node:path";
// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import
import * as Sentry from "@sentry/node-core/light";
import {
applyPatchChainInMemory,
extractStableChain as binpatchExtractStableChain,
filterAndSortChainTags as binpatchFilterAndSortChainTags,
validateChainStep as binpatchValidateChainStep,
type DeltaTelemetry,
type DeltaUnavailableReason,
type ExtractStableChainOpts,
type GitHubRelease,
getPatchFromVersion,
getPatchTargetSha256,
ghcrSource,
githubReleaseSource,
type InstrumentHook,
MAX_NIGHTLY_CHAIN_DEPTH,
makeCache,
OciClient,
type OciManifest,
...
diff --git a/packages/cli/src/lib/errors.ts b/packages/cli/src/lib/errors.ts
index f81b8c70c..b21505cd1 100644
--- a/packages/cli/src/lib/errors.ts
+++ b/packages/cli/src/lib/errors.ts
@@ -501,240 +501,248 @@ export class ResolutionError extends CliError {
override format(): string {
return this.message;
}
}
/**
* Build a {@link ValidationError} message with Try: examples and optional Note: section.
*
* Matches {@link buildResolutionMessage} / {@link buildContextMessage} formatting so
* agent consumers get actionable recovery commands plus diagnostic context in a
* predictable shape. Always pass `field` when constructing the error — unfielded
* validation errors collapse into one Sentry fingerprint. ...
diff --git a/packages/cli/src/lib/ghcr.ts b/packages/cli/src/lib/ghcr.ts index 43d6deb7e..5bca7c061 100644 --- a/packages/cli/src/lib/ghcr.ts +++ b/packages/cli/src/lib/ghcr.ts @@ -1,520 +1,659 @@ /** * GHCR (GitHub Container Registry) Client * * Encapsulates the OCI download protocol for fetching nightly CLI binaries * from ghcr.io/getsentry/cli. Nightly builds are pushed as OCI artifacts * via ORAS with the version baked into the manifest annotation. * * Key design decisions: * - Anonymous access: nightly package is public; no token needed beyond the * standard ghcr.io anonymous token exchange. * - Version discovery from manifest annotation: `annotations.version` in the * OCI manifest holds the nightly version. Checking the latest version only * requires a token exchange + manifest fetch (2 HTTP requests total). * - Redirect quirk: ghcr.io blob downloads return 307 to Azure Blob Storage. ...
diff --git a/packages/cli/src/lib/release-notes.ts b/packages/cli/src/lib/release-notes.ts index ddf617535..903ca51f6 100644 --- a/packages/cli/src/lib/release-notes.ts +++ b/packages/cli/src/lib/release-notes.ts @@ -1,142 +1,149 @@ /** * Release Notes Parser & Aggregation * * Extracts user-facing changelog entries from GitHub Release bodies (stable) * or conventional commit messages (nightly). Uses `marked.lexer()` for * AST-based section extraction and produces structured data that can be * re-serialized as filtered markdown for rendering via `renderMarkdown()`. ...
diff --git a/packages/cli/src/lib/upgrade.ts b/packages/cli/src/lib/upgrade.ts
index 945adc48d..b29d67ff2 100644
--- a/packages/cli/src/lib/upgrade.ts
+++ b/packages/cli/src/lib/upgrade.ts
@@ -1,149 +1,247 @@
/**
* Upgrade Module
*
* Detects how the CLI was installed and provides self-upgrade functionality.
* Binary management helpers (download URLs, locking, replacement) live in
* binary.ts and are shared with the setup --install flow.
*/
import { spawn } from "node:child_process";
import {
chmodSync,
closeSync,
existsSync,
openSync,
realpathSync,
statSync,
unlinkSync,
writeSync,
} from "node:fs";
import { writeFile } from "node:fs/promises";
import { homedir } from "node:os";
import { dirname, isAbsolute, join, sep } from "node:path";
import { setTimeout } from "node:timers/promises";
+import { prerelease as semverPrerelease, valid as semverValid } from "semver";
import {
acquireLock,
cleanupOldBinary,
+ compareVersions,
...
diff --git a/packages/cli/test/commands/cli.test.ts b/packages/cli/test/commands/cli.test.ts
index cd1514eb0..4eec11e9d 100644
--- a/packages/cli/test/commands/cli.test.ts
+++ b/packages/cli/test/commands/cli.test.ts
@@ -77,160 +77,154 @@ describe("feedbackCommand.func", () => {
});
test("throws ValidationError for whitespace-only message", async () => {
const func = await feedbackCommand.loader();
const mockContext = {
stdout: { write: vi.fn(() => true) },
stderr: { write: vi.fn(() => true) },
};
await expect(func.call(mockContext, {}, " ")).rejects.toThrow(
"Please provide a feedback message."
);
});
test("throws ConfigError when Sentry is disabled", async () => {
const func = await feedbackCommand.loader();
const mockContext = {
stdout: { write: vi.fn(() => true) },
stderr: { write: vi.fn(() => true) },
};
// Sentry is disabled in test environment (no DSN)
await expect(
...
diff --git a/packages/cli/test/commands/cli/upgrade.test.ts b/packages/cli/test/commands/cli/upgrade.test.ts index ca9688e46..b7fead58f 100644 --- a/packages/cli/test/commands/cli/upgrade.test.ts +++ b/packages/cli/test/commands/cli/upgrade.test.ts @@ -3,80 +3,81 @@ * * Tests the `sentry cli upgrade` command through Stricli's run(). * Covers resolveTargetVersion branches (check mode, already up-to-date, * version validation) and error paths. * * Status messages go through consola (→ process.stderr). Tests capture stderr * via a spy on process.stderr.write and assert on the collected output. ...
diff --git a/packages/cli/test/lib/binary.test.ts b/packages/cli/test/lib/binary.test.ts
index 6fcfb3b1a..29ead3828 100644
--- a/packages/cli/test/lib/binary.test.ts
+++ b/packages/cli/test/lib/binary.test.ts
@@ -1,113 +1,240 @@
/**
* Binary Management Tests
*
* Tests for shared binary helpers: install directory selection, paths,
* download URLs, locking, and binary installation.
*/
import {
chmodSync,
mkdirSync,
readFileSync,
realpathSync,
rmSync,
symlinkSync,
writeFileSync,
} from "node:fs";
import { access, readFile, writeFile } from "node:fs/promises";
import { join, sep } from "node:path";
import { afterEach, beforeEach, describe, expect, test } from "vitest";
import {
acquireLock,
compareVersions,
determineInstallDir,
fetchWithUpgradeError,
getBinaryDownloadUrl,
getBinaryFilename,
getBinaryPaths,
+ getGitHubReleaseByTagUrl,
getLegacyInstallDirs,
getPlatformBinaryName,
installBinary,
isDowngrade,
...
diff --git a/packages/cli/test/lib/delta-upgrade.mocked.test.ts b/packages/cli/test/lib/delta-upgrade.mocked.test.ts
index cd0a2fddc..f6186ab59 100644
--- a/packages/cli/test/lib/delta-upgrade.mocked.test.ts
+++ b/packages/cli/test/lib/delta-upgrade.mocked.test.ts
@@ -42,145 +42,145 @@ vi.mock("../../src/lib/constants.js", async (importOriginal) => {
import { getPlatformBinaryName } from "../../src/lib/binary.js";
import {
attemptDeltaUpgrade,
resolveNightlyDelta,
resolveStableDelta,
} from "../../src/lib/delta-upgrade.js";
// ============================================================================
// Fetch mock infrastructure
// ============================================================================
let originalFetch: typeof globalThis.fetch;
beforeEach(() => {
originalFetch = globalThis.fetch;
});
afterEach(() => {
globalThis.fetch = originalFetch;
});
function mockFetch(
fn: (url: string | URL | Request, init?: RequestInit) => ...
diff --git a/packages/cli/test/lib/ghcr.test.ts b/packages/cli/test/lib/ghcr.test.ts
index e5c81d532..a55612b5a 100644
--- a/packages/cli/test/lib/ghcr.test.ts
+++ b/packages/cli/test/lib/ghcr.test.ts
@@ -1,257 +1,365 @@
/**
* GHCR Client Tests
*
* Unit tests for the GHCR/OCI download protocol helpers.
* All HTTP calls are mocked via globalThis.fetch to avoid network access.
*/
import { afterEach, beforeEach, describe, expect, test } from "vitest";
+import { UPGRADE_SOURCES } from "../../src/lib/binary.js";
import { UpgradeError } from "../../src/lib/errors.js";
import {
downloadLayerBlob,
downloadNightlyBlob,
fetchManifest,
fetchNightlyManifest,
findLayerByFilename,
GHCR_REPO,
GHCR_TAG,
+ GhcrManifestHttpError,
getAnonymousToken,
getNightlyVersion,
listTags,
type OciManifest,
} from "../../src/lib/ghcr.js";
/** Store original fetch for restoration */
let originalFetch: typeof globalThis.fetch;
/** Helper to mock fetch without ...
diff --git a/packages/cli/test/lib/release-notes.test.ts b/packages/cli/test/lib/release-notes.test.ts
index 78963b471..ffccf4901 100644
--- a/packages/cli/test/lib/release-notes.test.ts
+++ b/packages/cli/test/lib/release-notes.test.ts
@@ -1,71 +1,77 @@
/**
* Unit Tests for Release Notes Parser & Aggregation
*
* Tests core invariants (section extraction, version filtering, truncation)
* that are hard to express as property-based tests due to format specifics.
*
* Core random-input invariants (category validity, filtering, commit parsing)
* are tested via property-based tests in release-notes.property.test.ts.
*/
import { marked } from "marked";
-import { describe, expect, test } from "vitest";
-import type { GitHubRelease } from "../../src/lib/delta-upgrade.js";
+import { afterEach, beforeEach, describe, expect, test } from "vitest";
+import { UPGRADE_SOURCES } from "../../src/lib/binary.js";
+import {
+ fetchRecentReleases,
+ type GitHubRelease,
+} from ...
diff --git a/packages/cli/test/lib/upgrade.test.ts b/packages/cli/test/lib/upgrade.test.ts
index 7c6f63a4b..d10f21dd6 100644
--- a/packages/cli/test/lib/upgrade.test.ts
+++ b/packages/cli/test/lib/upgrade.test.ts
@@ -103,60 +103,62 @@ const { spawnImpl } = vi.hoisted(() => ({
// Initialize with the real default now that fakeProcess is defined
spawnImpl.fn = () => fakeProcess(0);
vi.mock("node:child_process", async (importOriginal) => {
const orig = await importOriginal<typeof import("node:child_process")>();
return {
...orig,
spawn: (cmd: string, args: string[], opts: object) =>
spawnImpl.fn(cmd, args, opts),
};
});
// Dynamic imports: must run AFTER vi.mock() so upgrade.ts picks up the
// mocked spawn.
import { isEnoentSpawnError } from "../../src/commands/cli/upgrade.js";
import {
acquireLock,
getBinaryDownloadUrl,
isNightlyVersion,
releaseLock,
+ UPGRADE_SOURCES,
} from "../../src/lib/binary.js";
import {
clearInstallInfo,
...
diff --git a/packages/cli/test/lib/version-check.test.ts b/packages/cli/test/lib/version-check.test.ts
index 20172cd83..dd047a5cb 100644
--- a/packages/cli/test/lib/version-check.test.ts
+++ b/packages/cli/test/lib/version-check.test.ts
@@ -1,61 +1,64 @@
/**
* Version Check Logic Tests
*/
import { setTimeout as sleep } from "node:timers/promises";
-import { afterEach, beforeEach, describe, expect, test } from "vitest";
+import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
+import { UPGRADE_SOURCES } from "../../src/lib/binary.js";
import { setReleaseChannel } from "../../src/lib/db/release-channel.js";
import {
getVersionCheckInfo,
setVersionCheckInfo,
} from "../../src/lib/db/version-check.js";
+// biome-ignore lint/performance/noNamespaceImport: Vitest requires the module namespace to spy on an ESM export
+import * as deltaUpgrade from "../../src/lib/delta-upgrade.js";
import {
ApiError,
ContextError,
ValidationError,
} from ...
package.json packages/cli/package.json pnpm-lock.yaml
04867eaf9b412a62d4b1ea1c59c2b382c557bd0b ec83887a16f780f32fba4b7d710bad262dba3a22 feat(cli): add toolkit upgrade bridge 9c874298e3b57515ba36d35140627a668a46dc7b 04867eaf9b412a62d4b1ea1c59c2b382c557bd0b fix(cli): address toolkit bridge review 723375b6ce4e37884e760fa0f848e7e7e3effcc9 9c874298e3b57515ba36d35140627a668a46dc7b fix(cli): address upgrade review findings dbee7e9f2a4555e044f74dfebad002cccfa43a1f 723375b6ce4e37884e760fa0f848e7e7e3effcc9 test(cli): update toolkit upgrade fixtures 5ffa5cd0fe02c0a5dc5bec9a8a099f4135c0b447 dbee7e9f2a4555e044f74dfebad002cccfa43a1f fix(cli): harden upgrade source resolution c8c815bbecf472061ec02ab96d1a31f23f078d01 5ffa5cd0fe02c0a5dc5bec9a8a099f4135c0b447 fix(cli): validate GitHub release pagination b6484e0fb1eb0f8bce68272d538f5c3b26cd5bd5 c8c815bbecf472061ec02ab96d1a31f23f078d01 fix(cli): validate resolved upgrade metadata c6030fbf4d0c190f037c86d832edb6680d3e54ab b6484e0fb1eb0f8bce68272d538f5c3b26cd5bd5 fix(cli): bind nightly manifests to ...
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:309: const resolved = await resolveExistingUpgradeVersion(target); 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:341: ? await resolveLatestUpgradeVersion(channel) 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:497:export async function fetchLatestFromGitHubWithSource( 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:549:export async function fetchLatestFromGitHub( 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:554: await fetchLatestFromGitHubWithSource( 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:708: ? fetchLatestFromGitHub() 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:713:export function resolveLatestUpgradeVersion( 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:719: : ...
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:387: (await client.listTags(opts.token, PATCH_TAG_PREFIX, opts.signal)); 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:401: chainTags.map((tag) => client.fetchManifest(opts.token, tag, opts.signal)) 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:258:export async function getAnonymousToken( 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:310:export async function fetchManifest( 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:357:export async function fetchNightlyManifest( 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:362: return await fetchManifest(token, GHCR_TAG, signal, source); 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:432:export async function downloadNightlyBlob( 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:599:export ...
{
"name": "sentry",
"version": "0.45.0-dev.0",
"description": "Sentry CLI - A command-line interface for using Sentry built by robots and humans for robots and humans",
"repository": {
"type": "git",
"url": "git+https://github.com/getsentry/cli.git"
},
"main": "./dist/index.cjs",
"type": "module",
"types": "./dist/index.d.cts",
"exports": {
".": {
"import": {
"types": "./dist/index.d.mts",
"default": "./dist/index.mjs"
},
"require": {
"types": "./dist/index.d.cts",
"default": "./dist/index.cjs"
}
}
},
"bin": {
"sentry": "./dist/bin.cjs"
},
"files": [
"dist/bin.cjs",
"dist/index.cjs",
"dist/index.mjs",
"dist/index.d.cts",
"dist/index.d.mts",
"dist/ink-app.js",
"dist/node-sqlite3-wasm.wasm",
"dist/assets/spleen-8x16.bin",
"dist/vendor/symbolic_bg.wasm",
"LICENSE.md",
"THIRD_PARTY_LICENSES.md",
"README.md"
],
"license": ...
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:892: return fetchChangelog({
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/release-notes.test.ts:339: const changelog = await fetchChangelog({
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/release-notes.test.ts:376: const changelog = await fetchChangelog({
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/release-notes.test.ts:392: const changelog = await fetchChangelog({
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/release-notes.test.ts:410: const changelog = await fetchChangelog({
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/release-notes.test.ts:429: const changelog = await fetchChangelog({
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/release-notes.test.ts:443: const changelog = await ...
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/custom-ca.ts:305:export function customFetch( /** * Custom CA certificate loading for corporate TLS proxies. * * Reads CA bundles from (in priority order): * 1. `sentry cli defaults ca-cert` (stored path in SQLite) * 2. `NODE_EXTRA_CA_CERTS` env var * * Returns a `tls` options object for Bun's `fetch()`. On the Node.js npm * distribution, Node natively honors `NODE_EXTRA_CA_CERTS` so the extra * `tls.ca` option is harmless (ignored by Node's fetch). * * Security model: When the CA source is an env var (not a stored default) * AND the target is SaaS (`*.sentry.io`), a one-time warning is logged. * `sentry cli defaults ca-cert` silences the warning — the user has * explicitly acknowledged the custom CA. See CLI-1K6 plan for the full * threat model discussion. ...
9525f72c70fabbb23ec21d13cf86add58f6287cc:pnpm-lock.yaml:2696: binpatch@0.4.2:
9525f72c70fabbb23ec21d13cf86add58f6287cc:pnpm-lock.yaml:7801: binpatch@0.4.2: {}
9525f72c70fabbb23ec21d13cf86add58f6287cc:pnpm-lock.yaml:221: binpatch:
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:167: cleanup: () => base.cleanup(),
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:455: await client.downloadBlobBuffer(opts.token, digest, opts.signal)
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:488: `SHA-256 mismatch after patching: got ${sha256}, expected ${chain.expectedSha256}`
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:17: unlinkSync,
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:27: cleanupOldBinary,
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:275: * Start cleanup of the .old binary for this install.
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:280: cleanupOldBinary(oldPath);
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:1250: ...
2320 JSON.stringify({
2321 layers: [],
2322 annotations: { version: "0.14.0-dev.124" },
2323 }),
2324 { status: 200 }
2325 );
2326 }
2327 return new Response("Unexpected", { status: 500 });
2328 });
2329
2330 await expect(
2331 executeUpgrade(
2332 "curl",
2333 "0.14.0-dev.123",
2334 undefined,
2335 false,
2336 undefined,
2337 UPGRADE_SOURCES[0]
2338 )
2339 ).rejects.toMatchObject({ reason: "network_error" });
2340 expect(requests.some((request) => request.includes("/blobs/"))).toBe(false);
2341 });
2342 });
2343
2344 describe("downloadBinaryToTemp offline errors", () => {
2345 const offlineBinDir = join(TEST_TMP_DIR, "upgrade-offline-test");
2346 const offlineInstallPath = join(offlineBinDir, "sentry");
2347
2348 beforeEach(() => {
2349 clearInstallInfo();
...
2440
2441 test("throws execution_failed UpgradeError after retry budget is exhausted", async () => {
2442 // Serve an empty gzip payload. The outer stream completes cleanly, so
2443 // neither fetchWithUpgradeError nor streamDecompressToFile throws —
2444 // but `destPath` ends up with zero bytes. The verification loop polls
2445 // 5 times (~3.1s cumulative) before giving up with an actionable
2446 // error; without it the caller would spawn the empty file and fail
2447 // with "Executable not found in $PATH" (the CLI-1D3 symptom).
2448 const emptyGzip = gzipSync(new Uint8Array(0));
2449 mockFetch(async (url) => {
2450 const urlStr = String(url);
2451 if (urlStr.endsWith(".gz")) {
2452 return new Response(emptyGzip, { status: 200 });
2453 }
2454 return new Response("Not Found", { status: 404 });
2455 });
2456
2457 try {
2458 await downloadBinaryToTemp("0.26.1");
...
1080 });
1081
1082 proc.on("close", (code) => {
1083 if (code === 0) {
1084 resolve();
1085 } else {
1086 reject(
1087 new UpgradeError(
1088 "execution_failed",
1089 `${pm} install failed with exit code ${code}`
1090 )
1091 );
1092 }
1093 });
1094
1095 proc.on("error", (err) => {
1096 reject(
1097 new UpgradeError("execution_failed", `${pm} failed: ${err.message}`)
1098 );
1099 });
1100 });
1101 }
1102
1103 /**
1104 * Execute the upgrade using the appropriate method.
1105 *
1106 * For curl installs, downloads the new binary to a temp path and returns a
1107 * DownloadResult with the binary path and lock path. The caller should spawn
1108 * `setup --install` on the new binary, then release the lock. ...
ec83887a16f780f32fba4b7d710bad262dba3a22:packages/cli/src/lib/upgrade.ts:902:export async function downloadBinaryToTemp(
890 *
891 * If the child resolves to the same install path, it takes over the lock via
892 * process.ppid recognition in acquireLock — the parent's subsequent release
893 * is then a harmless no-op.
894 *
895 * @param version - Target version to download (used for display and comparison)
896 * @param downloadTag - Git tag to use in the download URL. Defaults to `version`.
897 * Pass `NIGHTLY_TAG` ("nightly") when installing from the rolling nightly release
898 * so the URL points to the prerelease assets regardless of the version string.
899 * @returns The downloaded binary path and lock path to release
900 * @throws {UpgradeError} When download fails
901 */
902 export async function downloadBinaryToTemp(
903 version: string,
904 downloadTag?: string,
905 offline?: OfflineMode,
906 setMessage?: SetMessage
907 ): Promise<DownloadResult> {
908 const { tempPath, lockPath } = getCurlInstallPaths();
909
...
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/binary.ts:238:export function getBinaryDownloadUrl(
{
"compilerOptions": {
"lib": ["ESNext"],
"target": "ESNext",
"module": "ESNext",
"jsx": "react-jsx",
"moduleDetection": "force",
"allowJs": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"verbatimModuleSyntax": true,
"noEmit": true,
"resolveJsonModule": true,
"strict": true,
"skipLibCheck": true,
"noFallthroughCasesInSwitch": true,
"noUncheckedIndexedAccess": true,
"noImplicitOverride": true,
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"noUnusedLocals": false,
"noUnusedParameters": false,
"noPropertyAccessFromIndexSignature": false,
"strictNullChecks": true
},
"include": ["src/**/*.ts", "src/**/*.tsx"],
"exclude": ["node_modules", "dist"]
}
import { existsSync } from "node:fs";
import { dirname, join } from "node:path";
import { defineConfig, type Plugin } from "vitest/config";
const JS_EXT_RE = /\.js$/;
/**
* Vite plugin to rewrite lazy ...