fix: id-only events updates last event id #100
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Release | |
| on: | |
| push: | |
| # `v[0-9]+` are the maintenance branches (`v4` today) that supported older majors | |
| # are patched from. The archived `v1.x`/`v2.x` branches predate the convention and | |
| # deliberately do not match. | |
| branches: [main, 'v[0-9]+'] | |
| workflow_dispatch: | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: false | |
| jobs: | |
| release: | |
| name: 'Release: Version PR or publish to npm' | |
| # `workflow_dispatch` can be triggered on any ref, and npm's trusted publisher | |
| # config pins the workflow file but not the branch. Without this, dispatching | |
| # from a feature branch could version and publish from an unreviewed ref. The | |
| # `Resolve release channel` step tightens this to the exact branch names, since | |
| # expressions here cannot match a pattern. | |
| if: github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/v') | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write # to push the version commit and tag, and create the GitHub release | |
| pull-requests: write # to open and update the "Version Packages" pull request | |
| id-token: write # to authenticate with npm through OIDC (trusted publishing) | |
| steps: | |
| # Runs before checkout so an unexpected ref never gets as far as building. | |
| - name: Resolve release channel | |
| id: channel | |
| run: | | |
| if [ "$GITHUB_REF_NAME" = 'main' ]; then | |
| echo 'npm-tag=latest' >> "$GITHUB_OUTPUT" | |
| elif [[ "$GITHUB_REF_NAME" =~ ^v[0-9]+$ ]]; then | |
| echo "npm-tag=$GITHUB_REF_NAME" >> "$GITHUB_OUTPUT" | |
| else | |
| echo "::error::Refusing to release from '$GITHUB_REF_NAME'. Releases run from 'main' or a 'vN' maintenance branch." | |
| exit 1 | |
| fi | |
| - uses: actions/checkout@v7 | |
| with: | |
| fetch-depth: 0 | |
| - uses: actions/setup-node@v7 | |
| with: | |
| node-version: lts/* | |
| cache: npm | |
| # Trusted publishing needs npm >= 11.5.1. Older versions silently skip OIDC | |
| # rather than erroring, so don't rely on whatever npm the runner ships with. | |
| # Pinned to the major so a new npm release can't land here unattended. | |
| - run: npm install --global npm@12 | |
| - run: npm ci | |
| - name: Create version pull request or publish to npm | |
| id: changesets | |
| uses: changesets/action@22ccf9aa43179fe9e27dc62e575971d28cce197c # v2.0.0 | |
| with: | |
| version-script: npm run version:packages | |
| # `--tag` keeps backports off the `latest` dist-tag: `main` publishes to | |
| # `latest`, `v4` publishes to `v4`, and so on. | |
| # | |
| # The action discovers what was published by reading the file named in the | |
| # `CHANGESETS_OUTPUT` environment variable, which it expects the script to | |
| # pass through to the changesets CLI. `npm run` forwards the environment, so | |
| # this works as-is - but don't wrap it in anything that drops env vars. | |
| publish-script: npm run release -- --tag ${{ steps.channel.outputs.npm-tag }} | |
| commit-message: 'chore(release): version packages' | |
| pr-title: 'chore(release): version packages' | |
| # `changesets/action` calls the create-release API without `make_latest`, and that | |
| # parameter defaults to true - so a backport would silently take the "Latest" badge | |
| # off the current major. Clear it on everything we just published, then assert the | |
| # repository's latest release is not one of them. | |
| # | |
| # This corrects the flag rather than never setting it: there are a few seconds | |
| # between the action creating the release and this step running. Closing that | |
| # window would mean turning off `create-github-releases` and rebuilding the | |
| # changelog-derived release notes by hand, which risks the worse failure of | |
| # publishing to npm with no GitHub release at all. The assertion below is what | |
| # makes the end state guaranteed - if the demotion does not stick, the job fails | |
| # loudly instead of leaving the badge on the wrong major. | |
| # | |
| # `make_latest` is a string enum ("true" / "false" / "legacy"), so it goes through | |
| # `gh api -f` rather than `gh release edit --latest=false`, which would depend on | |
| # how the runner's gh build handles negated boolean flags. | |
| - name: Keep backport releases off "Latest" | |
| if: steps.channel.outputs.npm-tag != 'latest' && steps.changesets.outputs.published == 'true' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| PUBLISHED_PACKAGES: ${{ steps.changesets.outputs.published-packages }} | |
| run: | | |
| # Single-package repo, so changesets tags releases `v<version>`. | |
| mapfile -t tags < <(jq -r '.[] | "v\(.version)"' <<< "$PUBLISHED_PACKAGES") | |
| for tag in "${tags[@]}"; do | |
| id=$(gh api "repos/$GITHUB_REPOSITORY/releases/tags/$tag" --jq '.id') | |
| echo "Clearing latest flag on $tag (release $id)" | |
| gh api --method PATCH "repos/$GITHUB_REPOSITORY/releases/$id" -f make_latest=false >/dev/null | |
| done | |
| latest=$(gh api "repos/$GITHUB_REPOSITORY/releases/latest" --jq '.tag_name' 2>/dev/null || echo '<none>') | |
| echo "Latest release is now $latest" | |
| for tag in "${tags[@]}"; do | |
| if [ "$latest" = "$tag" ]; then | |
| echo "::error::$tag is still flagged as the latest release." | |
| exit 1 | |
| fi | |
| done |