diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 3e5236ce..42c15ddd 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -1,17 +1,21 @@ name: npm-publish on: - release: - types: [published] workflow_dispatch: + inputs: + tag: + description: >- + Release tag to publish, for example v12.0.0. Leave empty to run + validation only (dry run). + type: string permissions: contents: read concurrency: - # tag_name grouping - queues actual releases for the same tag - # run_id - fallback value required for group as tag_name might not always be present (manual triggers) - group: npm-publish-${{ github.event.release.tag_name || github.run_id }} + # tag grouping - queues repeat publishes of the same release + # run_id - fallback for validation-only runs, which have no tag + group: npm-publish-${{ inputs.tag || github.run_id }} cancel-in-progress: false jobs: @@ -21,7 +25,7 @@ jobs: steps: - uses: actions/checkout@v7 with: - ref: ${{ github.event.release.tag_name || github.ref }} + ref: ${{ inputs.tag || github.ref }} persist-credentials: false - uses: actions/setup-node@v6 @@ -30,11 +34,11 @@ jobs: cache: npm - name: verify release version - if: github.event_name == 'release' + if: inputs.tag != '' run: | pkg_name=$(jq -r .name package.json) pkg_version=$(jq -r .version package.json) - tag_version="${GITHUB_EVENT_RELEASE_TAG_NAME#v}" + tag_version="${RELEASE_TAG#v}" if [ "$pkg_version" != "$tag_version" ]; then echo "package.json version ($pkg_version) does not match release tag ($tag_version)" exit 1 @@ -44,7 +48,7 @@ jobs: exit 1 fi env: - GITHUB_EVENT_RELEASE_TAG_NAME: ${{ github.event.release.tag_name }} + RELEASE_TAG: ${{ inputs.tag }} - name: install dependencies run: npm ci @@ -65,7 +69,7 @@ jobs: publish: name: publish needs: validate - if: github.event_name == 'release' + if: inputs.tag != '' runs-on: ubuntu-latest environment: Publish permissions: @@ -74,7 +78,7 @@ jobs: steps: - uses: actions/checkout@v7 with: - ref: ${{ github.event.release.tag_name }} + ref: ${{ inputs.tag }} persist-credentials: false - uses: actions/setup-node@v6 diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 623dcd14..3dc4a36b 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -7,13 +7,28 @@ permissions: contents: write issues: write pull-requests: write + # Releases created with GITHUB_TOKEN cannot trigger other workflows, so + # npm-publish is dispatched explicitly below. workflow_dispatch is one of the + # few events that always creates a run, even from GITHUB_TOKEN. + actions: write name: release-please jobs: release-please: runs-on: ubuntu-latest steps: - uses: googleapis/release-please-action@v5 + id: release with: token: ${{ secrets.GITHUB_TOKEN }} release-type: node target-branch: ${{ github.ref_name }} + + # releases_created is only true on the run that tagged a release, which is + # the run triggered by merging the release PR. + - name: trigger npm-publish + if: steps.release.outputs.releases_created == 'true' + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GH_REPO: ${{ github.repository }} + TAG: ${{ steps.release.outputs.tag_name }} + run: gh workflow run npm-publish.yml --ref "$GITHUB_REF_NAME" -f tag="$TAG" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index ab7a8127..f7ef0aae 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,13 +58,13 @@ Releases are automated with [release-please](https://github.com/googleapis/relea 2. `release-please` opens or updates a **Release PR** with the version bump and changelog. 3. Review and merge the Release PR when ready to ship. 4. `release-please` creates a GitHub Release and version tag (for example `v11.3.0`). -5. The `npm-publish` workflow runs automatically, re-runs tests, validates the package contents (`npm pack --dry-run`), then pauses at the `Publish` environment for reviewer approval. -6. After approval, the package is published to npm via [Trusted Publishing](https://docs.npmjs.com/trusted-publishers) (OIDC). Do not run `npm publish` manually. +5. The same `release-please` run then dispatches `npm-publish` with that tag. It re-runs tests, validates the package contents (`npm pack --dry-run`), then pauses at the `Publish` environment for reviewer approval. +6. After approval, the package is published to npm via [Trusted Publishing](https://docs.npmjs.com/trusted-publishers) (OIDC). ### Dry run -To validate the publish workflow without publishing, run **Actions → npm-publish → Run workflow**. This runs tests and `npm pack --dry-run`. +To validate the publish workflow without publishing, run **Actions → npm-publish → Run workflow** and leave `tag` empty. This runs tests and `npm pack --dry-run`, and skips the publish job. ### Retrying a failed run -If `npm-publish` fails, use **Re-run jobs** on the failed run itself (Actions tab) — it replays the same release/tag, so there's no need to cut a new one. This is safe even if `publish` partially ran, since `validate` checks whether the version is already on npm before continuing. +If `npm-publish` fails, use **Re-run jobs** on the failed run itself (Actions tab) — it replays the same tag, so there's no need to cut a new one. You can also re-run **Actions → npm-publish → Run workflow** with the same `tag`. Either is safe even if `publish` partially ran, since `validate` checks whether the version is already on npm before continuing.