Skip to content

Commit 49de1cf

Browse files
authored
CI: add npm Trusted Publisher workflows (#18)
* CI: add npm Trusted Publisher workflows - create-release-pr.yml: Creates release PRs with version bump and release notes - release.yml: Publishes to npm using Trusted Publisher (OIDC) when PR is merged - No npm tokens required - uses GitHub OIDC for authentication * Update .github/workflows/test.yml
1 parent 0992f32 commit 49de1cf

File tree

3 files changed

+263
-2
lines changed

3 files changed

+263
-2
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
name: Create Release PR
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: 'Version type'
8+
required: true
9+
type: choice
10+
options:
11+
- patch
12+
- minor
13+
- major
14+
15+
jobs:
16+
create-release-pr:
17+
runs-on: ubuntu-latest
18+
permissions:
19+
contents: write
20+
pull-requests: write
21+
steps:
22+
- name: Checkout
23+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
24+
with:
25+
persist-credentials: false
26+
27+
- name: Configure Git
28+
run: |
29+
git config user.name "github-actions[bot]"
30+
git config user.email "github-actions[bot]@users.noreply.github.com"
31+
32+
- name: Setup Node.js
33+
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
34+
with:
35+
node-version: 'lts/*'
36+
37+
# No need to install dependencies - npm version works without them
38+
- name: Version bump
39+
id: version
40+
run: |
41+
npm version "$VERSION_TYPE" --no-git-tag-version
42+
VERSION=$(jq -r '.version' package.json)
43+
echo "version=$VERSION" >> $GITHUB_OUTPUT
44+
env:
45+
VERSION_TYPE: ${{ github.event.inputs.version }}
46+
47+
- name: Get release notes
48+
id: release-notes
49+
run: |
50+
# Get the default branch
51+
DEFAULT_BRANCH=$(gh api "repos/$GITHUB_REPOSITORY" --jq '.default_branch')
52+
53+
# Get the last release tag using GitHub API
54+
LAST_TAG=$(gh api "repos/$GITHUB_REPOSITORY/releases/latest" --jq '.tag_name' 2>/dev/null || echo "")
55+
56+
# Log the tag status
57+
if [ -z "$LAST_TAG" ]; then
58+
echo "No previous releases found, generating notes from beginning"
59+
else
60+
echo "Previous release: $LAST_TAG"
61+
fi
62+
63+
# Generate release notes with or without previous tag
64+
if [ -n "$LAST_TAG" ]; then
65+
NOTES=$(gh api \
66+
--method POST \
67+
-H "Accept: application/vnd.github+json" \
68+
"/repos/$GITHUB_REPOSITORY/releases/generate-notes" \
69+
-f "tag_name=v$VERSION" \
70+
-f "target_commitish=$DEFAULT_BRANCH" \
71+
-f "previous_tag_name=$LAST_TAG" \
72+
--jq '.body')
73+
else
74+
NOTES=$(gh api \
75+
--method POST \
76+
-H "Accept: application/vnd.github+json" \
77+
"/repos/$GITHUB_REPOSITORY/releases/generate-notes" \
78+
-f "tag_name=v$VERSION" \
79+
-f "target_commitish=$DEFAULT_BRANCH" \
80+
--jq '.body')
81+
fi
82+
83+
# Save to file to handle multiline content
84+
echo "$NOTES" > release-notes.md
85+
env:
86+
GH_TOKEN: ${{ github.token }}
87+
VERSION: ${{ steps.version.outputs.version }}
88+
GITHUB_REPOSITORY: ${{ github.repository }}
89+
90+
- name: Create Pull Request
91+
uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e # v7.0.8
92+
with:
93+
branch: release/v${{ steps.version.outputs.version }}
94+
delete-branch: true
95+
title: "Release v${{ steps.version.outputs.version }}"
96+
body-path: release-notes.md
97+
commit-message: "chore: release v${{ steps.version.outputs.version }}"
98+
add-paths: |
99+
package.json
100+
yarn.lock
101+
labels: |
102+
Type: Release
103+
assignees: ${{ github.actor }}
104+
draft: true

.github/workflows/release.yml

Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
name: Release
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- master
7+
- main
8+
types:
9+
- closed
10+
workflow_dispatch:
11+
inputs:
12+
version:
13+
description: 'Version to publish (e.g., 1.2.3)'
14+
required: false
15+
type: string
16+
17+
jobs:
18+
release:
19+
if: |
20+
(github.event_name == 'pull_request' &&
21+
github.event.pull_request.merged == true &&
22+
contains(github.event.pull_request.labels.*.name, 'Type: Release')) ||
23+
github.event_name == 'workflow_dispatch'
24+
runs-on: ubuntu-latest
25+
permissions:
26+
contents: write
27+
id-token: write # OIDC
28+
pull-requests: write # PR comment
29+
steps:
30+
- name: Checkout
31+
uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5.0.0
32+
with:
33+
persist-credentials: false
34+
35+
- name: Get package info
36+
id: package
37+
run: |
38+
if [ "$EVENT_NAME" = "workflow_dispatch" ] && [ -n "$INPUT_VERSION" ]; then
39+
VERSION="$INPUT_VERSION"
40+
else
41+
VERSION=$(jq -r '.version' package.json)
42+
fi
43+
PACKAGE_NAME=$(jq -r '.name' package.json)
44+
echo "version=$VERSION" >> $GITHUB_OUTPUT
45+
echo "name=$PACKAGE_NAME" >> $GITHUB_OUTPUT
46+
env:
47+
EVENT_NAME: ${{ github.event_name }}
48+
INPUT_VERSION: ${{ github.event.inputs.version }}
49+
50+
- name: Check if tag exists
51+
id: tag-check
52+
run: |
53+
if git rev-parse "v$VERSION" >/dev/null 2>&1; then
54+
echo "exists=true" >> $GITHUB_OUTPUT
55+
else
56+
echo "exists=false" >> $GITHUB_OUTPUT
57+
fi
58+
env:
59+
VERSION: ${{ steps.package.outputs.version }}
60+
61+
- name: Setup Node.js
62+
if: steps.tag-check.outputs.exists == 'false'
63+
uses: actions/setup-node@a0853c24544627f65ddf259abe73b1d18a591444 # v5.0.0
64+
with:
65+
node-version: 'lts/*'
66+
registry-url: 'https://registry.npmjs.org'
67+
68+
- name: Ensure npm 11.5.1 or later is installed
69+
if: steps.tag-check.outputs.exists == 'false'
70+
run: |
71+
NPM_VERSION=$(npm -v)
72+
echo "Current npm version: $NPM_VERSION"
73+
if ! npx semver -r ">=11.5.1" "$NPM_VERSION"; then
74+
echo "npm version $NPM_VERSION is too old. Installing latest npm..."
75+
npm install -g npm@latest
76+
echo "Updated npm version: $(npm -v)"
77+
fi
78+
79+
- name: Install dependencies
80+
if: steps.tag-check.outputs.exists == 'false'
81+
run: yarn install --frozen-lockfile
82+
83+
- name: Build package
84+
if: steps.tag-check.outputs.exists == 'false'
85+
run: yarn run build
86+
87+
- name: Publish to npm with provenance
88+
if: steps.tag-check.outputs.exists == 'false'
89+
run: npm publish --provenance --access public
90+
91+
- name: Create GitHub Release with tag
92+
id: create-release
93+
if: steps.tag-check.outputs.exists == 'false'
94+
run: |
95+
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
96+
RELEASE_URL=$(gh release create "v$VERSION" \
97+
--title "v$VERSION" \
98+
--target "$SHA" \
99+
--generate-notes)
100+
else
101+
RELEASE_URL=$(gh release create "v$VERSION" \
102+
--title "v$VERSION" \
103+
--target "$SHA" \
104+
--notes "$PR_BODY")
105+
fi
106+
echo "url=$RELEASE_URL" >> $GITHUB_OUTPUT
107+
env:
108+
GH_TOKEN: ${{ github.token }}
109+
VERSION: ${{ steps.package.outputs.version }}
110+
SHA: ${{ github.sha }}
111+
EVENT_NAME: ${{ github.event_name }}
112+
PR_BODY: ${{ github.event.pull_request.body }}
113+
114+
- name: Comment on PR - Success
115+
if: |
116+
always() &&
117+
github.event_name == 'pull_request' &&
118+
steps.tag-check.outputs.exists == 'false' &&
119+
success()
120+
run: |
121+
gh pr comment "$PR_NUMBER" \
122+
--body "✅ **Release v$VERSION completed successfully!**
123+
124+
- 📦 npm package: https://www.npmjs.com/package/$PACKAGE_NAME/v/$VERSION
125+
- 🏷️ GitHub Release: $RELEASE_URL
126+
- 🔗 Workflow run: $SERVER_URL/$REPOSITORY/actions/runs/$RUN_ID"
127+
env:
128+
GH_TOKEN: ${{ github.token }}
129+
PR_NUMBER: ${{ github.event.pull_request.number }}
130+
VERSION: ${{ steps.package.outputs.version }}
131+
PACKAGE_NAME: ${{ steps.package.outputs.name }}
132+
RELEASE_URL: ${{ steps.create-release.outputs.url }}
133+
SERVER_URL: ${{ github.server_url }}
134+
REPOSITORY: ${{ github.repository }}
135+
RUN_ID: ${{ github.run_id }}
136+
137+
- name: Comment on PR - Failure
138+
if: |
139+
always() &&
140+
github.event_name == 'pull_request' &&
141+
steps.tag-check.outputs.exists == 'false' &&
142+
failure()
143+
run: |
144+
gh pr comment "$PR_NUMBER" \
145+
--body "❌ **Release v$VERSION failed**
146+
147+
Please check the workflow logs for details.
148+
🔗 Workflow run: $SERVER_URL/$REPOSITORY/actions/runs/$RUN_ID"
149+
env:
150+
GH_TOKEN: ${{ github.token }}
151+
PR_NUMBER: ${{ github.event.pull_request.number }}
152+
VERSION: ${{ steps.package.outputs.version }}
153+
SERVER_URL: ${{ github.server_url }}
154+
REPOSITORY: ${{ github.repository }}
155+
RUN_ID: ${{ github.run_id }}

.github/workflows/test.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,11 @@ jobs:
1313
node-version: [ 16, 18 ]
1414
steps:
1515
- name: checkout
16-
uses: actions/checkout@v2
16+
uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0
17+
with:
18+
persist-credentials: false
1719
- name: setup Node.js ${{ matrix.node-version }}
18-
uses: actions/setup-node@v1
20+
uses: actions/setup-node@f1f314fca9dfce2769ece7d933488f076716723e # v1.4.6
1921
with:
2022
node-version: ${{ matrix.node-version }}
2123
- name: Install

0 commit comments

Comments
 (0)