Dashboard › cli › Session 0miKUlxOBTVO
0miKUlxOBTVOUpcbuREAD-ONLY final correctness gate getsentry/cli PR #1569 exact base ec83887a16f780f32fba4b7d710bad262dba3a22 head 7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7. Immutable Git objects only; never worktree. Repo /home/byk/.local/share/opencode/worktree/0e8c2f3bbe145a3280d6fb6ed900a5121f382cf0/toolkit-bridge-upgrade. MUST return substantive non-empty report. Inspect every changed source/test. Verify all prior blockers, especially page-two pagination arbitrary abort reason exact identity; UpgradeTransportError transport-only cache fallback; HTTP/metadata fail closed cached; stable package latest/pins reject malformed/prerelease; all abort stages; branded source provenance; target-driven stable/nightly routing/channel; semantic prerelease all stable paths; exact metadata, 404-only, affinity, pagination, redirect, SHA/delta/cache/offline/API. Findings-first PASS/CONCERN/MUST-FIX/BLOCKED exact head file:line, deterministic regression for defect, residual gaps. Final exactly MERGE or DO-NOT-MERGE. Never modify; empty forbidden.
/home/byk/Code/getsentry/cli/.git/worktrees/toolkit-bridge-upgrade commit commit BASE ec83887a16f780f32fba4b7d710bad262dba3a22 110d6530e57ca4fbfb2a3f98e52013d2668b5b5e e7beceb6ba09b64dc277ae1732df04e65500062b HEAD? HEAD 7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7 b291f0ba3965767a808fa1eba1129f8dea9b427e 878459c490576dbb7abb76d3a3dc1b5d9fc62c1f 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/c...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_089953fcd001APjn3yUvFM1qn7
<!-- lore:019e6874-7d6f-7fb9-8085-ac2f3472e22f -->
* **sensitive argv flags must never reach telemetry — redactArgv() in cli.ts**: \`SENSITIVE\_ARGV\_FLAGS = new Set(\['token', 'auth-token'])\` in \`src/cli.ts\`. \`redactArgv()\` replaces values of these flags with \`\[REDACTED]\` before any telemetry call. This is an absolute invariant — never pass raw \`process.argv\` to telemetry without running through \`redactArgv()\` first.
<!-- lore:019e610e-c11c-7b34-ae65-02bf3183ae62 -->
* **setup.ts bestEffort() wrapper: post-install steps must never crash setup**: \`src/commands/cli/setup.ts\` \`bestEffort(stepName, fn)\` wraps non-essential post-install steps (recording install info, shell completions, agent skills) in try/catch. On failure: calls \`warn(stepName, error)\` + \`captureException(error, { level: 'warning', tags: { 'setup.step': stepName } })\`. ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_089957874001xRX1nTDw30XMiv
/** Changelog summary for the version range. Absent for offline or on fetch failure. */
changelog?: ChangelogSummary;
};
type UpgradeFlags = {
readonly check: boolean;
readonly force: boolean;
readonly offline: boolean;
readonly "no-agent-skills": boolean;
readonly method?: InstallationMethod;
/** Injected by buildCommand output wrapper — suppresses spinners */
readonly json?: boolean;
};
/**
* Resolve effective channel and version arg from the positional `version`
* parameter. "nightly" and "stable" are treated as channel selectors, not
* literal version strings.
*
* @returns `{ channel, versionArg }` where versionArg is undefined when the
* positional was a channel name (so we resolve to latest) or was omitted. ...
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 ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_08995cbec001AEkRaVblWCMAUY
163 url.protocol !== "https:" ||
164 url.hostname !== "api.github.com" ||
165 !(isSelectedSourcePath || isCanonicalRepositoryPath) ||
166 page === null ||
167 !PAGE_NUMBER_REGEX.test(page)
168 ) {
169 throw new UpgradeError(
170 "network_error",
171 "GitHub returned an invalid release pagination URL"
172 );
173 }
174 const nextPage = new URL(getGitHubLatestReleaseUrl(source));
175 nextPage.searchParams.set("page", page);
176 return nextPage.href;
177 }
178
179 // Curl Binary Helpers
180
181 /**
182 * Build the list of known curl install directories the binary may live in,
183 * each with a trailing separator so `startsWith` matches a directory boundary
184 * (e.g. `~/.local/bin/` won't match `~/.local/binaries/`). ...
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 ...
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. ...
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 ...
1 /**
2 * Release Notes Parser & Aggregation
3 *
4 * Extracts user-facing changelog entries from GitHub Release bodies (stable)
5 * or conventional commit messages (nightly). Uses `marked.lexer()` for
6 * AST-based section extraction and produces structured data that can be
7 * re-serialized as filtered markdown for rendering via `renderMarkdown()`.
8 *
9 * Only three categories are kept — everything else is filtered out:
10 * - **New Features** (✨) — from `### New Features` sections or `feat:` commits
11 * - **Bug Fixes** (🐛) — from `### Bug Fixes` sections or `fix:` commits
12 * - **Performance** (⚡) — from `### Performance` sections or `perf:` commits
13 */
14
15 import { marked, type Token, type Tokens } from "marked";
16 import {
17 compareVersions,
18 getGitHubHeaders,
19 getGitHubReleasesUrl,
20 PRIMARY_UPGRADE_SOURCE,
21 type UpgradeSource,
22 } from ...
1 /**
2 * sentry cli upgrade
3 *
4 * Self-update the Sentry CLI to the latest or a specific version.
5 * After upgrading, spawns the NEW binary with `cli setup` to update
6 * completions, agent skills, and record installation metadata.
7 *
8 * Supports two release channels:
9 * - stable (default): tracks the latest GitHub release
10 * - nightly: tracks the rolling nightly prerelease built from main
11 *
12 * The channel can be set via --channel or by passing "nightly"/"stable"
13 * as the version argument. The choice is persisted in the local database
14 * so that subsequent bare `sentry cli upgrade` calls use the same channel.
15 */
16
17 import { spawn } from "node:child_process";
18 import { homedir } from "node:os";
19 import { dirname, join } from "node:path";
20 import { setTimeout } from "node:timers/promises";
21 import type { SentryContext } from "../../context.js";
...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_089971daf0010eKQWzTiyLLObP
7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:packages/cli/test/commands/cli/upgrade.test.ts:1091: return new Response("Not Found", { status: 404 });
7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:packages/cli/test/commands/cli/upgrade.test.ts:1094: // "nightly" positional switches channel to nightly
7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:packages/cli/test/commands/cli/upgrade.test.ts:1178: test("migrates npm install to standalone binary for a pinned nightly", async () => {
7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:packages/cli/test/commands/cli/upgrade.test.ts:1206: digest: "sha256:abc456",
7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:packages/cli/test/commands/cli/upgrade.test.ts:1221: if (urlStr.includes("/blobs/sha256:abc456")) {
7d62ffa8317f9b98be0dc4afec0a927c1cabb0b7:packages/cli/test/commands/cli/upgrade.test.ts:1222: return ...
...output truncated...
Full output saved to: /home/byk/.local/share/opencode/tool-output/tool_089974a2f001AYciBdR4EeKPh1
const data = JSON.parse(getStdout()) as UpgradeResult;
expect(data.action).toBe("checked");
expect(data.warnings).toContain(
"Run 'sentry cli upgrade 2.0.0' to update."
);
});
- test("check mode shows already on target when versions match", async () => {
+ test("check mode compares the current version with a stable target", async () => {
globalThis.fetch = (async () =>
- new Response(JSON.stringify({ tag_name: "v0.0.0-dev" }), {
+ new Response(JSON.stringify([{ tag_name: "cli@1.0.0" }]), {
status: 200,
headers: { "Content-Type": "application/json" },
})) as typeof fetch;
const func = await upgradeCommand.loader();
const { context, getStdout, restore } = createMockContext();
restoreStderr = restore;
await func.call(context, { check: true, method: "curl", json: true ...
150
151 // Store original fetch for restoration
152 let originalFetch: typeof globalThis.fetch;
153
154 /** Helper to mock fetch without TypeScript errors about missing Bun-specific properties */
155 function mockFetch(
156 fn: (url: string | URL | Request, init?: RequestInit) => Promise<Response>
157 ): void {
158 globalThis.fetch = fn as typeof globalThis.fetch;
159 }
160
161 beforeEach(() => {
162 originalFetch = globalThis.fetch;
163 });
164
165 afterEach(() => {
166 globalThis.fetch = originalFetch;
167 });
168
169 describe("parseInstallationMethod", () => {
170 test("parses valid methods", () => {
171 expect(parseInstallationMethod("curl")).toBe("curl");
172 expect(parseInstallationMethod("brew")).toBe("brew");
173 expect(parseInstallationMethod("npm")).toBe("npm");
174 expect(parseInstallationMethod("pnpm")).toBe("pnpm");
175 ...