1+ name : Publish package to npm
2+
3+ on :
4+ release :
5+ # "created" does not fire when a draft release is published, "published" does
6+ types : [published]
7+
8+ concurrency :
9+ group : " ${{ github.workflow }} ✨ ${{ github.ref }}"
10+ cancel-in-progress : false
11+
12+ permissions :
13+ contents : read
14+
15+ jobs :
16+ publish :
17+ name : Publish to npm
18+ runs-on : ubuntu-latest
19+ permissions :
20+ contents : read
21+ id-token : write
22+ steps :
23+ - uses : actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
24+ with :
25+ persist-credentials : false
26+
27+ - name : Setup Node.js
28+ uses : actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
29+ with :
30+ node-version : " lts/*"
31+ registry-url : " https://registry.npmjs.org"
32+
33+ # npm stage publish requires npm >= 11.15.0
34+ - name : Upgrade npm
35+ run : npm install -g npm@latest
36+
37+ # Derive the npm dist-tag from the package version rather than from the
38+ # release target branch, which is just whatever the "Target" dropdown held
39+ # when the release was created and has been wrong in the past. Publishing
40+ # a maintenance line must never overwrite "latest":
41+ # prerelease (e.g. 3.0.0-beta.1) -> next
42+ # major >= current npm "latest" major -> latest
43+ # major < current npm "latest" major -> latest-<major>
44+ # No data from the release event is used, so nothing untrusted reaches
45+ # the script.
46+ - name : Resolve npm dist-tag from package version
47+ id : dist-tag
48+ run : |
49+ node <<'EOF'
50+ const fs = require('fs')
51+ const { execFileSync } = require('child_process')
52+
53+ const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/
54+ const { name, version } = JSON.parse(fs.readFileSync('package.json', 'utf8'))
55+ const parsed = SEMVER.exec(version)
56+ if (!parsed) throw new Error(`Invalid version in package.json: ${version}`)
57+
58+ const latest = execFileSync('npm', ['view', name, 'version'], { encoding: 'utf8' }).trim()
59+ const latestParsed = SEMVER.exec(latest)
60+ if (!latestParsed) throw new Error(`Unexpected "latest" version on npm: ${latest}`)
61+
62+ const major = Number(parsed[1])
63+ const prerelease = parsed[4]
64+ const latestMajor = Number(latestParsed[1])
65+
66+ let tag
67+ if (prerelease) tag = 'next'
68+ else if (major >= latestMajor) tag = 'latest'
69+ else tag = `latest-${major}`
70+
71+ console.log(`${name}@${version} (npm latest: ${latest}) -> dist-tag "${tag}"`)
72+ fs.appendFileSync(process.env.GITHUB_OUTPUT, `tag=${tag}\n`)
73+ EOF
74+
75+ - name : Stage publish to npm
76+ env :
77+ NPM_DIST_TAG : ${{ steps.dist-tag.outputs.tag }}
78+ run : npm stage publish --tag "$NPM_DIST_TAG"
0 commit comments