diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index f9237c4..9d4d25f 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -6,7 +6,9 @@ on: - stable permissions: - contents: read + # `lerna version` pushes the release commit and tags and creates the GitHub release, + # so the token needs write access to contents. id-token is for npm provenance. + contents: write id-token: write jobs: @@ -26,8 +28,11 @@ jobs: run: | git config user.name github-actions git config user.email github-actions@github.com - - name: 🟢 Ensure Latest npm - run: npm install -g npm@latest + # npm 12 requires Node >=22.22.2, so `npm@latest` cannot run on the Node 20 pinned + # above. npm 11 is the newest line that does, and it supports both provenance and + # OIDC trusted publishing. + - name: 🟢 Ensure Supported npm + run: npm install -g npm@11 shell: bash - name: 📦 Install Dependencies run: npm ci --no-package-lock @@ -35,9 +40,14 @@ jobs: - name: 🔄 Bootstrap run: npm run bootstrap -- --ignore-scripts shell: bash - - name: 🚀 Release - run: npm run publish:ci -- --provenance + - name: 🔖 Version + run: npm run version:ci shell: bash env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + # `lerna publish` uploads via libnpmpublish@4, which supports neither provenance nor + # OIDC trusted publishing, so the upload goes through the npm CLI instead. + - name: 🚀 Publish + run: npm run publish:ci + shell: bash diff --git a/package.json b/package.json index 184501b..dec91e3 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "format:check": "prettier \"**/*.ts\" --check", "prepublishOnly": "npm run build", "publish:testing": "lerna publish prerelease --preid=testing --exact --no-git-tag-version --no-push --dist-tag=testing", - "publish:ci": "lerna publish -m 'chore(release): publish [skip ci]' --exact --conventional-commits --yes --no-verify-access", + "version:ci": "lerna version -m 'chore(release): publish [skip ci]' --exact --conventional-commits --yes", + "publish:ci": "./scripts/publish-packages.sh", "publish:ci:testing": "lerna publish prerelease --preid=testing --no-git-tag-version --no-push --dist-tag=testing -m 'chore(release): publish [skip ci]' --exact --conventional-commits --yes", "cz": "git-cz" }, diff --git a/scripts/publish-packages.sh b/scripts/publish-packages.sh new file mode 100755 index 0000000..68a44f6 --- /dev/null +++ b/scripts/publish-packages.sh @@ -0,0 +1,36 @@ +#!/usr/bin/env bash +# +# Publishes each package whose current version is not on the registry yet. +# +# Versioning still runs through `lerna version`, but the upload has to go through the +# npm CLI: lerna 4 publishes with libnpmpublish@4, which supports neither provenance +# nor OIDC trusted publishing, so `lerna publish` cannot satisfy npm's requirements. +# +# Run this after `npm run version:ci`, from the repo root. + +set -euo pipefail + +published=0 + +for manifest in packages/*/package.json; do + pkg_dir=$(dirname "$manifest") + name=$(node -p "require('./$manifest').name") + version=$(node -p "require('./$manifest').version") + + if [ "$(node -p "require('./$manifest').private === true")" = "true" ]; then + echo "skip $name (private)" + continue + fi + + # Lerna versions packages independently, so a release may bump only one of them. + if npm view "$name@$version" version >/dev/null 2>&1; then + echo "skip $name@$version (already on registry)" + continue + fi + + echo "publish $name@$version" + (cd "$pkg_dir" && npm publish --provenance --access public) + published=$((published + 1)) +done + +echo "published $published package(s)"