Dashboard › cli › Distillation
60fe3309-01ca-40da-94c3-83e3459ba652["lore_tm_v1_2mCeQcqwOmoV8wNU5zoM2n9xl-Cb8z1XwWqx3Jivaro","lore_tm_v1_S1aLjjGDMdT0Vy6zhf0Un6kX_d93CDn5hDrupvBuN9g","lore_tm_v1_eJNxxR3uQqgYGC-WvWmui0zE8O2LgOiPgr0ZViTaf3k","lore_tm_v1_xKQ1AWZ3RbwwguxdgmeqOUYjNw_dXtq0tKF4KTDh_FY","lore_tm_v1_e5LdGlvlMDPUusflHN3BCvH3Olha3JaWUYYN3tnxZzY","lore_tm_v1_I5jA34_jJg746yfTocCOYWpqEYk_gTpG0MC5l1KeQos"]
packages/cli/src/lib/binary.ts defines KNOWN_CURL_DIRS = [".local/bin", "bin", ".sentry/bin"].getBinaryDownloadUrl(version) in packages/cli/src/lib/binary.ts returns https://github.com/getsentry/cli/releases/download/${version}/${getPlatformBinaryName()}; GITHUB_RELEASES_URL is https://api.github.com/repos/getsentry/cli/releases.isDowngrade(current, target) returns whether compareVersions(current, target) === 1.getBinaryFilename() returns "sentry.exe" on Windows and "sentry" elsewhere.getBinaryPaths(installPath) returns { installPath, tempPath: \${installPath}.download`, oldPath: `${installPath}.old`, lockPath: `${installPath}.lock` }`.determineInstallDir(homeDir, env) prioritizes: 1. env.SENTRY_INSTALL_DIR; 2. <homeDir>/.local/bin if it exists and is in PATH; 3. <homeDir>/bin if it exists and is in PATH; 4. fallback <homeDir>/.sentry/bin.getGitHubHeaders() returns Accept: "application/vnd.github.v3+json" and "User-Agent": getUserAgent().fetchWithUpgradeError(url, init, serviceName) calls customFetch(), rethrows AbortError unchanged, converts TLS certificate failures to new UpgradeError("network_error", buildTlsErrorDetail(error)), and converts other failures to new UpgradeError("network_error", \Failed to connect to ${serviceName}: ${stringifyUnknown(error)}`)`.replaceBinarySync(tempPath, installPath) is intentionally synchronous so its replacement sequence cannot be interrupted. On Unix it atomically renames tempPath over installPath; on Windows it first renames the current executable to ${installPath}.old, retries after removing an existing .old, tolerates a missing current binary, and then renames the temporary executable into place.cleanupOldBinary(oldPath) performs fire-and-forget unlink(oldPath) and ignores failures; it deliberately does not clean .download files because another upgrade may be running, leaving that cleanup to the locked upgrade flow.acquireLock(lockPath) uses PID-based atomic lock creation via writeFileSync(lockPath, String(process.pid), { flag: "wx" }). Before locking, it creates dirname(lockPath) recursively with mode 0o755.ENOENT ... open '.../sentry.lock' in CLI-1E1 and CLI-1RV. Fix: acquireLock() now calls mkdirSync(dirname(lockPath), { recursive: true, mode: 0o755 }) before writing the lock.mkdirSync() in acquireLock() intentionally remains outside the lock-write try/catch; otherwise directory errors such as EEXIST, ENOTDIR, or EACCES could be misinterpreted as lock contention, including misleading ENOTDIR errors while reading a lock beneath a non-directory.handleExistingLock(lockPath) reads and parses the owning PID. An ENOENT read race retries acquireLock(), while other read errors propagate. A live PID causes UpgradeError("execution_failed", "Another upgrade is already in progress"), except when the owner is process.ppid, in which case ownership is transferred by overwriting the lock with process.pid.unlinkSync(lockPath) and acquisition is recursively retried; ENOENT during removal is tolerated, while other errors propagate. releaseLock(lockPath) removes the lock and ignores all removal failures.installBinary(sourcePath, installDir) creates installDir recursively with mode 0o755, derives installPath using getBinaryFilename(), acquires the associated lock, and stages the binary at the derived .download path.installBinary() compares symlink-resolved canonical source and temporary paths using realpath(), falling back to resolve() for ENOENT and logging other realpath failures. This handles cases such as macOS /tmp resolving to /private/tmp.sourcePath already canonicalizes to tempPath—as when upgrade spawns setup --install with the .download file as process.execPath—installBinary() skips unlinking and copying. Otherwise it removes a stale temp file, logs non-ENOENT cleanup failures, copies with copyFile(), and applies mode 0o755 outside Windows.packages/cli/CONTRIBUTING.md says CLI command UX follows gh conventions. List commands use optional positional context: sentry org list [--limit N] [--json], sentry project list [org] [--limit N] [--json], and sentry issue list [<org>/<project>] [--json].packages/cli/CONTRIBUTING.md is: <org>/<project> for an explicit organization/project; <org>/ for all projects in an organization; <project> to search by project name across accessible organizations; omission for DSN/config auto-detection.packages/cli/CONTRIBUTING.md are: sentry org view [org-slug] [--json] [-w]; sentry project view [<org>/<project>] [--json] [-w]; sentry issue view <issue-id> [--json] [-w]; and sentry event view [<org>/<project>] <event-id> [--json] [-w]. org view and project view mirror gh repo view, while issue and event IDs are required.-w/--web to open the resource in the default browser rather than display it in the terminal.packages/cli/CONTRIBUTING.md is: 1. explicit positional <org>/<project>; 2. configuration defaults set through sentry config set; 3. DSN auto-detection from SENTRY_DSN or source code.packages/cli/CONTRIBUTING.md documents common flags as --json for all view/list commands, -w/--web for all view commands, and --limit for list commands.ContextError imported from ../../lib/errors.js; the documented constructor example supplies the required entity ("Organization"), primary usage ("sentry org view <org-slug>"), and alternatives (["Set SENTRY_DSN for auto-detection"]).resolveOrg() or resolveOrgAndProject() from lib/resolve-target.ts; 3. support --json; 4. give view commands -w/--web; 5. use ContextError for missing context; 6. add E2E tests under test/e2e/.packages/cli/CONTRIBUTING.md: TypeScript strict mode, explicit types for public APIs, JSDoc for functions, and small focused functions.packages/cli/src/lib/version-check.ts, packages/cli/src/lib/upgrade.ts, packages/cli/src/lib/scan/binary.ts, packages/cli/src/lib/release-notes.ts, packages/cli/src/lib/ghcr.ts, packages/cli/src/lib/delta-upgrade.ts, packages/cli/src/lib/db/version-check.ts, packages/cli/src/lib/binary.ts, and packages/cli/src/commands/cli/upgrade.ts.packages/cli/src/lib/version-check.ts imports prefetchNightlyPatches, prefetchStablePatches, fetchLatestFromGitHub, and fetchLatestNightlyVersion; it invokes nightly versus stable patch prefetching at lines 134–136 and selects nightly versus GitHub latest-version fetching at lines 183–184.packages/cli/src/lib/upgrade.ts contains fetchLatestFromGitHub() at line 375, fetchLatestNightlyVersion() at line 443, fetchLatestVersion() at line 474, versionExists() at line 527, downloadNightlyToPath() at line 683, downloadStableToPath() at line 716, downloadBinaryToTemp() at line 856, and a call to attemptDeltaUpgrade() at line 943.packages/cli/src/lib/upgrade.ts use getBinaryDownloadUrl(version); nightly downloads use GHCR’s OCI blob protocol. downloadBinaryToTemp() dispatches to downloadNightlyToPath(destPath, version, setMessage) for nightlies or downloadStableToPath(downloadTag ?? version, destPath, setMessage) for stable releases.packages/cli/src/lib/release-notes.ts fetches releases using ${GITHUB_RELEASES_URL}?per_page=${CHANGELOG_MAX_RELEASES} and exports fetchChangelog().packages/cli/src/lib/delta-upgrade.ts imports both GITHUB_RELEASES_URL and GHCR_REPO, queries ${GITHUB_RELEASES_URL}?per_page=12, and exports resolveStableDelta(), resolveNightlyDelta(), attemptDeltaUpgrade(), prefetchNightlyPatches(), and prefetchStablePatches().packages/cli/src/lib/ghcr.ts configures GHCR_BLOB_TIMEOUT = 30_000; its retry loop runs from attempt 0 through GHCR_MAX_RETRIES inclusive. Comments note GHCR cold-start latency spikes from 126ms to 30s for identical requests.Authorization header because retaining it can interfere with signed query-string credentials and produce HTTP 404.