Skip to content

Commit 82f8b37

Browse files
authored
feat: extract version bumping into reusable bump-npm-version workflow (#59)
## Summary - Removes version bumping, git tagging, and PR label detection from \`publish-npm-package\`'s \`publish-release\` job — it now only builds and publishes whatever version is already in \`package.json\` - Removes GitHub Release creation from \`publish-npm-package\` — callers are responsible for this - Adds a new \`bump-npm-version.yaml\` reusable workflow that owns the full version bump lifecycle: 1. **resolve-version** — computes the new version string from \`package.json\` + \`bump-type\` using inline Node.js (no git ops); outputs \`new-version\` (e.g. \`v1.3.0\`) 2. **bump** — runs \`npm version\`, creates the commit and tag 3. **push** — \`git push origin main --follow-tags\` ## Motivation The previous \`publish-release\` job did too much: it detected bump type from PR labels, bumped the version, pushed the tag, built, and published — all in one job. This made it hard to reuse across repos and coupled version calculation to the publish step. The new design separates concerns: - **bump-npm-version** owns the git side (version resolution → commit → tag → push) - **publish-npm-package** owns the npm side (build → publish) Callers compose them as sequential jobs, passing \`new-version\` between them. The resolved version is available as an output before any git operations run. ## Dependency \`datum-cloud/activity\` PR #160 references \`v1.13.0\` of both \`bump-npm-version.yaml\` and \`publish-npm-package.yaml\` — this PR should be merged and tagged \`v1.13.0\` first. ## Test plan - [ ] Merge this PR and cut tag \`v1.13.0\` - [ ] Merge datum-cloud/activity#160 which references \`v1.13.0\` - [ ] Trigger \`workflow_dispatch\` on activity with \`bump-type=minor\` → confirm minor bump commit, tag, npm publish, and GitHub Release all created correctly - [ ] Push to activity main without dispatch → confirm only dev build published, no release created
2 parents 6ceb751 + 6b6ee37 commit 82f8b37

2 files changed

Lines changed: 109 additions & 44 deletions

File tree

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
name: Bump npm Version
2+
3+
on:
4+
workflow_call:
5+
inputs:
6+
package-name:
7+
required: true
8+
type: string
9+
description: "The npm package name (e.g. `@datum-cloud/datum-ui`)"
10+
package-path:
11+
required: true
12+
type: string
13+
description: "Path to the package directory relative to the repo root (e.g. `packages/datum-ui`)"
14+
bump-type:
15+
required: true
16+
type: string
17+
description: "Version bump type: `major`, `minor`, or `patch`"
18+
node-version:
19+
required: false
20+
type: number
21+
description: "Node.js version to use"
22+
default: 24
23+
lockfile-path:
24+
required: false
25+
type: string
26+
description: >
27+
Path to pnpm-lock.yaml relative to the repo root.
28+
Defaults to "<package-path>/pnpm-lock.yaml".
29+
Set to "pnpm-lock.yaml" for monorepos with a root lockfile.
30+
default: ""
31+
outputs:
32+
new-version:
33+
description: "The new version after bumping (e.g. `v1.3.0`)"
34+
value: ${{ jobs.bump.outputs.new-version }}
35+
36+
jobs:
37+
bump:
38+
name: Bump version
39+
runs-on: ubuntu-latest
40+
permissions:
41+
contents: write
42+
outputs:
43+
new-version: ${{ steps.resolve-version.outputs.new-version }}
44+
45+
steps:
46+
- name: Checkout
47+
uses: actions/checkout@v4
48+
with:
49+
ref: main
50+
51+
- name: Setup pnpm
52+
uses: pnpm/action-setup@v4
53+
with:
54+
package_json_file: ${{ inputs.package-path }}/package.json
55+
56+
- name: Setup Node.js
57+
uses: actions/setup-node@v4
58+
with:
59+
node-version: ${{ inputs.node-version }}
60+
cache: pnpm
61+
cache-dependency-path: ${{ inputs.lockfile-path || format('{0}/pnpm-lock.yaml', inputs.package-path) }}
62+
63+
- name: Resolve new version
64+
id: resolve-version
65+
working-directory: ${{ inputs.package-path }}
66+
env:
67+
BUMP_TYPE: ${{ inputs.bump-type }}
68+
run: |
69+
if [[ "$BUMP_TYPE" != "major" && "$BUMP_TYPE" != "minor" && "$BUMP_TYPE" != "patch" ]]; then
70+
echo "::error::Invalid bump-type '$BUMP_TYPE'. Must be one of: major, minor, patch"
71+
exit 1
72+
fi
73+
NEW_VERSION=$(node -e "
74+
const v = require('./package.json').version.split('.').map(Number);
75+
const t = process.env.BUMP_TYPE;
76+
if (t==='major'){v[0]++;v[1]=0;v[2]=0;}
77+
else if(t==='minor'){v[1]++;v[2]=0;}
78+
else{v[2]++;}
79+
console.log(v.join('.'));
80+
")
81+
echo "new-version=v${NEW_VERSION}" >> "$GITHUB_OUTPUT"
82+
83+
- name: Bump version
84+
working-directory: ${{ inputs.package-path }}
85+
env:
86+
BUMP_TYPE: ${{ inputs.bump-type }}
87+
PACKAGE_NAME: ${{ inputs.package-name }}
88+
run: |
89+
git config user.name "github-actions[bot]"
90+
git config user.email "github-actions[bot]@users.noreply.github.com"
91+
npm version "$BUMP_TYPE" --message "chore: release ${PACKAGE_NAME} v%s [skip ci]"
92+
93+
- name: Push
94+
run: git push origin main --follow-tags

.github/workflows/publish-npm-package.yaml

Lines changed: 15 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -37,20 +37,21 @@ on:
3737
type: string
3838
description: >
3939
Publishing mode: "dev" publishes a pre-release version (x.y.z-dev.{sha})
40-
to the dev dist-tag, "release" publishes a stable version to the latest
41-
dist-tag with an auto-bumped version. Defaults to "dev".
40+
to the dev dist-tag. "release" publishes the version already present in
41+
package.json to the latest dist-tag. Version bumping, tagging, and git
42+
operations are the caller's responsibility. Defaults to "dev".
4243
default: dev
4344
outputs:
4445
new-version:
45-
description: "The new version that was published (e.g. `v1.2.3` or `0.1.2-dev.abc1234`)"
46+
description: "The version that was published (e.g. `v1.2.3` or `0.1.2-dev.abc1234`)"
4647
value: ${{ jobs.publish-release.outputs.new-version || jobs.publish-dev.outputs.new-version }}
4748

4849
jobs:
4950
changes:
5051
name: Detect changes
5152
runs-on: ubuntu-latest
52-
# Skip the bot commit that this workflow itself creates when bumping the
53-
# version. The committer name for GITHUB_TOKEN pushes is "github-actions[bot]".
53+
# Skip the bot commit that callers create when bumping the version.
54+
# The committer name for GITHUB_TOKEN pushes is "github-actions[bot]".
5455
if: github.actor != 'github-actions[bot]'
5556
outputs:
5657
# For release mode, always publish (skip path filtering). Releases are
@@ -135,17 +136,18 @@ jobs:
135136
echo "Pin: \`npm install ${{ inputs.package-name }}@${{ steps.version.outputs.new-version }}\`"
136137
} >> "$GITHUB_STEP_SUMMARY"
137138
138-
# ── Release publish ─────────────────────────────────────────────────────
139-
# Publishes a stable version to the "latest" npm dist-tag. Bumps the
140-
# version in package.json, creates a git tag, and pushes back to main.
141-
# Triggered when the caller sets release-mode to "release".
139+
# ── Release publish ──────────────────────────────────────────────────────
140+
# Publishes the version already present in package.json to the "latest"
141+
# npm dist-tag. The caller is responsible for bumping the version, creating
142+
# the git tag, and pushing before invoking this workflow. This job only
143+
# builds and publishes — it performs no git operations.
142144
publish-release:
143145
name: Publish release
144146
needs: changes
145147
if: needs.changes.outputs.package == 'true' && inputs.release-mode == 'release'
146148
runs-on: ubuntu-latest
147149
permissions:
148-
contents: write # needed to push the version bump commit and tag
150+
contents: read
149151
id-token: write # needed for npm trusted publishing (OIDC)
150152
outputs:
151153
new-version: ${{ steps.version.outputs.new-version }}
@@ -173,43 +175,12 @@ jobs:
173175
working-directory: ${{ inputs.install-path || inputs.package-path }}
174176
run: pnpm install --frozen-lockfile
175177

176-
- name: Determine version bump type from PR labels
177-
id: bump-type
178-
env:
179-
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
180-
run: |
181-
# Find the PR that was merged into this commit and read its labels.
182-
# Falls back to "patch" if no relevant label is found.
183-
PR_LABELS=$(gh pr list \
184-
--state merged \
185-
--base main \
186-
--search "${{ github.sha }}" \
187-
--json labels \
188-
--jq '.[0].labels[].name' 2>/dev/null || true)
189-
190-
if echo "$PR_LABELS" | grep -q "release:major"; then
191-
echo "type=major" >> "$GITHUB_OUTPUT"
192-
elif echo "$PR_LABELS" | grep -q "release:minor"; then
193-
echo "type=minor" >> "$GITHUB_OUTPUT"
194-
else
195-
echo "type=patch" >> "$GITHUB_OUTPUT"
196-
fi
197-
198-
- name: Configure git for version bump commit
199-
run: |
200-
git config user.name "github-actions[bot]"
201-
git config user.email "github-actions[bot]@users.noreply.github.com"
202-
203-
- name: Bump version and create git tag
178+
- name: Read version from package.json
204179
id: version
205180
working-directory: ${{ inputs.package-path }}
206181
run: |
207-
NEW_VERSION=$(npm version ${{ steps.bump-type.outputs.type }} \
208-
--message "chore: release ${{ inputs.package-name }} v%s [skip ci]")
209-
echo "new-version=${NEW_VERSION}" >> "$GITHUB_OUTPUT"
210-
211-
- name: Push version bump commit and tag
212-
run: git push origin main --follow-tags
182+
VERSION=$(node -p "require('./package.json').version")
183+
echo "new-version=v${VERSION}" >> "$GITHUB_OUTPUT"
213184
214185
- name: Build package
215186
working-directory: ${{ inputs.package-path }}

0 commit comments

Comments
 (0)