From 52f35ea270086498c9366c6eda571533699efa8c Mon Sep 17 00:00:00 2001 From: Hayden Bruin Date: Tue, 25 Aug 2026 12:56:13 +1000 Subject: [PATCH] ci: mirror every release to GitHub Packages as well as npm MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both registries now get every version. npm is canonical — it is what the consumer repos install from and the only one that needs no credential — and GitHub Packages receives a mirror of the same version immediately after. The mirror is a plain `npm publish --registry=https://npm.pkg.github.com` after semantic-release, so package.json stays pinned to npmjs and the second target is a command-line override. It authenticates with GITHUB_TOKEN, which GitHub Packages accepts for a same-repo publish, so no additional secret exists. It self-skips when nothing was released. semantic-release's npm plugin writes the real version into package.json in the workspace during prepare, so a version other than the 0.0.0-development placeholder is the signal that a release happened. A docs-only push leaves the placeholder and the step no-ops rather than failing. Worth being clear about what this does not buy: GitHub Packages still cannot be installed from without an access token, because that is a GitHub restriction on reads rather than on publishing. The mirror is for the org-internal package listing and for anything still pointed there — it is not a way to consume the package without credentials. If the two registries ever disagree, npm is right. GitHub Packages currently holds 0.2.0, 0.2.1 and 0.3.0 from before the move to npm, and has received nothing since; from 0.4.0 onward the two stay in step. --- .github/workflows/release.yml | 42 +++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 2a54405..9972ecd 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -16,10 +16,12 @@ name: release # possible, and @semantic-release/npm@13 detects the OIDC context and skips its # token check. # -# This replaced GitHub Packages, where a classic PAT was required to install — -# even for public packages, per GitHub's own docs — meaning every developer and -# every CI job needed one, forever, and re-made it on expiry. On npmjs a -# consumer needs nothing at all. +# Every release goes to BOTH registries: npmjs first (canonical), then mirrored +# to GitHub Packages. npm is what the consumer repos install from, because +# GitHub Packages requires an access token to install even for public packages — +# per GitHub's own docs — which would put a PAT back in every developer's +# ~/.npmrc and every CI job. The mirror exists for the org-internal listing, not +# for consumption. # # npm provenance is generated automatically under trusted publishing, so the # published package carries a verifiable link back to this repo and this @@ -54,6 +56,7 @@ concurrency: permissions: contents: write # create tags and releases id-token: write # mint the OIDC token for npm trusted publishing + packages: write # mirror the release to GitHub Packages issues: write # semantic-release/github verifies these even with pull-requests: write # comments switched off @@ -133,3 +136,34 @@ jobs: # secret. Under trusted publishing this whole block becomes # unnecessary and can go. NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + + # npm is the CANONICAL registry — it is what both consumer repos install + # from, and the only one that needs no credential. This mirrors the same + # version to GitHub Packages for the org-internal package listing and for + # anything still pointed there. If the two ever disagree, npm is right. + # + # Runs only when semantic-release actually released: its npm plugin writes + # the real version into package.json in the workspace during prepare, so a + # version other than the placeholder is the signal. A push with nothing + # releasable leaves the placeholder and this no-ops. + # + # --registry overrides publishConfig.registry on the command line, which + # is why package.json stays pinned to npmjs. GITHUB_TOKEN suffices — + # GitHub Packages accepts it for a same-repo publish, so there is no extra + # secret. Note this does NOT make GitHub Packages installable without a + # token: that is a GitHub restriction on reads, not on publishing. + - name: Mirror to GitHub Packages + run: | + set -euo pipefail + version=$(node -p "require('./package.json').version") + if [ "$version" = "0.0.0-development" ]; then + echo "no release this run — nothing to mirror" + exit 0 + fi + echo "mirroring @engineio/ui@$version to GitHub Packages" + npm publish \ + --registry=https://npm.pkg.github.com \ + --ignore-scripts \ + --tag latest + env: + NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}