Skip to content

Commit a7135dc

Browse files
committed
feat(upgrade): add nightly release channel
Add support for a nightly release channel that tracks rolling builds from the main branch. Users can opt in via the install script or upgrade command, and the choice is persisted so future bare `sentry cli upgrade` calls use the same channel automatically. Changes: - CI: new `nightly-version` job computes `0.x.y-dev.<unix_ts>` version on main pushes; `build-binary` injects it before building; new `publish-nightly` job uploads .gz binaries + version.json to a rolling GitHub prerelease tagged `nightly` after build+E2E pass - Install script: `--version nightly` downloads from the nightly release and persists the channel via `--channel nightly` passed to `cli setup` - DB: new `src/lib/db/release-channel.ts` persists channel in the metadata table - `cli setup`: new `--channel` flag to persist the release channel on install - `cli upgrade`: `nightly`/`stable` as positional version args switch channels; new `--channel` flag as an equivalent alternative; new `--force` flag to re-download even when already up to date; auto-migrates brew/npm/pnpm/bun/yarn installs to standalone binary when switching to nightly - Version check: reads persisted channel and checks nightly version.json for nightly users; shows "New nightly available:" in notification
1 parent 14490e7 commit a7135dc

File tree

10 files changed

+688
-75
lines changed

10 files changed

+688
-75
lines changed

.github/workflows/ci.yml

Lines changed: 87 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,31 @@ concurrency:
1111
cancel-in-progress: true
1212

