Publish package to npm workflow (opentypejs/opentype.js)
The Publish package to npm workflow from opentypejs/opentype.js, explained and optimized by Latchkey.
CI health: A - excellent
Point runs-on at Latchkey and get caching, self-healing for flaky steps, and up to 58% lower cost, applied automatically.
What it does
This is the Publish package to npm workflow from the opentypejs/opentype.js repository, a real project running GitHub Actions. It is shown here with attribution under its MIT license.
Below, Latchkey shows a faster, safer version produced by its optimization engine.
The workflow
name: Publish package to npm
run-name: npm - publish ${{ inputs.version }} to ${{ inputs.npmDistTag }}
on:
workflow_dispatch:
inputs:
version:
required: true
description: 'SemVer version for npm. (i.e. 1.3.5)'
npmDistTag:
required: true
default: 'latest'
type: choice
options:
- latest
- next
- beta
dryRun:
description: 'Do a dry run (does not publish packages)'
type: boolean
default: true
concurrency:
group: npm-publish
cancel-in-progress: false
jobs:
publish:
timeout-minutes: 25
runs-on: ubuntu-latest
environment: npm-publish
permissions:
contents: write
# required for publishing to npm with --provenance
# see https://docs.npmjs.com/generating-provenance-statements
id-token: write
steps:
- name: Print input
env:
THE_INPUT: '${{ toJson(inputs) }}'
run: |
echo $THE_INPUT
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: actions/setup-node@v5
with:
node-version: 24
registry-url: 'https://registry.npmjs.org'
- name: Upgrade npm for trusted publishing
run: npm install -g npm@latest
- name: Preflight checks
env:
VERSION: ${{ inputs.version }}
NPM_DIST_TAG: ${{ inputs.npmDistTag }}
DRY_RUN: ${{ inputs.dryRun }}
REF_NAME: ${{ github.ref_name }}
run: |
set -euo pipefail
if ! node -e "const v = process.env.VERSION; if (!/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.test(v)) process.exit(1);"; then
echo "::error::Version '$VERSION' is not valid SemVer."
exit 1
fi
case "$NPM_DIST_TAG" in
latest|next|beta) ;;
*)
echo "::error::npmDistTag '$NPM_DIST_TAG' is not allowed."
exit 1
;;
esac
git fetch --tags --force
if git rev-parse --verify --quiet "refs/tags/$VERSION"; then
echo "::error::Git tag '$VERSION' already exists."
exit 1
fi
if npm view "opentype.js@$VERSION" version >/dev/null 2>&1; then
echo "::error::npm version '$VERSION' already exists."
exit 1
fi
if [ "$DRY_RUN" != "true" ] && [ "$REF_NAME" != "master" ]; then
echo "::error::Real releases must run from the master branch."
exit 1
fi
- name: Install dependencies
run: npm ci
- name: Build
run: npm run build
- name: Make dist files
run: npm run dist
- name: Run tests
run: npm run test
- name: Set version in package*.json
run: npm pkg set version=${{ inputs.version }}
- name: Publish package to npm
run: npm publish --access public --tag ${{ inputs.npmDistTag }} ${{ env.DRY_RUN }}
env:
DRY_RUN: ${{ inputs.dryRun && '--dry-run' || '' }}
- name: Create git tag
if: ${{ !inputs.dryRun }}
env:
VERSION: ${{ inputs.version }}
run: |
set -euo pipefail
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
git tag "$VERSION"
git push origin "$VERSION"
- name: Create GitHub release
if: ${{ !inputs.dryRun }}
env:
GH_TOKEN: ${{ github.token }}
VERSION: ${{ inputs.version }}
run: gh release create "$VERSION" --title "$VERSION" --generate-notes
The same workflow, on Latchkey
Estimated ~20% faster on cache hits, plus fewer wasted runs and a safer supply chain. Added and changed lines are highlighted.
name: Publish package to npm run-name: npm - publish ${{ inputs.version }} to ${{ inputs.npmDistTag }} on: workflow_dispatch: inputs: version: required: true description: 'SemVer version for npm. (i.e. 1.3.5)' npmDistTag: required: true default: 'latest' type: choice options: - latest - next - beta dryRun: description: 'Do a dry run (does not publish packages)' type: boolean default: true concurrency: group: npm-publish cancel-in-progress: false jobs: publish: timeout-minutes: 25 runs-on: latchkey-small environment: npm-publish permissions: contents: write # required for publishing to npm with --provenance # see https://docs.npmjs.com/generating-provenance-statements id-token: write steps: - name: Print input env: THE_INPUT: '${{ toJson(inputs) }}' run: | echo $THE_INPUT - uses: actions/checkout@v5 with: fetch-depth: 0 - uses: actions/setup-node@v5 with: cache: 'npm' node-version: 24 registry-url: 'https://registry.npmjs.org' - name: Upgrade npm for trusted publishing run: npm install -g npm@latest - name: Preflight checks env: VERSION: ${{ inputs.version }} NPM_DIST_TAG: ${{ inputs.npmDistTag }} DRY_RUN: ${{ inputs.dryRun }} REF_NAME: ${{ github.ref_name }} run: | set -euo pipefail if ! node -e "const v = process.env.VERSION; if (!/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/.test(v)) process.exit(1);"; then echo "::error::Version '$VERSION' is not valid SemVer." exit 1 fi case "$NPM_DIST_TAG" in latest|next|beta) ;; *) echo "::error::npmDistTag '$NPM_DIST_TAG' is not allowed." exit 1 ;; esac git fetch --tags --force if git rev-parse --verify --quiet "refs/tags/$VERSION"; then echo "::error::Git tag '$VERSION' already exists." exit 1 fi if npm view "opentype.js@$VERSION" version >/dev/null 2>&1; then echo "::error::npm version '$VERSION' already exists." exit 1 fi if [ "$DRY_RUN" != "true" ] && [ "$REF_NAME" != "master" ]; then echo "::error::Real releases must run from the master branch." exit 1 fi - name: Install dependencies run: npm ci - name: Build run: npm run build - name: Make dist files run: npm run dist - name: Run tests run: npm run test - name: Set version in package*.json run: npm pkg set version=${{ inputs.version }} - name: Publish package to npm run: npm publish --access public --tag ${{ inputs.npmDistTag }} ${{ env.DRY_RUN }} env: DRY_RUN: ${{ inputs.dryRun && '--dry-run' || '' }} - name: Create git tag if: ${{ !inputs.dryRun }} env: VERSION: ${{ inputs.version }} run: | set -euo pipefail git config user.name "github-actions[bot]" git config user.email "41898282+github-actions[bot]@users.noreply.github.com" git tag "$VERSION" git push origin "$VERSION" - name: Create GitHub release if: ${{ !inputs.dryRun }} env: GH_TOKEN: ${{ github.token }} VERSION: ${{ inputs.version }} run: gh release create "$VERSION" --title "$VERSION" --generate-notes
What changed
- Run on Latchkey managed runners with one line (
runs-on), which apply the fixes below automatically and self-heal transient failures. This example useslatchkey-small; pick the runner size that fits the job. - Cache dependency installs on the setup step so they are served from cache.
What Latchkey heals here
This workflow has steps that commonly fail on transient issues (network, registries, flaky browsers). On Latchkey managed runners they are detected, retried, and self-healed instead of failing your build:
- Dependency installs
This workflow runs 1 job per trigger. On Latchkey the same minutes cost up to 58% less than GitHub-hosted, with zero queue time.