Dashboard › cli › Session 020F5D2EiWvo
020F5D2EiWvoPdcC5Your correctness review returned EMPTY. Resume the SAME immutable review and return a substantive report now. If any tool fails, start with BLOCKավայր? Use BLOCKED and quote the exact command/error, then end DO-NOT-MERGE. Otherwise inspect exact base ec83887a16f780f32fba4b7d710bad262dba3a22 and exact head 9525f72c70fabbb23ec21d13cf86add58f6287cc using only Git objects. Never edit or mutate VCS. State merge base, base/head tree SHAs, changed paths inspected, diff-check, and immutable patch hash. Report findings first as MUST-FIX, CONCERN, or PASS with exact HEAD file:line evidence. Cover source fallback/affinity, pagination, body/cancellation classification, npm and GHCR runtime validation, package-manager failures, pin/tag/channel/offline behavior, redirects, and delta integrity. Source-neutral patch caching is accepted under byte-identical dual publishing plus final SHA verification. Give deterministic regression and fix for each defect; state residual risks. Empty output is forbidden. Final line exactly MERGE or DO-NOT-MERGE.
I’m restoring the prior review context, then I’ll freeze and verify the two commit objects before inspecting every changed path and each required behavior through git show and git diff only.
[
{
"content": "Restore prior immutable review context and verify exact base/head objects, merge base, trees, paths, diff-check, and patch hash",
"status": "in_progress",
"priority": "high"
},
{
"content": "Inspect every changed source and test file from immutable Git objects",
"status": "pending",
"priority": "high"
},
{
"content": "Trace and adversarially assess all requested behavioral contracts",
"status": "pending",
"priority": "high"
},
{
"content": "Produce findings-first merge-gate report with deterministic regressions, fixes, and residual risks",
"status": "pending",
"priority": "high"
}
]
ec83887a16f780f32fba4b7d710bad262dba3a22 9525f72c70fabbb23ec21d13cf86add58f6287cc
ec83887a16f780f32fba4b7d710bad262dba3a22
110d6530e57ca4fbfb2a3f98e52013d2668b5b5e 22f49363a13e32c36dcd9e1ca95916809c2dc271
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
(no output)
4950719273dc17d5a9e061b8cfe70c62131fe3299dc1799d9c09bb522b112193 -
The revisions are valid and linear: the supplied base is the merge base. The immutable full-index patch hashes to 4950719273dc17d5a9e061b8cfe70c62131fe3299dc1799d9c09bb522b112193; git diff --check is clean, and the patch changes 17 CLI source/test files.
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 | 213 +++++- packages/cli/src/lib/release-notes.ts | 134 ++-- packages/cli/src/lib/upgrade.ts | 520 +++++++++++--- packages/cli/src/lib/version-check.ts | 21 +- packages/cli/test/commands/cli.test.ts | 40 +- packages/cli/test/commands/cli/upgrade.test.ts | 494 ++++++++++++-- packages/cli/test/lib/binary.test.ts | 188 ++++- packages/cli/test/lib/delta-upgrade.mocked.test.ts | 14 +- packages/cli/test/lib/delta-upgrade.test.ts | 220 +++++- packages/cli/test/lib/ghcr.test.ts | 235 ++++++- packages/cli/test/lib/release-notes.test.ts | 221 +++++- ...
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
@@ -521,200 +521,208 @@ export function buildValidationMessage(
note?: string
): string {
const lines = [headline];
if (examples.length > 0) {
lines.push("", "Try:");
for (const example of examples) {
lines.push(` ${example}`);
}
}
if (note) {
lines.push("", `Note: ${note}`);
}
return lines.join("\n");
}
/**
* Convenience wrapper around {@link buildValidationMessage} that returns a
* {@link ValidationError} with the given field name.
*/
export function validationError(
headline: string,
examples: string[],
field?: string,
note?: string
): ValidationError {
return new ValidationError(
buildValidationMessage(headline, examples, note),
field
);
}
/**
* Input validation errors. ...
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,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 945adc48d..b29d67ff2 100644
--- a/packages/cli/src/lib/upgrade.ts
+++ b/packages/cli/src/lib/upgrade.ts
@@ -1,124 +1,222 @@
/**
* 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/src/lib/version-check.ts b/packages/cli/src/lib/version-check.ts
index c69ff0466..34393b1c7 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 "./db/release-channel.js";
import {
getVersionCheckInfo,
markUpdateNotified,
...
The source establishes source affinity from version discovery through changelog, delta, and full download, and limits cross-repository fallback to typed 404 paths. I’m now checking the less obvious edges: pagination links, response-body failures versus transport failures, package-manager exit handling, redirect trust, and final artifact verification.
1 /**
2 * Upgrade Module
3 *
4 * Detects how the CLI was installed and provides self-upgrade functionality.
5 * Binary management helpers (download URLs, locking, replacement) live in
6 * binary.ts and are shared with the setup --install flow.
7 */
8
9 import { spawn } from "node:child_process";
10 import {
11 chmodSync,
12 closeSync,
13 existsSync,
14 openSync,
15 realpathSync,
16 statSync,
17 unlinkSync,
18 writeSync,
19 } from "node:fs";
20 import { writeFile } from "node:fs/promises";
21 import { homedir } from "node:os";
22 import { dirname, isAbsolute, join, sep } from "node:path";
23 import { setTimeout } from "node:timers/promises";
24 import { prerelease as semverPrerelease, valid as semverValid } from "semver";
25 import {
26 acquireLock,
27 cleanupOldBinary,
28 compareVersions,
29 determineInstallDir,
30 ...
561 /**
562 * Fetch the latest version from npm registry.
563 *
564 * @returns Latest version string
565 * @throws {UpgradeError} When fetch fails or response is invalid
566 */
567 export async function fetchLatestFromNpm(): Promise<string> {
568 const response = await fetchWithUpgradeError(
569 `${NPM_REGISTRY_URL}/latest`,
570 { headers: { Accept: "application/json" } },
571 "npm registry"
572 );
573
574 if (!response.ok) {
575 throw new UpgradeError(
576 "network_error",
577 `Failed to fetch from npm: ${response.status}`
578 );
579 }
580
581 const data = await parseUpgradeJson(
582 response,
583 undefined,
584 "npm registry returned invalid metadata"
585 );
586 if (
587 typeof data !== "object" ||
588 data === null ||
589 Array.isArray(data) ||
590 !("version" in data) ||
591 typeof data.version !== ...
1121 throw new UpgradeError(
1122 "execution_failed",
1123 `Failed to download binary: HTTP ${response.status}`
1124 );
1125 }
1126
1127 // Fully consume the response body before writing to disk.
1128 // Bun.write(path, Response) with a large streaming body can exit the
1129 // process before the download completes (Bun event-loop bug).
1130 // See: https://github.com/oven-sh/bun/issues/13237
1131 const body = await response.arrayBuffer();
1132 await writeFile(destPath, new Uint8Array(body));
1133 }
1134
1135 /**
1136 * Max probe attempts before giving up. Six probes run with five sleeps
1137 * in between, yielding ~3.1s total wall-clock budget (see backoff table
1138 * on {@link waitForBinaryVisible}).
1139 */
1140 const VERIFY_MAX_ATTEMPTS = 6;
1141
1142 /** Base delay (ms) between verify attempts. Doubles each retry. ...
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/custom-ca.ts-293- resolve(); 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/custom-ca.ts-294- return resolved?.tls.ca; 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/custom-ca.ts-295-} 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/custom-ca.ts-296- 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/custom-ca.ts-297-/** 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/custom-ca.ts-298- * Drop-in replacement for `fetch()` that injects custom CA certificates 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/custom-ca.ts-299- * when configured. All non-authenticated fetch call sites should use this 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/custom-ca.ts-300- * instead of bare `fetch()`. 9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/custom-ca.ts-301- ...
{
"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": ...
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
@@ -87,150 +87,144 @@ describe("feedbackCommand.func", () => {
"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(
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(() => {
...
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
@@ -13,60 +13,61 @@
import * as child_process from "node:child_process";
import { chmodSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
import { unlink } from "node:fs/promises";
import { homedir } from "node:os";
import { delimiter, join } from "node:path";
import { gzipSync } from "node:zlib";
import { run } from "@stricli/core";
import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
// Make child_process namespace mutable so vi.spyOn works on ESM exports
vi.mock("node:child_process", async (importOriginal) => {
const orig = await importOriginal<typeof import("node:child_process")>();
return { ...orig };
});
import { app } from "../../../src/app.js";
import {
isEbusyError,
...
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,98 +1,225 @@
/**
* 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
@@ -67,95 +67,95 @@ function mockFetch(
}
// ============================================================================
// 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(),
`delta-iso-${Date.now()}-${Math.random().toString(36).slice(2)}-${name}`
);
}
// ============================================================================
// resolveStableDelta
// ...
diff --git a/packages/cli/test/lib/delta-upgrade.test.ts b/packages/cli/test/lib/delta-upgrade.test.ts
index 3752d4ae6..68d1cfbdf 100644
--- a/packages/cli/test/lib/delta-upgrade.test.ts
+++ b/packages/cli/test/lib/delta-upgrade.test.ts
@@ -1,70 +1,79 @@
/**
* Unit Tests for Delta Upgrade Module
*
* Tests the exported pure-computation functions that drive chain resolution
* for both stable (GitHub Releases) and nightly (GHCR) channels, plus
* async orchestration functions tested via fetch mocking.
*/
import { createHash } from "node:crypto";
import { existsSync, unlinkSync } from "node:fs";
import { access, readFile } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
-import { afterEach, beforeEach, describe, expect, test } from "vitest";
-import { getPlatformBinaryName } from "../../src/lib/binary.js";
+import { afterEach, beforeEach, describe, expect, test, vi } from "vitest";
+import {
+ getPlatformBinaryName,
+ ...
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,242 +1,350 @@
/**
* 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 ...
+ {
+ schemaVersion: 2,
+ layers: [
+ {
+ digest: `sha256:${"a".repeat(64)}`,
+ mediaType: "application/octet-stream",
+ size: 1,
+ annotations: ["value"],
+ },
+ ],
+ },
+ ])("rejects invalid OCI manifest %#", async (manifest) => {
+ mockFetch(async () => Response.json(manifest));
+
+ await expect(fetchManifest("token", "nightly")).rejects.toThrow(
+ 'Manifest for tag "nightly" returned invalid metadata'
+ );
+ });
+
+ test("classifies manifest body termination as transport failure", async () => {
+ mockFetch(async () => {
+ const response = Response.json(makeManifest());
+ response.json = async () => {
+ throw new TypeError("terminated");
+ };
+ return response;
+ });
+
+ await expect(fetchManifest("token", "nightly")).rejects.toMatchObject({
+ name: "UpgradeTransportError",
+ reason: "network_error",
+ });
+ });
test("fetches manifest for an ...
500 downloadNightlyBlob("token", "sha256:abc", controller.signal)
501 ).rejects.toBe(reason);
502 expect(requestCount).toBe(1);
503 });
504
505 test("preserves external cancellation during the redirect request", async () => {
506 const controller = new AbortController();
507 const reason = { kind: "cancelled" };
508 const headers: Headers[] = [];
509 mockFetch(async (_url, init) => {
510 headers.push(new Headers(init?.headers));
511 if (headers.length === 1) {
512 return Response.redirect("https://blob.storage.azure.com/file", 307);
513 }
514 controller.abort(reason);
515 throw new TypeError("invalid_argument");
516 });
517
518 await expect(
519 downloadNightlyBlob("token", "sha256:abc", controller.signal)
520 ).rejects.toBe(reason);
521 expect(headers).toHaveLength(2);
522 ...
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,51 +1,57 @@
/**
* 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
@@ -98,201 +98,395 @@ const { spawnImpl } = vi.hoisted(() => ({
fn: (() => {
// placeholder — replaced per-test
}) as (cmd: string, args: string[], opts: object) => FakeProc,
},
}));
// 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,
...
}
if (u.includes("/manifests/nightly-")) {
return new Response(JSON.stringify(manifest), { status: 200 });
}
return new Response(null, { status: 404 });
});
const exists = await versionExists("curl", "0.14.0-dev.1772661724");
expect(exists).toBe(true);
});
test("checks GHCR for nightly version - version does not exist", async () => {
mockFetch(async (url) => {
const u = String(url);
+ if (u === "https://api.github.com/repos/getsentry/toolkit") {
+ return new Response(null, { status: 200 });
+ }
if (u.includes("ghcr.io/token")) {
return new Response(JSON.stringify({ token: "tok" }), { status: 200 });
}
if (u.includes("/manifests/nightly-")) {
return new Response(null, { status: 404 });
}
return new Response(null, { status: 404 });
});
const exists = await versionExists("curl", "0.14.0-dev.9999999999");
...
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,46 +1,49 @@
/**
* 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 ...
04867eaf9b412a62d4b1ea1c59c2b382c557bd0b feat(cli): add toolkit upgrade bridge 9c874298e3b57515ba36d35140627a668a46dc7b fix(cli): address toolkit bridge review 723375b6ce4e37884e760fa0f848e7e7e3effcc9 fix(cli): address upgrade review findings dbee7e9f2a4555e044f74dfebad002cccfa43a1f test(cli): update toolkit upgrade fixtures 5ffa5cd0fe02c0a5dc5bec9a8a099f4135c0b447 fix(cli): harden upgrade source resolution c8c815bbecf472061ec02ab96d1a31f23f078d01 fix(cli): validate GitHub release pagination b6484e0fb1eb0f8bce68272d538f5c3b26cd5bd5 fix(cli): validate resolved upgrade metadata c6030fbf4d0c190f037c86d832edb6680d3e54ab fix(cli): bind nightly manifests to versions 2f9b5d0d26124d445a916b8bfc584ad518720584 fix(cli): isolate pinned upgrade resolution 4a1d5bad105c006193c68459175f5eb3c7f78e08 test(cli): update pinned version fixture e565b1afb39a341605adc883de8812851de7d581 fix(cli): validate stable upgrade metadata f1c10a6cb1a753b8c4b8c613cd78735c3f12cba6 fix(cli): preserve pinned upgrade ...
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts-31-import { buildCommand } from "../../lib/command.js";
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts-32-import { CLI_VERSION } from "../../lib/constants.js";
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts-33-import {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts-34- getReleaseChannel,
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts-35- type ReleaseChannel,
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:36: setReleaseChannel,
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts-37-} from "../../lib/db/release-channel.js";
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts-38-import { getVersionCheckInfo } from ...
120 } {
121 // "nightly" and "stable" as positional args select the channel rather than
122 // installing a specific version. Match case-insensitively for convenience.
123 const lower = positional?.toLowerCase();
124 if (lower === "nightly" || lower === "stable") {
125 return {
126 channel: lower,
127 versionArg: undefined,
128 };
129 }
130
131 return {
132 channel: getReleaseChannel(),
133 versionArg: positional,
134 };
135 }
136
137 /**
138 * Resolve the target version from the local cache (SQLite) instead of
139 * fetching from the network. Used by `--offline` and as automatic
140 * fallback when `fetchLatestVersion()` hits a network error. ...
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:7: applyPatchChainInMemory,
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:237:export async function fetchRecentReleases(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:369:export async function resolveNightlyChain(opts: {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:468:export function applyPatchChain(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:480: const sha256 = await applyPatchChainInMemory(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:580:export function resolveStableDelta(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:599:export function resolveNightlyDelta(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/release-notes.ts:588: * Uses a higher `per_page` than `fetchRecentReleases()` in ...
1 /**
2 * GHCR (GitHub Container Registry) Client
3 *
4 * Encapsulates the OCI download protocol for fetching nightly CLI binaries
5 * from ghcr.io/getsentry/cli. Nightly builds are pushed as OCI artifacts
6 * via ORAS with the version baked into the manifest annotation.
7 *
8 * Key design decisions:
9 * - Anonymous access: nightly package is public; no token needed beyond the
10 * standard ghcr.io anonymous token exchange.
11 * - Version discovery from manifest annotation: `annotations.version` in the
12 * OCI manifest holds the nightly version. Checking the latest version only
13 * requires a token exchange + manifest fetch (2 HTTP requests total).
14 * - Redirect quirk: ghcr.io blob downloads return 307 to Azure Blob Storage.
15 * Using `fetch` with `redirect: "follow"` would forward the Authorization
16 * header to Azure, which returns 404. ...
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:152: const first = steps.at(0);
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/delta-upgrade.ts:153: const last = steps.at(-1);
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:75: ? AbortSignal.any([timeoutSignal, externalSignal])
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/ghcr.ts:625: const nextTag = tags.at(-1);
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:158: if (!URL.canParse(match[1])) {
240 manifest.layers.every(isOciLayer) &&
241 (manifest.mediaType === undefined ||
242 typeof manifest.mediaType === "string") &&
243 (manifest.config === undefined || isOciLayer(manifest.config)) &&
244 (manifest.annotations === undefined || isStringRecord(manifest.annotations))
245 );
246 }
247
248 /**
249 * Fetch a short-lived anonymous bearer token for read-only access to the
250 * public `ghcr.io/getsentry/cli` package.
251 *
252 * The token exchange endpoint returns a JSON object with a `token` field.
253 * No credentials are required for public packages.
254 *
255 * @returns Bearer token string
256 * @throws {UpgradeError} On network failure or malformed response
257 */
258 export async function getAnonymousToken(
259 sourceOrSignal: UpgradeSource | AbortSignal = PRIMARY_UPGRADE_SOURCE,
260 signal?: AbortSignal
261 ): Promise<string> {
262 const source =
263 ...
1 /** Delta upgrade discovery and application backed by binpatch. */
2
3 import { join } from "node:path";
4 // biome-ignore lint/performance/noNamespaceImport: Sentry SDK recommends namespace import
5 import * as Sentry from "@sentry/node-core/light";
6 import {
7 applyPatchChainInMemory,
8 extractStableChain as binpatchExtractStableChain,
9 filterAndSortChainTags as binpatchFilterAndSortChainTags,
10 validateChainStep as binpatchValidateChainStep,
11 type DeltaTelemetry,
12 type DeltaUnavailableReason,
13 type ExtractStableChainOpts,
14 type GitHubRelease,
15 getPatchFromVersion,
16 getPatchTargetSha256,
17 ghcrSource,
18 githubReleaseSource,
19 type InstrumentHook,
20 MAX_NIGHTLY_CHAIN_DEPTH,
21 makeCache,
22 OciClient,
23 type OciManifest,
24 PATCH_TAG_PREFIX,
25 type PatchCache,
26 type PatchChain,
27 type ...
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts-1138- restoreStderr = restore;
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts-1139-
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts-1140- await run(app, ["cli", "upgrade", "--method", "curl"], context);
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts-1141-
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts:1142: expect(errors.join("")).toContain("Setup failed with exit code 1");
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts-1143- });
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts-1144-
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts-1145- test("downloads nightly binary from GHCR for nightly channel", async () => ...
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/upgrade.test.ts-608- "This operation is not supported for this installation method."
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/upgrade.test.ts-609- );
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/upgrade.test.ts-610- });
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/upgrade.test.ts-611-
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/upgrade.test.ts:612: test("creates error with default message for offline_cache_miss", () => {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/upgrade.test.ts:613: const error = new UpgradeError("offline_cache_miss");
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/upgrade.test.ts:614: expect(error.reason).toBe("offline_cache_miss");
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/lib/upgrade.test.ts-615- ...
(no output)
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts-410- ).toBe(true);
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts-411- });
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts-412-
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts:413: test("uses the cached target only after a transport failure", async () => {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts-414- setVersionCheckInfo("88.88.88");
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts-415- mockFetch(async () => {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts-416- throw new TypeError("fetch failed");
--
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/test/commands/cli/upgrade.test.ts-426- ...
(no output)
1 /**
2 * Release channel persistence.
3 *
4 * Stores the user's chosen release channel ("stable" or "nightly") in the
5 * metadata table. Defaults to "stable" if not set.
6 *
7 * The channel controls which version stream `upgrade` and `version-check` use:
8 * - "stable": tracks the latest GitHub release (default)
9 * - "nightly": tracks the rolling nightly prerelease built from main
10 */
11
12 import { getDatabase } from "./index.js";
13 import { getMetadata, setMetadata } from "./utils.js";
14 import { clearVersionCheckCache } from "./version-check.js";
15
16 const KEY = "release_channel";
17
18 /** The release channel a user tracks for upgrades and version-check notifications. */
19 export type ReleaseChannel = "stable" | "nightly";
20
21 /**
22 * Get the persisted release channel.
23 *
24 * @returns The stored channel, or "stable" if not yet set. ...
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:514: // CLI-1D3). `downloadBinaryToTemp`'s visibility-race retry loop
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:695: executeUpgrade(method, target, downloadTag, offline, setMessage, source)
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/upgrade.ts:783: executeUpgrade("curl", target, downloadTag, undefined, setMessage, source)
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/binary.ts:238:export function getBinaryDownloadUrl(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:549:export async function fetchLatestFromGitHub(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:676:export async function fetchLatestNightlyVersion(
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/lib/upgrade.ts:705: return ...
2620 });
2621 });
2622
2623 // ---------------------------------------------------------------------------
2624 // executeUpgrade — package managers (npm, pnpm, bun, yarn)
2625 // ---------------------------------------------------------------------------
2626
2627 describe("executeUpgrade (package managers)", () => {
2628 test("npm: returns null on success", async () => {
2629 spawnImpl.fn = () => fakeProcess(0);
2630 expect(await executeUpgrade("npm", "1.0.0")).toBeNull();
2631 });
2632
2633 test("npm: uses correct install arguments", async () => {
2634 let capturedCmd = "";
2635 let capturedArgs: string[] = [];
2636 let capturedOpts: object = {};
2637 spawnImpl.fn = (cmd, args, opts) => {
2638 capturedCmd = cmd;
2639 capturedArgs = args;
2640 capturedOpts = opts;
2641 return fakeProcess(0);
2642 };
2643 await executeUpgrade("npm", "1.2.3");
2644 ...
381 },
382 };
383 }
384
385 return { kind: "target", target: resolvedTarget, source };
386 }
387
388 /**
389 * Build the structured result for check-only mode.
390 */
391 function buildCheckResult(opts: {
392 target: string;
393 versionArg: string | undefined;
394 method: InstallationMethod;
395 channel: ReleaseChannel;
396 flags: UpgradeFlags;
397 }): UpgradeResult {
398 const { target, versionArg, method, channel, flags } = opts;
399 const result: UpgradeResult = {
400 action: "checked",
401 currentVersion: CLI_VERSION,
402 targetVersion: target,
403 channel,
404 method,
405 forced: flags.force,
406 };
407
408 // When already on target, no update hint needed
409 if (CLI_VERSION !== target) {
410 const cmd =
411 versionArg && !CHANNEL_VERSIONS.has(versionArg)
412 ? ...
761 * @returns Warnings about the old installation that may shadow the new one
762 */
763 async function migrateToStandaloneForNightly(opts: {
764 method: InstallationMethod;
765 target: string;
766 versionArg: string | undefined;
767 noAgentSkills: boolean;
768 json?: boolean;
769 source?: UpgradeSource;
770 channel: ReleaseChannel;
771 }): Promise<string[]> {
772 const { method, target, versionArg, noAgentSkills, json, source, channel } =
773 opts;
774 log.info("Nightly builds are only available as standalone binaries.");
775 log.info("Migrating to standalone installation...");
776
777 // Use the rolling "nightly" tag for latest nightly; use the specific version
778 // tag if the user requested a pinned version.
779 const downloadTag = versionArg ? undefined : NIGHTLY_TAG;
780 const downloadResult = await withProgress(
781 { message: `Downloading ${target}...`, json },
782 ...
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/setup.ts-53-} from "../../lib/shell.js";
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/setup.ts-54-
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/setup.ts-55-type SetupFlags = {
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/setup.ts-56- readonly install: boolean;
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/setup.ts-57- readonly method?: InstallationMethod;
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/setup.ts-58- readonly channel?: ReleaseChannel;
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/setup.ts:59: readonly "no-modify-path": boolean;
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/setup.ts-60- readonly "no-completions": boolean;
9525f72c70fabbb23ec21d13cf86add58f6287cc:packages/cli/src/commands/cli/setup.ts-61- readonly ...
Finish the review now. Your prior response was only a progress sentence and supplied no merge-gate verdict. Return the FINAL substantive report in this response; do not describe future work. Exact base ec83887a16f780f32fba4b7d710bad262dba3a22, exact head 9525f72c70fabbb23ec21d13cf86add58f6287cc. Include: immutable merge base and base/head tree SHAs; changed paths/scope inspected; full-diff identity/hash and git diff --check; findings first with MUST-FIX/CONCERN/PASS and exact HEAD file:line evidence; deterministic regression plus concrete fix for every defect; end-to-end state, full-download-byte, and runtime-compatibility conclusions; residual risks. If blocked, state BLOCKED and exact command/error. Source-neutral cache remains accepted under byte-identical dual publishing and final SHA verification. Empty, progress-only, or deferred output is forbidden. Final line must be exactly MERGE or DO-NOT-MERGE.
1 /**
2 * Binary Management
3 *
4 * Shared utilities for installing, replacing, and managing the CLI binary.
5 * Used by both `setup --install` (fresh installs) and `upgrade` (self-updates).
6 */
7
8 import { spawnSync } from "node:child_process";
9 import {
10 existsSync,
11 mkdirSync,
12 readFileSync,
13 renameSync,
14 unlinkSync,
15 writeFileSync,
16 } from "node:fs";
17 import { chmod, copyFile, mkdir, realpath, unlink } from "node:fs/promises";
18 import { delimiter, dirname, isAbsolute, join, resolve, sep } from "node:path";
19 import { compare as semverCompare } from "semver";
20 import { getUserAgent } from "./constants.js";
21 import {
22 buildTlsErrorDetail,
23 customFetch,
24 isTlsCertError,
25 } from "./custom-ca.js";
26 import {
27 stringifyUnknown,
28 UpgradeError,
29 UpgradeTransportError,
30 } from ...
The immutable source inspection is complete enough to narrow the out broadusercontent?. I am now cross-checking the remaining edge contracts against exact HEAD line numbers, especially source-neutral cache integrity, platform roots, redirect trust, and channel persistence.
180
181 expect(error).toBeInstanceOf(UpgradeSourceNotFoundError);
182 expect(error).toMatchObject({ name: "UpgradeSourceNotFoundError" });
183
184 expect(requests).toEqual([
185 "https://api.github.com/repos/getsentry/toolkit/releases/tags/cli%400.45.0",
186 "https://api.github.com/repos/getsentry/cli/releases/tags/0.45.0",
187 ]);
188 });
189 });
190
191 describe("getBinaryFilename", () => {
192 test("returns sentry on non-Windows", () => {
193 if (process.platform !== "win32") {
194 expect(getBinaryFilename()).toBe("sentry");
195 }
196 });
197 });
198
199 describe("getBinaryPaths", () => {
200 test("returns all derived paths from install path", () => {
201 const paths = getBinaryPaths("/usr/local/bin/sentry");
202
203 expect(paths.installPath).toBe("/usr/local/bin/sentry");
204 expect(paths.tempPath).toBe("/usr/local/bin/sentry.download");
...
Date: Sep 10, 2026 * 🟡 (12:10) `packages/cli/test/lib/upgrade.test.ts` expanded upgrade coverage and now imports `UPGRADE_SOURCES` from `../../src/lib/binary.js` and `resolveExistingUpgradeVersion` from `../../src/lib/upgrade.js`. * 🟡 (12:10) New `fetchLatestFromGitHub()` pagination coverage tracks request counts, includes product-mismatched release tags such as `mcp@9.0.${index}`, expects a 2-…
* 🟡 (12:09) `downloadNightlyBlob("token", "sha256:abc", controller.signal)` cancellation coverage verifies rejection with the exact external abort `reason`, including cancellation during the redirect request; the redirect flow makes 2 requests and strips the `authorization` header from the second request to `https://blob.storage.azure.com/file`. * 🟡 (12:09) `listTags()` coverage verifies: no pr…
* 🟡 (12:07) `packages/cli/test/lib/delta-upgrade.mocked.test.ts` migrated stable delta fixtures from legacy `getsentry/cli` release tags to Toolkit product-prefixed tags: `0.14.0` → `cli@0.14.0` and `0.13.0` → `cli@0.13.0`. * 🟡 (12:07) The mocked stable patch URL in `packages/cli/test/lib/delta-upgrade.mocked.test.ts` changed from `https://github.com/getsentry/cli/releases/download/0.14.0/${BIN…
* 🔴 (12:06) User specified that upgrade checks “never uses the cached target after %s”; `packages/cli/test/commands/cli/upgrade.test.ts` adds parameterized coverage ensuring failed or empty fresh release responses do not fall back to a cached target populated through `setVersionCheckInfo`. * 🔴 (12:06) User stated the upgrade migration scenario: “Switch to nightly and use npm method → triggers m…
Date: September 10, 2026 * 🟡 (12:04) Full binary download in `packages/cli/src/lib/upgrade.ts` fully consumes `response.arrayBuffer()` before calling `writeFile(destPath, new Uint8Array(body))`; this avoids a Bun event-loop bug where `Bun.write(path, Response)` with a large streaming body can let the process exit before download completion. Referenced issue: `https://github.com/oven-sh/bun/issue…
Date: September 10, 2026 * 🔴 (12:02) User specified that installation detection must always check Homebrew first because stored install information may be stale; this rule appears in `packages/cli/src/lib/upgrade.ts` at line 454. * 🟡 (12:02) `packages/cli/src/lib/upgrade.ts` documents that upgrade installation detection begins around line 443 and falls back to `detectLegacyInstallationMethod()`…
Date: September 10, 2026 * 🟡 (12:01) `packages/cli/src/lib/upgrade.ts` expanded from 124 to 222 lines in the shown opening section and added `prerelease as semverPrerelease` and `valid as semverValid` imports from `semver`, plus source-aware upgrade machinery including `compareVersions`. * 🟡 (12:01) `packages/cli/src/lib/upgrade.ts` added source-aware pinned-version validation paths, including …
Date: September 10, 2026 * 🟡 (12:00) `packages/cli/src/lib/errors.ts` added `UpgradeTransportError extends UpgradeError`; its constructor calls `super("network_error", message)` and sets `this.name = "UpgradeTransportError"`, distinguishing transport failures from HTTP or metadata failures. * 🟡 (12:00) `packages/cli/src/lib/ghcr.ts` expanded from 520 to 659 lines and now imports `valid as semve…
* 🟡 (12:00) `packages/cli/src/lib/delta-upgrade.ts` was expanded from 388 to 481 lines and now imports `prerelease as semverPrerelease` and `valid as semverValid` from `semver`. * 🟡 (12:00) `packages/cli/src/lib/delta-upgrade.ts` replaced the fixed `GITHUB_RELEASES_URL` and `GHCR_REPO` dependencies with source-aware imports: `getGitHubReleasesUrl`, `PRIMARY_UPGRADE_SOURCE`, and `UpgradeSource` …
Date: Sep 10, 2026 * 🟡 (11:58) Immutable Git verification established merge base `ec83887a16f780f32fba4b7d710bad262dba3a22`, base tree `110d6530e57ca4fbfb2a3f98e52013d2668b5b5e`, and head tree `22f49363a13e32c36dcd9e1ca95916809c2dc271`; the supplied base is the merge base and the revisions are linear. * 🟡 (11:58) Immutable full-index patch SHA-256 is `4950719273dc17d5a9e061b8cfe70c62131fe3299dc…
Date: Sep 10, 2026 * 🔴 [requested-review] (11:56) User requested resumption of the same correctness review after the prior review returned empty; substantive, non-empty output is required. * 🔴 [enforced-workflow] (11:56) User directed that VCS must never be edited or mutated; review must be immutable and use only Git objects. * 🔴 (11:56) User specified exact review base `ec83887a16f780f32fba4b…