Dashboard › cli › Session 0vl3ZcZkl9YX
0vl3ZcZkl9YXpkOxdYou are the replacement READ-ONLY correctness/adversarial merge-gate reviewer for getsentry/cli PR #1569. The prior reviewer returned empty twice. A substantive evidence-based response is mandatory. Repository: /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade. Review exact immutable base ec83887a16f780f32fba4b7d710bad262dba3a22 and exact immutable head 9525f72c70fabbb23ec21d13cf86add58f6287cc using Git objects only. Never inspect or modify the worktree, mutate VCS, or write files. Verify merge-base/base ancestry, base/head trees, changed paths, full diff, and git diff --check. Inspect every changed source and test object. Adversarially verify: Toolkit-first fallback advances only on genuine typed HTTP 404; non-404 HTTP, transport, TLS, malformed success never fall through; successful source sticks through stable/nightly exact/latest lookup, token scope,? no source should mix through manifests, blobs, delta, prefetch, changelog, setup; trusted GitHub pagination and cycle detection; response-body cancellation identity, body-termination transport typing, malformed JSON terminal typing; npm runtime shape and canonical stable SemVer; pinned npm/pnpm/bun/yarn 404 versus 401/403/429/500 with one request and no subprocess; Toolkit only cli@VERSION and legacy vVERSION compatibility; malformed/prerelease standalone pins and draft/prerelease exact releases fail before download/setup; GHCR trimmed non-empty token, OCI manifest/layer/digest/size/annotation and tag-list validation, array annotations rejection, repeated tag cursor protection; redirect auth removal; exact nightly annotation binding; artifact versus persisted tracking channel; offline and transport-only cache fallback; delta final SHA verification and cleanup; strengthened nightly end-to-end Toolkit blob/setup success assertion. ...
OBJECTS commit commit BASE ec83887a16f780f32fba4b7d710bad262dba3a22 110d6530e57ca4fbfb2a3f98e52013d2668b5b5e e7beceb6ba09b64dc277ae1732df04e65500062b 1788976872 fix(event): Prevent immediate retry of rate-limited org in event view fallback (#1566) HEAD 9525f72c70fabbb23ec21d13cf86add58f6287cc 22f49363a13e32c36dcd9e1ca95916809c2dc271 a84012184c79c2566c7466aa6beda5f767199218 1789037022 fix(cli): harden upgrade metadata validation MERGE_BASE ec83887a16f780f32fba4b7d710bad262dba3a22 ANCESTRY_OK PATCH_SHA256 4950719273dc17d5a9e061b8cfe70c62131fe3299dc1799d9c09bb522b112193 - DIFF_CHECK DIFF_CHECK_OK
COMMITS 04867eaf9b412a62d4b1ea1c59c2b382c557bd0b cc356b77799633918710e0c8d358034894f86147 ec83887a16f780f32fba4b7d710bad262dba3a22 feat(cli): add toolkit upgrade bridge 9c874298e3b57515ba36d35140627a668a46dc7b 4dd2c6153592d15804b1aaef8da9015e0df0345b 04867eaf9b412a62d4b1ea1c59c2b382c557bd0b fix(cli): address toolkit bridge review 723375b6ce4e37884e760fa0f848e7e7e3effcc9 c0c0ace97578535769b33929fb2494113eec3e5d 9c874298e3b57515ba36d35140627a668a46dc7b fix(cli): address upgrade review findings dbee7e9f2a4555e044f74dfebad002cccfa43a1f 024dbc7afa15d0632eb4c15ed6c808dab974a9c5 723375b6ce4e37884e760fa0f848e7e7e3effcc9 test(cli): update toolkit upgrade fixtures 5ffa5cd0fe02c0a5dc5bec9a8a099f4135c0b447 a1cbad382fe2fec2c40f31bd64eaf1898b779428 dbee7e9f2a4555e044f74dfebad002cccfa43a1f fix(cli): harden upgrade source resolution c8c815bbecf472061ec02ab96d1a31f23f078d01 ecd342742030111dd72ce73739016ac3b803d521 5ffa5cd0fe02c0a5dc5bec9a8a099f4135c0b447 fix(cli): validate GitHub release ...
BASE_BLOBS 100644 blob 8d8282368307f48d894a54c6315a9c94af9f3ed2 35436 packages/cli/src/commands/cli/upgrade.ts 100644 blob 75582612a31fba6ee68111fcfb144f34c28eb58b 20123 packages/cli/src/lib/binary.ts 100644 blob ec709b8c8d49447c9b7a5376b61fdf19c0468f9d 18694 packages/cli/src/lib/delta-upgrade.ts 100644 blob f81b8c70c6c36f026b268c15b1ce16ab67e30f66 33782 packages/cli/src/lib/errors.ts 100644 blob 43d6deb7e6cf9e7911f868b757d52a6ad7d2610e 16146 packages/cli/src/lib/ghcr.ts 100644 blob ddf617535ea20623682cff59d28c8687aaa9e8cd 23782 packages/cli/src/lib/release-notes.ts 100644 blob 945adc48d962ce15e82b986e93196caabea21672 38103 packages/cli/src/lib/upgrade.ts 100644 blob c69ff04663447b2ae2ce62093b527554983c1568 16298 packages/cli/src/lib/version-check.ts 100644 blob cd1514eb0c2f3c5db5d5ac9bed0f2d37be35de13 7816 packages/cli/test/commands/cli.test.ts 100644 blob ca9688e46730b0cb31a85c9538a65a2eca1e004c 39108 packages/cli/test/commands/cli/upgrade.test.ts 100644 blob ...
diff --git a/packages/cli/src/commands/cli/upgrade.ts b/packages/cli/src/commands/cli/upgrade.ts index 8d8282368307f48d894a54c6315a9c94af9f3ed2..b3039d86b5935a806f265c906460686d83ee2c64 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 75582612a31fba6ee68111fcfb144f34c28eb58b..e64f5059e53d1321c3910ba46ead18391343348f 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 { ...
diff --git a/packages/cli/src/lib/delta-upgrade.ts b/packages/cli/src/lib/delta-upgrade.ts
index ec709b8c8d49447c9b7a5376b61fdf19c0468f9d..204875dce4325434ff6c4b7b0fcc4d2abb9bbd50 100644
--- a/packages/cli/src/lib/delta-upgrade.ts
+++ b/packages/cli/src/lib/delta-upgrade.ts
@@ -1,368 +1,461 @@
/** 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,
...
diff --git a/packages/cli/src/lib/errors.ts b/packages/cli/src/lib/errors.ts
index f81b8c70c6c36f026b268c15b1ce16ab67e30f66..b21505cd11bdfddf7c51f0b89d942cc850ea29fe 100644
--- a/packages/cli/src/lib/errors.ts
+++ b/packages/cli/src/lib/errors.ts
@@ -541,160 +541,168 @@ export function validationError(
headline: string,
examples: string[],
field?: string,
note?: string
): ValidationError {
return new ValidationError(
buildValidationMessage(headline, examples, note),
field
);
}
/**
* Input validation errors.
*
* @param message - Validation failure description
* @param field - Name of the invalid field
*/
export class ValidationError extends CliError {
readonly field?: string;
constructor(message: string, field?: string) {
super(message, EXIT.VALIDATION);
this.name = "ValidationError";
this.field = field;
}
}
/**
* OAuth device flow errors (RFC 8628).
*
* @param code - OAuth error code (e.g., ...
diff --git a/packages/cli/src/lib/ghcr.ts b/packages/cli/src/lib/ghcr.ts index 43d6deb7e6cf9e7911f868b757d52a6ad7d2610e..5bca7c0615a2cdd1ee956e88259d4d046e3bc97b 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 ddf617535ea20623682cff59d28c8687aaa9e8cd..903ca51f67706a30a1e203ff1df3b288018ed3d5 100644 --- a/packages/cli/src/lib/release-notes.ts +++ b/packages/cli/src/lib/release-notes.ts @@ -1,122 +1,129 @@ /** * 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 945adc48d962ce15e82b986e93196caabea21672..b29d67ff2da3f1ba4940ec971aa9b11f41ed390f 100644 --- a/packages/cli/src/lib/upgrade.ts +++ b/packages/cli/src/lib/upgrade.ts @@ -1,169 +1,267 @@ /** * 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. ...
diff --git a/packages/cli/src/lib/version-check.ts b/packages/cli/src/lib/version-check.ts
index c69ff04663447b2ae2ce62093b527554983c1568..34393b1c777e09c0a1ab683a493a6f15d91811c3 100644
--- a/packages/cli/src/lib/version-check.ts
+++ b/packages/cli/src/lib/version-check.ts
@@ -1,391 +1,396 @@
/**
* Background version check for "new version available" notifications.
*
* For nightly builds (CLI_VERSION contains "-dev.<timestamp>"), checks GHCR for the
* latest nightly version via the OCI manifest annotation. For stable builds,
* checks GitHub Releases. Results are cached in the database and shown on
* subsequent runs.
*/
// biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import
import * as Sentry from "@sentry/node-core/light";
import { compare as semverCompare } from "semver";
+import type { UpgradeSource } from "./binary.js";
import { CLI_VERSION } from "./constants.js";
import { getReleaseChannel } from ...
diff --git a/packages/cli/test/commands/cli.test.ts b/packages/cli/test/commands/cli.test.ts
index cd1514eb0c2f3c5db5d5ac9bed0f2d37be35de13..4eec11e9d8fa20ef1c55738a82df0a7f3bbad6bd 100644
--- a/packages/cli/test/commands/cli.test.ts
+++ b/packages/cli/test/commands/cli.test.ts
@@ -97,140 +97,134 @@ describe("feedbackCommand.func", () => {
// Sentry is disabled in test environment (no DSN)
await expect(
func.call(mockContext, {}, "test", "feedback")
).rejects.toThrow("Feedback not sent: telemetry is disabled.");
});
});
// Test the upgrade command func
describe("upgradeCommand.func", () => {
let originalFetch: typeof globalThis.fetch;
let restoreStderr: (() => void) | undefined;
beforeEach(() => {
originalFetch = globalThis.fetch;
});
afterEach(() => {
restoreStderr?.();
restoreStderr = undefined;
globalThis.fetch = originalFetch;
});
// Note: We skip testing "unknown installation method" case because
// ...
285 describe("sentry cli upgrade", () => {
305 describe("--check mode", () => {
306 test("shows the current and latest stable versions", async () => {
326 test("shows upgrade command hint when newer version available", async () => {
345 test("shows version-specific upgrade hint when user-specified version", async () => {
366 test("resolves a pinned check target from its exact source", async () => {
413 test("uses the cached target only after a transport failure", async () => {
432 test("uses the cached target after response body transport failure", async () => {
486 describe("stable target", () => {
487 test("reports the resolved stable target in check mode", async () => {
507 describe("brew method", () => {
508 test("errors immediately when specific version requested with brew", async () => {
523 test("check mode works for brew method", async () => {
543 test("uses the selected legacy source for ...
44 describe("getBinaryDownloadUrl", () => {
45 test("builds correct URL for current platform", () => {
58 test("includes .exe suffix on Windows", () => {
69 describe("UPGRADE_SOURCES", () => {
70 test("checks Toolkit before the legacy CLI repository", () => {
86 describe("resolveUpgradeSource", () => {
87 test("uses the first source when it exists", async () => {
109 test("falls back to the legacy source only on HTTP 404", async () => {
152 test("does not fall back on a network failure", async () => {
170 test("fails after every source returns 404", async () => {
191 describe("getBinaryFilename", () => {
192 test("returns sentry on non-Windows", () => {
199 describe("getBinaryPaths", () => {
200 test("returns all derived paths from install path", () => {
210 describe("samePath", () => {
211 test("matches identical paths", () => {
217 test("distinguishes genuinely different paths", () => {
223 ...
92 describe("resolveStableDelta", () => {
93 test("resolves and applies a stable delta patch", async () => {
174 test("returns null when no chain is available", async () => {
198 describe("resolveNightlyDelta", () => {
199 test("returns null when nightly manifest has no .gz layer", async () => {
255 test("returns null when no patch chain exists", async () => {
315 describe("attemptDeltaUpgrade", () => {
316 test("returns null for cross-channel upgrade (stable → nightly)", async () => {
326 test("returns null when stable chain resolution fails", async () => {
346 test("catches errors from patch application and returns null", async () => {
418 test("returns DeltaResult with telemetry on successful stable patch", async () => {
--- delta-upgrade.test.ts ---
101 describe("getPlatformBinaryName", () => {
102 test("returns a string starting with 'sentry-'", () => {
107 test("contains platform and arch components", () => {
...
80 describe("getAnonymousToken", () => {
81 test("returns token from successful response", async () => {
96 test("uses the selected source's GHCR repository", async () => {
109 test("throws UpgradeError on HTTP error", async () => {
118 test("throws UpgradeError on network failure", async () => {
129 test("propagates caller cancellation without retrying", async () => {
150 test("preserves a primitive caller cancellation reason without retrying", async () => {
165 test("throws UpgradeError when response has no token field", async () => {
180 test("rejects a non-string token", async () => {
196 test("preserves cancellation during token body consumption", async () => {
214 describe("fetchNightlyManifest", () => {
215 test("fetches manifest with correct headers", async () => {
240 test("uses the selected source's GHCR repository", async () => {
252 test("throws UpgradeError on HTTP error", async () => {
261 ...
169 describe("parseInstallationMethod", () => {
170 test("parses valid methods", () => {
179 test("parses case-insensitively", () => {
185 test("throws on invalid method", () => {
192 describe("fetchLatestFromGitHub", () => {
193 test("selects the latest CLI-prefixed Toolkit release", async () => {
215 test("follows Toolkit release pagination to find the latest CLI release", async () => {
246 test("preserves an arbitrary abort reason during pagination", async () => {
267 test("selects the highest CLI SemVer across Toolkit release pages", async () => {
287 test("rejects GitHub release pagination outside the selected source", async () => {
305 test("classifies malformed GitHub release pagination as a network error", async () => {
322 test("rejects cyclic GitHub release pagination", async () => {
340 test("falls back to the legacy latest release only on Toolkit HTTP 404", async () => {
359 test("rejects an object from the ...
190 });
191
192 describe("fetchLatestFromGitHub", () => {
193 test("selects the latest CLI-prefixed Toolkit release", async () => {
194 const requests: string[] = [];
195 mockFetch(async (url) => {
196 requests.push(String(url));
197 return new Response(
198 JSON.stringify([
199 { tag_name: "mcp@9.0.0" },
200 { tag_name: "cli@not-a-version" },
201 { tag_name: "cli@99.0.0-dev.1", prerelease: false },
202 { tag_name: "cli@1.2.3" },
203 { tag_name: "cli@1.3.0" },
204 ]),
205 { status: 200 }
206 );
207 });
208
209 await expect(fetchLatestFromGitHub()).resolves.toBe("1.3.0");
210 expect(requests).toEqual([
211 "https://api.github.com/repos/getsentry/toolkit/releases?per_page=100",
212 ]);
213 });
214
215 test("follows Toolkit release pagination to find the latest CLI release", async ...
780 );
781
782 const version = await fetchLatestVersion("curl");
783 expect(version).toBe("3.0.0");
784 });
785 });
786
787 describe("versionExists", () => {
788 test.each([
789 401, 403, 429, 500,
790 ])("does not classify npm HTTP %i as a missing version", async (status) => {
791 mockFetch(async () => new Response(null, { status }));
792
793 await expect(versionExists("npm", "1.0.0")).rejects.toMatchObject({
794 reason: "network_error",
795 });
796 });
797
798 test.each([
799 "not-semver",
800 "v1.2.3",
801 "1.2.3-beta.1",
802 "mcp@1.0.0",
803 ])("rejects invalid standalone stable version %s before network access", async (version) => {
804 let requests = 0;
805 mockFetch(async () => {
806 requests += 1;
807 return new Response(null, { status: 200 });
808 });
809
810 await ...
2020 });
2021 });
2022
2023 describe("isNightlyVersion", () => {
2024 test("returns true for nightly version strings", () => {
2025 expect(isNightlyVersion("0.0.0-dev.1740000000")).toBe(true);
2026 expect(isNightlyVersion("0.0.0-dev.1")).toBe(true);
2027 });
2028
2029 test("returns false for stable version strings", () => {
2030 expect(isNightlyVersion("1.0.0")).toBe(false);
2031 expect(isNightlyVersion("0.13.0")).toBe(false);
2032 expect(isNightlyVersion("2.0.0-beta.1")).toBe(false);
2033 expect(isNightlyVersion("0.0.0-dev")).toBe(false);
2034 });
2035 });
2036
2037 describe("fetchLatestNightlyVersion", () => {
2038 test("preserves an already-aborted signal reason", async () => {
2039 const controller = new AbortController();
2040 const reason = { kind: "cancelled" };
2041 controller.abort(reason);
2042
2043 await expect(fetchLatestNightlyVersion(controller.signal)).rejects.toBe(
...
280 }
281 return new Response("Not Found", { status: 404 });
282 });
283 }
284
285 describe("sentry cli upgrade", () => {
286 let testDir: string;
287 let restoreStderr: (() => void) | undefined;
288
289 beforeEach(() => {
290 testDir = join(
291 "/tmp",
292 `upgrade-cmd-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
293 );
294 mkdirSync(testDir, { recursive: true });
295 originalFetch = globalThis.fetch;
296 });
297
298 afterEach(() => {
299 restoreStderr?.();
300 restoreStderr = undefined;
301 globalThis.fetch = originalFetch;
302 rmSync(testDir, { recursive: true, force: true });
303 });
304
305 describe("--check mode", () => {
306 test("shows the current and latest stable versions", async () => {
307 mockGitHubVersion("1.0.0");
308
309 const { context, getOutput, restore } = ...
840 // ---------------------------------------------------------------------------
841
842 /**
843 * Create a fake ChildProcess-like object that emits "close" with the given
844 * exit code on the next microtask. Used to mock child_process.spawn in tests.
845 */
846 function fakeChildProcess(exitCode: number): child_process.ChildProcess {
847 const { EventEmitter } = require("node:events");
848 const emitter = new EventEmitter();
849 // Emit "close" asynchronously so the caller can attach listeners first
850 queueMicrotask(() => emitter.emit("close", exitCode));
851 return emitter as unknown as child_process.ChildProcess;
852 }
853
854 describe("sentry cli upgrade — curl full upgrade path (child_process.spawn spy)", () => {
855 useTestConfigDir("test-upgrade-spawn-");
856
857 let testDir: string;
858 let spawnedArgs: Array<{ cmd: string; args: string[] }>;
859 let spawnSpy: ReturnType<typeof spyOn>;
...
1250 });
1251
1252 describe("sentry cli upgrade — migrateToStandaloneForNightly (child_process.spawn spy)", () => {
1253 useTestConfigDir("test-upgrade-migrate-");
1254
1255 let testDir: string;
1256 let migrateSpawnSpy: ReturnType<typeof spyOn>;
1257 let restoreStderr: (() => void) | undefined;
1258
1259 /** Redirect curl install paths to temp dir instead of ~/.sentry/bin/ */
1260 const migrateBinDir = join(TEST_TMP_DIR, "upgrade-migrate-bin");
1261 const migrateBinName = process.platform === "win32" ? "sentry.exe" : "sentry";
1262 const migrateInstallPath = join(migrateBinDir, migrateBinName);
1263
1264 beforeEach(() => {
1265 testDir = join(
1266 TEST_TMP_DIR,
1267 `upgrade-migrate-test-${Date.now()}-${Math.random().toString(36).slice(2)}`
1268 );
1269 mkdirSync(testDir, { recursive: true });
1270 mkdirSync(migrateBinDir, { recursive: true });
1271 // Redirect getCurlInstallPaths() to ...
1 /**
2 * Binary Management Tests
3 *
4 * Tests for shared binary helpers: install directory selection, paths,
5 * download URLs, locking, and binary installation.
6 */
7
8 import {
9 chmodSync,
10 mkdirSync,
11 readFileSync,
12 realpathSync,
13 rmSync,
14 symlinkSync,
15 writeFileSync,
16 } from "node:fs";
17 import { access, readFile, writeFile } from "node:fs/promises";
18 import { join, sep } from "node:path";
19 import { afterEach, beforeEach, describe, expect, test } from "vitest";
20 import {
21 acquireLock,
22 compareVersions,
23 determineInstallDir,
24 fetchWithUpgradeError,
25 getBinaryDownloadUrl,
26 getBinaryFilename,
27 getBinaryPaths,
28 getGitHubReleaseByTagUrl,
29 getLegacyInstallDirs,
30 getPlatformBinaryName,
31 installBinary,
32 isDowngrade,
33 isMusl,
34 parseUpgradeJson,
...
70 }
71
72 beforeEach(() => {
73 originalFetch = globalThis.fetch;
74 });
75
76 afterEach(() => {
77 globalThis.fetch = originalFetch;
78 });
79
80 describe("getAnonymousToken", () => {
81 test("returns token from successful response", async () => {
82 mockFetch(async (url) => {
83 expect(String(url)).toContain(
84 `https://ghcr.io/token?scope=repository:${GHCR_REPO}:pull`
85 );
86 return new Response(JSON.stringify({ token: "test-token-abc" }), {
87 status: 200,
88 headers: { "Content-Type": "application/json" },
89 });
90 });
91
92 const token = await getAnonymousToken();
93 expect(token).toBe("test-token-abc");
94 });
95
96 test("uses the selected source's GHCR repository", async () => {
97 mockFetch(async (url) => {
98 ...
290 expect(countListItems(tokens)).toBe(3);
291 });
292
293 test("returns 0 for non-list content", () => {
294 const tokens = marked.lexer("Just a paragraph.");
295 expect(countListItems(tokens)).toBe(0);
296 });
297
298 test("returns 0 for empty token array", () => {
299 expect(countListItems([])).toBe(0);
300 });
301 });
302
303 describe("fetchChangelog source affinity", () => {
304 const toolkitSource = UPGRADE_SOURCES[0]!;
305 const legacySource = UPGRADE_SOURCES[1]!;
306 let originalFetch: typeof globalThis.fetch;
307
308 beforeEach(() => {
309 originalFetch = globalThis.fetch;
310 });
311
312 afterEach(() => {
313 globalThis.fetch = originalFetch;
314 });
315
316 test("fetches stable releases only from the explicitly selected Toolkit source", async () => {
317 const requestedUrls: string[] = [];
318 globalThis.fetch = ...
810 fn: (url: string | URL | Request, init?: RequestInit) => Promise<Response>
811 ): void {
812 globalThis.fetch = fn as typeof globalThis.fetch;
813 }
814
815 /** Store original fetch for restoration */
816 let originalFetch: typeof globalThis.fetch;
817
818 beforeEach(() => {
819 originalFetch = globalThis.fetch;
820 });
821
822 afterEach(() => {
823 globalThis.fetch = originalFetch;
824 });
825
826 // fetchRecentReleases
827
828 describe("fetchRecentReleases", () => {
829 test("returns releases from GitHub API", async () => {
830 const releases: GitHubRelease[] = [
831 makeRelease("cli@0.14.0", [makeAsset({ name: "sentry-linux-x64" })]),
832 makeRelease("cli@0.13.0", [makeAsset({ name: "sentry-linux-x64" })]),
833 makeRelease("mcp@9.0.0", [makeAsset({ name: "sentry-linux-x64" })]),
834 ];
835
836 mockFetch(async (url) => {
837 ...
packages/cli/package.json packages/cli/test/init-eval/templates/express-app/package.json packages/cli/test/init-eval/templates/nextjs-app/package.json packages/cli/test/init-eval/templates/react-vite-app/package.json packages/cli/test/init-eval/templates/sveltekit-app/package.json
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/package.json:111: "binpatch": "^0.4.2",
9525f72c70fabbb23ec21d13cf86add58f6287cc:pnpm-lock.yaml:221: binpatch:
9525f72c70fabbb23ec21d13cf86add58f6287cc:pnpm-lock.yaml:2696: binpatch@0.4.2:
9525f72c70fabbb23ec21d13cf86add58f6287cc:pnpm-lock.yaml:7801: binpatch@0.4.2: {}
diff --git a/packages/cli/test/lib/delta-upgrade.mocked.test.ts b/packages/cli/test/lib/delta-upgrade.mocked.test.ts
index cd0a2fddc29722048fa5f8ec0efa268406242e08..f6186ab59706e32b5624f094934060e99805738f 100644
--- a/packages/cli/test/lib/delta-upgrade.mocked.test.ts
+++ b/packages/cli/test/lib/delta-upgrade.mocked.test.ts
@@ -62,105 +62,105 @@ afterEach(() => {
function mockFetch(
fn: (url: string | URL | Request, init?: RequestInit) => Promise<Response>
): void {
globalThis.fetch = fn as typeof globalThis.fetch;
}
// ============================================================================
// Helpers
// ============================================================================
const BINARY_NAME = getPlatformBinaryName();
function versionHex(version: string): string {
return Array.from(version)
.map((c) => c.charCodeAt(0).toString(16).padStart(2, "0"))
.join("");
}
function tempFile(name: string): string {
return join(
tmpdir(),
...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_08b0229200014o4IWFejLNlqBu
* and `org.opencontainers.image.created` (push time).
*/
export type OciLayer = {
/** Content-addressable digest for the blob (e.g., "sha256:abc123...") */
digest: string;
/** MIME type of the layer content */
mediaType: string;
/** Size in bytes */
size: number;
/** Per-layer OCI annotations */
annotations?: Record<string, string>;
};
/**
* OCI image manifest returned by the registry.
*
* The `annotations` map at the manifest level holds metadata about the
* nightly push, including the `version` string baked in during `oras push`.
*/
export type OciManifest = {
/** OCI manifest schema version (always 2) */
schemaVersion: number;
/** Manifest media type */
mediaType?: string;
/** Config layer (empty for ORAS artifacts) */
config?: OciLayer;
/** Content layers — one per binary/file pushed */
...
400 });
401
402 describe("abortPendingVersionCheck", () => {
403 test("does not throw when no pending check", () => {
404 // Should be safe to call even when nothing is pending
405 expect(() => abortPendingVersionCheck()).not.toThrow();
406 });
407
408 test("does not throw when called multiple times", () => {
409 // Should be safe to call multiple times
410 expect(() => {
411 abortPendingVersionCheck();
412 abortPendingVersionCheck();
413 abortPendingVersionCheck();
414 }).not.toThrow();
415 });
416 });
417
418 describe("maybeCheckForUpdateInBackground", () => {
419 useTestConfigDir("test-version-bg-");
420 let savedNoUpdateCheck: string | undefined;
421 let originalFetch: typeof globalThis.fetch;
422
423 beforeEach(() => {
424 // Save and clear the env var to test real implementation
425 savedNoUpdateCheck = ...
--- packages/cli/src/commands/cli/upgrade.ts ---
39 import { UpgradeError, UpgradeTransportError } from "../../lib/errors.js";
47 fetchChangelog,
59 resolveExistingUpgradeVersion,
60 resolveLatestUpgradeVersion,
62 versionExists,
166 * Returns `null` when `resolveTargetVersion` returns a "done" result
169 async function resolveTargetWithFallback(opts: {
211 const resolved = await resolveTargetVersion(resolveOpts);
225 if (method !== "curl" || !(error instanceof UpgradeTransportError)) {
296 async function resolvePinnedVersion(
301 if (!(await versionExists(lookupMethod, target))) {
309 const resolved = await resolveExistingUpgradeVersion(target);
322 async function resolveTargetVersion(
336 source = await resolvePinnedVersion(lookupMethod, pinnedTarget);
341 ? await resolveLatestUpgradeVersion(channel)
514 // CLI-1D3). ...
90 signal?: AbortSignal;
91 };
92
93 /**
94 * Fetch with timeout and retry for GHCR requests.
95 *
96 * GHCR exhibits cold-start latency spikes (126ms → 30s for identical
97 * requests). A short timeout + retry keeps the worst case at ~20s instead
98 * of 30s, and helps when the first request hits a cold instance.
99 *
100 * @param url - Request URL
101 * @param init - Fetch init options (signal will be added/overridden)
102 * @param context - Human-readable context for error messages
103 * @param options - Retry options (timeout override, external abort signal)
104 * @returns Response from a successful fetch
105 * @throws {UpgradeError} On all attempts exhausted
106 */
107 async function fetchWithRetry(
108 url: string,
109 init: RequestInit,
110 context: string,
111 options?: RetryOptions
112 ): Promise<Response> {
113 const timeout = options?.timeout ?? ...
9525f72c70fabbb23ec21d13cf86add58f6287cc:.github/workflows/ci.yml:332: curl -L "https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F0.29.0/${RCODESIGN_ARCHIVE}" -o 'rcodesign.tar.gz'
9525f72c70fabbb23ec21d13cf86add58f6287cc:.github/workflows/cleanup-nightlies.yml:22: curl -sfLo "$TARBALL" "https://github.com/oras-project/oras/releases/download/v${VERSION}/${TARBALL}"
9525f72c70fabbb23ec21d13cf86add58f6287cc:.github/workflows/cleanup-nightlies.yml:30: - name: Prune old versioned nightly and patch tags
9525f72c70fabbb23ec21d13cf86add58f6287cc:.github/workflows/cleanup-nightlies.yml:55: version="${tag#nightly-}"
9525f72c70fabbb23ec21d13cf86add58f6287cc:.github/workflows/sentry-release.yml:38: VERSION: ${{ github.event.release.tag_name || inputs.version }}
9525f72c70fabbb23ec21d13cf86add58f6287cc:.github/workflows/sentry-release.yml:74: --url "https://github.com/${{ github.repository ...
Your replacement correctness review returned EMPTY. Resume the SAME exact-head immutable review now. A non-empty response is mandatory. If any repository or tool access fails, report BLOCKED first with the exact command/error and end DO-NOT-MERGE. Otherwise provide substantive findings-first evidence for exact base ec83887a16f780f32fba4b7d710bad262dba3a22 and exact head 9525f72c70fabbb23ec21d13cf86add58f6287cc. Use immutable Git objects only; never inspect or modify the worktree, write files, or mutate VCS. State merge base, immutable base/head trees, changed paths inspected, patch hash or equivalent immutable verification, and git diff --check result. Re-evaluate every correctness contract from the prior prompt with exact HEAD file:line citations: typed genuine-404-only fallback; terminal non-404, transport, and malformed responses; selected-source propagation through discovery, changelog, delta, prefetch, token, manifest, blob, and setup; trusted pagination and cycle detection; response-body cancellation identity and transport/malformed classification; npm runtime shape/status across npm/pnpm/bun/yarn with one request and no subprocess on failure; canonical Toolkit cli@VERSION and legacy vVERSION; stable/nightly pin syntax and metadata; strict GHCR token/OCI/tag validation and repeated cursor handling; auth-free redirects; target versus tracking channel; offline/cache behavior; delta final SHA and cleanup; end-to-end nightly Toolkit blob/setup success. Source-neutral patch caching is settled under mandatory byte-identical dual publication and final SHA verification; raise it only with a demonstrated defect that survives both guarantees. Report PASS/CONCERN/MUST-FIX/BLOCKED, exact evidence, deterministic regression and concrete fix for every defect, inspected scope, and residual risks. Empty output is forbidden. End exactly MERGE or DO-NOT-MERGE.