From f8d0e315832345cb196aa2376ccb3c2e8ac126d5 Mon Sep 17 00:00:00 2001 From: gabriel Date: Mon, 27 Jul 2026 11:47:48 +0200 Subject: [PATCH] ci: adopt release-please for site releases MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Releases are cut by merging a release PR; tags stay bare (1.10.3, not v1.10.3) to match every existing tag. A release created with GITHUB_TOKEN does not emit release:published, so deploy-ftp.yml gains a workflow_call trigger and release-please.yml invokes it gated on release_created, otherwise a release would publish nothing. sync-firmware.yml now opens its PR as fix: rather than chore:, because chore does not bump a version — a firmware sync would have merged and then sat undeployed until an unrelated fix came along. sync-firmware-release.sh now refuses a release carrying no firmware assets instead of silently stamping the new version into every *_full.json manifest, which would advertise binaries that were never built. --- .github/scripts/sync-firmware-release.sh | 21 +++++++++++ .github/workflows/deploy-ftp.yml | 24 ++++++++++-- .github/workflows/release-please.yml | 47 ++++++++++++++++++++++++ .github/workflows/sync-firmware.yml | 9 ++++- .release-please-config.json | 19 ++++++++++ .release-please-manifest.json | 3 ++ 6 files changed, 117 insertions(+), 6 deletions(-) create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-config.json create mode 100644 .release-please-manifest.json diff --git a/.github/scripts/sync-firmware-release.sh b/.github/scripts/sync-firmware-release.sh index 4e96120..1f887c9 100755 --- a/.github/scripts/sync-firmware-release.sh +++ b/.github/scripts/sync-firmware-release.sh @@ -46,6 +46,27 @@ fi echo "Syncing firmware release $latest_tag (was: ${pinned_tag:-none})" +# A release whose build failed is still "latest" via the API, just with no (or +# incomplete) assets. Without this guard the download loop below simply matches +# nothing — no error — and the manifest rewrite further down would still stamp +# the new version into every *_full.json, advertising a firmware whose binaries +# were never built. Fail loudly instead; the workflow goes red and opens no PR. +# +# Counted with jq rather than over a bash array: on a zero-asset release the +# array is empty, and "${arr[@]}" under `set -u` is an error on bash < 4.4. +missing=() +[[ $(echo "$release_json" | jq '[.assets[].name | select(endswith("_full.bin"))] | length') -eq 0 ]] \ + && missing+=("*_full.bin") +[[ $(echo "$release_json" | jq '[.assets[].name | select(. == "NRF52840.uf2")] | length') -eq 0 ]] \ + && missing+=("NRF52840.uf2") + +if [[ ${#missing[@]} -gt 0 ]]; then + echo "Release $latest_tag is missing expected assets: ${missing[*]}" >&2 + echo "Refusing to sync — this usually means the firmware build failed after the release was created." >&2 + echo "Re-run the firmware release build, then re-run this sync." >&2 + exit 1 +fi + mapfile -t asset_names < <(echo "$release_json" | jq -r '.assets[].name') for name in "${asset_names[@]}"; do diff --git a/.github/workflows/deploy-ftp.yml b/.github/workflows/deploy-ftp.yml index 278d819..3514aa1 100644 --- a/.github/workflows/deploy-ftp.yml +++ b/.github/workflows/deploy-ftp.yml @@ -1,9 +1,26 @@ name: Deploy to FTP on: + # Called by release-please.yml once it has created a release. Needed because a + # release created with GITHUB_TOKEN does not emit `release: published`. + workflow_call: + inputs: + tag: + description: "Tag to deploy" + required: true + type: string + # Kept for releases published by hand in the GitHub UI, and for manual redeploys. release: types: [published] workflow_dispatch: + inputs: + tag: + description: "Tag to deploy (leave empty to deploy the current branch)" + required: false + type: string + +permissions: + contents: read jobs: deploy: @@ -13,10 +30,9 @@ jobs: - name: Checkout release tag uses: actions/checkout@v4 with: - ref: ${{ github.event.release.tag_name || github.ref }} - - - name: Install curl - run: sudo apt-get update && sudo apt-get install -y curl + # Under workflow_call the default ref is the caller's (main), which is + # not what we want to publish — pin to the tag being released. + ref: ${{ inputs.tag || github.event.release.tag_name || github.ref }} - name: Deploy httpdocs to FTP env: diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 0000000..17b52c1 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,47 @@ +name: Release Please + +on: + push: + branches: [main] + workflow_dispatch: + +permissions: {} + +concurrency: + group: release + cancel-in-progress: false + +jobs: + release-please: + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: write + pull-requests: write + outputs: + release_created: ${{ steps.release.outputs.release_created }} + tag_name: ${{ steps.release.outputs.tag_name }} + steps: + - uses: googleapis/release-please-action@v5 + id: release + with: + # Passed explicitly: the action's config-file default is + # `release-please-config.json` (no leading dot), so relying on the + # default would silently ignore this repo's dotted config. + config-file: .release-please-config.json + manifest-file: .release-please-manifest.json + + # A release created with GITHUB_TOKEN does not emit `release: published`, so + # the deploy has to be called directly rather than left to trigger itself. + deploy: + needs: release-please + if: ${{ needs.release-please.outputs.release_created == 'true' }} + # A called workflow cannot hold more permissions than its caller; the + # top-level `permissions: {}` would otherwise cap it at nothing and fail + # the run at startup. + permissions: + contents: read + uses: ./.github/workflows/deploy-ftp.yml + with: + tag: ${{ needs.release-please.outputs.tag_name }} + secrets: inherit diff --git a/.github/workflows/sync-firmware.yml b/.github/workflows/sync-firmware.yml index 60b8c4a..3bfec0f 100644 --- a/.github/workflows/sync-firmware.yml +++ b/.github/workflows/sync-firmware.yml @@ -38,8 +38,13 @@ jobs: uses: peter-evans/create-pull-request@v6 with: token: ${{ secrets.GITHUB_TOKEN }} - commit-message: 'chore: sync firmware binaries to OpenDisplay/Firmware v${{ steps.sync.outputs.tag }}' - title: 'chore: sync firmware binaries to OpenDisplay/Firmware v${{ steps.sync.outputs.tag }}' + # `fix:`, not `chore:` — release-please does not bump a version for + # chore commits, so a firmware sync would merge to main and then sit + # there undeployed until some unrelated fix happened along. As a fix + # it cuts a patch release, which is what actually publishes the new + # toolbox binaries to opendisplay.org. + commit-message: 'fix: sync firmware binaries to OpenDisplay/Firmware v${{ steps.sync.outputs.tag }}' + title: 'fix: sync firmware binaries to OpenDisplay/Firmware v${{ steps.sync.outputs.tag }}' body: | New release detected in [OpenDisplay/Firmware](https://github.com/OpenDisplay/Firmware). diff --git a/.release-please-config.json b/.release-please-config.json new file mode 100644 index 0000000..1f66132 --- /dev/null +++ b/.release-please-config.json @@ -0,0 +1,19 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "include-v-in-tag": false, + "include-component-in-tag": false, + "packages": { + ".": { + "release-type": "simple", + "package-name": "opendisplay.org", + "changelog-sections": [ + { "type": "feat", "section": "Features" }, + { "type": "fix", "section": "Bug Fixes" }, + { "type": "perf", "section": "Performance Improvements" }, + { "type": "docs", "section": "Documentation" }, + { "type": "refactor", "section": "Code Refactoring" }, + { "type": "ci", "section": "Continuous Integration" } + ] + } + } +} diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 0000000..f1c2ec3 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1,3 @@ +{ + ".": "1.10.2" +}