Skip to content

Commit 4286c74

Browse files
committed
improve github workflows
1 parent 000b4aa commit 4286c74

File tree

2 files changed

+213
-18
lines changed

2 files changed

+213
-18
lines changed

.github/workflows/release.yml

Lines changed: 135 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,153 @@
11
name: Release
22

33
on:
4-
release:
5-
types: [published]
4+
workflow_dispatch:
5+
inputs:
6+
version:
7+
description: "Version to publish"
8+
required: true
9+
dry_run:
10+
description: "Perform a dry run without publishing"
11+
type: boolean
12+
required: false
13+
default: true
14+
15+
concurrency:
16+
group: npm-publish-${{ github.repository }}
17+
cancel-in-progress: false
618

719
jobs:
820
release:
921
name: Release workflow
10-
1122
runs-on: ubuntu-latest
1223

24+
permissions:
25+
contents: read
26+
id-token: write # Required for OIDC trusted publishing
27+
1328
steps:
14-
- uses: actions/checkout@v4
29+
- name: Validate GitHub release and tag exists
30+
env:
31+
GH_TOKEN: ${{ github.token }}
32+
run: |
33+
TAG="v${{ inputs.version }}"
34+
echo "Looking for release with tag $TAG..."
35+
36+
RELEASE=$(gh release view "$TAG" --repo ${{ github.repository }} --json tagName,name 2>/dev/null)
37+
if [ $? -ne 0 ]; then
38+
echo "❌ No GitHub release found with tag $TAG"
39+
exit 1
40+
fi
41+
42+
RELEASE_NAME=$(echo "$RELEASE" | jq -r '.name')
43+
if [ "$RELEASE_NAME" != "$TAG" ]; then
44+
echo "❌ Release name '$RELEASE_NAME' does not match expected '$TAG'"
45+
exit 1
46+
fi
47+
48+
echo "✅ GitHub release '$RELEASE_NAME' confirmed"
49+
50+
- name: Checkout tag
51+
uses: actions/checkout@v4
52+
with:
53+
ref: "v${{ inputs.version }}"
54+
fetch-depth: 0
55+
56+
- name: Ensure tag commit is on master
57+
run: |
58+
if ! git branch -r --contains "$(git rev-parse HEAD)" | grep -q "origin/master"; then
59+
echo "❌ Tag is not based on master branch"
60+
exit 1
61+
fi
62+
echo "✅ Tag commit is on master"
1563
1664
- name: Setup Node.js
1765
uses: actions/setup-node@v4
1866
with:
19-
node-version: '22.x'
20-
registry-url: 'https://registry.npmjs.org/'
67+
node-version: "lts/*"
68+
registry-url: "https://registry.npmjs.org/"
2169

22-
- name: Install
23-
run: yarn --frozen-lockfile --non-interactive
70+
- name: Enable Corepack
71+
run: corepack enable
72+
73+
- name: Detect Yarn version
74+
id: yarn-version
75+
run: |
76+
# Resolve the Yarn major version from the packageManager field in
77+
# package.json if present, otherwise fall back to the installed version.
78+
YARN_VERSION=$(node -p "
79+
try {
80+
const pm = require('./package.json').packageManager ?? '';
81+
const match = pm.match(/^yarn@(\d+)/);
82+
match ? match[1] : '';
83+
} catch { '' }
84+
")
85+
if [ -z "$YARN_VERSION" ]; then
86+
YARN_VERSION=$(yarn --version | cut -d. -f1)
87+
fi
88+
echo "major=$YARN_VERSION" >> "$GITHUB_OUTPUT"
89+
echo "Detected Yarn major version: $YARN_VERSION"
90+
91+
- name: Validate version matches package.json
92+
run: |
93+
PKG_VERSION=$(node -p "require('./package.json').version")
94+
INPUT_VERSION="${{ inputs.version }}"
95+
if [ "$PKG_VERSION" != "$INPUT_VERSION" ]; then
96+
echo "❌ Version mismatch: package.json has $PKG_VERSION but input was $INPUT_VERSION"
97+
exit 1
98+
fi
99+
echo "✅ Version $PKG_VERSION confirmed"
100+
101+
- name: Install dependencies
102+
run: |
103+
if [ "${{ steps.yarn-version.outputs.major }}" = "1" ]; then
104+
yarn install --frozen-lockfile --non-interactive
105+
else
106+
yarn install --immutable
107+
fi
108+
109+
- name: Build package (if build script exists)
110+
run: |
111+
if node -e "process.exit(require('./package.json').scripts?.build ? 0 : 1)" 2>/dev/null; then
112+
yarn build
113+
else
114+
echo "No build script found — skipping build step"
115+
fi
116+
117+
- name: Publish (dry run)
118+
if: ${{ inputs.dry_run }}
119+
env:
120+
# The npm CLI automatically detects the OIDC environment via
121+
# ACTIONS_ID_TOKEN_REQUEST_URL / ACTIONS_ID_TOKEN_REQUEST_TOKEN and
122+
# handles the token exchange itself. NODE_AUTH_TOKEN must still be set
123+
# (even if empty) to satisfy the .npmrc written by actions/setup-node,
124+
# otherwise npm errors before it reaches OIDC auth.
125+
NODE_AUTH_TOKEN: ""
126+
run: npm publish --provenance --access public --dry-run
24127

25128
- name: Publish
26-
run: yarn publish
129+
if: ${{ !inputs.dry_run }}
27130
env:
28-
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}
131+
NODE_AUTH_TOKEN: ""
132+
run: npm publish --provenance --access public
133+
134+
- name: Verify published version
135+
if: ${{ !inputs.dry_run }}
136+
run: |
137+
PACKAGE_NAME=$(node -p "require('./package.json').name")
138+
EXPECTED_VERSION="${{ inputs.version }}"
139+
140+
echo "Waiting for npm propagation..."
141+
142+
for i in {1..10}; do
143+
PUBLISHED_VERSION=$(npm view "$PACKAGE_NAME" version 2>/dev/null)
144+
if [ "$PUBLISHED_VERSION" = "$EXPECTED_VERSION" ]; then
145+
echo "✅ Version $PUBLISHED_VERSION confirmed on npm"
146+
exit 0
147+
fi
148+
echo "Not visible yet (attempt $i)..."
149+
sleep 15
150+
done
151+
152+
echo "❌ Published version not visible after waiting"
153+
exit 1

