Skip to content

Commit 1db87e8

Browse files
committed
fix(install): use release registry with GitHub fallback
1 parent 478eb9b commit 1db87e8

2 files changed

Lines changed: 166 additions & 4 deletions

File tree

‎packages/cli/install‎

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -277,10 +277,23 @@ else
277277
# Stable build: resolve version and download from GitHub Releases.
278278

279279
if [[ -z "$requested_version" ]]; then
280-
version=$(curl -fsSL https://api.github.com/repos/getsentry/cli/releases/latest \
281-
| sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')
282-
if [[ -z "$version" ]]; then
283-
die "Failed to fetch latest version" "gh-version"
280+
# Craft publishes this app's stable version to the release registry.
281+
# Avoid GitHub's anonymous API quota, which is shared by every user of an IP.
282+
# Consume the full JSON response: an early parser exit can cause SIGPIPE under pipefail.
283+
if metadata=$(curl -fsSL --max-time 10 https://release-registry.services.sentry.io/apps/sentry/latest); then
284+
version=$(printf '%s\n' "$metadata" \
285+
| sed -n 's/.*"version": *"\([^"]*\)".*/\1/p')
286+
fi
287+
if [[ ! "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
288+
echo -e "${MUTED}Could not resolve latest version from Sentry's release registry. Trying GitHub...${NC}" >&2
289+
version=""
290+
if metadata=$(curl -fsSL --max-time 10 https://api.github.com/repos/getsentry/cli/releases/latest); then
291+
version=$(printf '%s\n' "$metadata" \
292+
| sed -n 's/.*"tag_name": *"\([^"]*\)".*/\1/p')
293+
fi
294+
if [[ ! "$version" =~ ^v?[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
295+
die "Failed to fetch latest stable version from Sentry's release registry and GitHub. Retry or install a specific version with --version <version>." "resolve-version"
296+
fi
284297
fi
285298
else
286299
version="$requested_version"

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

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ describe("install script", () => {
3838
SENTRY_CLI_NO_TELEMETRY: "1",
3939
SENTRY_TEST_DIR: testDir,
4040
SENTRY_TEST_INSTALL_SCRIPT: installScript,
41+
SENTRY_TEST_GITHUB_FAIL: "22",
4142
TMPDIR: testDir,
4243
};
4344

@@ -46,6 +47,30 @@ describe("install script", () => {
4647
writeFileSync(
4748
join(binDir, "curl"),
4849
`#!/usr/bin/env bash
50+
for arg in "$@"; do
51+
case "$arg" in
52+
https://*) url="$arg" ;;
53+
esac
54+
done
55+
printf '%s\\n' "$url" >> "$SENTRY_TEST_DIR/download-urls"
56+
case "$url" in
57+
https://api.github.com/*)
58+
if [[ "\${SENTRY_TEST_GITHUB_FAIL:-0}" != "0" ]]; then
59+
echo "GitHub request failed (curl exit $SENTRY_TEST_GITHUB_FAIL)" >&2
60+
exit "$SENTRY_TEST_GITHUB_FAIL"
61+
fi
62+
printf '%s\\n' "$SENTRY_TEST_GITHUB_RESPONSE"
63+
exit 0
64+
;;
65+
https://release-registry.services.sentry.io/apps/sentry/latest)
66+
if [[ "\${SENTRY_TEST_REGISTRY_FAIL:-0}" != "0" ]]; then
67+
echo "Registry request failed (curl exit $SENTRY_TEST_REGISTRY_FAIL)" >&2
68+
exit "$SENTRY_TEST_REGISTRY_FAIL"
69+
fi
70+
printf '%s\\n' "$SENTRY_TEST_REGISTRY_RESPONSE"
71+
exit 0
72+
;;
73+
esac
4974
cat <<'SCRIPT'
5075
#!/usr/bin/env bash
5176
set -euo pipefail
@@ -88,6 +113,125 @@ SCRIPT
88113
rmSync(testDir, { recursive: true, force: true });
89114
});
90115

116+
test.each([
117+
'{"canonical":"app:sentry","version":"0.45.0"}',
118+
'{\n "canonical": "app:sentry",\n "version": "0.45.0"\n}',
119+
JSON.stringify(
120+
{ version: "0.45.0", description: "x".repeat(96 * 1024) },
121+
null,
122+
2
123+
),
124+
])("installs the latest stable release when GitHub API access is blocked", (metadata) => {
125+
env.SENTRY_TEST_REGISTRY_RESPONSE = metadata;
126+
const result = spawnSync("bash", [installScript], {
127+
env,
128+
encoding: "utf8",
129+
timeout: 10_000,
130+
});
131+
132+
expect(result.status, result.stdout + result.stderr).toBe(0);
133+
expect(recorded("download-urls")).toEqual([
134+
"https://release-registry.services.sentry.io/apps/sentry/latest",
135+
expect.stringMatching(
136+
/^https:\/\/github\.com\/getsentry\/cli\/releases\/download\/0\.45\.0\/sentry-.+\.gz$/
137+
),
138+
]);
139+
expect(existsSync(join(installDir, "sentry"))).toBe(true);
140+
});
141+
142+
test.each([
143+
{ name: "HTTP error", metadata: "", failure: "22" },
144+
{ name: "timeout", metadata: "", failure: "28" },
145+
{ name: "missing version", metadata: "{}" },
146+
{ name: "non-JSON response", metadata: "<html>Unavailable</html>" },
147+
{ name: "nonstable version", metadata: '{"version":"nightly"}' },
148+
])("falls back to GitHub after a registry $name", ({ metadata, failure }) => {
149+
env.SENTRY_TEST_REGISTRY_RESPONSE = metadata;
150+
env.SENTRY_TEST_REGISTRY_FAIL = failure;
151+
env.SENTRY_TEST_GITHUB_FAIL = "0";
152+
env.SENTRY_TEST_GITHUB_RESPONSE = '{"tag_name":"0.45.0"}';
153+
const result = spawnSync("bash", [installScript], {
154+
env,
155+
encoding: "utf8",
156+
timeout: 10_000,
157+
});
158+
159+
expect(result.status, result.stdout + result.stderr).toBe(0);
160+
expect(result.stderr).toContain("Trying GitHub");
161+
expect(recorded("download-urls")).toEqual([
162+
"https://release-registry.services.sentry.io/apps/sentry/latest",
163+
"https://api.github.com/repos/getsentry/cli/releases/latest",
164+
expect.stringMatching(
165+
/^https:\/\/github\.com\/getsentry\/cli\/releases\/download\/0\.45\.0\/sentry-.+\.gz$/
166+
),
167+
]);
168+
expect(existsSync(join(installDir, "sentry"))).toBe(true);
169+
});
170+
171+
test.each([
172+
{ name: "a leading v", metadata: '{\n "tag_name": "v0.45.0"\n}' },
173+
{
174+
name: "large release metadata",
175+
metadata: JSON.stringify(
176+
{ tag_name: "0.45.0", body: "x".repeat(96 * 1024) },
177+
null,
178+
2
179+
),
180+
},
181+
])("accepts a GitHub release with $name", ({ metadata }) => {
182+
env.SENTRY_TEST_REGISTRY_FAIL = "22";
183+
env.SENTRY_TEST_GITHUB_FAIL = "0";
184+
env.SENTRY_TEST_GITHUB_RESPONSE = metadata;
185+
const result = spawnSync("bash", [installScript], {
186+
env,
187+
encoding: "utf8",
188+
timeout: 10_000,
189+
});
190+
191+
expect(result.status, result.stdout + result.stderr).toBe(0);
192+
expect(recorded("download-urls").at(-1)).toMatch(
193+
/^https:\/\/github\.com\/getsentry\/cli\/releases\/download\/0\.45\.0\/sentry-.+\.gz$/
194+
);
195+
expect(existsSync(join(installDir, "sentry"))).toBe(true);
196+
});
197+
198+
test.each([
199+
{ name: "HTTP error", metadata: "", failure: "22" },
200+
{ name: "timeout", metadata: "", failure: "28" },
201+
{ name: "missing tag", metadata: "{}", failure: "0" },
202+
{
203+
name: "non-JSON response",
204+
metadata: "<html>Unavailable</html>",
205+
failure: "0",
206+
},
207+
{ name: "nonstable tag", metadata: '{"tag_name":"nightly"}', failure: "0" },
208+
])("stops when the registry and GitHub fail: $name", ({
209+
metadata,
210+
failure,
211+
}) => {
212+
env.SENTRY_TEST_REGISTRY_FAIL = "22";
213+
env.SENTRY_TEST_GITHUB_RESPONSE = metadata;
214+
env.SENTRY_TEST_GITHUB_FAIL = failure;
215+
const result = spawnSync("bash", [installScript], {
216+
env,
217+
encoding: "utf8",
218+
timeout: 10_000,
219+
});
220+
221+
expect(result.status).toBe(1);
222+
expect(result.stderr).toContain(
223+
"Failed to fetch latest stable version from Sentry's release registry and GitHub"
224+
);
225+
expect(result.stderr).toContain("--version <version>");
226+
expect(result.stderr).not.toContain("Unexpected failure at line");
227+
expect(recorded("download-urls")).toEqual([
228+
"https://release-registry.services.sentry.io/apps/sentry/latest",
229+
"https://api.github.com/repos/getsentry/cli/releases/latest",
230+
]);
231+
expect(recorded("setup-args")).toEqual([]);
232+
expect(existsSync(join(installDir, "sentry"))).toBe(false);
233+
});
234+
91235
function recorded(name: string): string[] {
92236
const path = join(testDir, name);
93237
return existsSync(path)
@@ -169,6 +313,11 @@ process.exitCode = result.status ?? 1;
169313
]);
170314
expect(recorded("post-args")).toEqual([]);
171315
expect(existsSync(join(installDir, "sentry"))).toBe(true);
316+
expect(recorded("download-urls")).toEqual([
317+
expect.stringMatching(
318+
/^https:\/\/github\.com\/getsentry\/cli\/releases\/download\/0\.31\.0\/sentry-.+\.gz$/
319+
),
320+
]);
172321
});
173322

174323
test("connects setup to the controlling terminal without launching another process", () => {

0 commit comments

Comments
 (0)