Skip to content

Commit 8abc20e

Browse files
feat: create or update Release to support chained auto-release flow
The legacy flow (caller triggered on release: published) always has a pre-existing Release, so update-release.sh could assume gh release edit. The chained flow (auto-release.yml -> release.yml via workflow_call) pushes a tag but no Release exists yet; edit fails. Probe with gh release view; edit if present, create otherwise. On the create path, target the Release at $GITHUB_SHA (or git rev-parse HEAD), falling back to gh's default when neither resolves. Extends the bats fixture with a GH_RELEASE_MODE switch and adds three tests covering the create, edit, and missing-SHA branches.
1 parent 0400870 commit 8abc20e

2 files changed

Lines changed: 143 additions & 9 deletions

File tree

steps/update-release.sh

Lines changed: 44 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,25 @@
11
#!/usr/bin/env bash
2-
# update-release.sh — update the GitHub Release body from CHANGELOG.md.
2+
# update-release.sh — create or update the GitHub Release body from CHANGELOG.md.
33
#
4-
# We do NOT create the release — the maintainer does that manually as the
5-
# trigger. This step runs after publish succeeds and replaces the release
6-
# body with the section from CHANGELOG.md, so the release notes on GitHub
7-
# match exactly what was published.
4+
# Runs after publish succeeds and stamps the Release body with the
5+
# CHANGELOG section for the published version plus the tarball integrity
6+
# block. Two call paths:
7+
#
8+
# Manual path (caller triggered on release: published): the maintainer
9+
# already created the Release; we edit the body.
10+
# Chained path (auto-release.yml -> release.yml via workflow_call):
11+
# auto-release pushed a tag but no Release object exists yet; we
12+
# create the Release and stamp it.
13+
#
14+
# The create-or-update decision is probed via `gh release view` — if it
15+
# succeeds, edit; otherwise create.
816
#
917
# Env:
1018
# GIT_TAG release tag to edit (default: $GITHUB_REF_NAME)
1119
# CHANGELOG_FILE path to CHANGELOG.md (default: CHANGELOG.md)
1220
# GITHUB_TOKEN auth for gh (GitHub provides this automatically)
21+
# GITHUB_SHA commit SHA to target a new Release at (default:
22+
# `git rev-parse HEAD`; only used on the create path)
1323

1424
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1525
# shellcheck source-path=SCRIPTDIR
@@ -151,9 +161,35 @@ else
151161
log "no tarball meta at $meta_file — skipping integrity block"
152162
fi
153163

154-
log "updating release $tag"
155-
if ! gh release edit "$tag" --notes "$notes"; then
156-
die "gh release edit failed"
164+
# Create-or-update logic. The legacy flow (caller triggered on
165+
# release: published) always has a pre-existing Release -- the event is
166+
# the trigger, so `gh release view` succeeds and we edit. The chained
167+
# flow (auto-release.yml calls release.yml via workflow_call) pushed a
168+
# tag but no human created a Release, so `gh release view` returns
169+
# non-zero and we create. `gh release view` distinguishes 404 (absent)
170+
# from auth/network failure by exit code 1 vs other non-zero. We treat
171+
# any non-zero as "missing" and let `gh release create` surface real
172+
# auth failures itself -- simpler than parsing stderr for "not found".
173+
if gh release view "$tag" >/dev/null 2>&1; then
174+
log "updating release $tag"
175+
if ! gh release edit "$tag" --notes "$notes"; then
176+
die "gh release edit failed"
177+
fi
178+
else
179+
log "creating release $tag"
180+
# --target pins the Release to a specific SHA. In the chained flow the
181+
# publish job checks out the tag, so either $GITHUB_SHA (set by the
182+
# runner) or `git rev-parse HEAD` resolves to the tagged commit. When
183+
# neither is available (e.g. running outside a git checkout, as in
184+
# bats fixtures), we omit --target and let gh default.
185+
target_sha="${GITHUB_SHA:-$(git rev-parse HEAD 2>/dev/null || true)}"
186+
create_args=(release create "$tag" --title "$tag" --notes "$notes")
187+
if [[ -n "$target_sha" ]]; then
188+
create_args+=(--target "$target_sha")
189+
fi
190+
if ! gh "${create_args[@]}"; then
191+
die "gh release create failed"
192+
fi
157193
fi
158194

159195
# Upload the canonical tarball as a release asset so consumers have a

test/update-release.bats

