Repository navigation
chore: add release workflow using npm trusted publishing #403
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Draft
captbaritone
wants to merge
3
commits into
main
Choose a base branch
from
add-release-workflow
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Draft
Changes from all commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,161 @@ | ||
| name: Release | ||
| on: | ||
| push: | ||
| branches: | ||
| - main | ||
| permissions: {} | ||
| jobs: | ||
| build: | ||
| name: Check version and build package | ||
| # Keep this guard on every job for defense-in-depth in case job dependencies are refactored. | ||
| if: ${{ !github.event.repository.fork && github.repository == 'graphql/graphql-relay-js' }} | ||
| runs-on: ubuntu-latest | ||
| outputs: | ||
| version: ${{ steps.metadata.outputs.version }} | ||
| dist_tag: ${{ steps.metadata.outputs.dist_tag }} | ||
| should_publish: ${{ steps.metadata.outputs.should_publish }} | ||
| permissions: | ||
| contents: read # for actions/checkout | ||
| pull-requests: read # for gen-changelog.js | ||
| steps: | ||
| - name: Checkout repo | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| # gen-changelog.js shells out to 'git rev-list' against release tags, | ||
| # so it needs full history rather than the default shallow clone. | ||
| fetch-depth: 0 | ||
| persist-credentials: false | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| cache: npm | ||
| node-version-file: '.node-version' | ||
|
|
||
| - name: Read release metadata | ||
| id: metadata | ||
| run: | | ||
| version="$(node -p 'require("./package.json").version')" | ||
| echo "version=${version}" >> "${GITHUB_OUTPUT}" | ||
|
|
||
| # A prerelease must never take the 'latest' dist-tag. | ||
| case "${version}" in | ||
| *-*) dist_tag='next' ;; | ||
| *) dist_tag='latest' ;; | ||
| esac | ||
| echo "dist_tag=${dist_tag}" >> "${GITHUB_OUTPUT}" | ||
|
|
||
| # Publishing is driven entirely by whether this version is already on | ||
| # npm, so ordinary merges to main are a no-op and re-runs are safe. | ||
| # 'npm view pkg versions' returns a bare string when only one version | ||
| # exists, hence the array coercion. | ||
| versions_json="$(npm view graphql-relay versions --json)" | ||
| if jq -e --arg v "${version}" \ | ||
| '(if type == "array" then . else [.] end) | index($v)' \ | ||
| > /dev/null <<< "${versions_json}" | ||
| then | ||
| echo "graphql-relay@${version} is already published. Nothing to do." | ||
| echo "should_publish=false" >> "${GITHUB_OUTPUT}" | ||
| else | ||
| echo "graphql-relay@${version} is not published yet." | ||
| echo "should_publish=true" >> "${GITHUB_OUTPUT}" | ||
| fi | ||
|
|
||
| - name: Install Dependencies | ||
| if: steps.metadata.outputs.should_publish == 'true' | ||
| run: npm ci | ||
|
|
||
| - name: Run tests | ||
| if: steps.metadata.outputs.should_publish == 'true' | ||
| run: npm run test | ||
|
|
||
| - name: Build npm package | ||
| if: steps.metadata.outputs.should_publish == 'true' | ||
| run: npm run build | ||
|
|
||
| - name: Upload npmDist package | ||
| if: steps.metadata.outputs.should_publish == 'true' | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: npmDist | ||
| path: ./npmDist | ||
|
|
||
| # Generated here rather than in the publish job so that a PR missing its | ||
| # 'PR: *' label fails the release before anything reaches npm. Needs the | ||
| # dependencies installed above, since resources/utils.js requires prettier. | ||
| - name: Generate release notes | ||
| if: steps.metadata.outputs.should_publish == 'true' | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| run: node resources/gen-changelog.js > ./release-notes.md | ||
|
|
||
| - name: Upload release notes | ||
| if: steps.metadata.outputs.should_publish == 'true' | ||
| uses: actions/upload-artifact@v4 | ||
| with: | ||
| name: release-notes | ||
| path: ./release-notes.md | ||
|
|
||
| publish: | ||
| name: Publish to npm and create release | ||
| needs: build | ||
| # Keep this guard on every job for defense-in-depth in case job dependencies are refactored. | ||
| if: ${{ !github.event.repository.fork && github.repository == 'graphql/graphql-relay-js' && needs.build.outputs.should_publish == 'true' }} | ||
| runs-on: ubuntu-latest | ||
| permissions: | ||
| contents: write # for creating the tag and GitHub release | ||
| id-token: write # for npm trusted publishing via OIDC | ||
| steps: | ||
| # This job holds the OIDC token, so it deliberately never runs 'npm ci': | ||
| # no dependency lifecycle scripts execute while the token is available. | ||
| # It only publishes the artifact the build job already produced. | ||
| - name: Checkout repo | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| persist-credentials: false | ||
|
|
||
| # Trusted publishing needs npm >= 11.5.1, which is newer than the npm | ||
| # bundled with some Node releases. '.node-version' is pinned accordingly. | ||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version-file: '.node-version' | ||
|
|
||
| - name: Download npmDist package | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: npmDist | ||
| path: ./npmDist | ||
|
|
||
| - name: Download release notes | ||
| uses: actions/download-artifact@v4 | ||
| with: | ||
| name: release-notes | ||
| path: . | ||
|
|
||
| - name: Publish to npm | ||
| working-directory: ./npmDist | ||
| env: | ||
| DIST_TAG: ${{ needs.build.outputs.dist_tag }} | ||
| run: npm publish --provenance --tag "${DIST_TAG}" | ||
|
|
||
| - name: Create GitHub release | ||
| env: | ||
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||
| VERSION: ${{ needs.build.outputs.version }} | ||
| DIST_TAG: ${{ needs.build.outputs.dist_tag }} | ||
| run: | | ||
| tag="v${VERSION}" | ||
|
|
||
| latest_flag='--latest=false' | ||
| if [ "${DIST_TAG}" = 'latest' ]; then | ||
| latest_flag='--latest' | ||
| fi | ||
|
|
||
| # '--target' creates the tag at the commit that was actually built, | ||
| # so a tag can never disagree with what was published. | ||
| gh release create "${tag}" \ | ||
| --target "${GITHUB_SHA}" \ | ||
| --title "${tag}" \ | ||
| --notes-file ./release-notes.md \ | ||
| "${latest_flag}" | ||
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| v24 |
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,29 +1,52 @@ | ||
| ## Publishing | ||
|
|
||
| This package uses a custom publish flow. | ||
| ## Pull requests | ||
|
|
||
| **IMPORTANT**: Do not push directly to `main` - every change must go through a PR | ||
| otherwise changelog generation will fail. | ||
|
|
||
| Every PR must carry exactly one `PR: *` label before it is merged. Changelog | ||
| generation reads these labels and throws if a merged PR is missing one: | ||
|
|
||
| - `PR: breaking change 💥` | ||
| - `PR: feature 🚀` | ||
| - `PR: bug fix 🐞` | ||
| - `PR: docs 📝` | ||
| - `PR: polish 💅` | ||
| - `PR: internal 🏠` | ||
| - `PR: dependency 📦` | ||
|
|
||
| ## Publishing | ||
|
|
||
| Releases are cut by the `Release` workflow. Merging to `main` is the only gate: | ||
| the workflow checks whether the version in `package.json` is already on npm and | ||
| publishes it if it is not. Ordinary merges are therefore a no-op, and a release | ||
| is just a merged version bump. | ||
|
|
||
| Publishing uses [trusted publishing][], so no npm token is involved. The workflow | ||
| builds the package, publishes it with provenance, creates the `vX.Y.Z` tag, and | ||
| opens a GitHub release. | ||
|
|
||
| To cut a release, open a PR that bumps the version and merge it: | ||
|
|
||
| ```sh | ||
| git checkout -b release-VERSION_NUMBER | ||
| npm install | ||
| npm version patch # or minor or major | ||
| git push --follow-tags | ||
| npm run build | ||
| cd npmDist | ||
| npm publish --tag=next | ||
| npm version VERSION_NUMBER --no-git-tag-version | ||
| git commit --all --message VERSION_NUMBER | ||
| ``` | ||
|
|
||
| Then test it by installing `graphql-relay@next` from npm... | ||
| Label the PR `PR: internal 🏠` and merge once CI is green. That is the whole | ||
| release - do not tag or run `npm publish` by hand. | ||
|
|
||
| All good? Publish: | ||
| A version containing a `-` is treated as a prerelease and published under the | ||
| `next` dist-tag instead of `latest`, so a release can be rehearsed with a version | ||
| like `0.11.1-rc.0`. | ||
|
|
||
| ```sh | ||
| npm dist-tags add graphql-relay@VERSION_NUMBER latest | ||
| ``` | ||
| Release notes are generated from the `PR: *` labels by | ||
| `resources/gen-changelog.js` and attached to the GitHub release - there is no | ||
| `CHANGELOG.md` file. A merged PR missing its label fails the release before | ||
| anything reaches npm, so add the label and re-run. | ||
|
|
||
| Finally generate the CHANGELOG: | ||
| If a publish fails partway through, re-run the workflow. It re-checks npm before | ||
| doing anything, so re-runs are safe. | ||
|
|
||
| ```sh | ||
| node resources/gen-changelog.js | ||
| ``` | ||
| [trusted publishing]: https://docs.npmjs.com/trusted-publishers |
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
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These versions are a little old, I think v7 is latest?