Skip to content

Commit fd0f940

Browse files
committed
Update workflows
1 parent 7c27a91 commit fd0f940

File tree

2 files changed

+237
-17
lines changed

2 files changed

+237
-17
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: '20.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: 102 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,72 @@ name: Tests
33
on: [push, pull_request]
44

55
jobs:
6-
build:
6+
build-test-matrix:
7+
name: Build test matrix
8+
runs-on: ubuntu-latest
9+
outputs:
10+
matrix: ${{ steps.set-matrix.outputs.matrix }}
11+
steps:
12+
- id: set-matrix
13+
run: |
14+
node <<'EOF' >> $GITHUB_OUTPUT
15+
const https = require('https');
16+
17+
function compareSemver(a, b) {
18+
const pa = a.split('.').map(Number);
19+
const pb = b.split('.').map(Number);
20+
for (let i = 0; i < 3; i++) {
21+
if (pa[i] !== pb[i]) return pb[i] - pa[i];
22+
}
23+
return 0;
24+
}
25+
26+
https.get('https://nodejs.org/dist/index.json', res => {
27+
let data = '';
28+
res.on('data', chunk => data += chunk);
29+
res.on('end', () => {
30+
const releases = JSON.parse(data);
31+
32+
// Filter LTS only
33+
const lts = releases
34+
.filter(r => r.lts)
35+
.map(r => r.version.replace(/^v/, ''));
36+
37+
// Group by major
38+
const byMajor = {};
39+
for (const v of lts) {
40+
const major = v.split('.')[0];
41+
if (!byMajor[major]) byMajor[major] = [];
42+
byMajor[major].push(v);
43+
}
44+
45+
// For each major, pick highest semver
46+
const latestPerMajor = Object.entries(byMajor)
47+
.map(([major, versions]) => {
48+
versions.sort(compareSemver);
49+
return versions[0]; // highest patch/minor
50+
});
51+
52+
// Sort majors descending
53+
latestPerMajor.sort(compareSemver);
54+
55+
// Take latest 4 majors
56+
const final = latestPerMajor.slice(0, 4);
57+
58+
const matrix = { "node-version": final };
59+
console.log(`matrix=${JSON.stringify(matrix)}`);
60+
});
61+
});
62+
EOF
763
64+
test:
65+
needs: build-test-matrix
866
runs-on: ubuntu-latest
967

1068
strategy:
11-
matrix:
12-
node-version: [16.x, 18.x, 20.x, 22.x]
69+
fail-fast: false
70+
matrix: ${{ fromJson(needs.build-test-matrix.outputs.matrix) }}
71+
name: Node ${{ matrix.node-version }}
1372

1473
steps:
1574
- uses: actions/checkout@v4
@@ -18,13 +77,49 @@ jobs:
1877
uses: actions/setup-node@v4
1978
with:
2079
node-version: ${{ matrix.node-version }}
21-
cache: 'yarn'
80+
81+
- name: Enable Corepack
82+
run: corepack enable
83+
84+
- name: Detect Yarn version
85+
id: yarn-version
86+
run: |
87+
YARN_VERSION=$(node -p "
88+
try {
89+
const pm = require('./package.json').packageManager ?? '';
90+
const match = pm.match(/^yarn@(\d+)/);
91+
match ? match[1] : '';
92+
} catch { '' }
93+
")
94+
if [ -z "$YARN_VERSION" ]; then
95+
YARN_VERSION=$(yarn --version | cut -d. -f1)
96+
fi
97+
echo "major=$YARN_VERSION" >> "$GITHUB_OUTPUT"
98+
echo "Detected Yarn major version: $YARN_VERSION"
2299
23100
- name: Install dependencies
24-
run: yarn --frozen-lockfile --non-interactive --prefer-offline
101+
run: |
102+
if [ "${{ steps.yarn-version.outputs.major }}" = "1" ]; then
103+
yarn install --frozen-lockfile --non-interactive --prefer-offline
104+
else
105+
yarn install --immutable
106+
fi
107+
108+
- name: Lint
109+
run: yarn lint
25110

26111
- name: Test
27112
run: yarn test
28113

29-
- name: Lint
30-
run: yarn lint
114+
test-results:
115+
name: Tests passed
116+
needs: test
117+
runs-on: ubuntu-latest
118+
if: always()
119+
steps:
120+
- name: Check all matrix jobs passed
121+
run: |
122+
if [ "${{ needs.test.result }}" != "success" ]; then
123+
echo "One or more test jobs failed"
124+
exit 1
125+
fi

0 commit comments

Comments
 (0)