Skip to content

Commit 959cbaf

Browse files
committed
feat(release): add Homebrew install support
- Add brew target to .craft.yml to publish Formula/sentry.rb to getsentry/homebrew-tools tap on each stable release - Detect Homebrew installs (binary under /Cellar/) and run `brew upgrade getsentry/tools/sentry` for self-upgrades - Add brew to VALID_METHODS and fetchLatestVersion/versionExists routing (uses GitHub releases, same as curl) - Document `brew install getsentry/tools/sentry` in getting-started and cli-upgrade docs Closes #228
1 parent 24fe62c commit 959cbaf

File tree

5 files changed

+114
-5
lines changed

5 files changed

+114
-5
lines changed

.craft.yml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,3 +17,43 @@ targets:
1717
- name: npm
1818
- name: github
1919
- name: gh-pages
20+
- name: brew
21+
tap: getsentry/tools
22+
formula: sentry
23+
path: Formula
24+
includeNames: '/^sentry-(darwin|linux)-(arm64|x64)$/'
25+
template: |
26+
class Sentry < Formula
27+
desc "Sentry command-line tool for error monitoring and debugging"
28+
homepage "https://cli.sentry.dev"
29+
version "{{version}}"
30+
license "FSL-1.1-MIT"
31+
32+
if OS.mac?
33+
if Hardware::CPU.arm?
34+
url "https://github.com/getsentry/cli/releases/download/{{version}}/sentry-darwin-arm64"
35+
sha256 "{{checksums.sentry-darwin-arm64}}"
36+
elsif Hardware::CPU.intel?
37+
url "https://github.com/getsentry/cli/releases/download/{{version}}/sentry-darwin-x64"
38+
sha256 "{{checksums.sentry-darwin-x64}}"
39+
end
40+
elsif OS.linux?
41+
if Hardware::CPU.arm? && Hardware::CPU.is_64_bit?
42+
url "https://github.com/getsentry/cli/releases/download/{{version}}/sentry-linux-arm64"
43+
sha256 "{{checksums.sentry-linux-arm64}}"
44+
elsif Hardware::CPU.intel? && Hardware::CPU.is_64_bit?
45+
url "https://github.com/getsentry/cli/releases/download/{{version}}/sentry-linux-x64"
46+
sha256 "{{checksums.sentry-linux-x64}}"
47+
end
48+
else
49+
raise "Unsupported operating system"
50+
end
51+
52+
def install
53+
bin.install Dir["sentry-*"].first => "sentry"
54+
end
55+
56+
test do
57+
assert_match version.to_s, shell_output("#{bin}/sentry --version").chomp
58+
end
59+
end

docs/src/content/docs/commands/cli/upgrade.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ sentry cli upgrade --method npm # Force using npm to upgrade
2020
|--------|-------------|
2121
| `<version>` | Target version to install (defaults to latest) |
2222
| `--check` | Check for updates without installing |
23-
| `--method <method>` | Force installation method: curl, npm, pnpm, bun, yarn |
23+
| `--method <method>` | Force installation method: curl, brew, npm, pnpm, bun, yarn |
2424

2525
## Installation Detection
2626

@@ -29,6 +29,7 @@ The CLI auto-detects how it was installed and uses the same method to upgrade:
2929
| Method | Detection |
3030
|--------|-----------|
3131
| curl | Binary located in `~/.sentry/bin` (installed via cli.sentry.dev) |
32+
| brew | Binary located in a Homebrew Cellar (installed via `brew install getsentry/tools/sentry`) |
3233
| npm | Globally installed via `npm install -g sentry` |
3334
| pnpm | Globally installed via `pnpm add -g sentry` |
3435
| bun | Globally installed via `bun install -g sentry` |

