Skip to content

Commit fad78b2

Browse files
BYKcodex
andcommitted
Fix release and installer review findings
Co-Authored-By: OpenAI Codex <noreply@openai.com>
1 parent 6ca76cd commit fad78b2

4 files changed

Lines changed: 144 additions & 35 deletions

File tree

‎.github/workflows/cli-build.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ permissions:
1616
packages: write
1717

1818
env:
19-
SENTRY_CLIENT_ID: ${{ vars.SENTRY_CLIENT_ID || 'ci-release-dummy' }}
19+
SENTRY_CLIENT_ID: ${{ vars.SENTRY_CLIENT_ID }}
2020
NODE_VERSION_20: "20.20.2"
2121
NODE_VERSION_22: "22.23.1"
2222
NODE_VERSION_24: "24.18.0"

‎apps/cli-docs/src/content/docs/migrating-from-v3.md‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,12 +288,15 @@ biggest migration gotcha is flags that silently no longer exist.
288288

289289
| v3 flag | v4 replacement |
290290
|---------|----------------|
291-
| `--auth-token <tok>` | `SENTRY_AUTH_TOKEN` (or `sentry auth login`) |
291+
| `--auth-token <tok>` | `SENTRY_AUTH_TOKEN` plus `SENTRY_FORCE_ENV_TOKEN=1` to override stored OAuth credentials (or `sentry auth login`) |
292292
| `--url <url>` (self-hosted) | `SENTRY_URL` / `SENTRY_HOST`, or pass the URL as a command argument |
293293
| `--header "K: V"` | `SENTRY_CUSTOM_HEADERS` |
294294

295295
The [compatibility shim](#drop-in-compatibility-shim) above translates
296-
`--auth-token`, `--url`, and `--header` into these env vars automatically.
296+
`--auth-token`, `--url`, and `--header` into these env vars automatically. For
297+
`--auth-token`, it sets both `SENTRY_AUTH_TOKEN` and
298+
`SENTRY_FORCE_ENV_TOKEN=1` so the explicit flag overrides stored OAuth
299+
credentials as it did in v3.
297300

298301
### Dropped
299302

‎packages/cli/install‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -273,7 +273,7 @@ if [[ "$requested_version" == "nightly" ]]; then
273273

274274
case "$blob_status" in
275275
200)
276-
gunzip "$blob_file" > "$tmp_binary"
276+
gunzip -c "$blob_file" > "$tmp_binary"
277277
;;
278278
3??)
279279
redir_url=$(awk '/^[Ll][Oo][Cc][Aa][Tt][Ii][Oo][Nn]:[[:space:]]*/ { sub(/^[^:]*:[[:space:]]*/, ""); sub(/\r$/, ""); print; exit }' "$blob_headers")

‎packages/cli/test/lib/install-script.test.ts‎

Lines changed: 137 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,39 +15,11 @@ import {
1515
} from "node:fs";
1616
import { tmpdir } from "node:os";
1717
import { join } from "node:path";
18+
import { gzipSync } from "node:zlib";
1819
import { afterEach, beforeEach, describe, expect, test } from "vitest";
1920

