Dashboard › cli › Distillation
810ca765-6928-4122-89dd-de126125148f["lore_tm_v1_Ds0T-70EP5EnIvqpBdJr6NlFyw1NIBkR9xNl4eKd6bY","lore_tm_v1_9Oow_cGLKW-ys_a3GPBjq9jiIJ9dlJMzar3Vwm_wsDU"]
"drain".UpgradeError when the downloaded file never becomes visible or remains empty.packages/cli/src/lib/binary.ts defines shared binary installation, replacement, download-support, and locking utilities used by both setup --install and upgrade.packages/cli/src/lib/binary.ts defines InstallationMethod as "curl" | "brew" | "npm" | "pnpm" | "bun" | "yarn" | "unknown"; parseInstallationMethod() lowercases input and accepts all except "unknown" through VALID_METHODS, otherwise throwing Invalid method: ${value}. Must be one of: ${VALID_METHODS.join(", ")}.isMusl() in packages/cli/src/lib/binary.ts caches its result and detects musl Linux in 2 ordered stages: 1. checks /lib/ld-musl-${muslArch}.so.1, mapping x64 to x86_64 and all other supported architectures to aarch64; 2. runs ldd --version, combines stdout and stderr, and checks case-insensitively for "musl". Non-Linux and failed/missing ldd are treated as non-musl/glibc.getPlatformBinaryName() generates sentry-<os>-<arch>[-musl][.exe]: OS is darwin, windows, or fallback linux; architecture is arm64 or fallback x64; musl adds -musl; Windows adds .exe. getBinaryFilename() returns sentry.exe on Windows and sentry elsewhere.getBinaryDownloadUrl(version) returns https://github.com/getsentry/cli/releases/download/${version}/${getPlatformBinaryName()}; GITHUB_RELEASES_URL is https://api.github.com/repos/getsentry/cli/releases.isNightlyVersion(version) identifies nightlies by version.includes("-dev."). compareVersions(a, b) actually delegates to semverCompare imported from semver and returns -1 | 0 | 1, despite its comment saying it uses Bun.semver.order; isDowngrade(current, target) returns whether compareVersions(current, target) === 1.getBinaryPaths(installPath) derives exact sibling paths: tempPath=${installPath}.download, oldPath=${installPath}.old, and lockPath=${installPath}.lock.determineInstallDir(homeDir, env) uses this exact priority: 1. SENTRY_INSTALL_DIR if set; 2. ~/.local/bin if it exists and is in PATH; 3. ~/bin if it exists and is in PATH; 4. fallback ~/.sentry/bin, with setup responsible for updating PATH.getGitHubHeaders() returns Accept: "application/vnd.github.v3+json" and "User-Agent": getUserAgent().fetchWithUpgradeError(url, init, serviceName) calls customFetch; it rethrows errors named "AbortError" unchanged, maps TLS certificate errors using new UpgradeError("network_error", buildTlsErrorDetail(error)), and maps other failures to new UpgradeError("network_error", \Failed to connect to ${serviceName}: ${stringifyUnknown(error)}`)`.replaceBinarySync(tempPath, installPath) is intentionally synchronous so its multi-step replacement cannot be interrupted. On Unix it atomically renames temp over install. On Windows it tries to rename the running install to ${installPath}.old; after failure it unlinks an existing .old and retries, tolerates another failure as a potentially absent current binary, then renames temp into place.cleanupOldBinary(oldPath) fire-and-forgets unlink(oldPath) and ignores failures. It intentionally does not clean .download files because another process may be upgrading; .download cleanup occurs inside the upgrade flow under its exclusive lock.acquireLock(lockPath) first creates dirname(lockPath) recursively with mode 0o755, then atomically writes process.pid using { flag: "wx" }. Root cause addressed: fresh or purged install directories caused ENOENT ... open '.../sentry.lock'; mkdirSync remains outside the lock-creation try/catch so directory errors such as EEXIST, ENOTDIR, and EACCES are not misinterpreted as lock contention.handleExistingLock(lockPath) retries only when reading the lock races with deletion and yields ENOENT; other read errors propagate. A live PID normally causes UpgradeError("execution_failed", "Another upgrade is already in progress"), but if it equals process.ppid, the child takes over by rewriting the lock with process.pid, supporting the upgrade β setup --install handoff while retaining exclusion.ENOENT during removal is tolerated, while other removal errors propagate. releaseLock(lockPath) synchronously unlinks the lock and ignores all errors.installBinary(sourcePath, installDir) creates installDir recursively with mode 0o755, acquires the PID lock, installs through the sibling .download path, calls replaceBinarySync(), and always calls releaseLock() in finally; it returns the absolute installed binary path.installBinary() canonicalizes source and temp paths with realpath(), falling back to resolve() on ENOENT and also on other errors after logging "realpath failed, falling back to resolve()". This handles symlink differences such as macOS /tmp β /private/tmp.sourcePath equals canonical tempPathβnotably when upgrade launches setup --install with the .download file as the childβs process.execPathβinstallBinary() skips unlinking and copying. Otherwise it removes a leftover temp file, logs non-ENOENT cleanup failures as "Failed to clean up temp file", copies the source to temp, and applies mode 0o755 on non-Windows systems.