diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 39d8abe..0a45c39 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,16 +1,21 @@ name: Release +# Only ever by hand. A release is a decision, and nothing about merging a pull request says one +# has been taken. on: - push: - branches: [main] workflow_dispatch: + inputs: + dry_run: + description: Build and sign, publish nothing + type: boolean + default: false permissions: contents: write jobs: - # A minute of Linux in front of six minutes of Windows. Most pushes to main are not releases, and - # this is what tells them apart before anything is compiled. + # A few seconds of Linux in front of six minutes of Windows, so a version that is already out is + # refused before anything is compiled. decide: runs-on: ubuntu-latest outputs: @@ -19,7 +24,7 @@ jobs: steps: - uses: actions/checkout@v4 - - name: Is this version already released? + - name: Work out what would be released id: check env: GH_TOKEN: ${{ github.token }} @@ -28,28 +33,24 @@ jobs: tag="v$version" echo "tag=$tag" >> "$GITHUB_OUTPUT" - # A run started by hand is a rehearsal whatever the version says: it builds and signs and - # publishes nothing, which is what makes it safe to press. - if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then + if [ "${{ inputs.dry_run }}" = "true" ]; then echo "publish=false" >> "$GITHUB_OUTPUT" echo "::notice::Rehearsal of $tag. Nothing will be published." exit 0 fi + # Refused rather than overwritten: a tag that already names a release is the one thing + # somebody may already have downloaded. if gh api "repos/${{ github.repository }}/git/ref/tags/$tag" >/dev/null 2>&1; then - echo "publish=false" >> "$GITHUB_OUTPUT" - echo "::notice::$tag is already released. Bump version in src-tauri/tauri.conf.json to cut a new one." - exit 0 + echo "::error::$tag is already released. Run 'npm version patch', merge that, and trigger again." + exit 1 fi echo "publish=true" >> "$GITHUB_OUTPUT" - echo "::notice::$tag has no release yet. Building one." + echo "::notice::Releasing $tag. The pipeline creates the tag; nobody has to." release: needs: decide - # A push whose version is already out has nothing to do; a rehearsal still builds, because - # finding out that a release *would* work is the whole point of pressing the button. - if: needs.decide.outputs.publish == 'true' || github.event_name == 'workflow_dispatch' runs-on: windows-latest defaults: run: diff --git a/AGENTS.md b/AGENTS.md index 731e855..3b7f319 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -76,12 +76,15 @@ The public half goes into `plugins.updater.pubkey` in `src-tauri/tauri.conf.json and its password are the GitHub secrets `TAURI_SIGNING_PRIVATE_KEY` and `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` and never enter the repository. -**Cutting a release is bumping a number.** `package.json` holds the version; `tauri.conf.json` -points at it and the interface reads it through `__APP_VERSION__`, so it is written once. A push to -`main` whose version has no tag yet builds, signs, tags at that commit and publishes the installer, -its `.sig` and a `latest.json` written from that signature. A push whose version is already out -stops in under a minute. Running the workflow by hand is a rehearsal: it builds and signs and -attaches the result to the run, and publishes nothing. +**A release happens when somebody presses the button, and never otherwise.** `release.yml` runs on +`workflow_dispatch` only — merging a pull request releases nothing. Pressing it builds, signs, +creates the tag `v` at that commit and publishes the installer, its `.sig` and a +`latest.json` written from that signature. Nobody tags by hand. Tick `dry_run` to build and sign +without publishing. + +`package.json` holds the version; `tauri.conf.json` points at it and the interface reads it through +`__APP_VERSION__`, so it is written once. A version that already has a tag is refused before +anything is compiled — bump it first: ```bash npm version patch # or minor, or major