diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index d66e44ef..65cc764d 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -39,6 +39,7 @@ on: permissions: contents: read + pull-requests: read jobs: release: @@ -46,6 +47,7 @@ jobs: timeout-minutes: 60 permissions: contents: write + pull-requests: read outputs: version: ${{ steps.release.outputs.version }} publish: ${{ steps.release.outputs.should-publish }} @@ -63,7 +65,7 @@ jobs: release-notes: ${{ github.event.inputs.release-notes }} publish-npm-packages: - if: needs.release.outputs.publish == 'true' + if: needs.release.outputs.publish == 'true' || (github.event_name == 'workflow_dispatch' && github.event.inputs.version != '0.0.0') runs-on: ubuntu-latest timeout-minutes: 60 needs: [release] @@ -76,7 +78,7 @@ jobs: uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 - name: Setup node - uses: actions/setup-node@49933ea5288ca8642d1e84afbd3f7d6820020 # v4 + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: node-version: 23.x registry-url: 'https://registry.npmjs.org' @@ -118,6 +120,8 @@ jobs: run: yarn install --immutable - name: Publish NPM packages + env: + RELEASE_VERSION: ${{ needs.release.outputs.version || github.event.inputs.version }} run: | echo "npm $(npm --version) | node $(node --version)" if [[ -n "${ACTIONS_ID_TOKEN_REQUEST_URL:-}" ]]; then @@ -130,7 +134,7 @@ jobs: cat "$NPM_CONFIG_USERCONFIG" 2>/dev/null || echo "(no .npmrc)" echo "---" yarn build - yarn publish-version ${{ needs.release.outputs.version }} + yarn publish-version "$RELEASE_VERSION" - name: Trigger Documentation Build uses: peter-evans/repository-dispatch@ff45666b9427631e3450c54a1bcbee4d9ff4d7c0 # v3 @@ -160,7 +164,29 @@ jobs: needs: [release] steps: - - name: Report that nothing was published + - name: Verify an intentional no-release merge + env: + GH_TOKEN: ${{ github.token }} run: | - echo "::error::Nothing was published and no release was cut (reason: ${{ needs.release.outputs.reason }}). For 'no-label', add exactly one of major, minor or patch to the merged pull request and re-run this workflow - see verify-semver-label, which is meant to catch this before the merge." + reason='${{ needs.release.outputs.reason }}' + if [[ "$reason" == 'error' ]]; then + echo '::error::The release action failed; no-release cannot suppress a release error.' + exit 1 + fi + + pulls=$(gh api "repos/${GITHUB_REPOSITORY}/commits/${GITHUB_SHA}/pulls") + merged_count=$(printf '%s' "$pulls" | jq --arg sha "$GITHUB_SHA" '[.[] | select(.merge_commit_sha == $sha)] | length') + if [[ "$merged_count" -ne 1 ]]; then + echo "::error::Expected exactly one pull request whose merge commit is $GITHUB_SHA; found $merged_count." + exit 1 + fi + no_release=$(printf '%s' "$pulls" | jq -r --arg sha "$GITHUB_SHA" \ + '[.[] | select(.merge_commit_sha == $sha) | .labels[].name] | any(. == "no-release")') + + if [[ "$no_release" == 'true' ]]; then + echo 'The merged pull request explicitly selected no-release; publishing nothing is the intended result.' + exit 0 + fi + + echo "::error::Nothing was published and no release was cut (reason: $reason). Add exactly one release-intent label before merge; use no-release when publishing nothing is intentional." exit 1