diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9ce0032..f1ca4a4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -33,6 +33,12 @@ jobs: steps: - uses: actions/checkout@v7 with: + # Check out the BRANCH TIP, not the commit that triggered the run. + # On a re-run the triggering sha is stale, and committing on top of it + # produces a non-fast-forward push that is rejected. Combined with the + # changelog logic being idempotent -- an empty [Unreleased] releases + # nothing -- this makes a re-run safe rather than broken. + ref: main fetch-depth: 0 token: ${{ secrets.RELEASE_TOKEN || secrets.GITHUB_TOKEN }} @@ -49,16 +55,31 @@ jobs: - name: Commit the changelog and tag if: steps.cut.outputs.released == 'true' + env: + TAG: ${{ steps.cut.outputs.tag }} run: | + set -euo pipefail git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" - git add CHANGELOG.md package.json - # [skip ci] so this push cannot re-enter this workflow when a PAT is - # in use -- with a PAT, pushes DO trigger workflows. - git commit -m "Release ${{ steps.cut.outputs.tag }} [skip ci]" - git tag -a "${{ steps.cut.outputs.tag }}" -m "${{ steps.cut.outputs.tag }}" - git push origin HEAD:main - git push origin "${{ steps.cut.outputs.tag }}" + + # Each push is guarded, so a partially-completed earlier attempt can + # be resumed rather than blocking every retry. + if git diff --quiet -- CHANGELOG.md package.json; then + echo "::notice::Changelog and version already committed." + else + git add CHANGELOG.md package.json + # [skip ci] so this push cannot re-enter this workflow when a PAT + # is in use -- with a PAT, pushes DO trigger workflows. + git commit -m "Release ${TAG} [skip ci]" + git push origin HEAD:main + fi + + if git ls-remote --exit-code --tags origin "refs/tags/${TAG}" >/dev/null 2>&1; then + echo "::notice::${TAG} already exists on the remote." + else + git tag -a "${TAG}" -m "${TAG}" + git push origin "${TAG}" + fi - name: Create the GitHub release if: steps.cut.outputs.released == 'true' @@ -67,7 +88,8 @@ jobs: run: | gh release create "${{ steps.cut.outputs.tag }}" \ --title "${{ steps.cut.outputs.tag }}" \ - --notes "${{ steps.cut.outputs.notes }}" + --notes "${{ steps.cut.outputs.notes }}" \ + || echo "::notice::Release already exists." # Only needed while no PAT is configured; with one, the tag push above # has already started Deploy. diff --git a/.gitignore b/.gitignore index c50dd73..7306d56 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,10 @@ design/ .wrangler/ *.local .DS_Store + +# Local environment files. Nothing in this project needs them at runtime -- +# the app has no backend and no secrets -- but an untracked .env is exactly the +# kind of thing that gets committed by accident on a public repo. +.env +.env.* +!.env.example diff --git a/CHANGELOG.md b/CHANGELOG.md index 242ac36..d59aecc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,14 @@ this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] -Nothing yet. +### Fixed +- The Release workflow checked out the commit that triggered it rather than the + branch tip, so any re-run built on a stale base and its push was rejected as + a non-fast-forward. It now checks out `main`, and each push is guarded so a + partially-completed attempt resumes instead of blocking every retry. +- `.env` is now ignored. Nothing here needs one — the app has no backend and no + runtime secrets — but an untracked `.env` is exactly what gets committed by + accident on a public repository. ## [0.2.0] — 2026-09-24