Skip to content

Commit 1a72a39

Browse files
committed
fix(brew): resolve symlinks in Homebrew detection, error on version pinning
- Use realpathSync to resolve process.execPath before checking for /Cellar/, since Homebrew exposes binaries via symlinks in the prefix bin dir - Error early with a clear message when brew is the method and a specific version is requested, since Homebrew manages versioning via the formula
1 parent bb2c353 commit 1a72a39

File tree

2 files changed

+23
-7
lines changed

2 files changed

+23
-7
lines changed

src/commands/cli/upgrade.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,15 @@ export const upgradeCommand = buildCommand({
177177
throw new UpgradeError("unknown_method");
178178
}
179179

180+
// Homebrew manages versioning through the formula in the tap — the installed
181+
// version is always whatever the formula specifies, not an arbitrary release.
182+
if (method === "brew" && version) {
183+
throw new UpgradeError(
184+
"unknown_method",
185+
"Homebrew does not support installing a specific version. Run 'brew upgrade getsentry/tools/sentry' to upgrade to the latest formula version."
186+
);
187+
}
188+
180189
stdout.write(`Installation method: ${method}\n`);
181190
stdout.write(`Current version: ${CLI_VERSION}\n`);
182191

src/lib/upgrade.ts

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
*/
88

99
import { spawn } from "node:child_process";
10-
import { chmodSync, unlinkSync } from "node:fs";
10+
import { chmodSync, realpathSync, unlinkSync } from "node:fs";
1111
import { homedir } from "node:os";
1212
import { join, sep } from "node:path";
1313
import {
@@ -166,14 +166,21 @@ async function isInstalledWith(pm: PackageManager): Promise<boolean> {
166166
/**
167167
* Detect if the CLI binary is running from a Homebrew Cellar.
168168
*
169-
* Homebrew installs binaries as symlinks into the Cellar directory
170-
* (e.g. `/opt/homebrew/Cellar/sentry/1.2.3/bin/sentry` or
171-
* `/usr/local/Cellar/sentry/1.2.3/bin/sentry`). Checking for `/Cellar/`
172-
* in the resolved exec path is a reliable heuristic that works on both
173-
* Apple Silicon (`/opt/homebrew`) and Intel Macs (`/usr/local`).
169+
* Homebrew places the real binary deep in the Cellar
170+
* (e.g. `/opt/homebrew/Cellar/sentry/1.2.3/bin/sentry`) and exposes it
171+
* via a symlink at the prefix bin dir (e.g. `/opt/homebrew/bin/sentry`).
172+
* `process.execPath` typically reflects the symlink, not the realpath, so
173+
* we resolve symlinks first before checking for `/Cellar/`. Falls back to
174+
* the unresolved path if `realpathSync` throws (e.g. binary was deleted).
174175
*/
175176
function isHomebrewInstall(): boolean {
176-
return process.execPath.includes("/Cellar/");
177+
let execPath = process.execPath;
178+
try {
179+
execPath = realpathSync(execPath);
180+
} catch {
181+
// Binary may have been deleted or moved; use the original path
182+
}
183+
return execPath.includes("/Cellar/");
177184
}
178185

179186
/**

0 commit comments

Comments
 (0)