From 4f84e1e001f30f2041efe782177a051475d09932 Mon Sep 17 00:00:00 2001 From: Kevin Codex Date: Thu, 2 Jul 2026 12:14:18 +0800 Subject: [PATCH 1/4] feat: publish zero to npm via release-please Adds release-please to automate versioning and releases from conventional commits on main: - release-please-config.json / .release-please-manifest.json: node release type (package.json is the version source of truth), plain v-prefixed tags, bootstrap-sha at current HEAD, and release-as 0.1.0 to pin the first release (remove after v0.1.0 ships). - .github/workflows/release-please.yml: maintains the release PR; on merge it tags + creates the GitHub Release, then dispatches release-artifacts.yml at the tag (GITHUB_TOKEN-created tags cannot fire on-push-tags workflows). - .github/workflows/release-artifacts.yml: tag-gated steps now match the ref instead of the push event so the dispatched-at-tag run publishes assets, and a new publish-npm job publishes @gitlawb/zero after verifying every postinstall asset URL is publicly downloadable (also blocks publishing while the repo is private). --- .github/workflows/release-artifacts.yml | 69 ++++++++++++++++++++++--- .github/workflows/release-please.yml | 34 ++++++++++++ .release-please-manifest.json | 1 + release-please-config.json | 12 +++++ 4 files changed, 109 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/release-please.yml create mode 100644 .release-please-manifest.json create mode 100644 release-please-config.json diff --git a/.github/workflows/release-artifacts.yml b/.github/workflows/release-artifacts.yml index ed21e512..f1e1fd96 100644 --- a/.github/workflows/release-artifacts.yml +++ b/.github/workflows/release-artifacts.yml @@ -54,8 +54,10 @@ jobs: # is published under the pushed git tag. If they disagree, install.sh/install.ps1 # and the npm postinstall all build a download URL that 404s. Fail fast so a # forgotten version bump can never publish mis-versioned, un-installable assets. + # Matches on the ref (not event_name) because release-please dispatches this + # workflow at the tag ref — its GITHUB_TOKEN-created tag cannot fire `push`. - name: Check tag matches package.json version - if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }} + if: ${{ startsWith(github.ref, 'refs/tags/v') }} shell: bash run: | pkg_version="$(node -p "require('./package.json').version")" @@ -78,13 +80,15 @@ jobs: path: dist/release/* if-no-files-found: error - # On a `v*` tag push, publish the packaged archive + .sha256 as GitHub Release - # assets so the documented `scripts/install.sh` / `install.ps1` path (which reads - # /releases/latest) works. Uses the preinstalled `gh` (no new action dependency); - # each matrix OS creates the release if absent (best-effort) then uploads its own - # assets with --clobber. NOTE: maintainers must push the tag — this never tags. + # On a `v*` tag ref (pushed manually, or dispatched at the tag by the + # release-please workflow), publish the packaged archive + .sha256 as GitHub + # Release assets so the documented `scripts/install.sh` / `install.ps1` path + # (which reads /releases/latest) works. Uses the preinstalled `gh` (no new + # action dependency); each matrix OS creates the release if absent + # (best-effort — release-please normally creates it first) then uploads its + # own assets with --clobber. Tags come from merging the release-please PR. - name: Publish to GitHub Release - if: ${{ github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') }} + if: ${{ startsWith(github.ref, 'refs/tags/v') }} shell: bash env: GH_TOKEN: ${{ github.token }} @@ -92,3 +96,54 @@ jobs: tag="${GITHUB_REF_NAME}" gh release create "$tag" --repo "$GITHUB_REPOSITORY" --title "$tag" --generate-notes 2>/dev/null || true gh release upload "$tag" dist/release/* --repo "$GITHUB_REPOSITORY" --clobber + + # Publishes the npm wrapper (@gitlawb/zero) once every platform's release + # assets are live — the package's postinstall downloads its binary from the + # GitHub Release, so publishing before the assets exist would ship a package + # that fails to install. + publish-npm: + name: Publish npm package + needs: package + if: ${{ startsWith(github.ref, 'refs/tags/v') }} + runs-on: ubuntu-latest + permissions: + contents: read + id-token: write # npm --provenance attestation + steps: + - name: Checkout + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Setup Node + uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 + with: + node-version: 22 + registry-url: https://registry.npmjs.org + + # Probe the exact URLs postinstall will build (internal/release/release.go + # asset-name scheme) as an anonymous client. This also blocks publishing + # while the repo is still private — private release assets 404 without + # auth, exactly as they would for an installing user. + - name: Verify release assets are downloadable + shell: bash + run: | + version="$(node -p "require('./package.json').version")" + base="https://github.com/${GITHUB_REPOSITORY}/releases/download/v${version}" + for asset in \ + "zero-v${version}-linux-x64.tar.gz" \ + "zero-v${version}-linux-arm64.tar.gz" \ + "zero-v${version}-macos-x64.tar.gz" \ + "zero-v${version}-macos-arm64.tar.gz" \ + "zero-v${version}-windows-x64.zip"; do + for url in "${base}/${asset}" "${base}/${asset}.sha256"; do + if ! curl -fsSLI -o /dev/null "$url"; then + echo "::error::${url} is not publicly downloadable — npm postinstall would fail. Is the repo public and are all assets uploaded?" >&2 + exit 1 + fi + done + done + echo "all release assets for v${version} are publicly downloadable." + + - name: Publish to npm + run: npm publish --access public --provenance + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml new file mode 100644 index 00000000..0e5640b0 --- /dev/null +++ b/.github/workflows/release-please.yml @@ -0,0 +1,34 @@ +name: Release Please + +on: + push: + branches: + - main + +permissions: + contents: write # push the release-PR branch, create the tag + GitHub Release + pull-requests: write # open/update the release PR + actions: write # dispatch release-artifacts.yml once a release is cut + +jobs: + release-please: + name: Release PR / tag + runs-on: ubuntu-latest + steps: + - name: Run release-please + id: release + uses: googleapis/release-please-action@a02a34c4d625f9be7cb89156071d8567266a2445 # v4.2.0 + + # Tags created with the default GITHUB_TOKEN never fire `on: push: tags` + # workflows (GitHub suppresses workflows triggered by workflow-created + # events), so release-artifacts.yml would not run for a release-please + # tag. workflow_dispatch invoked through the API is exempt from that + # suppression — dispatch the artifacts build at the new tag explicitly. + - name: Build and publish release artifacts + if: ${{ steps.release.outputs.release_created }} + env: + GH_TOKEN: ${{ github.token }} + run: | + gh workflow run release-artifacts.yml \ + --repo "$GITHUB_REPOSITORY" \ + --ref "${{ steps.release.outputs.tag_name }}" diff --git a/.release-please-manifest.json b/.release-please-manifest.json new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/.release-please-manifest.json @@ -0,0 +1 @@ +{} diff --git a/release-please-config.json b/release-please-config.json new file mode 100644 index 00000000..914fa934 --- /dev/null +++ b/release-please-config.json @@ -0,0 +1,12 @@ +{ + "$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json", + "release-type": "node", + "include-component-in-tag": false, + "bootstrap-sha": "02f0c0929bec65f5fc41bdcab3eb518d2b0b329a", + "packages": { + ".": { + "package-name": "@gitlawb/zero", + "release-as": "0.1.0" + } + } +} From 74367697f1ab76627cc8f5de099dcc937cd8ca0e Mon Sep 17 00:00:00 2001 From: Kevin Codex Date: Thu, 2 Jul 2026 14:42:04 +0800 Subject: [PATCH 2/4] Update .github/workflows/release-please.yml Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .github/workflows/release-please.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index 0e5640b0..f9ada4fb 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -8,6 +8,7 @@ on: permissions: contents: write # push the release-PR branch, create the tag + GitHub Release pull-requests: write # open/update the release PR + issues: write # create/apply release-tracking labels on the release PR actions: write # dispatch release-artifacts.yml once a release is cut jobs: From 0d0e5e9147c51357c5310877eb154b324e9f4ecd Mon Sep 17 00:00:00 2001 From: Kevin Codex Date: Thu, 2 Jul 2026 14:46:00 +0800 Subject: [PATCH 3/4] feat: publish to npm via OIDC trusted publishing instead of NPM_TOKEN npm's trusted publishing exchanges the workflow's OIDC token for publish auth, so no long-lived NPM_TOKEN secret exists to leak, and provenance is generated automatically. Drops registry-url from setup-node (its .npmrc line hard-fails on the unset NODE_AUTH_TOKEN) and updates npm first (OIDC needs npm >= 11.5.1). One-time caveat: npm only lets you configure a trusted publisher on an existing package, so the first @gitlawb/zero publish is a manual bootstrap. --- .github/workflows/release-artifacts.yml | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/.github/workflows/release-artifacts.yml b/.github/workflows/release-artifacts.yml index f1e1fd96..afdf6141 100644 --- a/.github/workflows/release-artifacts.yml +++ b/.github/workflows/release-artifacts.yml @@ -101,6 +101,12 @@ jobs: # assets are live — the package's postinstall downloads its binary from the # GitHub Release, so publishing before the assets exist would ship a package # that fails to install. + # + # Auth is npm OIDC trusted publishing: no NPM_TOKEN secret. The package's + # npmjs.com settings must list this repo + workflow file as a trusted + # publisher, and provenance is then generated automatically. Note npm only + # allows configuring a trusted publisher on an EXISTING package, so the very + # first @gitlawb/zero publish is a one-time manual `npm publish` bootstrap. publish-npm: name: Publish npm package needs: package @@ -108,16 +114,22 @@ jobs: runs-on: ubuntu-latest permissions: contents: read - id-token: write # npm --provenance attestation + id-token: write # OIDC token exchange for npm trusted publishing + provenance steps: - name: Checkout uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + # No registry-url here on purpose: setup-node's registry-url writes an + # .npmrc line referencing $NODE_AUTH_TOKEN, and npm hard-fails on the + # unset env var. Trusted publishing needs no token at all. - name: Setup Node uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020 # v4 with: - node-version: 22 - registry-url: https://registry.npmjs.org + node-version: 24 + + # OIDC trusted publishing needs npm >= 11.5.1; runner images can lag. + - name: Update npm for trusted publishing + run: npm install -g npm@latest && npm --version # Probe the exact URLs postinstall will build (internal/release/release.go # asset-name scheme) as an anonymous client. This also blocks publishing @@ -144,6 +156,4 @@ jobs: echo "all release assets for v${version} are publicly downloadable." - name: Publish to npm - run: npm publish --access public --provenance - env: - NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: npm publish --access public From 94d8148a060a8e8e3e7025ab8845043eca311e83 Mon Sep 17 00:00:00 2001 From: Kevin Codex Date: Thu, 2 Jul 2026 14:55:59 +0800 Subject: [PATCH 4/4] fix: drop npm@latest from the OIDC publish path Rely on the npm bundled with the pinned Node 24 toolchain (>= 11.5.1, the trusted-publishing minimum) instead of pulling an unpinned npm@latest from the registry inside the publish job, and fail fast with a clear error if a future toolchain change regresses it. --- .github/workflows/release-artifacts.yml | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-artifacts.yml b/.github/workflows/release-artifacts.yml index afdf6141..682f743d 100644 --- a/.github/workflows/release-artifacts.yml +++ b/.github/workflows/release-artifacts.yml @@ -127,9 +127,28 @@ jobs: with: node-version: 24 - # OIDC trusted publishing needs npm >= 11.5.1; runner images can lag. - - name: Update npm for trusted publishing - run: npm install -g npm@latest && npm --version + # OIDC trusted publishing needs npm >= 11.5.1. Use the npm bundled with + # the pinned Node toolchain (Node 24 ships >= 11.5.1) rather than pulling + # npm@latest from the registry inside the publish path — an unpinned + # install there would be avoidable supply-chain exposure. Fail fast if a + # future runner/toolchain change ever regresses the bundled npm. + - name: Check npm supports trusted publishing + run: | + npm --version | node -e ' + const version = require("fs").readFileSync(0, "utf8").trim(); + const [major, minor, patch] = version.split(".").map(Number); + const ok = + major > 11 || + (major === 11 && (minor > 5 || (minor === 5 && patch >= 1))); + if (!ok) { + console.error( + `::error::bundled npm ${version} is too old for OIDC trusted ` + + `publishing (needs >= 11.5.1) — bump node-version in this job.`, + ); + process.exit(1); + } + console.log(`npm ${version} supports trusted publishing.`); + ' # Probe the exact URLs postinstall will build (internal/release/release.go # asset-name scheme) as an anonymous client. This also blocks publishing