Dashboard › cli › Distillation
b659907e-23b7-49bb-b4f3-9982ef667c56["lore_tm_v1_6WFivWArYPPlyx-CnwQ8VCjAB1JYzPvkTYm9iNFEqKU","lore_tm_v1_xsSz_X7tQIZpwOnzELQcXM0vfpdMmwTEj1eCzGATAtk"]
Date: September 9, 2026
packages/cli within a pnpm workspace, and has its docs site in apps/cli-docs.gh conventions; agent-friendly JSON output and predictable behavior; and fast native binaries via Node SEA (fossilize) with SQLite API-response caching..cursor/rules/ultracite.mdc before working in the codebase.package.json for the latest scripts; commands run from packages/cli or via pnpm --filter sentry run <script> at the repository root.pnpm install from the repo root; pnpm run dev; pnpm run cli -- <args>; pnpm run build; pnpm run build:all; pnpm run typecheck; pnpm run lint; pnpm run lint:fix before committing; pnpm run test:unit; pnpm run test:e2e; pnpm exec vitest run test/lib/foo.test.ts for one test file while skipping generate steps; and pnpm exec vitest for watch mode.devDependencies, never dependencies, because everything is bundled at build time through esbuild and CI enforces the rule with pnpm run check:deps.pnpm add -D <package>, including the -D flag.@sentry/api supplies API-response types, code should import those types directly rather than creating redundant Valibot schemas in src/types/sentry.ts.node:* APIs and has no Bun runtime; it was migrated from Bun, so contributors must not reintroduce Bun.* globals, bun:test, or bun CLI commands.readFile(path, "utf-8") and writeFile(path, content) from node:fs/promises; existsSync(path) from node:fs; spawn() / execFile() and execSync() from node:child_process; executable lookup through src/lib/which.ts; globbing through src/lib/scan/; sleep via setTimeout(ms) from node:timers/promises; and JSON-file parsing via JSON.parse(await readFile(path, "utf-8")).mkdirSync(dir, { recursive: true, mode: 0o700 }); when arguments contain user-controlled values, prefer execFileSync with an argument array over a string passed to execSync to avoid shell injection.apps/cli-docs/src/content/docs/contributing.md, generated by script/generate-docs-sections.ts; the current command list can be obtained with ls src/commands/ or sentry --help.src/bin.ts is the entry point; src/app.ts configures the Stricli application; src/context.ts provides dependency injection; src/commands/ contains command groups and standalone commands; src/lib/ contains shared utilities including api/, db/, dsn/, and formatters/; src/types/ holds TypeScript types and Valibot schemas; and test/ mirrors src/ with unit, property, model-based, e2e, fixture, and mock coverage.buildCommand from ../../lib/command.js, never directly from @stricli/core, because the wrapper adds telemetry, --json/--fields injection, and output rendering.async *func() generators, yield new CommandOutput(data), and return { hint }; output.human must receive the same object serialized to JSON; commands must not define a separate json flag, call stdout.write(), or branch on flags.json.yield new CommandOutput(data). Formatting belongs in src/lib/formatters/<domain>.ts, and formatting helpers should be extracted when a command file exceeds approximately 400 lines.stderr.write() is banned in command files by a GritQL rule; diagnostics should use logger, while data output should use CommandOutput.buildRouteMap from ../../lib/route-map.js, never directly from @stricli/core; the wrapper auto-generates aliases list β ls, view β show, delete β remove, rm, and create β new. Explicit aliases take precedence, and standard aliases must not be manually duplicated.[<org>/<project>/]<id> form should be parsed with parseSlashSeparatedArg from src/lib/arg-parsing.ts, followed by parseOrgProjectArg; required trace IDs, span IDs, and similar identifiers must be positional arguments rather than flags.project create argument is a name:platform pair and project names cannot contain whitespace; optional flags are comma-separated and may also be repeatable, using value.split(",") or flags.x.flatMap((v) => v.split(",")).mdKvTable(), colorTag(), escapeMarkdownCell(), and renderMarkdown(); raw muted()/chalk must not be embedded in output strings. Tree output that cannot use renderMarkdown() should follow isPlainOutput() ? text : muted(text).packages/cli/test/lib/delta-upgrade.test.ts as a 1,846-line Vitest suite covering pure chain-resolution computation for stable GitHub Releases and nightly GHCR channels, plus async orchestration via fetch mocking.packages/cli/test/lib/delta-upgrade.test.ts: makeAsset() defaults to name "sentry-linux-x64", size 100_000, and URL "https://example.com/download"; makeRelease(tag, assets) returns { tag_name: tag, assets }; makePatchManifest(fromVersion, sha256Map, layers) emits OCI schema version 2, manifest media type application/vnd.oci.image.manifest.v1+json, config digest "sha256:config", empty-config media type, size 2, "from-version" annotation, and per-platform sha256-${key} annotations.getPlatformBinaryName() tests requiring a name beginning with "sentry-", exactly 3 hyphen-delimited components after removing .exe, platform in ["linux", "darwin", "windows"], architecture in ["x64", "arm64"], and a .exe suffix only when process.platform === "win32".canAttemptDelta() tests in the development test build expecting false for target "0.14.0-dev.123", stable target "0.14.0", and nightly target "0.14.0-dev.abc123".extractSha256() tests covering valid "sha256:abcdef0123456789", absent and empty digests, rejection of "md5:abcdef", lowercase normalization of "sha256:ABCDEF0123456789", and acceptance of mixed-case prefix "SHA256:abc123".getStableTargetSha256() tests: matching "sentry-linux-x64" with digest "sha256:deadbeef" returns "deadbeef"; a mismatched platform, matching asset without a digest, or empty asset list returns null.versionToHex() concatenates each characterβs padded hexadecimal char code, buildReleases() defaults to patch size 1000 and gzip size 100_000, and makeOpts() defaults to current "0.12.0", target "0.14.0", binary "sentry-linux-x64", and fullGzSize: 100_000.extractStableChain() tests for: a one-hop 0.12.0 β 0.13.0 chain; a two-hop 0.12.0 β 0.13.0 β 0.14.0 chain; oldest-first patch URL ordering; and target SHA-256 derived from versionToHex(targetVersion).extractStableChain() rejection tests for a target absent from releases, downgrade, target equal to current, cumulative patch size above threshold, missing patch asset, and target binary lacking a SHA-256 digest."0.1.0" to "0.12.0" return null because MAX_STABLE_CHAIN_DEPTH is 10, while exactly 10 hops from "0.1.0" to "0.11.0" succeed with 10 patch URLs.15_000 and 50_000 bytes against fullGzSize: 100_000, expecting chain extraction to return null.getPatchFromVersion() tests returning "0.12.0" from the "from-version" annotation and returning null when the annotation is missing or annotations is undefined.getPatchTargetSha256() tests retrieving platform-specific values "abc123" for "sentry-linux-x64" and "def456" for "sentry-darwin-arm64", while returning null for an unknown platform or undefined annotations.filterAndSortChainTags() tests requiring patch tags to be strictly newer than the current version and no newer than the target, sorted ascending; the current tag is excluded, target tag included, out-of-range tags excluded, and empty/no-match inputs return [].filterAndSortChainTags() tests with timestamp nightly versions, a 25-hop chain from "0.14.0-dev.1000" through "patch-0.14.0-dev.1025" under MAX_NIGHTLY_CHAIN_DEPTH (30), a cross-minor chain from "0.16.0-dev.100" to "0.17.0-dev.500", and a single-hop chain returning only "patch-0.0.0-dev.102".