Skip to content

Commit 9c8a8ef

Browse files
BYKGPT-6 Sol (OpenAI)
andauthored
fix(action): Resolve major tag to matching binary (#889)
## Summary - Resolve moving action refs such as `getsentry/craft@v2` to the release version recorded in that action revision’s `package.json`. The current `v2` ref points to 2.33.0, but no GitHub release named `v2` exists, so the action previously downloaded the latest release instead. - Fail closed if the major tag points to a prerelease or different major, or if the matching release asset is missing. Keep explicit `craft_version` and `latest` support. ## Validation - A regression failed before the fix. Focused action tests: 37 passed. Full Vitest: 1,242 passed, 1 skipped across 62 files. - Typecheck, lint (0 errors, 7 existing warnings), format, build, and shell syntax checks passed. - Verified `v2` and `2.33.0` tags point to the same release commit and `v2` contains package version 2.33.0. Co-authored-by: GPT-6 Sol (OpenAI) <agent@openai.com>
1 parent a4b961b commit 9c8a8ef

3 files changed

Lines changed: 137 additions & 11 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/usr/bin/env bash
2+
set -euo pipefail
3+
4+
if [[ $# -ne 3 ]]; then
5+
echo "Expected the requested version, action ref, and action path" >&2
6+
exit 1
7+
fi
8+
9+
version="$1"
10+
if [[ -z "$version" ]]; then
11+
version="$2"
12+
fi
13+
14+
if [[ "$version" =~ ^v([1-9][0-9]*)$ ]]; then
15+
major="${BASH_REMATCH[1]}"
16+
version="$(jq -er '.version | select(type == "string")' "$3/package.json")"
17+
if [[ ! "$version" =~ ^${major}\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]]; then
18+
echo "The v${major} action ref does not point to a published v${major} version" >&2
19+
exit 1
20+
fi
21+
fi
22+
23+
if [[ -z "$version" ]]; then
24+
version="latest"
25+
fi
26+
27+
if [[ ! "$version" =~ ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$ ]]; then
28+
echo "Invalid Craft release tag" >&2
29+
exit 1
30+
fi
31+
32+
printf '%s\n' "$version"

‎action.yml‎

Lines changed: 7 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ inputs:
4141
craft_version:
4242
description: >
4343
Version of Craft to install (tag or "latest").
44-
Defaults to the action ref (e.g., "v2") if not specified.
44+
Defaults to the action ref; major refs such as "v2" select the
45+
corresponding version from the action's package.json.
4546
required: false
4647
default: ''
4748

@@ -130,6 +131,7 @@ runs:
130131
env:
131132
CRAFT_VERSION_INPUT: ${{ inputs.craft_version }}
132133
ACTION_REF: ${{ github.action_ref }}
134+
ACTION_PATH: ${{ github.action_path }}
133135
run: |
134136
set -euo pipefail
135137
@@ -138,25 +140,19 @@ runs:
138140
sudo install -m 755 /tmp/craft-artifact/dist/craft /usr/local/bin/craft
139141
else
140142
# Download from release (for external repos or if artifact unavailable)
141-
# Use explicit craft_version input if provided, otherwise fall back to ACTION_REF
142-
CRAFT_VERSION="$CRAFT_VERSION_INPUT"
143-
if [[ -z "$CRAFT_VERSION" ]]; then
144-
CRAFT_VERSION="$ACTION_REF"
145-
fi
143+
CRAFT_VERSION="$(bash "$ACTION_PATH/.github/scripts/resolve-craft-version.sh" "$CRAFT_VERSION_INPUT" "$ACTION_REF" "$ACTION_PATH")"
146144
147-
if [[ "$CRAFT_VERSION" == "latest" || -z "$CRAFT_VERSION" ]]; then
145+
if [[ "$CRAFT_VERSION" == "latest" ]]; then
148146
echo "Downloading latest Craft release..."
149147
CRAFT_URL=$(curl -fsSL "https://api.github.com/repos/getsentry/craft/releases/latest" \
150148
| jq -r '.assets[] | select(.name == "craft") | .browser_download_url')
151149
else
152150
CRAFT_URL="https://github.com/getsentry/craft/releases/download/${CRAFT_VERSION}/craft"
153151
echo "Downloading Craft ${CRAFT_VERSION} from: ${CRAFT_URL}"
154152
155-
# Fallback to latest if specified version doesn't have a release
156153
if ! curl -sfI "$CRAFT_URL" >/dev/null 2>&1; then
157-
echo "Release not found for version '${CRAFT_VERSION}', falling back to latest..."
158-
CRAFT_URL=$(curl -fsSL "https://api.github.com/repos/getsentry/craft/releases/latest" \
159-
| jq -r '.assets[] | select(.name == "craft") | .browser_download_url')
154+
echo "::error::Craft release '${CRAFT_VERSION}' has no downloadable binary."
155+
exit 1
160156
fi
161157
fi
162158

‎src/__tests__/action.test.ts‎

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,104 @@ test('forwards workspace input to every Craft command', () => {
159159
);
160160
});
161161

162+
test('resolves a moving major action tag to its matching release', () => {
163+
const environment = createActionEnvironment();
164+
writeFileSync(
165+
join(environment.directory, 'package.json'),
166+
JSON.stringify({ version: '2.33.0' }),
167+
);
168+
169+
const result = spawnSync(
170+
'bash',
171+
[
172+
join(__dirname, '../../.github/scripts/resolve-craft-version.sh'),
173+
'',
174+
'v2',
175+
environment.directory,
176+
],
177+
{ encoding: 'utf8' },
178+
);
179+
180+
expect(result.status).toBe(0);
181+
expect(result.stdout.trim()).toBe('2.33.0');
182+
});
183+
184+
test.each(['3.0.0', '2.34.0-dev.0', 'not-a-version'])(
185+
'refuses a v2 action tag pointing at %s instead of using latest',
186+
version => {
187+
const environment = createActionEnvironment();
188+
writeFileSync(
189+
join(environment.directory, 'package.json'),
190+
JSON.stringify({ version }),
191+
);
192+
193+
const result = spawnSync(
194+
'bash',
195+
[
196+
join(__dirname, '../../.github/scripts/resolve-craft-version.sh'),
197+
'',
198+
'v2',
199+
environment.directory,
200+
],
201+
{ encoding: 'utf8' },
202+
);
203+
204+
expect(result.status).not.toBe(0);
205+
expect(result.stdout).toBe('');
206+
},
207+
);
208+
209+
test('does not install the latest release when a major-tagged binary is missing', () => {
210+
const environment = createActionEnvironment();
211+
const curlCalls = join(environment.directory, 'curl-calls');
212+
const scriptsDirectory = join(environment.directory, '.github/scripts');
213+
mkdirSync(scriptsDirectory, { recursive: true });
214+
writeFileSync(
215+
join(scriptsDirectory, 'resolve-craft-version.sh'),
216+
readFileSync(
217+
join(__dirname, '../../.github/scripts/resolve-craft-version.sh'),
218+
),
219+
);
220+
writeFileSync(
221+
join(environment.directory, 'package.json'),
222+
JSON.stringify({ version: '2.33.0' }),
223+
);
224+
writeFileSync(curlCalls, '');
225+
writeFileSync(
226+
join(environment.binDirectory, 'curl'),
227+
'#!/usr/bin/env bash\nprintf "%s\\n" "$*" >> "$CURL_CALLS"\nexit 22\n',
228+
);
229+
writeFileSync(
230+
join(environment.binDirectory, 'sudo'),
231+
'#!/usr/bin/env bash\nexit 99\n',
232+
);
233+
chmodSync(join(environment.binDirectory, 'curl'), 0o755);
234+
chmodSync(join(environment.binDirectory, 'sudo'), 0o755);
235+
236+
const result = spawnSync(
237+
'bash',
238+
['-e', '-c', getActionStep('Install Craft from artifact or release').run!],
239+
{
240+
encoding: 'utf8',
241+
env: {
242+
...process.env,
243+
ACTION_PATH: environment.directory,
244+
ACTION_REF: 'v2',
245+
CRAFT_VERSION_INPUT: '',
246+
CURL_CALLS: curlCalls,
247+
PATH: `${environment.binDirectory}:${process.env.PATH}`,
248+
},
249+
},
250+
);
251+
252+
expect(result.status).toBe(1);
253+
expect(result.stdout).toContain("Craft release '2.33.0'");
254+
expect(readFileSync(curlCalls, 'utf8')).toContain(
255+
'/releases/download/2.33.0/craft',
256+
);
257+
expect(readFileSync(curlCalls, 'utf8')).not.toContain('/releases/latest');
258+
});
259+
162260
test.each([
163261
['control', 'cli\tnext'],
164262
['format', 'cli\u202enext'],

0 commit comments

Comments
 (0)