ci: attach the build to a release the web UI already created

Cutting a release from GitHub's "Draft a new release" page would have produced a
release with no app in it.

Publishing there creates the tag and the release in one action, so by the time
the tag event reaches the release job a release for that tag already exists.
`gh release create` fails on that, the job errors, and the ZIP is never
attached - leaving a published release that looks finished and contains nothing
installable. The catalog would then have no artifact to pin.

Check for an existing release first. If there is one, upload the assets into it
with --clobber and leave its title and notes alone, since whoever wrote them in
the UI meant them; otherwise create the draft as before. Both routes now end
with the same three assets attached.

The contentVersion rule is unchanged and still enforced either way: the web
route means editing sce_sys/param.json in the browser and committing it before
drafting the release, because the tag the UI creates points at the current head
of the branch. For v1.0.0 that happens to need no edit, the committed baseline
already being 01.000.000.

Tested: make test passes (13 tests, including a new assertion that the workflow
handles an existing release); the YAML parses and every run: block passes
bash -n. The round trip is exercised against the real repository next.
This commit is contained in:
jmelovich committed 2026-10-10 13:21:02 -07:00
1 parent 61f23e44cd
commit ab1d8c0d28
3 files changed
+34 -1

No files matched your search

+21 -1
View File
@@ -106,11 +106,31 @@ jobs:
name: ${{ needs.build.outputs.title-id }}
path: dist
- name: Publish a draft release
# Two ways a tag gets here, and both must work.
#
# tools/release.sh + git push - the tag arrives with no release, so
# create a draft for review.
# GitHub's "Draft a new release" - publishing in the web UI creates the
# tag AND the release together, so by
# the time this runs the release already
# exists. Creating it again would fail,
# and the ZIP would never be attached;
# upload into it instead and leave the
# notes the author wrote alone.
- name: Attach the build to its release
env:
GH_TOKEN: ${{ github.token }}
ZIP: dist/${{ needs.build.outputs.title-id }}.zip
run: |
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
echo "release $GITHUB_REF_NAME already exists; uploading assets into it"
gh release upload "$GITHUB_REF_NAME" \
"$ZIP" LICENSE THIRD_PARTY_NOTICES.md --clobber
gh release view "$GITHUB_REF_NAME" --json isDraft,assets \
--jq '"draft=\(.isDraft) assets=\([.assets[].name]|join(","))"'
exit 0
fi
previous=$(git describe --tags --abbrev=0 "$GITHUB_REF_NAME^" 2>/dev/null || true)
{
printf 'Unpack `%s` into `/data/homebrew/` on the console, then launch\n' "$(basename "$ZIP")"
+7
View File
@@ -179,6 +179,13 @@ git push origin v1.1.0
CI then builds and opens a **draft** release with the ZIP attached. **Publish it** from the
Releases page — a draft cannot be downloaded, so stores and users can't see it.
You can do it entirely in the browser instead: edit `sce_sys/param.json` to raise
`contentVersion`, commit it to `main`, then **Releases → Draft a new release**, type the
matching new tag (`v1.1.0`), and **Publish release**. That creates the tag and the release
together; CI then verifies the version and uploads the ZIP into the release you just made,
leaving your notes untouched. Either route works — the only rule is that the committed
`contentVersion` and the tag agree, and CI fails the release if they don't.
Why the order matters: a PS5 decides an installed copy is out of date by comparing
`sce_sys/param.json`'s `contentVersion`, and [ps5-homebrew-catalog][catalog] reads that value
from the **committed** `param.json` at the release tag. If the bump isn't in the commit the tag
+6
View File
@@ -303,6 +303,12 @@ class ToolTests(unittest.TestCase):
# stamping it at build time would leave the committed value behind.
self.assertIn("tools/set-content-version.sh --check", workflow)
self.assertIn("--draft", workflow)
# Publishing from GitHub's web UI creates the tag and the release
# together, so the release can already exist when this job runs.
# Creating it again fails and the ZIP is never attached; the workflow
# has to upload into the existing release instead.
self.assertIn("gh release view", workflow)
self.assertIn("gh release upload", workflow)
# Pinned, not floating: this toolchain needs the Clang 18 image, which
# only ubuntu-24.04 carries. Ignore comments, which explain the pin.
code = "\n".join(