Skip to content

Commit be9ad45

Browse files
authored
Merge pull request #2 from xylophonez/chore/npm-oidc
chore: add npm oidc workflow
2 parents ccfc0d4 + 51bfc54 commit be9ad45

5 files changed

Lines changed: 202 additions & 3 deletions

File tree

.github/PULL_REQUEST_TEMPLATE.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
## Version Update
2+
3+
Select exactly one version update type. This choice is used by CI after the PR is merged into
4+
`main` to update `package.json`, create the release tag, and publish the package to npm.
5+
6+
- [ ] major
7+
- [ ] minor
8+
- [ ] patch

.github/workflows/publish-npm.yaml

Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
name: Auto Version, Publish, and Tag NPM
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
workflow_dispatch:
8+
inputs:
9+
update_type:
10+
description: Version update type
11+
required: true
12+
default: patch
13+
type: choice
14+
options:
15+
- major
16+
- minor
17+
- patch
18+
19+
concurrency:
20+
group: npm-release
21+
cancel-in-progress: false
22+
23+
jobs:
24+
publish:
25+
if: "${{ github.event_name == 'workflow_dispatch' || !startsWith(github.event.head_commit.message, 'chore: release ') }}"
26+
runs-on: ubuntu-latest
27+
permissions:
28+
contents: write
29+
id-token: write
30+
pull-requests: read
31+
32+
steps:
33+
- name: Checkout code
34+
uses: actions/checkout@v4
35+
with:
36+
fetch-depth: 0
37+
token: ${{ secrets.DEPLOY_PAT || github.token }}
38+
39+
- name: Setup Node.js
40+
uses: actions/setup-node@v4
41+
with:
42+
node-version: '22.14.0'
43+
registry-url: 'https://registry.npmjs.org'
44+
45+
- name: Install npm with OIDC support
46+
run: npm install --global npm@^11.5.1
47+
48+
- name: Get version update type
49+
id: release-type
50+
uses: actions/github-script@v7
51+
with:
52+
script: |
53+
const updateTypes = ['major', 'minor', 'patch'];
54+
55+
function selectedUpdateTypes(body) {
56+
return updateTypes.filter((type) => {
57+
const pattern = new RegExp(`^- \\[[xX]\\]\\s*${type}\\s*$`, 'im');
58+
return pattern.test(body ?? '');
59+
});
60+
}
61+
62+
if (context.eventName === 'workflow_dispatch') {
63+
const updateType = context.payload.inputs?.update_type;
64+
65+
if (!updateTypes.includes(updateType)) {
66+
core.setFailed(`Invalid version update type: ${updateType}`);
67+
return;
68+
}
69+
70+
core.setOutput('update-type', updateType);
71+
core.info(`Selected version update: ${updateType}`);
72+
return;
73+
}
74+
75+
const { owner, repo } = context.repo;
76+
const response = await github.rest.repos.listPullRequestsAssociatedWithCommit({
77+
owner,
78+
repo,
79+
commit_sha: context.sha,
80+
});
81+
82+
const pullRequest =
83+
response.data.find((pr) => pr.merged_at && pr.base.ref === 'main') ??
84+
response.data.find((pr) => pr.merged_at);
85+
86+
if (!pullRequest) {
87+
core.setFailed(`No merged pull request found for commit ${context.sha}.`);
88+
return;
89+
}
90+
91+
const selected = selectedUpdateTypes(pullRequest.body);
92+
93+
if (selected.length !== 1) {
94+
core.setFailed(
95+
`PR #${pullRequest.number} must have exactly one version update checkbox selected. Found ${selected.length}.`,
96+
);
97+
return;
98+
}
99+
100+
core.setOutput('update-type', selected[0]);
101+
core.setOutput('pr-number', String(pullRequest.number));
102+
core.info(`Selected version update from PR #${pullRequest.number}: ${selected[0]}`);
103+
104+
- name: Bump package version
105+
id: package-version
106+
run: |
107+
npm version "$UPDATE_TYPE" --no-git-tag-version
108+
PACKAGE_NAME=$(node -p "require('./package.json').name")
109+
VERSION=$(node -p "require('./package.json').version")
110+
echo "package-name=$PACKAGE_NAME" >> "$GITHUB_OUTPUT"
111+
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
112+
echo "Bumped $PACKAGE_NAME to $VERSION"
113+
env:
114+
UPDATE_TYPE: ${{ steps.release-type.outputs.update-type }}
115+
116+
- name: Check release availability
117+
run: |
118+
if git tag --list "v${PACKAGE_VERSION}" | grep -q .; then
119+
echo "::error::Tag v${PACKAGE_VERSION} already exists"
120+
exit 1
121+
fi
122+
123+
if npm view "${PACKAGE_NAME}@${PACKAGE_VERSION}" version --registry=https://registry.npmjs.org > /dev/null 2>&1; then
124+
echo "::error::${PACKAGE_NAME}@${PACKAGE_VERSION} already exists on npm"
125+
exit 1
126+
fi
127+
env:
128+
PACKAGE_NAME: ${{ steps.package-version.outputs.package-name }}
129+
PACKAGE_VERSION: ${{ steps.package-version.outputs.version }}
130+
131+
- name: Install dependencies
132+
run: npm ci
133+
134+
- name: Build package
135+
run: npm run build
136+
137+
- name: Publish to NPM
138+
run: npm publish
139+
140+
- name: Commit version bump and tag
141+
run: |
142+
git config user.name "github-actions[bot]"
143+
git config user.email "github-actions[bot]@users.noreply.github.com"
144+
git add package.json package-lock.json
145+
git commit -m "chore: release v${PACKAGE_VERSION} [skip ci]"
146+
git tag "v${PACKAGE_VERSION}"
147+
git push origin HEAD:main
148+
git push origin "v${PACKAGE_VERSION}"
149+
env:
150+
PACKAGE_VERSION: ${{ steps.package-version.outputs.version }}
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
name: Validate Version Selection
2+
3+
on:
4+
pull_request:
5+
branches:
6+
- main
7+
types:
8+
- opened
9+
- synchronize
10+
- reopened
11+
- edited
12+
- ready_for_review
13+
14+
permissions:
15+
contents: read
16+
17+
jobs:
18+
validate-version-selection:
19+
name: Validate version selection
20+
runs-on: ubuntu-latest
21+
22+
steps:
23+
- name: Validate release checkbox
24+
uses: actions/github-script@v7
25+
with:
26+
script: |
27+
const body = context.payload.pull_request?.body ?? '';
28+
const updateTypes = ['major', 'minor', 'patch'];
29+
const selected = updateTypes.filter((type) => {
30+
const pattern = new RegExp(`^- \\[[xX]\\]\\s*${type}\\s*$`, 'im');
31+
return pattern.test(body);
32+
});
33+
34+
if (selected.length !== 1) {
35+
core.setFailed(
36+
`Select exactly one version update checkbox in the PR body. Found ${selected.length}.`,
37+
);
38+
return;
39+
}
40+
41+
core.info(`Selected version update: ${selected[0]}`);

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@
3535
"license": "MIT",
3636
"repository": {
3737
"type": "git",
38-
"url": "git+https://github.com/xylophonez/hyperbalance.git"
38+
"url": "https://github.com/xylophonez/hyperbalance"
3939
},
4040
"bugs": {
4141
"url": "https://github.com/xylophonez/hyperbalance/issues"

0 commit comments

Comments
 (0)