From 6325c71a693df46fe1af63473dfa374c0eb53541 Mon Sep 17 00:00:00 2001 From: Nate Otto Date: Tue, 20 Oct 2026 18:08:44 -0500 Subject: [PATCH] ci(publish): publish prereleases under the beta tag, and check before publishing npm 11 refuses a bare `npm publish` of a prerelease version. A release marked as a pre-release now publishes with `--tag beta`; any other release publishes with `--tag latest`. Before publishing, the workflow stops if the release tag (without its leading `v`) differs from the package.json version, or if the pre-release setting disagrees with whether the version has a prerelease suffix. It then runs lint, the build, and the Node and browser tests, as CI does. npm is pinned to 11.21.1 instead of the latest. RELEASING.md documents the npm trusted-publisher setup and the release form, and the README links to it. Closes #55 Co-Authored-By: Claude Opus 5.5 --- .github/workflows/publish.yml | 39 +++++++++++++++++++++--- README.md | 2 ++ RELEASING.md | 57 +++++++++++++++++++++++++++++++++++ 3 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 RELEASING.md diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 086e210..7132bde 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -9,10 +9,29 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - id-token: write # required for npm provenance + id-token: write # required for npm trusted publishing and provenance steps: - uses: actions/checkout@v6 + - name: Check release against package version + env: + TAG: ${{ github.event.release.tag_name }} + PRERELEASE: ${{ github.event.release.prerelease }} + run: | + VERSION=$(node -p "require('./package.json').version") + if [ "${TAG#v}" != "$VERSION" ]; then + echo "::error::Release tag $TAG does not match package.json version $VERSION" + exit 1 + fi + case "$VERSION" in + *-*) IS_PRE=true ;; + *) IS_PRE=false ;; + esac + if [ "$PRERELEASE" != "$IS_PRE" ]; then + echo "::error::Version $VERSION needs the release's pre-release setting to be $IS_PRE" + exit 1 + fi + - uses: pnpm/action-setup@v6 - uses: actions/setup-node@v6 @@ -20,14 +39,26 @@ jobs: node-version: '24' registry-url: 'https://registry.npmjs.org' - - name: Update npm for trusted publishing - run: npm install -g npm@latest # trusted publishing needs npm >= 11.5.1 + - name: Install pinned npm + run: npm install -g npm@11.21.1 # trusted publishing needs npm >= 11.5.1 - name: Install dependencies run: pnpm install --frozen-lockfile + - name: Lint + run: pnpm run lint + - name: Build run: pnpm run build + - name: Test (Node) + run: pnpm run test:node + + - name: Install Playwright browsers + run: pnpm exec playwright install --with-deps chromium + + - name: Test (Browser) + run: pnpm run test:browser + - name: Publish to npm - run: npm publish + run: npm publish --tag ${{ github.event.release.prerelease && 'beta' || 'latest' }} diff --git a/README.md b/README.md index c525858..1de477b 100644 --- a/README.md +++ b/README.md @@ -1302,6 +1302,8 @@ pnpm test # lint + node tests (vitest) pnpm run test:browser # playwright (run `pnpm exec playwright install chromium` first) ``` +To publish a release, see [RELEASING.md](RELEASING.md). + ## Contribute PRs accepted. diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..1d7e631 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,57 @@ +# Releasing + +Publishing to npm is done by the +[Publish workflow](.github/workflows/publish.yml), which runs when a GitHub +release is published. + +## One-time setup + +The workflow uses npm +[trusted publishing](https://docs.npmjs.com/trusted-publishers), so no npm token +or GitHub secret is needed. An npm admin of `@digitalcredentials/verifier-core` +registers the workflow once, on npmjs.com: package **Settings** → **Trusted +Publisher** → **GitHub Actions**: + +- Organization: `digitalcredentials` +- Repository: `verifier-core` +- Workflow filename: `publish.yml` +- Environment: leave blank +- Allowed actions: tick **Allow npm publish**; leave **Allow npm dist-tag** + unticked + +Renaming `publish.yml` breaks this registration. + +## Cut a release + +The npm version comes from `package.json`, not from the tag. First set `version` +in `package.json` in a pull request, and merge it to `main`. + +Then, on GitHub, go to **Releases** → **Draft a new release**: + +| Field | Prerelease | Release | +| ------------------------- | ----------------------------------- | ---------------------------- | +| `package.json` version | `2.0.0-beta.1` | `2.0.0` | +| Tag | `v2.0.0-beta.1` (create on publish) | `v2.0.0` (create on publish) | +| Target | `main` | `main` | +| Title | `v2.0.0-beta.1` | `v2.0.0` | +| Set as a pre-release | ticked | unticked | +| Set as the latest release | unticked | ticked | +| npm dist-tag | `beta` | `latest` | + +Click **Publish release**. Saving a draft does not publish. + +A prerelease installs with `npm install @digitalcredentials/verifier-core@beta`; +a release is what a plain `npm install` gets. + +## When the workflow fails + +The workflow stops before publishing if: + +- the tag, without its leading `v`, differs from the `package.json` version; +- a version with a prerelease suffix (`-beta.1`) is not marked as a pre-release, + or a version without one is; +- lint, the build, or the Node or browser tests fail. + +To retry, fix the problem on `main`, delete the GitHub release and its tag, and +publish the release again. npm never accepts the same version twice, so if a +version did reach npm, bump the version instead.