Skip to content

Commit 635101f

Browse files
authored
chore: auto npm publish on main (#32)
1 parent 37a6ce8 commit 635101f

3 files changed

Lines changed: 143 additions & 33 deletions

File tree

Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
name: Publish to NPM
2+
description: Publish package to NPM with automatic version generation and idempotent checks
3+
4+
runs:
5+
using: composite
6+
steps:
7+
- name: Detect trigger type
8+
id: detect
9+
shell: bash
10+
run: |
11+
if [[ $GITHUB_REF == refs/tags/* ]]; then
12+
echo "is_tag=true" >> $GITHUB_OUTPUT
13+
echo "trigger_type=tag" >> $GITHUB_OUTPUT
14+
echo "trigger_name=${GITHUB_REF#refs/tags/}" >> $GITHUB_OUTPUT
15+
echo "📦 Detected tag push: ${GITHUB_REF#refs/tags/}"
16+
else
17+
echo "is_tag=false" >> $GITHUB_OUTPUT
18+
echo "trigger_type=branch" >> $GITHUB_OUTPUT
19+
echo "trigger_name=${GITHUB_REF#refs/heads/}" >> $GITHUB_OUTPUT
20+
echo "🚧 Detected branch push: ${GITHUB_REF#refs/heads/}"
21+
fi
22+
23+
- name: Setup Node.js with npm 11+ (for trusted publishing)
24+
uses: actions/setup-node@v4
25+
with:
26+
node-version: '20'
27+
registry-url: 'https://registry.npmjs.org'
28+
29+
- name: Generate version
30+
id: version
31+
shell: bash
32+
run: |
33+
# Get base version from package.json
34+
BASE_VERSION=$(jq -r .version package.json)
35+
36+
if [[ "${{ steps.detect.outputs.is_tag }}" == "true" ]]; then
37+
# For tags, use the base version as-is (stable release)
38+
NPM_VERSION="${BASE_VERSION}"
39+
NPM_TAG="latest"
40+
echo "📦 Publishing stable release: ${NPM_VERSION}"
41+
else
42+
# For main branch, create a pre-release version using git describe
43+
# Format: 0.3.0-next.5.g1a2b3c4 (base-next.commits.hash)
44+
GIT_COMMIT=$(git rev-parse --short HEAD)
45+
COMMITS_SINCE_TAG=$(git rev-list --count HEAD ^$(git describe --tags --abbrev=0 2>/dev/null || echo HEAD) 2>/dev/null || echo "0")
46+
NPM_VERSION="${BASE_VERSION}-next.${COMMITS_SINCE_TAG}.g${GIT_COMMIT}"
47+
NPM_TAG="next"
48+
echo "🚧 Publishing pre-release: ${NPM_VERSION}"
49+
fi
50+
51+
echo "version=${NPM_VERSION}" >> $GITHUB_OUTPUT
52+
echo "tag=${NPM_TAG}" >> $GITHUB_OUTPUT
53+
54+
# Update package.json with the new version
55+
node -e "const fs = require('fs'); const pkg = JSON.parse(fs.readFileSync('package.json')); pkg.version = '${NPM_VERSION}'; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2) + '\n');"
56+
57+
echo "Updated package.json to version ${NPM_VERSION}"
58+
59+
- name: Validate tag matches package.json version
60+
if: steps.detect.outputs.is_tag == 'true'
61+
shell: bash
62+
run: |
63+
# Extract version from package.json
64+
PKG_VERSION=$(jq -r .version package.json)
65+
66+
# Extract version from git tag (strip 'v' prefix)
67+
TAG_VERSION=${GITHUB_REF#refs/tags/v}
68+
69+
echo "Package version: $PKG_VERSION"
70+
echo "Tag version: $TAG_VERSION"
71+
72+
if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
73+
echo "❌ Error: Version mismatch!"
74+
echo " package.json version: $PKG_VERSION"
75+
echo " Git tag version: $TAG_VERSION"
76+
echo ""
77+
echo "Please ensure the git tag matches the version in package.json"
78+
exit 1
79+
fi
80+
81+
echo "✅ Version validation passed: $PKG_VERSION"
82+
83+
- name: Check if version exists
84+
id: check-exists
85+
shell: bash
86+
run: |
87+
PACKAGE_NAME=$(node -p "require('./package.json').name")
88+
VERSION="${{ steps.version.outputs.version }}"
89+
90+
if npm view "${PACKAGE_NAME}@${VERSION}" version &>/dev/null; then
91+
echo "exists=true" >> $GITHUB_OUTPUT
92+
echo "Version ${VERSION} already exists on npm"
93+
else
94+
echo "exists=false" >> $GITHUB_OUTPUT
95+
echo "Version ${VERSION} does not exist, will publish"
96+
fi
97+
98+
- name: Publish to npm (with OIDC trusted publishing)
99+
if: steps.check-exists.outputs.exists == 'false'
100+
shell: bash
101+
run: npm publish --tag ${{ steps.version.outputs.tag }} --provenance --access public
102+
103+
- name: Update dist-tag (version already exists)
104+
if: steps.check-exists.outputs.exists == 'true' && steps.detect.outputs.is_tag == 'true'
105+
shell: bash
106+
run: |
107+
PACKAGE_NAME=$(node -p "require('./package.json').name")
108+
VERSION="${{ steps.version.outputs.version }}"
109+
TAG="${{ steps.version.outputs.tag }}"
110+
111+
echo "Version ${VERSION} already published, updating dist-tag to ${TAG}"
112+
npm dist-tag add "${PACKAGE_NAME}@${VERSION}" "${TAG}"
113+
114+
- name: Skip (pre-release already exists)
115+
if: steps.check-exists.outputs.exists == 'true' && steps.detect.outputs.is_tag != 'true'
116+
shell: bash
117+
run: |
118+
echo "⏭️ Pre-release version already exists, skipping"

.github/workflows/ci.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,3 +113,21 @@ jobs:
113113

114114
- name: Build library
115115
run: bun run build
116+
117+
publish:
118+
name: publish to npm
119+
runs-on: ubuntu-latest
120+
# Only publish on main branch pushes after all checks pass
121+
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
122+
needs: [fmt, lint, typecheck, test, build]
123+
permissions:
124+
contents: read
125+
id-token: write # Required for OIDC trusted publishing
126+
steps:
127+
- name: Checkout code
128+
uses: actions/checkout@v4
129+
with:
130+
fetch-depth: 0 # Required for git describe to find tags
131+
132+
- name: Publish to NPM
133+
uses: ./.github/actions/npm-publish

.github/workflows/publish.yml

Lines changed: 7 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,20 @@ on:
55
tags:
66
- 'v*'
77

8+
permissions:
9+
contents: read
10+
id-token: write # Required for OIDC trusted publishing
11+
812
jobs:
913
publish:
1014
name: publish to npm
1115
runs-on: ubuntu-latest
12-
permissions:
13-
contents: read
14-
id-token: write # Required for OIDC trusted publishing
1516
steps:
1617
- name: Checkout tag
1718
uses: actions/checkout@v4
1819
with:
1920
ref: ${{ github.ref }}
21+
fetch-depth: 0 # Required for git describe to find tags
2022
submodules: recursive
2123

2224
- name: Setup Bun
@@ -29,28 +31,6 @@ jobs:
2931
with:
3032
version: 0.15.2
3133

32-
- name: Validate tag matches package.json version
33-
run: |
34-
# Extract version from package.json
35-
PKG_VERSION=$(jq -r .version package.json)
36-
37-
# Extract version from git tag (strip 'v' prefix)
38-
TAG_VERSION=${GITHUB_REF#refs/tags/v}
39-
40-
echo "Package version: $PKG_VERSION"
41-
echo "Tag version: $TAG_VERSION"
42-
43-
if [ "$PKG_VERSION" != "$TAG_VERSION" ]; then
44-
echo "❌ Error: Version mismatch!"
45-
echo " package.json version: $PKG_VERSION"
46-
echo " Git tag version: $TAG_VERSION"
47-
echo ""
48-
echo "Please ensure the git tag matches the version in package.json"
49-
exit 1
50-
fi
51-
52-
echo "✅ Version validation passed: $PKG_VERSION"
53-
5434
- name: Build WASM
5535
run: ./scripts/build-wasm.sh
5636

@@ -72,11 +52,5 @@ jobs:
7252
- name: Build library
7353
run: bun run build
7454

75-
- name: Setup Node.js with npm 11+ (for trusted publishing)
76-
uses: actions/setup-node@v4
77-
with:
78-
node-version: '20'
79-
registry-url: 'https://registry.npmjs.org'
80-
81-
- name: Publish to npm (with OIDC trusted publishing)
82-
run: npm publish --provenance --access public
55+
- name: Publish to NPM
56+
uses: ./.github/actions/npm-publish

0 commit comments

Comments
 (0)