From b53c82732a329e5bf829118ff3d5b8ba7b2a870e Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Nov 2026 16:58:14 +0000 Subject: [PATCH 1/2] Stage releases with npm stage publish instead of publishing directly publish.yml now runs `npm stage publish`, so a tagged release lands in the registry uninstallable until a maintainer approves it with 2FA (`npm stage approve ` or on npmjs). The stage ID and the approve/reject commands go to the run's summary. npm 11.15.0 is the first release with `npm stage`. The trusted-publisher link on npmjs grants only `npm stage publish` (and `npm dist-tag`), as npm recommends, which is why the direct `npm publish` of v1.0.0-alpha.1 failed with ENEEDAUTH. The README's Releasing section describes the approval step and the expiring link. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016NSWj8mSZ8LrMj16R2cozL --- .github/workflows/publish.yml | 42 +++++++++++++++++++++++++++++------ README.md | 28 ++++++++++++++++++----- 2 files changed, 57 insertions(+), 13 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index a6ac6d4..05bfb0b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -7,11 +7,19 @@ name: Publish # (alpha, beta, rc), so `npm install @versionone/agility-sdk` never picks it # up; only a plain version moves `latest`. # +# Staged publishing: the workflow runs `npm stage publish`, which places the +# version in the registry where nobody can install it yet. A maintainer then +# approves it with 2FA, and only then does it go out: +# npm stage approve (or Approve on the package's npmjs page) +# The stage ID is printed in the run's summary. `npm stage reject ` +# discards it instead. A trusted-publisher token can stage but never approve, +# so CI alone can never put a version on npm. +# # Authentication is npm trusted publishing (OIDC): on the package's npmjs # settings, link organization versionone, repository VersionOne.SDK.JavaScript, -# workflow publish.yml and environment npm; no token is stored anywhere. -# (1.0.0-alpha.0 was published by hand, as a package must exist before it -# can be linked.) An NPM_TOKEN secret, if set, is used as a fallback. +# workflow publish.yml and environment npm, and allow `npm stage publish` +# (npm recommends leaving plain `npm publish` disallowed). No token is stored. +# An NPM_TOKEN secret, if set, is used as a fallback; it, too, can only stage. on: push: @@ -38,8 +46,8 @@ jobs: with: node-version: 24 registry-url: https://registry.npmjs.org - # Trusted publishing needs npm 11.5.1 or later. - - run: npm install -g npm@^11.5.1 + # `npm stage` needs npm 11.15.0 or later. + - run: npm install -g npm@^11.15.0 - run: npm ci - name: The tag names the version in package.json run: | @@ -58,7 +66,27 @@ jobs: esac if [ -z "${tag}" ]; then tag=next; fi echo "DIST_TAG=${tag}" >> "$GITHUB_ENV" - echo "Publishing ${VERSION} with dist-tag ${tag}" - - run: npm publish --provenance --access public --tag "${DIST_TAG}" + echo "Staging ${VERSION} with dist-tag ${tag}" + - name: Stage the release + run: | + npm stage publish --provenance --access public --tag "${DIST_TAG}" | tee stage.out + stage_id="$(sed -n 's/.*(staged with id \([^)]*\)).*/\1/p' stage.out | tail -n 1)" + { + echo "## Staged @versionone/agility-sdk@${VERSION} (dist-tag ${DIST_TAG})" + echo + echo "Nothing is published until a maintainer approves it with 2FA:" + echo + echo '```sh' + if [ -n "${stage_id}" ]; then + echo "npm stage view ${stage_id} # check what was staged" + echo "npm stage approve ${stage_id} # publish it" + echo "npm stage reject ${stage_id} # discard it" + else + echo "npm stage list @versionone/agility-sdk # find the stage ID" + fi + echo '```' + echo + echo "Or approve it on https://www.npmjs.com/package/@versionone/agility-sdk" + } >> "$GITHUB_STEP_SUMMARY" env: NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/README.md b/README.md index 866a99e..1ce5f17 100644 --- a/README.md +++ b/README.md @@ -75,13 +75,29 @@ npm version 1.0.0-alpha.1 --no-git-tag-version # bump, commit, merge git tag v1.0.0-alpha.1 && git push origin v1.0.0-alpha.1 ``` +The workflow does not publish directly: it runs `npm stage publish`, which +puts the version in the registry where nobody can install it yet, and prints +its stage ID in the run's summary. A maintainer then approves it with 2FA, +which is what publishes it: + +```bash +npm stage view # what was staged +npm stage approve # publish it (asks for 2FA) +npm stage reject # discard it +``` + +or approves it on the package's page on npmjs. `npm stage` needs npm +11.15.0 or later. + Authentication is npm trusted publishing: on npmjs, the package's settings -link this repository, `publish.yml` and the GitHub environment `npm`, and no -token is stored. The `npm` environment (repository settings) requires an -approval, so a pushed tag waits for a maintainer before it publishes. A package -must exist before it can be linked, so the very first version is published -once by hand by an `@versionone` org member (`npm publish --access public ---tag alpha` from a clean checkout of the tagged commit), then linked. +link this repository, `publish.yml` and the GitHub environment `npm`, with +the `npm stage publish` permission (leave plain `npm publish` off, as npm +recommends), and no token is stored. A trusted-publisher link expires; an +expired one must be deleted and created again, and until then the workflow +fails with `ENEEDAUTH`. The `npm` environment (repository settings) requires +an approval, so a pushed tag waits for a maintainer before it stages. A +package must exist before it can be linked, so the very first version was +published once by hand by an `@versionone` org member. ## Before PR From 88a377d809785f253e0ca7db15939d22137944e0 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 11 Nov 2026 16:59:06 +0000 Subject: [PATCH 2/2] Bump version to 1.0.0-alpha.2 1.0.0-alpha.1 was tagged but never published: its workflow ran a plain npm publish, which the trusted-publisher link does not allow. The next release goes out through npm stage publish as 1.0.0-alpha.2. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_016NSWj8mSZ8LrMj16R2cozL --- README.md | 4 ++-- package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 1ce5f17..608096c 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,8 @@ prerelease (`1.0.0-alpha.0`, `-beta.N`, `-rc.N`) goes out on its own dist-tag (`alpha`, `beta`, `rc`), so `latest` is never a prerelease. ```bash -npm version 1.0.0-alpha.1 --no-git-tag-version # bump, commit, merge -git tag v1.0.0-alpha.1 && git push origin v1.0.0-alpha.1 +npm version 1.0.0-alpha.2 --no-git-tag-version # bump, commit, merge +git tag v1.0.0-alpha.2 && git push origin v1.0.0-alpha.2 ``` The workflow does not publish directly: it runs `npm stage publish`, which diff --git a/package-lock.json b/package-lock.json index 7bdc70d..a532fae 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@versionone/agility-sdk", - "version": "1.0.0-alpha.1", + "version": "1.0.0-alpha.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@versionone/agility-sdk", - "version": "1.0.0-alpha.1", + "version": "1.0.0-alpha.2", "license": "MIT", "devDependencies": { "@types/node": "^22.13.10", diff --git a/package.json b/package.json index 660b9c7..125796e 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@versionone/agility-sdk", - "version": "1.0.0-alpha.1", + "version": "1.0.0-alpha.2", "description": "TypeScript SDK for the Digital.ai Agility (VersionOne) API: queries, batches, schema-translated rows, guarded writes, attachments", "keywords": [ "agility",