2021
const installScript = join(import.meta.dirname, "..", "..", "install");
21-
22-
describe("install script", () => {
23-
let testDir: string;
24-
let binDir: string;
25-
let installDir: string;
26-
let env: NodeJS.ProcessEnv;
27-
28-
beforeEach(() => {
29-
testDir = mkdtempSync(join(tmpdir(), "sentry-install-test-"));
30-
binDir = join(testDir, "bin");
31-
installDir = join(testDir, "installed bin");
32-
mkdirSync(binDir, { recursive: true });
33-
mkdirSync(join(testDir, "home"));
34-
env = {
35-
PATH: `${binDir}:/usr/bin:/bin:/usr/sbin:/sbin`,
36-
HOME: join(testDir, "home"),
37-
SENTRY_INSTALL_DIR: installDir,
38-
SENTRY_CLI_NO_TELEMETRY: "1",
39-
SENTRY_TEST_DIR: testDir,
40-
SENTRY_TEST_INSTALL_SCRIPT: installScript,
41-
TMPDIR: testDir,
42-
};
43-
44-
// The artifact records each invocation and emulates setup's binary copy and
45-
// POSIX cleanup. Every install location and output is inside the fixture.
46-
writeFileSync(
47-
join(binDir, "curl"),
48-
`#!/usr/bin/env bash
49-
cat <<'SCRIPT'
50-
#!/usr/bin/env bash
22+
const downloadedExecutable = `#!/usr/bin/env bash
5123
set -euo pipefail
5224
record_tty() {
5325
for fd in 0 1 2; do
@@ -76,7 +48,35 @@ printf '%s\\n' "$@" >> "$SENTRY_TEST_DIR/post-args"
7648
printf '%s\\n' "$0" >> "$SENTRY_TEST_DIR/post-binary"
7749
record_tty 3> "$SENTRY_TEST_DIR/post-tty"
7850
exit "\${SENTRY_TEST_POST_EXIT:-0}"
79-
SCRIPT
51+
`;
52+
53+
describe("install script", () => {
54+
let testDir: string;
55+
let binDir: string;
56+
let installDir: string;
57+
let env: NodeJS.ProcessEnv;
58+
59+
beforeEach(() => {
60+
testDir = mkdtempSync(join(tmpdir(), "sentry-install-test-"));
61+
binDir = join(testDir, "bin");
62+
installDir = join(testDir, "installed bin");
63+
mkdirSync(binDir, { recursive: true });
64+
mkdirSync(join(testDir, "home"));
65+
env = {
66+
PATH: `${binDir}:/usr/bin:/bin:/usr/sbin:/sbin`,
67+
HOME: join(testDir, "home"),
68+
SENTRY_INSTALL_DIR: installDir,
69+
SENTRY_CLI_NO_TELEMETRY: "1",
70+
SENTRY_TEST_DIR: testDir,
71+
SENTRY_TEST_INSTALL_SCRIPT: installScript,
72+
TMPDIR: testDir,
73+
};
74+
75+
writeFileSync(
76+
join(binDir, "curl"),
77+
`#!/usr/bin/env bash
78+
cat <<'SCRIPT'
79+
${downloadedExecutable}SCRIPT
8080
`
8181
);
8282
chmodSync(join(binDir, "curl"), 0o755);
@@ -95,6 +95,87 @@ SCRIPT
9595
: [];
9696
}
9797

98+
function configureNightlyDownload(redirect: boolean): void {
99+
const platform = process.platform === "darwin" ? "darwin" : "linux";
100+
const architecture = process.arch === "arm64" ? "arm64" : "x64";
101+
const assetName = `sentry-${platform}-${architecture}.gz`;
102+
const gzipPath = join(testDir, assetName);
103+
const manifest = JSON.stringify({
104+
version: "nightly-test",
105+
layers: [
106+
{
107+
mediaType: "application/vnd.oci.image.layer.v1.tar",
108+
digest: "sha256:test",
109+
annotations: { "org.opencontainers.image.title": assetName },
110+
},
111+
],
112+
});
113+
114+
writeFileSync(gzipPath, gzipSync(downloadedExecutable));
115+
env.SENTRY_TEST_GZIP = gzipPath;
116+
env.SENTRY_TEST_NIGHTLY_REDIRECT = redirect ? "1" : "0";
117+
writeFileSync(
118+
join(binDir, "curl"),
119+
`#!/usr/bin/env bash
120+
set -euo pipefail
121+
url="\${!#}"
122+
case "$url" in
123+
*"/token?"*)
124+
printf '{"token":"test-token"}'
125+
;;
126+
*"/manifests/nightly")
127+
cat <<'JSON'
128+
${manifest}
129+
JSON
130+
;;
131+
"https://objects.example/nightly.gz")
132+
for arg in "$@"; do
133+
if [[ "$arg" == Authorization:* ]]; then
134+
exit 90
135+
fi
136+
done
137+
cat "$SENTRY_TEST_GZIP"
138+
;;
139+
*"/blobs/"*)
140+
headers=""
141+
output=""
142+
while [[ $# -gt 0 ]]; do
143+
case "$1" in
144+
-D) headers="$2"; shift 2 ;;
145+
-o) output="$2"; shift 2 ;;
146+
*) shift ;;
147+
esac
148+
done
149+
if [[ "$SENTRY_TEST_NIGHTLY_REDIRECT" == "1" ]]; then
150+
printf 'HTTP/1.1 307 Temporary Redirect\\r\\nLocation: https://objects.example/nightly.gz\\r\\n\\r\\n' > "$headers"
151+
: > "$output"
152+
printf '307'
153+
else
154+
printf 'HTTP/1.1 200 OK\\r\\n\\r\\n' > "$headers"
155+
cp "$SENTRY_TEST_GZIP" "$output"
156+
printf '200'
157+
fi
158+
;;
159+
*)
160+
exit 91
161+
;;
162+
esac
163+
`
164+
);
165+
chmodSync(join(binDir, "curl"), 0o755);
166+
writeFileSync(
167+
join(binDir, "gunzip"),
168+
`#!/usr/bin/env bash
169+
set -euo pipefail
170+
if [[ $# -gt 0 && "$1" != "-c" ]]; then
171+
exit 64
172+
fi
173+
exec /usr/bin/gunzip "$@"
174+
`
175+
);
176+
chmodSync(join(binDir, "gunzip"), 0o755);
177+
}
178+
98179
/** Run curl-style piped installation in a real controlling terminal. */
99180
function runInTerminal(
100181
options: { redirect?: string; detached?: boolean } = {}
@@ -171,6 +252,31 @@ process.exitCode = result.status ?? 1;
171252
expect(existsSync(join(installDir, "sentry"))).toBe(true);
172253
});
173254

255+
test.each([
256+
{ response: "direct HTTP 200 response", redirect: false },
257+
{ response: "HTTP redirect", redirect: true },
258+
])("installs nightly from a $response", ({ redirect }) => {
259+
configureNightlyDownload(redirect);
260+
const result = spawnSync(
261+
"bash",
262+
[installScript, "--version", "nightly", "--no-modify-path"],
263+
{ env, encoding: "utf8", timeout: 10_000 }
264+
);
265+
266+
expect(result.status, result.stdout + result.stderr).toBe(0);
267+
expect(recorded("setup-args")).toEqual([
268+
"cli",
269+
"setup",
270+
"--install",
271+
"--method",
272+
"curl",
273+
"--channel",
274+
"nightly",
275+
"--no-modify-path",
276+
]);
277+
expect(existsSync(join(installDir, "sentry"))).toBe(true);
278+
});
279+
174280
test("connects setup to the controlling terminal without launching another process", () => {
175281
const result = runInTerminal();
176282
expect(result.status, result.stdout + result.stderr).toBe(0);

0 commit comments

Comments
 (0)