mirror of
https://github.com/Twigpine/zero.git
synced 2026-11-09 18:08:31 -10:00
Motivation: release-artifacts.yml's three actions/checkout steps (ordinary checkout, workflow-dispatch requested-ref checkout, and the npm-publish job checkout) and pr-auto-review.yml's single checkout step all leave persist-credentials at its default, so the workflow token stays in the job's local git config for every later step, including build/test/package steps that never need it. ci.yml and zero-action-smoke.yml already disable this. Neither modified file runs git push/commit/tag: the GitHub Release publish step supplies its own explicit GH_TOKEN, and npm publishing uses OIDC trusted publishing, so removing the persisted credential changes no behavior. Approach: Add `persist-credentials: false` to each of the four checkout steps, matching the exact convention already used in ci.yml/zero-action-smoke.yml. Validation: - go build ./... and go vet ./... pass. - gofmt -l . reports nothing. - go test ./internal/installtest/... passes. - actionlint on both changed files reports only two pre-existing shellcheck warnings in release-artifacts.yml (SC2016, SC2034) that are unrelated to this diff and were confirmed present before the change as well (verified by stashing and re-running actionlint against the original files). - A live GitHub Actions run of the release/publish workflows was not reproducible locally; this change only disables a default the workflows never rely on, so no functional change is expected. Report: https://github.com/Gitlawb/zero/issues/1029 Assisted-by: claude-sonnet-5 (via Claude Code) Signed-off-by: Pujitha Paladugu <[email protected]> Co-authored-by: Pujitha Paladugu <[email protected]>
248 lines
11 KiB
YAML
248 lines
11 KiB
YAML
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
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Checkout requested ref
|
|
if: ${{ github.event_name == 'workflow_dispatch' && inputs.ref != '' }}
|
|
uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4
|
|
with:
|
|
ref: ${{ inputs.ref }}
|
|
persist-credentials: false
|
|
|
|
- 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<package.json version>-… 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 @gitlawb/zero to npm: first the five platform payloads (versions
|
|
# of the SAME package at suffixed versions like X.Y.Z-linux-x64, carrying the
|
|
# native binary + vendored helpers), then the wrapper at X.Y.Z whose
|
|
# optionalDependencies alias those platform versions. Platform versions MUST
|
|
# publish first — the wrapper pins them exactly, so the reverse order would
|
|
# open a window where installs resolve aliases that 404. See
|
|
# docs/NPM_PACKAGING.md for the full contract.
|
|
#
|
|
# 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.
|
|
# Because the platform payloads are versions of the same package, they reuse
|
|
# that single trusted-publisher configuration.
|
|
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
|
|
with:
|
|
persist-credentials: false
|
|
|
|
# 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 the wrapper's first-run downloader builds
|
|
# (internal/release/release.go asset-name scheme) as an anonymous client
|
|
# — install.sh/install.ps1 and `zero update` read them too. 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 — the wrapper's fallback downloader and install.sh 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: Download release artifacts
|
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
|
|
with:
|
|
pattern: zero-*
|
|
path: dist/artifacts
|
|
merge-multiple: true
|
|
|
|
- name: Assemble npm packages
|
|
run: node scripts/npm/build-platform-packages.mjs --artifacts-dir dist/artifacts --out-dir dist/npm
|
|
|
|
# Platform versions are semver PRERELEASES of the wrapper version
|
|
# (X.Y.Z-linux-x64), so they must never publish under the default
|
|
# `latest` dist-tag — that would make `npm install @gitlawb/zero`
|
|
# deliver a platform payload as the CLI. Publish them under a dedicated
|
|
# tag and assert `latest` survived before the wrapper goes out.
|
|
- name: Publish platform packages to npm
|
|
shell: bash
|
|
run: |
|
|
version="$(node -p "require('./package.json').version")"
|
|
for dir in dist/npm/platforms/*/; do
|
|
echo "publishing ${dir}"
|
|
(cd "$dir" && npm publish --access public --tag platform)
|
|
done
|
|
# Tolerate a failed lookup (registry lag, or no latest tag yet on a
|
|
# bootstrap publish): under set -e a bare failure here would abort
|
|
# AFTER the platform payloads went out, leaving no wrapper and a
|
|
# re-run that fails on republishing existing versions. An empty
|
|
# value falls through the guard below.
|
|
latest="$(npm view @gitlawb/zero dist-tags.latest || true)"
|
|
case "$latest" in
|
|
"${version}-"*)
|
|
echo "::error::dist-tag latest points at platform version ${latest} — a platform publish clobbered it. Fix the dist-tag before the wrapper publish." >&2
|
|
exit 1
|
|
;;
|
|
esac
|
|
echo "dist-tag latest is ${latest} (unchanged by platform publishes)."
|
|
|
|
- name: Publish wrapper to npm
|
|
shell: bash
|
|
run: |
|
|
version="$(node -p "require('./package.json').version")"
|
|
(cd dist/npm/wrapper && npm publish --access public)
|
|
# Confirm the wrapper owns `latest` (retry: registry metadata can lag
|
|
# a publish by a few seconds).
|
|
for attempt in 1 2 3 4 5; do
|
|
latest="$(npm view @gitlawb/zero dist-tags.latest || true)"
|
|
if [ "$latest" = "$version" ]; then
|
|
echo "dist-tag latest is ${version}."
|
|
exit 0
|
|
fi
|
|
sleep 5
|
|
done
|
|
echo "::error::dist-tag latest is ${latest}, expected ${version} after the wrapper publish." >&2
|
|
exit 1
|