Skip to content
Merged
Show file tree
Hide file tree
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
61 changes: 57 additions & 4 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
name: Publish to npm

# Two publish paths share this one file because npm trusted publishing (OIDC) allows only one
# Three publish paths share this one file because npm trusted publishing (OIDC) allows only one
# workflow filename per package:
# - publish (manual) : workflow_dispatch → stable release under `latest`, gated on PUBLISH env.
# - dev-publish (auto): merged PR to main → ephemeral <next-patch>-dev-<sha> under `dev`, unattended.
# - publish (manual) : workflow_dispatch → stable release under `latest`, gated on PUBLISH env.
# - manual-dev-publish (manual) : workflow_dispatch + dev_release checkbox → ephemeral
# <next-patch>-dev-<sha> under `dev` from the selected branch, ungated.
# - dev-publish (auto) : merged PR to main → ephemeral <next-patch>-dev-<sha> under `dev`, unattended.

on:
workflow_dispatch:
inputs:
dev_release:
description: 'Publish dev release from the selected branch'
type: boolean
default: false
pull_request:
types: [closed]
branches: [main]
Expand All @@ -20,7 +27,7 @@ on:

jobs:
publish:
if: github.event_name == 'workflow_dispatch'
if: github.event_name == 'workflow_dispatch' && inputs.dev_release != true
runs-on: ubuntu-latest
environment: PUBLISH
permissions:
Expand Down Expand Up @@ -90,6 +97,52 @@ jobs:
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

# Same ephemeral-version recipe as dev-publish, but fired manually from the selected branch
# and intentionally ungated (no `environment:`) — for cutting a dev build of in-progress work.
manual-dev-publish:
if: github.event_name == 'workflow_dispatch' && inputs.dev_release == true
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write

steps:
- name: Check out repository
# Checks out the branch chosen in the workflow_dispatch ref picker.
uses: actions/checkout@v6

- name: Set up pnpm
uses: pnpm/action-setup@v6
with:
version: 11

- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: '24'
registry-url: 'https://registry.npmjs.org'
cache: 'pnpm'

- name: Compute ephemeral dev version
run: |
base="$(node -p "require('./package.json').version")"
# Next patch: split on '.', bump the last field. No semver dependency needed.
next="$(node -p "const [a,b,c]=require('./package.json').version.split('.'); [a,b,Number(c)+1].join('.')")"
sha="$(git rev-parse --short HEAD)"
devver="${next}-dev-${sha}"
# Edits the runner's copy only; --no-git-tag-version makes no commit and no tag.
npm version --no-git-tag-version "$devver"
echo "Publishing manual dev release \`$devver\` from \`${{ github.ref_name }}\` (base $base)" >> "$GITHUB_STEP_SUMMARY"

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Build package
run: pnpm build

- name: Publish to npm
run: pnpm publish --no-git-checks --access public --tag dev

dev-publish:
if: github.event_name == 'pull_request' && github.event.pull_request.merged == true
runs-on: ubuntu-latest
Expand Down
22 changes: 21 additions & 1 deletion RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ Publishing is triggered manually via the **Publish to npm** workflow
under `latest`, `X.Y.Z-dev.N` under `dev`. The workflow creates and pushes the matching
`vX.Y.Z` git tag itself — don't tag by hand.

To cut a quick dev build of in-progress branch work, tick the **dev_release** checkbox
when dispatching — see [Ungated dev release](#ungated-dev-release) below.

## Steps

1. **Bump the version** in `package.json` via a PR. Either edit it manually, or run an
Expand All @@ -24,12 +27,29 @@ under `latest`, `X.Y.Z-dev.N` under `dev`. The workflow creates and pushes the m
```sh
npm version prerelease --preid=dev --no-git-tag-version
```

2. **Merge the PR** into `main`.
3. **Run the workflow** — *Publish to npm* in the Actions tab, or
3. **Run the workflow** — _Publish to npm_ in the Actions tab, or
`gh workflow run publish.yml`. It pauses on the `PUBLISH` environment for approval,
then verifies the tag doesn't exist, builds, publishes, pushes the tag, and (for
formal releases only) generates release notes.

## Ungated dev release

To publish a `dev`-tagged build straight from a feature branch without the `PUBLISH`
approval gate:

1. Actions → **Publish to npm** → **Run workflow**.
2. In the branch dropdown, pick the branch to publish from.
3. Tick **dev_release**, then run.

This runs the `manual-dev-publish` job: it computes an ephemeral
`<next-patch>-dev-<short-sha>` version from the branch's `package.json` (no
`package.json` edit or version bump needed), builds, and publishes under the `dev`
tag. It does **not** pause on the `PUBLISH` environment, push a git tag, or create
release notes. With the box unchecked, dispatch behaves exactly as the formal release
above.

## Reviewer's job

The `PUBLISH` environment gates the run for approval. Before approving, confirm:
Expand Down