diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ef71f9b..b817b60 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,10 +31,17 @@ jobs: name: Build and check runs-on: ubuntu-latest env: - # Stamp the APK as a dev-channel build only when this is a real push to - # dev. Pull request builds stay unstamped at 1.0.0-dev, since they are - # never published and must not claim a channel version. - PLG_CHANNEL: ${{ (github.event_name == 'push' && github.ref == 'refs/heads/dev') && 'dev' || '' }} + # Stamp the APK as a dev-channel build exactly when it is a build of the + # dev branch, which is exactly when it can be published below. Pull + # request builds run against refs/pull/N/merge, so they stay unstamped at + # 1.0.0-dev: they are never published and must not claim a channel + # version. + # + # This condition has to stay in step with the publish steps' own. Stamping + # a build the publish steps would ship, or shipping one this left + # unstamped, both end the same way — an APK claiming versionCode 1, which + # the updater can never see as an upgrade over anything. + PLG_CHANNEL: ${{ github.ref == 'refs/heads/dev' && 'dev' || '' }} steps: - name: Checkout uses: actions/checkout@v4 @@ -77,11 +84,16 @@ jobs: if-no-files-found: ignore retention-days: 14 - # Everything below publishes the dev channel, and only ever from a push - # to dev that has already built, tested and linted clean above. + # Everything below publishes the dev channel, and only ever from the dev + # branch itself, after it has built, tested and linted clean above. + # + # Gated on the branch alone rather than on the event, which is how + # release.yml has always gated main. A pull request is still excluded + # without needing to name it: a PR build runs against refs/pull/N/merge, + # never refs/heads/dev, so a fork still cannot ship anything. - name: Stage dev APK id: stage - if: github.event_name == 'push' && github.ref == 'refs/heads/dev' + if: github.ref == 'refs/heads/dev' run: | set -euo pipefail version="v1.0.${{ github.run_number }}-dev" @@ -92,7 +104,7 @@ jobs: echo "apk=$dest" >> "$GITHUB_OUTPUT" - name: Publish dev channel prerelease - if: github.event_name == 'push' && github.ref == 'refs/heads/dev' + if: github.ref == 'refs/heads/dev' env: GH_TOKEN: ${{ github.token }} run: | diff --git a/README.md b/README.md index 7b5d7e1..a8d08af 100644 --- a/README.md +++ b/README.md @@ -139,15 +139,28 @@ Both pipelines publish, to two separate channels: | Channel | Published by | Tag | Marked | | --- | --- | --- | --- | | Production | `release.yml`, push to `main` | `v1.0.` | release | -| Dev | `ci.yml`, push to `dev` | `v1.0.-dev` | prerelease | +| Dev | `ci.yml`, any build of `dev` | `v1.0.-dev` | prerelease | Dev builds are published as **prereleases** deliberately: GitHub's "latest release" endpoint skips prereleases, so a production-channel install can never be handed a dev build even by accident. Pull requests build and check but never publish, so a PR from a fork cannot ship anything. -`release.yml` additionally refuses to run off any branch but `main`, so a manual -dispatch aimed elsewhere does nothing rather than shipping unreviewed code. +**Both pipelines gate publishing on the branch rather than on what triggered +the build**, so a manual run from the Actions tab publishes exactly as a push +does — useful when a push cannot fire the workflow itself, which is the case +for anything pushed by an automation token. The safety property is unchanged, +because publishing still happens only after that same job has built, tested and +linted clean. A pull request is still excluded without having to be named: a PR +build runs against `refs/pull//merge`, never `refs/heads/dev`. + +In `ci.yml` the `PLG_CHANNEL` stamp is gated on the same condition as the +publish steps, and the two have to stay in step. Stamping a build that then +ships, or shipping one that was left unstamped, both end with an APK claiming +`versionCode` 1 — which the updater can never see as an upgrade over anything. + +`release.yml` refuses to run off any branch but `main`, so a manual dispatch +aimed elsewhere does nothing rather than shipping unreviewed code. **The two channels have independent version sequences** — a build's `versionCode` is the run number of the workflow that produced it, and the two workflows count