|
15 | 15 | publish-npm: |
16 | 16 | runs-on: ubuntu-latest |
17 | 17 | permissions: |
18 | | - contents: write # for `git push --follow-tags` of the version bump |
| 18 | + contents: write # for the atomic version-bump push (branch + tag) |
19 | 19 | id-token: write # for npm OIDC trusted publishing |
20 | 20 | steps: |
21 | 21 | - uses: actions/setup-node@v6 |
|
60 | 60 | git config user.name 'github-actions[bot]' |
61 | 61 | git config user.email '41898282+github-actions[bot]@users.noreply.github.com' |
62 | 62 | pnpm i |
| 63 | + # `pnpm version patch` bumps package.json, makes a commit, and creates |
| 64 | + # a `v<new-version>` tag. Capture the new tag name from package.json |
| 65 | + # rather than parsing pnpm's output, which has historically varied. |
63 | 66 | pnpm version patch |
64 | | - git push --follow-tags |
| 67 | + NEW_TAG="v$(node -p "require('./package.json').version")" |
| 68 | + # CRITICAL: use --atomic so the branch update and the tag update |
| 69 | + # succeed (or fail) as a single transaction on the server. The old |
| 70 | + # `git push --follow-tags` was non-atomic per ref: if a concurrent |
| 71 | + # publish run won the race, the branch fast-forward would be rejected |
| 72 | + # but the tag push would still land — leaving a dangling tag with no |
| 73 | + # matching commit on the branch. Subsequent runs would then forever |
| 74 | + # try to bump to the same already-existing tag and fail with |
| 75 | + # `tag 'vN+1' already exists`. With --atomic, a rejected branch push |
| 76 | + # rejects the tag push too, and the next workflow tick can retry |
| 77 | + # cleanly against the up-to-date refs. |
| 78 | + git push --atomic origin "${GITHUB_REF_NAME}" "${NEW_TAG}" |
65 | 79 | # This is required if the package has a prepare script that uses something |
66 | 80 | # in dependencies or devDependencies. |
67 | 81 | - |
|
0 commit comments