1313
jobs:
14+
nightly-version:
15+
name: Compute Nightly Version
16+
# Only compute a nightly version on direct pushes to main — not on PRs or
17+
# release branches. Downstream jobs (build-binary, publish-nightly) check
18+
# whether this output is non-empty before acting on it; when this job is
19+
# skipped the output is an empty string, so they behave as normal CI runs.
20+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
21+
runs-on: ubuntu-latest
22+
outputs:
23+
version: ${{ steps.version.outputs.version }}
24+
steps:
25+
- uses: actions/checkout@v4
26+
with:
27+
sparse-checkout: package.json
28+
- id: version
29+
name: Compute version
30+
run: |
31+
VERSION=$(node -e "
32+
const pkg = JSON.parse(require('fs').readFileSync('package.json', 'utf8'));
33+
const ts = Math.floor(Date.now() / 1000);
34+
// Replace trailing -dev.<n> suffix with -dev.<unix_seconds>
35+
console.log(pkg.version.replace(/-dev\.\d+$/, '-dev.' + ts));
36+
")
37+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
38+
1439
changes:
1540
name: Detect Changes
1641
runs-on: ubuntu-latest
@@ -133,7 +158,7 @@ jobs:
133158

134159
build-binary:
135160
name: Build Binary (${{ matrix.target }})
136-
needs: [lint, test-unit]
161+
needs: [lint, test-unit, nightly-version]
137162
runs-on: ${{ matrix.os }}
138163
strategy:
139164
fail-fast: false
@@ -178,6 +203,19 @@ jobs:
178203
done
179204
echo "All install attempts failed"
180205
exit 1
206+
- name: Set nightly version
207+
# Inject the computed nightly version into package.json before the build
208+
# so it gets baked into the binary. Only runs on main-branch pushes where
209+
# nightly-version produced a non-empty output.
210+
if: needs.nightly-version.outputs.version != ''
211+
shell: bash
212+
run: |
213+
node -e "
214+
const fs = require('fs');
215+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
216+
pkg.version = process.argv[1];
217+
fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');
218+
" "${{ needs.nightly-version.outputs.version }}"
181219
- name: Build
182220
env:
183221
SENTRY_CLIENT_ID: ${{ vars.SENTRY_CLIENT_ID }}
@@ -282,17 +320,63 @@ jobs:
282320
name: gh-pages
283321
path: gh-pages.zip
284322

323+
publish-nightly:
324+
name: Publish Nightly
325+
# Only publish after a successful main-branch build+test. Skipped on PRs
326+
# and release branches (nightly-version output will be empty).
327+
needs: [nightly-version, build-binary, test-e2e]
328+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
329+
runs-on: ubuntu-latest
330+
permissions:
331+
contents: write
332+
steps:
333+
- name: Download all binary artifacts
334+
uses: actions/download-artifact@v4
335+
with:
336+
pattern: sentry-*
337+
path: artifacts
338+
merge-multiple: true
339+
- name: Create version.json
340+
run: |
341+
cat > version.json <<EOF
342+
{"version":"${{ needs.nightly-version.outputs.version }}","sha":"${{ github.sha }}","date":"$(date -u +%Y-%m-%dT%H:%M:%SZ)"}
343+
EOF
344+
- name: Create or update nightly release
345+
env:
346+
GH_TOKEN: ${{ github.token }}
347+
GH_REPO: ${{ github.repository }}
348+
run: |
349+
# Create the release the first time; subsequent runs are a no-op
350+
gh release create nightly \
351+
--prerelease \
352+
--title "Nightly" \
353+
--notes "" \
354+
2>/dev/null || true
355+
356+
# Update release notes with the latest version + commit
357+
gh release edit nightly \
358+
--prerelease \
359+
--notes "Latest nightly build from the \`main\` branch.
360+
361+
**Version:** \`${{ needs.nightly-version.outputs.version }}\`
362+
**Commit:** ${{ github.sha }}"
363+
364+
# Upload/replace all .gz binaries and the version manifest
365+
gh release upload nightly --clobber \
366+
artifacts/dist-bin/*.gz \
367+
version.json
368+
285369
ci-status:
286370
name: CI Status
287371
if: always()
288-
needs: [changes, check-skill, build-binary, build-npm, build-docs, test-e2e]
372+
needs: [changes, check-skill, build-binary, build-npm, build-docs, test-e2e, publish-nightly]
289373
runs-on: ubuntu-latest
290374
permissions: {}
291375
steps:
292376
- name: Check CI status
293377
run: |
294378
# Check for explicit failures or cancellations in all jobs
295-
results="${{ needs.check-skill.result }} ${{ needs.build-binary.result }} ${{ needs.build-npm.result }} ${{ needs.build-docs.result }} ${{ needs.test-e2e.result }}"
379+
results="${{ needs.check-skill.result }} ${{ needs.build-binary.result }} ${{ needs.build-npm.result }} ${{ needs.build-docs.result }} ${{ needs.test-e2e.result }} ${{ needs.publish-nightly.result }}"
296380
for result in $results; do
297381
if [[ "$result" == "failure" || "$result" == "cancelled" ]]; then
298382
echo "::error::CI failed"

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

Lines changed: 52 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,20 +8,37 @@ Self-update the Sentry CLI to the latest or a specific version.
88
## Usage
99

1010
```bash
11-
sentry cli upgrade # Update to latest version
12-
sentry cli upgrade 0.5.0 # Update to specific version
11+
sentry cli upgrade # Update using the persisted channel (default: stable)
12+
sentry cli upgrade nightly # Switch to nightly channel and update
13+
sentry cli upgrade stable # Switch back to stable channel and update
14+
sentry cli upgrade 0.5.0 # Update to a specific stable version
1315
sentry cli upgrade --check # Check for updates without installing
16+
sentry cli upgrade --force # Force re-download even if already up to date
1417
sentry cli upgrade --method npm # Force using npm to upgrade
1518
```
1619

1720
## Options
1821

1922
| Option | Description |
2023
|--------|-------------|
21-
| `<version>` | Target version to install (defaults to latest) |
24+
| `<version>` | Target version, or `nightly`/`stable` to switch channel (defaults to latest) |
2225
| `--check` | Check for updates without installing |
26+
| `--force` | Re-download even if already on the latest version |
27+
| `--channel <channel>` | Set release channel: `stable` or `nightly` |
2328
| `--method <method>` | Force installation method: curl, brew, npm, pnpm, bun, yarn |
2429

30+
## Release Channels
31+
32+
The CLI supports two release channels:
33+
34+
| Channel | Description |
35+
|---------|-------------|
36+
| `stable` | Latest stable release (default) |
37+
| `nightly` | Built from `main`, updated on every commit |
38+
39+
The chosen channel is persisted locally so that subsequent bare `sentry cli upgrade`
40+
calls use the same channel without requiring a flag.
41+
2542
## Installation Detection
2643

2744
The CLI auto-detects how it was installed and uses the same method to upgrade:
@@ -35,6 +52,11 @@ The CLI auto-detects how it was installed and uses the same method to upgrade:
3552
| bun | Globally installed via `bun install -g sentry` |
3653
| yarn | Globally installed via `yarn global add sentry` |
3754

55+
> **Note:** Nightly builds are only available as standalone binaries (via the curl
56+
> install method). If you switch to the nightly channel from a package manager or
57+
> Homebrew install, the CLI will automatically migrate to a standalone binary and
58+
> warn you about the existing package-manager installation.
59+
3860
## Examples
3961

4062
### Check for updates
@@ -46,12 +68,13 @@ sentry cli upgrade --check
4668
```
4769
Installation method: curl
4870
Current version: 0.4.0
71+
Channel: stable
4972
Latest version: 0.5.0
5073
5174
Run 'sentry cli upgrade' to update.
5275
```
5376

54-
### Upgrade to latest
77+
### Upgrade to latest stable
5578

5679
```bash
5780
sentry cli upgrade
@@ -60,19 +83,44 @@ sentry cli upgrade
6083
```
6184
Installation method: curl
6285
Current version: 0.4.0
86+
Channel: stable
6387
Latest version: 0.5.0
6488
6589
Upgrading to 0.5.0...
6690
6791
Successfully upgraded to 0.5.0.
6892
```
6993

94+
### Switch to nightly channel
95+
96+
```bash
97+
sentry cli upgrade nightly
98+
# or equivalently:
99+
sentry cli upgrade --channel nightly
100+
```
101+
102+
After switching, bare `sentry cli upgrade` will continue tracking nightly.
103+
104+
### Switch back to stable
105+
106+
```bash
107+
sentry cli upgrade stable
108+
# or equivalently:
109+
sentry cli upgrade --channel stable
110+
```
111+
70112
### Upgrade to specific version
71113

72114
```bash
73115
sentry cli upgrade 0.5.0
74116
```
75117

118+
### Force re-download
119+
120+
```bash
121+
sentry cli upgrade --force
122+
```
123+
76124
### Force installation method
77125

78126
If auto-detection fails or you want to switch installation methods:

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

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,21 @@ import PackageManagerCode from "../../components/PackageManagerCode.astro";
99

1010
### Install Script
1111

12-
Install the Sentry CLI using the install script:
12+
Install the latest stable release:
1313

1414
```bash
1515
curl https://cli.sentry.dev/install -fsS | bash
1616
```
1717

18+
Install the nightly build (built from `main`, updated on every commit):
19+
20+
```bash
21+
curl https://cli.sentry.dev/install -fsS | bash -s -- --version nightly
22+
```
23+
24+
The chosen channel is persisted so that `sentry cli upgrade` automatically
25+
tracks the same channel on future updates.
26+
1827
### Homebrew
1928

2029
```bash

install

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ Usage: install [options]
1313
1414
Options:
1515
-h, --help Display this help message
16-
-v, --version <version> Install a specific version (e.g., 0.2.0)
16+
-v, --version <version> Install a specific version (e.g., 0.2.0) or "nightly"
1717
--no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.)
1818
--no-completions Don't install shell completions
1919
@@ -22,6 +22,7 @@ Environment Variables:
2222
2323
Examples:
2424
curl -fsSL https://cli.sentry.dev/install | bash
25+
curl -fsSL https://cli.sentry.dev/install | bash -s -- --version nightly
2526
curl -fsSL https://cli.sentry.dev/install | bash -s -- --version 0.2.0
2627
SENTRY_INSTALL_DIR=~/.local/bin curl -fsSL https://cli.sentry.dev/install | bash
2728
EOF
@@ -80,21 +81,44 @@ if [[ "$os" == "windows" ]]; then
8081
fi
8182
fi
8283

83-
# Resolve version
84+
# Resolve version and download tag.
85+
#
86+
# "nightly" is a special value that installs from the rolling nightly prerelease
87+
# built from the main branch. In this case:
88+
# - `version` holds the human-readable version string (e.g. "0.12.0-dev.1740393600")
89+
# - `download_tag` holds the GitHub release tag to download from ("nightly")
90+
#
91+
# For stable releases both are the same version string (e.g. "0.5.0").
92+
channel="stable"
93+
download_tag=""
94+
8495
if [[ -z "$requested_version" ]]; then
8596
version=$(curl -fsSL https://api.github.com/repos/getsentry/cli/releases/latest | sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')
8697
if [[ -z "$version" ]]; then
8798
echo -e "${RED}Failed to fetch latest version${NC}"
8899
exit 1
89100
fi
101+
download_tag="$version"
102+
elif [[ "$requested_version" == "nightly" ]]; then
103+
channel="nightly"
104+
download_tag="nightly"
105+
version_json=$(curl -fsSL "https://github.com/getsentry/cli/releases/download/nightly/version.json")
106+
version=$(echo "$version_json" | sed -n 's/.*"version": *"\([^"]*\)".*/\1/p')
107+
if [[ -z "$version" ]]; then
108+
echo -e "${RED}Failed to fetch nightly version${NC}"
109+
exit 1
110+
fi
90111
else
91112
version="$requested_version"
113+
download_tag="$requested_version"
92114
fi
93115

94116
# Strip leading 'v' if present (releases use version without 'v' prefix)
95117
version="${version#v}"
118+
download_tag="${download_tag#v}"
119+
96120
filename="sentry-${os}-${arch}${suffix}"
97-
url="https://github.com/getsentry/cli/releases/download/${version}/${filename}"
121+
url="https://github.com/getsentry/cli/releases/download/${download_tag}/${filename}"
98122

99123
# Download binary to a temp location
100124
tmpdir="${TMPDIR:-${TMP:-${TEMP:-/tmp}}}"
@@ -119,7 +143,9 @@ chmod +x "$tmp_binary"
119143
# Delegate installation and configuration to the binary itself.
120144
# setup --install handles: directory selection, binary placement, PATH,
121145
# completions, agent skills, and the welcome message.
122-
setup_args="--install --method curl"
146+
# --channel persists the release channel so future `sentry cli upgrade`
147+
# calls track the same channel without requiring a flag.
148+
setup_args="--install --method curl --channel $channel"
123149
if [[ "$no_modify_path" == "true" ]]; then
124150
setup_args="$setup_args --no-modify-path"
125151
fi

src/commands/cli/setup.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@ import { buildCommand } from "../../lib/command.js";
1515
import { installCompletions } from "../../lib/completions.js";
1616
import { CLI_VERSION } from "../../lib/constants.js";
1717
import { setInstallInfo } from "../../lib/db/install-info.js";
18+
import {
19+
parseReleaseChannel,
20+
type ReleaseChannel,
21+
setReleaseChannel,
22+
} from "../../lib/db/release-channel.js";
1823
import {
1924
addToGitHubPath,
2025
addToPath,
@@ -32,6 +37,7 @@ import {
3237
type SetupFlags = {
3338
readonly install: boolean;
3439
readonly method?: InstallationMethod;
40+
readonly channel?: ReleaseChannel;
3541
readonly "no-modify-path": boolean;
3642
readonly "no-completions": boolean;
3743
readonly "no-agent-skills": boolean;
@@ -264,6 +270,13 @@ export const setupCommand = buildCommand({
264270
placeholder: "method",
265271
optional: true,
266272
},
273+
channel: {
274+
kind: "parsed",
275+
parse: parseReleaseChannel,
276+
brief: "Release channel to persist (stable or nightly)",
277+
placeholder: "channel",
278+
optional: true,
279+
},
267280
"no-modify-path": {
268281
kind: "boolean",
269282
brief: "Skip PATH modification",
@@ -329,6 +342,14 @@ export const setupCommand = buildCommand({
329342
}
330343
}
331344

345+
// 1b. Persist release channel (set by install script or upgrade --channel)
346+
if (flags.channel) {
347+
setReleaseChannel(flags.channel);
348+
if (!flags.install) {
349+
log(`Recorded release channel: ${flags.channel}`);
350+
}
351+
}
352+
332353
// 2. Handle PATH modification
333354
if (!flags["no-modify-path"]) {
334355
await handlePathModification(binaryDir, shell, process.env, log);

0 commit comments

Comments
 (0)