Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 78 additions & 0 deletions .github/workflows/npm-publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
name: Publish package to npm

on:
release:
# "created" does not fire when a draft release is published, "published" does
types: [published]

concurrency:
group: "${{ github.workflow }} ✨ ${{ github.ref }}"
cancel-in-progress: false

permissions:
contents: read

jobs:
publish:
name: Publish to npm
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- name: Setup Node.js
uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "lts/*"
registry-url: "https://registry.npmjs.org"

# npm stage publish requires npm >= 11.15.0
- name: Upgrade npm
run: npm install -g npm@latest

# Derive the npm dist-tag from the package version rather than from the
# release target branch, which is just whatever the "Target" dropdown held
# when the release was created and has been wrong in the past. Publishing
# a maintenance line must never overwrite "latest":
# prerelease (e.g. 3.0.0-beta.1) -> next
# major >= current npm "latest" major -> latest
# major < current npm "latest" major -> latest-<major>
# No data from the release event is used, so nothing untrusted reaches
# the script.
- name: Resolve npm dist-tag from package version
id: dist-tag
run: |
node <<'EOF'
const fs = require('fs')
const { execFileSync } = require('child_process')

const SEMVER = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/
const { name, version } = JSON.parse(fs.readFileSync('package.json', 'utf8'))
const parsed = SEMVER.exec(version)
if (!parsed) throw new Error(`Invalid version in package.json: ${version}`)

const latest = execFileSync('npm', ['view', name, 'version'], { encoding: 'utf8' }).trim()
const latestParsed = SEMVER.exec(latest)
if (!latestParsed) throw new Error(`Unexpected "latest" version on npm: ${latest}`)

const major = Number(parsed[1])
const prerelease = parsed[4]
const latestMajor = Number(latestParsed[1])

let tag
if (prerelease) tag = 'next'
else if (major >= latestMajor) tag = 'latest'
else tag = `latest-${major}`

console.log(`${name}@${version} (npm latest: ${latest}) -> dist-tag "${tag}"`)
fs.appendFileSync(process.env.GITHUB_OUTPUT, `tag=${tag}\n`)
EOF

- name: Stage publish to npm
env:
NPM_DIST_TAG: ${{ steps.dist-tag.outputs.tag }}
run: npm stage publish --tag "$NPM_DIST_TAG"
Loading