name: Release Artifacts on: workflow_dispatch: inputs: ref: description: Git ref to package. Defaults to the workflow branch. required: false type: string push: tags: - 'v*' jobs: package: name: Package (${{ matrix.os }}) runs-on: ${{ matrix.os }} permissions: contents: write # create/upload the GitHub Release on a tag push strategy: fail-fast: false matrix: # `zero-release package` builds + smoke-tests natively (host==target), so each # target platform needs its own native runner. These five cover the platforms # install.sh / install.ps1 / the npm wrapper resolve. os: - ubuntu-latest # linux-x64 - ubuntu-24.04-arm # linux-arm64 - macos-latest # macos-arm64 (Apple Silicon) - macos-15-intel # macos-x64 (Intel) - windows-latest # windows-x64 steps: - name: Checkout if: ${{ github.event_name != 'workflow_dispatch' || inputs.ref == '' }} uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Checkout requested ref if: ${{ github.event_name == 'workflow_dispatch' && inputs.ref != '' }} uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 with: ref: ${{ inputs.ref }} - name: Setup Go uses: actions/setup-go@40f1582b2485089dde7abd97c1529aa768e1baff with: go-version-file: go.mod cache: true - name: Test run: go test ./... # zero-release names assets zero-v-… while the release # 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: ${{ startsWith(github.ref, 'refs/tags/v') }} shell: bash run: | pkg_version="$(node -p "require('./package.json').version")" if [ "v$pkg_version" != "$GITHUB_REF_NAME" ]; then echo "::error::tag $GITHUB_REF_NAME does not match package.json version v$pkg_version — bump package.json before tagging so the release assets and the npm package agree." >&2 exit 1 fi echo "tag $GITHUB_REF_NAME matches package.json version v$pkg_version." - name: Package release artifact run: go run ./cmd/zero-release package - name: Verify release checksums run: go run ./cmd/zero-release verify - name: Upload package uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: zero-${{ runner.os }}-${{ runner.arch }} path: dist/release/* if-no-files-found: error # 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: ${{ startsWith(github.ref, 'refs/tags/v') }} shell: bash env: GH_TOKEN: ${{ github.token }} run: | 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. # # 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 if: ${{ startsWith(github.ref, 'refs/tags/v') }} runs-on: ubuntu-latest # Manual approval gate: this environment carries a required-reviewer rule # (Settings -> Environments -> npm-publish), so the job pauses until a # maintainer approves it in the Actions UI before anything reaches npm. environment: name: npm-publish url: https://www.npmjs.com/package/@gitlawb/zero permissions: contents: read 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: 24 # 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 # 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