Lines changed: 99 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,27 @@ setup() {
3333
cat > "$bin_dir/gh" <<'GH'
3434
#!/usr/bin/env bash
3535
# Fake gh: log all args (including the multi-line --notes value) to
36-
# a file the test can grep against. Always exits 0.
36+
# a file the test can grep against. Exit behaviour for `release view`
37+
# is driven by $GH_RELEASE_MODE so tests can exercise both the
38+
# create-or-update branches in update-release.sh:
39+
# (unset|existing) - all commands succeed (legacy / pre-existing Release)
40+
# missing - `release view` exits 1 (chained flow / 404 case)
41+
# error - `release view` exits 2 (non-404 error)
42+
# All other subcommands always exit 0 so tests can inspect the call log.
3743
{
3844
printf 'GH_CALL: '
3945
printf '%s ' "$@"
4046
printf '\n'
4147
} >> "$GH_LOG"
48+
49+
if [[ "${1:-}" == "release" && "${2:-}" == "view" ]]; then
50+
case "${GH_RELEASE_MODE:-existing}" in
51+
missing) exit 1 ;;
52+
error) exit 2 ;;
53+
*) exit 0 ;;
54+
esac
55+
fi
56+
4257
exit 0
4358
GH
4459
chmod +x "$bin_dir/gh"
@@ -54,6 +69,8 @@ teardown() {
5469
unset TARBALL_META_DIR
5570
unset GH_LOG
5671
unset GIT_TAG
72+
unset GH_RELEASE_MODE
73+
unset GITHUB_SHA
5774
}
5875

5976
write_meta_for() {
@@ -153,3 +170,84 @@ EOF
153170
# idempotency.
154171
grep -q "release upload v1.5.0 ${meta_dir}/nsec-tree-1.5.0.tgz --clobber" "$GH_LOG"
155172
}
173+
174+
# ---------------------------------------------------------------------
175+
# Create-or-update branch coverage (see update-release.sh header).
176+
# The legacy tests above all run under the default $GH_RELEASE_MODE
177+
# (= existing), exercising the edit branch. The tests below force the
178+
# missing and error modes.
179+
# ---------------------------------------------------------------------
180+
181+
@test "update-release: creates a Release when none exists (chained flow)" {
182+
command -v jq >/dev/null 2>&1 || skip "jq not available"
183+
184+
write_package_json '{"name":"nsec-tree","version":"1.5.0","files":["dist"]}'
185+
write_file "CHANGELOG.md" '## 1.5.0
186+
187+
- changes
188+
'
189+
write_meta_for "nsec-tree-1.5.0.tgz"
190+
export GIT_TAG="v1.5.0"
191+
export GH_RELEASE_MODE="missing"
192+
export GITHUB_SHA="deadbeefcafef00d1234567890abcdef12345678"
193+
194+
run "$ACTION_ROOT/steps/update-release.sh"
195+
[ "$status" -eq 0 ]
196+
197+
# On the missing branch we must call `release create`, not `release edit`.
198+
grep -q "GH_CALL: release create v1.5.0" "$GH_LOG"
199+
! grep -q "GH_CALL: release edit" "$GH_LOG"
200+
201+
# --target must be passed and equal $GITHUB_SHA. --notes contains
202+
# multi-line content so the whole call may span several log lines;
203+
# check for --target and the SHA individually rather than on one line.
204+
grep -q -- "--target" "$GH_LOG"
205+
grep -q "$GITHUB_SHA" "$GH_LOG"
206+
207+
# Asset upload still runs after create.
208+
grep -q "release upload v1.5.0" "$GH_LOG"
209+
}
210+
211+
@test "update-release: edits existing Release (legacy flow)" {
212+
command -v jq >/dev/null 2>&1 || skip "jq not available"
213+
214+
write_package_json '{"name":"nsec-tree","version":"1.5.0","files":["dist"]}'
215+
write_file "CHANGELOG.md" '## 1.5.0
216+
217+
- changes
218+
'
219+
write_meta_for "nsec-tree-1.5.0.tgz"
220+
export GIT_TAG="v1.5.0"
221+
export GH_RELEASE_MODE="existing"
222+
223+
run "$ACTION_ROOT/steps/update-release.sh"
224+
[ "$status" -eq 0 ]
225+
226+
# On the existing branch we must call `release edit`, not `release create`.
227+
grep -q "GH_CALL: release edit v1.5.0" "$GH_LOG"
228+
! grep -q "GH_CALL: release create" "$GH_LOG"
229+
}
230+
231+
@test "update-release: create omits --target when no SHA resolvable" {
232+
command -v jq >/dev/null 2>&1 || skip "jq not available"
233+
234+
write_package_json '{"name":"nsec-tree","version":"1.5.0","files":["dist"]}'
235+
write_file "CHANGELOG.md" '## 1.5.0
236+
237+
- changes
238+
'
239+
write_meta_for "nsec-tree-1.5.0.tgz"
240+
export GIT_TAG="v1.5.0"
241+
export GH_RELEASE_MODE="missing"
242+
# GITHUB_SHA deliberately unset; fixture has no git repo so git
243+
# rev-parse HEAD also fails. Script should omit --target rather
244+
# than pass an empty string.
245+
unset GITHUB_SHA
246+
247+
run "$ACTION_ROOT/steps/update-release.sh"
248+
[ "$status" -eq 0 ]
249+
250+
grep -q "GH_CALL: release create v1.5.0" "$GH_LOG"
251+
# --target must NOT appear in any create call when no SHA is available.
252+
! grep -qE "release create v1\.5\.0[^\n]*--target" "$GH_LOG"
253+
}

0 commit comments

Comments
 (0)