docs/src/content/docs/getting-started.mdx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,12 @@ Install the Sentry CLI using the install script:
1515
curl https://cli.sentry.dev/install -fsS | bash
1616
```
1717

18+
### Homebrew
19+
20+
```bash
21+
brew install getsentry/tools/sentry
22+
```
23+
1824
### Package Managers
1925

2026
Install globally with your preferred package manager:

src/commands/cli/upgrade.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ export const upgradeCommand = buildCommand({
157157
method: {
158158
kind: "parsed",
159159
parse: parseInstallationMethod,
160-
brief: "Installation method to use (curl, npm, pnpm, bun, yarn)",
160+
brief: "Installation method to use (curl, brew, npm, pnpm, bun, yarn)",
161161
optional: true,
162162
placeholder: "method",
163163
},

src/lib/upgrade.ts

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import { UpgradeError } from "./errors.js";
2929

3030
export type InstallationMethod =
3131
| "curl"
32+
| "brew"
3233
| "npm"
3334
| "pnpm"
3435
| "bun"
@@ -162,13 +163,31 @@ async function isInstalledWith(pm: PackageManager): Promise<boolean> {
162163
}
163164
}
164165

166+
/**
167+
* Detect if the CLI binary is running from a Homebrew Cellar.
168+
*
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`).
174+
*/
175+
function isHomebrewInstall(): boolean {
176+
return process.execPath.includes("/Cellar/");
177+
}
178+
165179
/**
166180
* Legacy detection for existing installs that don't have stored install info.
167181
* Checks known curl install paths and package managers.
168182
*
169183
* @returns Detected installation method, or "unknown" if unable to determine
170184
*/
171185
async function detectLegacyInstallationMethod(): Promise<InstallationMethod> {
186+
// Check for Homebrew Cellar path before curl — both may match known bin dirs
187+
if (isHomebrewInstall()) {
188+
return "brew";
189+
}
190+
172191
// Check known curl install paths
173192
for (const dir of KNOWN_CURL_PATHS) {
174193
if (process.execPath.startsWith(dir)) {
@@ -289,7 +308,7 @@ export async function fetchLatestFromNpm(): Promise<string> {
289308

290309
/**
291310
* Fetch the latest available version based on installation method.
292-
* curl installations check GitHub releases; package managers check npm.
311+
* curl and brew installations check GitHub releases; package managers check npm.
293312
*
294313
* @param method - How the CLI was installed
295314
* @returns Latest version string (without 'v' prefix)
@@ -298,7 +317,9 @@ export async function fetchLatestFromNpm(): Promise<string> {
298317
export function fetchLatestVersion(
299318
method: InstallationMethod
300319
): Promise<string> {
301-
return method === "curl" ? fetchLatestFromGitHub() : fetchLatestFromNpm();
320+
return method === "curl" || method === "brew"
321+
? fetchLatestFromGitHub()
322+
: fetchLatestFromNpm();
302323
}
303324

304325
/**
@@ -314,7 +335,7 @@ export async function versionExists(
314335
method: InstallationMethod,
315336
version: string
316337
): Promise<boolean> {
317-
if (method === "curl") {
338+
if (method === "curl" || method === "brew") {
318339
const response = await fetchWithUpgradeError(
319340
`${GITHUB_RELEASES_URL}/tags/${version}`,
320341
{ method: "HEAD", headers: getGitHubHeaders() },
@@ -453,6 +474,43 @@ export async function downloadBinaryToTemp(
453474
}
454475
}
455476

477+
/**
478+
* Execute upgrade via Homebrew.
479+
*
480+
* Runs `brew upgrade getsentry/tools/sentry` which fetches the latest
481+
* formula from the tap and installs the new version. The version argument
482+
* is intentionally ignored: Homebrew manages versioning through the formula
483+
* file in the tap and does not support pinning to an arbitrary release.
484+
*
485+
* @throws {UpgradeError} When brew upgrade fails
486+
*/
487+
function executeUpgradeHomebrew(): Promise<void> {
488+
return new Promise((resolve, reject) => {
489+
const proc = spawn("brew", ["upgrade", "getsentry/tools/sentry"], {
490+
stdio: "inherit",
491+
});
492+
493+
proc.on("close", (code) => {
494+
if (code === 0) {
495+
resolve();
496+
} else {
497+
reject(
498+
new UpgradeError(
499+
"execution_failed",
500+
`brew upgrade failed with exit code ${code}`
501+
)
502+
);
503+
}
504+
});
505+
506+
proc.on("error", (err) => {
507+
reject(
508+
new UpgradeError("execution_failed", `brew failed: ${err.message}`)
509+
);
510+
});
511+
});
512+
}
513+
456514
/**
457515
* Execute upgrade via package manager global install.
458516
*
@@ -516,6 +574,9 @@ export async function executeUpgrade(
516574
switch (method) {
517575
case "curl":
518576
return downloadBinaryToTemp(version);
577+
case "brew":
578+
await executeUpgradeHomebrew();
579+
return null;
519580
case "npm":
520581
case "pnpm":
521582
case "bun":
@@ -530,6 +591,7 @@ export async function executeUpgrade(
530591
/** Valid methods that can be specified via --method flag */
531592
const VALID_METHODS: InstallationMethod[] = [
532593
"curl",
594+
"brew",
533595
"npm",
534596
"pnpm",
535597
"bun",

0 commit comments

Comments
 (0)