Skip to content

Commit 0201241

Browse files
committed
refactor(nightly): address PR review feedback
- Remove redundant job-level permissions (workflow-level already covers it) - Remove checkout step from publish-nightly (not needed — no repo files used) - Add sha256 verification for ORAS CLI tarball download - Add comment explaining why binaries are compressed in publish-nightly - Use -dev.<timestamp> version format (consistent with build-binary job) - Add AbortError class to errors.ts, replace Object.assign hack in upgrade.ts - Use Record mapping for platform name in getNightlyGzFilename - Revert --nightly flag to --version nightly in install script
1 parent a1fcdc9 commit 0201241

File tree

9 files changed

+70
-74
lines changed

9 files changed

+70
-74
lines changed

.github/workflows/ci.yml

Lines changed: 21 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -227,26 +227,18 @@ jobs:
227227
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
228228
needs: [build-binary]
229229
runs-on: ubuntu-latest
230-
permissions:
231-
contents: read
232-
packages: write
233230
steps:
234-
- uses: actions/checkout@v4
235-
236231
- name: Download all binary artifacts
237232
uses: actions/download-artifact@v4
238233
with:
239234
pattern: sentry-*
240235
path: artifacts
241236
merge-multiple: true
242237

243-
- name: Install ORAS CLI
244-
run: |
245-
VERSION=1.2.3
246-
curl -sfL "https://github.com/oras-project/oras/releases/download/v${VERSION}/oras_${VERSION}_linux_amd64.tar.gz" \
247-
| tar -xz -C /usr/local/bin oras
248-
249238
- name: Compress binaries
239+
# Build artifacts are raw binaries — compression for stable releases
240+
# happens in Craft during publishing, not during build. GHCR nightly
241+
# needs .gz for efficient OCI layers.
250242
run: |
251243
ls artifacts/
252244
for f in artifacts/sentry-linux-* artifacts/sentry-darwin-*; do
@@ -258,23 +250,29 @@ jobs:
258250
done
259251
ls artifacts/*.gz
260252
253+
- name: Install ORAS CLI
254+
run: |
255+
VERSION=1.2.3
256+
EXPECTED_SHA256="b4efc97a91f471f323f193ea4b4d63d8ff443ca3aab514151a30751330852827"
257+
TARBALL="oras_${VERSION}_linux_amd64.tar.gz"
258+
curl -sfLo "$TARBALL" "https://github.com/oras-project/oras/releases/download/v${VERSION}/${TARBALL}"
259+
echo "${EXPECTED_SHA256} ${TARBALL}" | sha256sum -c -
260+
tar -xz -C /usr/local/bin oras < "$TARBALL"
261+
rm "$TARBALL"
262+
261263
- name: Compute nightly version
262264
id: version
263265
run: |
264-
# Derive a deterministic Unix timestamp from the commit so that all
265-
# jobs in this workflow run produce the same version string.
266-
TS=$(date -d '${{ github.event.head_commit.timestamp }}' +%s 2>/dev/null \
267-
|| date -j -f "%Y-%m-%dT%H:%M:%S%z" '${{ github.event.head_commit.timestamp }}' +%s 2>/dev/null \
268-
|| echo ${{ github.run_number }})
269-
VERSION="0.0.0-nightly.${TS}"
266+
# The build-binary job bakes X.Y.Z-dev.<TS> into each binary using the
267+
# same COMMIT_TIMESTAMP env var. Re-derive the version here so the GHCR
268+
# manifest annotation matches the version string inside the binary.
269+
TS=$(date -d "$COMMIT_TIMESTAMP" +%s)
270+
CURRENT=$(curl -sf "https://raw.githubusercontent.com/${{ github.repository }}/${{ github.sha }}/package.json" \
271+
| awk -F'"' '/"version"/{print $4}')
272+
VERSION=$(echo "$CURRENT" | sed "s/-dev\.[0-9]*$/-dev.${TS}/")
270273
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"
271274
echo "Nightly version: ${VERSION}"
272275
273-
- name: Create version.json
274-
run: |
275-
echo '{"version":"${{ steps.version.outputs.version }}"}' > version.json
276-
cat version.json
277-
278276
- name: Log in to GHCR
279277
run: echo "${{ secrets.GITHUB_TOKEN }}" | oras login ghcr.io -u ${{ github.actor }} --password-stdin
280278

@@ -285,8 +283,7 @@ jobs:
285283
--artifact-type application/vnd.sentry.cli.nightly \
286284
--annotation "org.opencontainers.image.source=https://github.com/getsentry/cli" \
287285
--annotation "version=${VERSION}" \
288-
artifacts/*.gz \
289-
version.json
286+
artifacts/*.gz
290287
291288
test-e2e:
292289
name: E2E Tests

install

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +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)
17-
--nightly Install the latest nightly build from GHCR
16+
-v, --version <version> Install a specific version (e.g., 0.2.0) or "nightly"
1817
--no-modify-path Don't modify shell config files (.zshrc, .bashrc, etc.)
1918
--no-completions Don't install shell completions
2019
@@ -25,13 +24,11 @@ Examples:
2524
curl -fsSL https://cli.sentry.dev/install | bash
2625
curl -fsSL https://cli.sentry.dev/install | bash -s -- --version nightly
2726
curl -fsSL https://cli.sentry.dev/install | bash -s -- --version 0.2.0
28-
curl -fsSL https://cli.sentry.dev/install | bash -s -- --nightly
2927
SENTRY_INSTALL_DIR=~/.local/bin curl -fsSL https://cli.sentry.dev/install | bash
3028
EOF
3129
}
3230

3331
requested_version=""
34-
nightly=false
3532
no_modify_path=false
3633
no_completions=false
3734
while [[ $# -gt 0 ]]; do
@@ -46,10 +43,6 @@ while [[ $# -gt 0 ]]; do
4643
exit 1
4744
fi
4845
;;
49-
--nightly)
50-
nightly=true
51-
shift
52-
;;
5346
--no-modify-path)
5447
no_modify_path=true
5548
shift
@@ -88,13 +81,6 @@ if [[ "$os" == "windows" ]]; then
8881
fi
8982
fi
9083

91-
# Validate flag combinations
92-
if [[ "$nightly" == "true" && -n "$requested_version" ]]; then
93-
echo -e "${RED}Error: --nightly and --version are mutually exclusive${NC}"
94-
exit 1
95-
fi
96-
97-
9884
# Download binary to a temp location
9985
tmpdir="${TMPDIR:-${TMP:-${TEMP:-/tmp}}}"
10086
tmp_binary="${tmpdir}/sentry-install-$$${suffix}"
@@ -103,7 +89,7 @@ version=""
10389
# Clean up temp binary on failure (setup handles cleanup on success)
10490
trap 'rm -f "$tmp_binary"' EXIT
10591

106-
if [[ "$nightly" == "true" ]]; then
92+
if [[ "$requested_version" == "nightly" ]]; then
10793
# Nightly build: download from GHCR via OCI blob protocol.
10894
# No jq needed — parse JSON with awk.
10995
# ghcr.io blob downloads redirect to Azure Blob Storage. curl -L would
@@ -202,8 +188,9 @@ chmod +x "$tmp_binary"
202188
# completions, agent skills, and the welcome message.
203189
# --channel persists the release channel so future `sentry cli upgrade`
204190
# calls track the same channel without requiring a flag.
205-
channel="nightly"
206-
if [[ "$nightly" != "true" ]]; then
191+
if [[ "$requested_version" == "nightly" ]]; then
192+
channel="nightly"
193+
else
207194
channel="stable"
208195
fi
209196
setup_args="--install --method curl --channel $channel"

src/lib/errors.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -376,6 +376,20 @@ export class SeerError extends CliError {
376376

377377
// Error Utilities
378378

379+
/**
380+
* Thrown when an operation is cancelled via an AbortSignal.
381+
*
382+
* Matches the `error.name === "AbortError"` convention used throughout the
383+
* codebase (version-check.ts, sentry-client.ts, binary.ts) to detect and
384+
* silently swallow cancellation errors.
385+
*/
386+
export class AbortError extends Error {
387+
override name = "AbortError" as const;
388+
constructor() {
389+
super("The operation was aborted");
390+
}
391+
}
392+
379393
/**
380394
* Convert an unknown value to a human-readable string.
381395
*

src/lib/ghcr.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ export async function fetchNightlyManifest(
156156
* The version is set via `--annotation "version=<ver>"` during `oras push`.
157157
*
158158
* @param manifest - OCI manifest from {@link fetchNightlyManifest}
159-
* @returns Version string (e.g., "0.0.0-nightly.1740000000")
159+
* @returns Version string (e.g., "0.13.0-dev.1740000000")
160160
* @throws {UpgradeError} When the version annotation is missing
161161
*/
162162
export function getNightlyVersion(manifest: OciManifest): string {

src/lib/upgrade.ts

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
import { CLI_VERSION } from "./constants.js";
2525
import { getInstallInfo, setInstallInfo } from "./db/install-info.js";
2626
import type { ReleaseChannel } from "./db/release-channel.js";
27-
import { UpgradeError } from "./errors.js";
27+
import { AbortError, UpgradeError } from "./errors.js";
2828
import {
2929
downloadNightlyBlob,
3030
fetchNightlyManifest,
@@ -334,7 +334,7 @@ export async function fetchLatestFromNpm(): Promise<string> {
334334
* only 2 HTTP requests total (token + manifest), no blob download needed.
335335
*
336336
* @param signal - Optional AbortSignal to cancel the requests
337-
* @returns Latest nightly version string (e.g., "0.0.0-nightly.1740000000")
337+
* @returns Latest nightly version string (e.g., "0.13.0-dev.1740000000")
338338
* @throws {UpgradeError} When fetch fails or the version annotation is missing
339339
*/
340340
export async function fetchLatestNightlyVersion(
@@ -343,13 +343,13 @@ export async function fetchLatestNightlyVersion(
343343
// AbortSignal is not threaded through ghcr helpers, but checking it before
344344
// each network call ensures we bail out promptly when the process exits.
345345
if (signal?.aborted) {
346-
throw Object.assign(new Error("AbortError"), { name: "AbortError" });
346+
throw new AbortError();
347347
}
348348

349349
const token = await getAnonymousToken();
350350

351351
if (signal?.aborted) {
352-
throw Object.assign(new Error("AbortError"), { name: "AbortError" });
352+
throw new AbortError();
353353
}
354354

355355
const manifest = await fetchNightlyManifest(token);
@@ -359,13 +359,14 @@ export async function fetchLatestNightlyVersion(
359359
/**
360360
* Detect if the given version string represents a nightly build.
361361
*
362-
* Nightly versions follow the pattern `0.0.0-nightly.<timestamp>`.
362+
* Nightly versions follow the pattern `X.Y.Z-dev.<timestamp>` (the same
363+
* format the build system bakes in via `sed "s/-dev\.[0-9]*$/-dev.${TS}/"`).
363364
*
364365
* @param version - Version string to check
365366
* @returns true if the version is a nightly build
366367
*/
367368
export function isNightlyVersion(version: string): boolean {
368-
return version.includes("-nightly.");
369+
return version.includes("-dev.");
369370
}
370371

371372
/**
@@ -468,14 +469,11 @@ async function streamDecompressToFile(
468469
* @returns Filename of the gzip-compressed binary for this platform
469470
*/
470471
function getNightlyGzFilename(): string {
471-
let os: string;
472-
if (process.platform === "darwin") {
473-
os = "darwin";
474-
} else if (process.platform === "win32") {
475-
os = "windows";
476-
} else {
477-
os = "linux";
478-
}
472+
const platformNames: Record<string, string> = {
473+
darwin: "darwin",
474+
win32: "windows",
475+
};
476+
const os = platformNames[process.platform] ?? "linux";
479477
const arch = process.arch === "arm64" ? "arm64" : "x64";
480478
const suffix = process.platform === "win32" ? ".exe" : "";
481479
return `sentry-${os}-${arch}${suffix}.gz`;

src/lib/version-check.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/**
22
* Background version check for "new version available" notifications.
33
*
4-
* For nightly builds (CLI_VERSION contains "-nightly."), checks GHCR for the
4+
* For nightly builds (CLI_VERSION contains "-dev.<timestamp>"), checks GHCR for the
55
* latest nightly version via the OCI manifest annotation. For stable builds,
66
* checks GitHub Releases. Results are cached in the database and shown on
77
* subsequent runs.

test/commands/cli/upgrade.test.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -367,7 +367,7 @@ describe("sentry cli upgrade", () => {
367367

368368
describe("nightly version check", () => {
369369
test("--check mode with 'nightly' positional fetches latest from GHCR", async () => {
370-
const nightlyVersion = "0.0.0-nightly.1740000000";
370+
const nightlyVersion = "0.0.0-dev.1740000000";
371371
// 'nightly' as positional switches channel to nightly — fetches from GHCR
372372
mockGhcrNightlyVersion(nightlyVersion);
373373

@@ -386,7 +386,7 @@ describe("sentry cli upgrade", () => {
386386
});
387387

388388
test("--check with 'nightly' positional shows upgrade hint when newer nightly available", async () => {
389-
const nightlyVersion = "0.0.0-nightly.1740000000";
389+
const nightlyVersion = "0.0.0-dev.1740000000";
390390
mockGhcrNightlyVersion(nightlyVersion);
391391

392392
const { context, output } = createMockContext({ homeDir: testDir });
@@ -691,7 +691,7 @@ describe("sentry cli upgrade — curl full upgrade path (Bun.spawn spy)", () =>
691691
}
692692
return new Response(
693693
JSON.stringify({
694-
annotations: { version: "0.99.0-nightly.1234567890" },
694+
annotations: { version: "0.99.0-dev.1234567890" },
695695
layers: [
696696
{
697697
digest: "sha256:abc123",
@@ -808,7 +808,7 @@ describe("sentry cli upgrade — migrateToStandaloneForNightly (Bun.spawn spy)",
808808
}
809809
return new Response(
810810
JSON.stringify({
811-
annotations: { version: "0.99.0-nightly.1234567890" },
811+
annotations: { version: "0.99.0-dev.1234567890" },
812812
layers: [
813813
{
814814
digest: "sha256:abc456",

test/lib/ghcr.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ function makeManifest(overrides: Partial<OciManifest> = {}): OciManifest {
5757
},
5858
],
5959
annotations: {
60-
version: "0.0.0-nightly.1740000000",
60+
version: "0.0.0-dev.1740000000",
6161
"org.opencontainers.image.source": "https://github.com/getsentry/cli",
6262
},
6363
...overrides,
@@ -174,7 +174,7 @@ describe("fetchNightlyManifest", () => {
174174
describe("getNightlyVersion", () => {
175175
test("extracts version from manifest annotations", () => {
176176
const manifest = makeManifest();
177-
expect(getNightlyVersion(manifest)).toBe("0.0.0-nightly.1740000000");
177+
expect(getNightlyVersion(manifest)).toBe("0.0.0-dev.1740000000");
178178
});
179179

180180
test("throws UpgradeError when version annotation is missing", () => {

test/lib/upgrade.test.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -377,7 +377,7 @@ describe("fetchLatestVersion", () => {
377377
if (urlStr.includes("/manifests/nightly")) {
378378
return new Response(
379379
JSON.stringify({
380-
annotations: { version: "0.0.0-nightly.1740393600" },
380+
annotations: { version: "0.0.0-dev.1740393600" },
381381
}),
382382
{ status: 200 }
383383
);
@@ -386,7 +386,7 @@ describe("fetchLatestVersion", () => {
386386
});
387387

388388
const version = await fetchLatestVersion("curl", "nightly");
389-
expect(version).toBe("0.0.0-nightly.1740393600");
389+
expect(version).toBe("0.0.0-dev.1740393600");
390390
});
391391

392392
test("uses GHCR manifest when channel is nightly (npm method)", async () => {
@@ -399,7 +399,7 @@ describe("fetchLatestVersion", () => {
399399
if (urlStr.includes("/manifests/nightly")) {
400400
return new Response(
401401
JSON.stringify({
402-
annotations: { version: "0.0.0-nightly.1740393600" },
402+
annotations: { version: "0.0.0-dev.1740393600" },
403403
}),
404404
{ status: 200 }
405405
);
@@ -408,7 +408,7 @@ describe("fetchLatestVersion", () => {
408408
});
409409

410410
const version = await fetchLatestVersion("npm", "nightly");
411-
expect(version).toBe("0.0.0-nightly.1740393600");
411+
expect(version).toBe("0.0.0-dev.1740393600");
412412
});
413413

414414
test("defaults to stable channel (uses GitHub) when channel omitted", async () => {
@@ -1074,8 +1074,8 @@ describe("startCleanupOldBinary", () => {
10741074

10751075
describe("isNightlyVersion", () => {
10761076
test("returns true for nightly version strings", () => {
1077-
expect(isNightlyVersion("0.0.0-nightly.1740000000")).toBe(true);
1078-
expect(isNightlyVersion("0.0.0-nightly.1")).toBe(true);
1077+
expect(isNightlyVersion("0.0.0-dev.1740000000")).toBe(true);
1078+
expect(isNightlyVersion("0.0.0-dev.1")).toBe(true);
10791079
});
10801080

10811081
test("returns false for stable version strings", () => {
@@ -1104,7 +1104,7 @@ describe("fetchLatestNightlyVersion", () => {
11041104
JSON.stringify({
11051105
schemaVersion: 2,
11061106
layers: [],
1107-
annotations: { version: "0.0.0-nightly.1740000000" },
1107+
annotations: { version: "0.0.0-dev.1740000000" },
11081108
}),
11091109
{
11101110
status: 200,
@@ -1118,7 +1118,7 @@ describe("fetchLatestNightlyVersion", () => {
11181118
});
11191119

11201120
const version = await fetchLatestNightlyVersion();
1121-
expect(version).toBe("0.0.0-nightly.1740000000");
1121+
expect(version).toBe("0.0.0-dev.1740000000");
11221122
expect(callCount).toBe(2); // token + manifest
11231123
});
11241124

@@ -1219,7 +1219,7 @@ describe("executeUpgrade with curl method (nightly)", () => {
12191219
annotations: { "org.opencontainers.image.title": title },
12201220
},
12211221
],
1222-
annotations: { version: "0.0.0-nightly.1740000000" },
1222+
annotations: { version: "0.0.0-dev.1740000000" },
12231223
}),
12241224
{ status: 200 }
12251225
);
@@ -1231,7 +1231,7 @@ describe("executeUpgrade with curl method (nightly)", () => {
12311231
return new Response("Not Found", { status: 404 });
12321232
});
12331233

1234-
const result = await executeUpgrade("curl", "0.0.0-nightly.1740000000");
1234+
const result = await executeUpgrade("curl", "0.0.0-dev.1740000000");
12351235

12361236
expect(result).not.toBeNull();
12371237
expect(result).toHaveProperty("tempBinaryPath");

0 commit comments

Comments
 (0)