diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index df74f924b..1706bc327 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,8 +1,8 @@ name: Release on: push: - tags: - - 'v*' + branches: + - 'main' workflow_dispatch: inputs: tag: @@ -31,7 +31,41 @@ jobs: ref: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref }} fetch-depth: 0 + - name: Check if release tag exists for this commit + id: check_tag + run: | + # For manual dispatch, we already know we should proceed + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "proceed=true" >> $GITHUB_OUTPUT + echo "release_tag=${{ inputs.tag }}" >> $GITHUB_OUTPUT + exit 0 + fi + + # For push to main branch, check if a release tag points to this commit + TAGS=$(git tag --points-at ${{ github.sha }} | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?(\+[a-zA-Z0-9.-]+)?$' || true) + + if [[ -z "$TAGS" ]]; then + echo "No release tag found for commit ${{ github.sha }} on main branch" + echo "proceed=false" >> $GITHUB_OUTPUT + exit 0 + fi + + # Check for multiple tags and fail if found + TAG_COUNT=$(echo "$TAGS" | wc -l) + if [[ $TAG_COUNT -gt 1 ]]; then + echo "Multiple release tags found for this commit:" + echo "$TAGS" + echo "Please use workflow dispatch to specify which tag to release" + exit 1 + fi + + RELEASE_TAG="$TAGS" + echo "Found release tag: $RELEASE_TAG" + echo "proceed=true" >> $GITHUB_OUTPUT + echo "release_tag=$RELEASE_TAG" >> $GITHUB_OUTPUT + - name: Verify tag matches package.json version + if: steps.check_tag.outputs.proceed == 'true' run: | jq --raw-output --exit-status --arg tag "$RELEASE_TAG" ' if (.version == ($tag | ltrimstr("v"))) then @@ -40,21 +74,10 @@ jobs: "Package version (\(.version)) does not match tag version (\($tag | ltrimstr("v")))" | halt_error(1) end' package.json env: - RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} - - - name: Verify commit is in main branch - run: | - # Check if the tagged commit is included in the main branch - if git merge-base --is-ancestor ${{ github.sha }} origin/main; then - echo "Tagged commit is properly included in main branch" - else - echo "Tagged commit is not included in the main branch" - echo "Please push the commit to main before releasing" - exit 1 - fi + RELEASE_TAG: ${{ steps.check_tag.outputs.release_tag }} - name: Check CI status - if: ${{ !inputs.skip_ci_check }} + if: ${{ steps.check_tag.outputs.proceed == 'true' && !inputs.skip_ci_check }} run: | # Check if CI has completed successfully for this commit gh run list --commit ${{ github.sha }} --status success --json workflowName | jq --raw-output --exit-status ' @@ -67,6 +90,7 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Setup Node.js + if: steps.check_tag.outputs.proceed == 'true' uses: actions/setup-node@v4 with: node-version-file: package.json @@ -74,16 +98,18 @@ jobs: registry-url: https://registry.npmjs.org - name: Publish to npm with provenance + if: steps.check_tag.outputs.proceed == 'true' run: npm publish --provenance env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Create GitHub Release + if: steps.check_tag.outputs.proceed == 'true' run: | gh release create "$RELEASE_TAG" \ --title "$RELEASE_TAG" \ --draft \ --generate-notes env: - RELEASE_TAG: ${{ github.event_name == 'workflow_dispatch' && inputs.tag || github.ref_name }} + RELEASE_TAG: ${{ steps.check_tag.outputs.release_tag }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/maintaining.md b/maintaining.md index cc107ac31..e6a77c2ce 100644 --- a/maintaining.md +++ b/maintaining.md @@ -43,22 +43,22 @@ Speaking of, using newer TypeScript features could be considered a breaking chan ## Release process -1. In the `main` branch, use `npm version` with the correct increment. -1. Push the resulting tag (`git push --tags`). -1. Wait for minimal CI checks to pass and push the `main` branch. -1. Wait for full CI run to complete on the tag. -1. The *Release* workflow will automatically run and publish to npm with provenance. It will also create a draft GitHub release. +1. Create a release branch, then use `npm version` with the correct increment. +1. Push the resulting tag (`git push --tags`) and create a pull request from the branch. +1. Wait for full CI checks to pass on the pull request. +1. Locally, merge the release branch into `main` using `git merge --ff-only`. +1. Push `main`. +1. The *Release* workflow will automatically run when the tagged commit is pushed to `main` and publish to npm with provenance. It will also create a draft GitHub release. 1. Review and publish the [draft GitHub release](https://github.com/avajs/ava/releases). The *Release* workflow includes several safety checks: - Validates the tag version matches `package.json` -- Verifies the tagged commit is included in the `main` branch - Confirms CI has passed for the commit ### Manual Release -If CI fails for the tag and you're confident this is not due to a fault in the release, you can manually trigger the *Release* workflow: +If CI fails and you're confident this is not due to a fault in the release, or if there are multiple tags pointing to the same commit, you can manually trigger the *Release* workflow: 1. Go to the [*Release* workflow](https://github.com/avajs/ava/actions/workflows/release.yml) 1. Click "Run workflow"