.github/workflows/tests.yml

Lines changed: 78 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,60 @@ name: Tests
33
on: [push, pull_request]
44

55
jobs:
6-
build:
6+
resolve-node-versions:
7+
runs-on: ubuntu-latest
8+
outputs:
9+
matrix: ${{ steps.set-matrix.outputs.matrix }}
10+
steps:
11+
- id: set-matrix
12+
run: |
13+
node <<'EOF' >> $GITHUB_OUTPUT
14+
const https = require('https');
15+
16+
function compareSemver(a, b) {
17+
const pa = a.split('.').map(Number);
18+
const pb = b.split('.').map(Number);
19+
for (let i = 0; i < 3; i++) {
20+
if (pa[i] !== pb[i]) return pb[i] - pa[i];
21+
}
22+
return 0;
23+
}
724
25+
https.get('https://nodejs.org/dist/index.json', res => {
26+
let data = '';
27+
res.on('data', chunk => data += chunk);
28+
res.on('end', () => {
29+
const releases = JSON.parse(data);
30+
const lts = releases
31+
.filter(r => r.lts)
32+
.map(r => r.version.replace(/^v/, ''));
33+
const byMajor = {};
34+
for (const v of lts) {
35+
const major = v.split('.')[0];
36+
if (!byMajor[major]) byMajor[major] = [];
37+
byMajor[major].push(v);
38+
}
39+
const latestPerMajor = Object.entries(byMajor)
40+
.map(([major, versions]) => {
41+
versions.sort(compareSemver);
42+
return versions[0];
43+
});
44+
latestPerMajor.sort(compareSemver);
45+
const final = latestPerMajor.slice(0, 4);
46+
const matrix = { "node-version": final };
47+
console.log(`matrix=${JSON.stringify(matrix)}`);
48+
});
49+
});
50+
EOF
51+
52+
test:
53+
needs: resolve-node-versions
854
runs-on: ubuntu-latest
955

1056
strategy:
11-
matrix:
12-
node-version: [18.x, 20.x, 22.x, 24.x]
57+
fail-fast: false
58+
matrix: ${{ fromJson(needs.resolve-node-versions.outputs.matrix) }}
59+
name: Node ${{ matrix.node-version }}
1360

1461
steps:
1562
- uses: actions/checkout@v4
@@ -18,13 +65,36 @@ jobs:
1865
uses: actions/setup-node@v4
1966
with:
2067
node-version: ${{ matrix.node-version }}
21-
cache: 'yarn'
2268

23-
- name: Install dependencies
24-
run: yarn --frozen-lockfile --non-interactive --prefer-offline
69+
- name: Enable Corepack
70+
run: corepack enable
2571

26-
- name: Test
27-
run: yarn test
72+
- name: Detect Yarn version
73+
id: yarn-version
74+
run: |
75+
YARN_VERSION=$(node -p "
76+
try {
77+
const pm = require('./package.json').packageManager ?? '';
78+
const match = pm.match(/^yarn@(\d+)/);
79+
match ? match[1] : '';
80+
} catch { '' }
81+
")
82+
if [ -z "$YARN_VERSION" ]; then
83+
YARN_VERSION=$(yarn --version | cut -d. -f1)
84+
fi
85+
echo "major=$YARN_VERSION" >> "$GITHUB_OUTPUT"
86+
echo "Detected Yarn major version: $YARN_VERSION"
87+
88+
- name: Install dependencies
89+
run: |
90+
if [ "${{ steps.yarn-version.outputs.major }}" = "1" ]; then
91+
yarn install --frozen-lockfile --non-interactive --prefer-offline
92+
else
93+
yarn install --immutable
94+
fi
2895
2996
- name: Lint
3097
run: yarn lint
98+
99+
- name: Test
100+
run: yarn test

0 commit comments

Comments
 (0)