From f56d2811cb9452ec15c191b829335161e0e31d51 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 16:10:42 +0000 Subject: [PATCH 1/2] =?UTF-8?q?=F0=9F=93=A6=20docs:=20cereale=20is=20on=20?= =?UTF-8?q?npm,=20and=20release.yml=20moves=20to=20trusted=20publishing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 0.4.0 published, so the three places that said it had not stopped being true: the README install section, the landing page's install panel, and the "what cereale is not" item. All three now say `npm install cereale`, and the last becomes a limitation that is actually still true — it is 0.x, where a minor bump is allowed to break you. An npm version badge joins the row, tinted the same brand brown as the rest. It was held back deliberately while the package did not exist, because a badge that renders "not found" is worse than no badge. page.js loses the install-shell substitution: it rewrote the tarball filename in a code block that no longer exists. --- release.yml: trusted publishing --- npm exchanges the workflow's short-lived GitHub identity token for a publish token scoped to this package, so no long-lived npm token has to exist. The header documents exactly what to enter on npmjs.com, including the two fields npm checks against the OIDC claims and refuses on mismatch: the workflow filename must match this file, and Environment must stay blank while the job declares none. The find that matters: Node 22 bundles npm 10.x, which has no OIDC code at all. Verified by unpacking the CLI — lib/utils/oidc.js is absent in 11.4.2 and present in 11.5.0. Since npm's OIDC step is deliberately non-throwing, an old CLI would have skipped trusted publishing in silence and fallen back to token auth while appearing to work. So the workflow raises npm and then asserts the version, rather than assuming it. NPM_TOKEN stays as a fallback for the same non-throwing reason: this can land before the registry side is configured, and nothing breaks. Delete the secret once a real run shows OIDC working. --provenance stays explicit. Under OIDC npm enables it itself for a public repo, but only when the flag is left at its default (config.isDefault check in oidc.js), so passing it just skips that auto-enable and lands in the same place — while remaining the only thing that produces an attestation on the token path. actionlint clean; the version guard tested against 10.9.7, 11.4.2, 11.5.0 and 12.0.2; the page re-rendered with no errors and no "not on npm" text left. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK --- .github/workflows/release.yml | 49 +++++++++++++++++++++++++++++++---- README.md | 16 ++++-------- docs/index.html | 25 +++++++----------- docs/page.js | 6 ----- 4 files changed, 59 insertions(+), 37 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d40b306..cf80fe4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -4,10 +4,28 @@ name: Publish to npm # not a decision to make it public and immutable, and npm's 72-hour unpublish window makes the # second one hard to take back. Run this workflow from the Actions tab when you mean it. # -# Before the first real run: -# 1. Create an npm automation token and add it as the NPM_TOKEN repository secret. -# 2. Run once with dry_run left as `true` and read the file list it prints. -# 3. Run again with dry_run set to `false`. +# Authentication is by **trusted publishing** (OIDC): npm exchanges the workflow's +# short-lived GitHub identity token for a publish token scoped to this package, so no +# long-lived npm token has to exist. To enable it, on npmjs.com → the cereale package → +# Settings → Trusted publisher → GitHub Actions, set: +# +# Organization or user: avalon-vanguard +# Repository: cereale +# Workflow filename: release.yml +# Environment: (leave blank — this workflow does not use one) +# +# The workflow filename must match this file's name, and the Environment field must be +# blank unless a matching `environment:` is added to the publish job; npm checks both +# against the OIDC claims and refuses the exchange if either disagrees. +# +# NPM_TOKEN is kept as a fallback. npm's OIDC step is deliberately non-throwing — if the +# trusted publisher is not configured, or the CLI is too old, it logs and falls through +# to token auth. That makes the switch safe to land before the registry side is set up. +# Once a real run shows OIDC working, the NPM_TOKEN secret can be deleted. +# +# Before a real run: +# 1. Run once with dry_run left as `true` and read the file list it prints. +# 2. Run again with dry_run set to `false`. on: workflow_dispatch: inputs: @@ -18,7 +36,7 @@ on: permissions: contents: read - id-token: write # required for npm provenance + id-token: write # the OIDC identity npm exchanges, and provenance jobs: publish: @@ -37,6 +55,21 @@ jobs: cache: 'npm' registry-url: 'https://registry.npmjs.org' + # Node 22 bundles npm 10.x, which has no OIDC support at all — it would skip + # trusted publishing in silence and fall back to token auth. Trusted publishing + # landed in npm 11.5.0 (lib/utils/oidc.js), so the version is raised and then + # asserted rather than assumed. + - name: Use an npm that can do trusted publishing + run: | + npm install -g npm@latest + V=$(npm --version) + echo "npm $V" + MAJ=${V%%.*}; REST=${V#*.}; MIN=${REST%%.*} + if [ "$MAJ" -lt 11 ] || { [ "$MAJ" -eq 11 ] && [ "$MIN" -lt 5 ]; }; then + echo "::error::npm $V has no OIDC support; trusted publishing needs >= 11.5.0" + exit 1 + fi + - name: Install dependencies run: npm ci @@ -76,8 +109,14 @@ jobs: - name: Publish if: ${{ inputs.dry_run == false }} + # --provenance stays explicit. Under OIDC npm would enable it on its own for a + # public repo, but only when the flag was left at its default; asking for it + # directly just skips that auto-enable and reaches the same place. On the token + # fallback path it is the only thing that produces an attestation at all. run: npm publish --provenance --access public env: + # Fallback only. Ignored once the trusted publisher is configured, because npm + # sets its own short-lived token before reading credentials. NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} - name: Summary diff --git a/README.md b/README.md index eaa6bf0..bde6f87 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,6 @@ # Cereale +[![npm](https://img.shields.io/npm/v/cereale?color=a0784a&label=npm)](https://www.npmjs.com/package/cereale) [![CI](https://github.com/avalon-vanguard/cereale/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/avalon-vanguard/cereale/actions/workflows/ci.yml) [![Docs](https://github.com/avalon-vanguard/cereale/actions/workflows/pages.yml/badge.svg)](https://avalon-vanguard.github.io/cereale/) [![Node ≥20](https://img.shields.io/badge/node-%E2%89%A520-a0784a)](https://github.com/avalon-vanguard/cereale/blob/main/package.json) @@ -79,20 +80,13 @@ entity, anything with behaviour attached. Reach for Zod when you just want the d ## Installation -Not on npm yet: `npm install cereale` does **not** resolve to this library — the name is -unclaimed on the registry. Installing straight from GitHub will not work either, because the -build output is not committed. Until the first publish, install from a clone: - ```bash -git clone https://github.com/avalon-vanguard/cereale -cd cereale -npm install && npm run build -npm pack # → cereale-0.4.0.tgz - -# then, from your own project -npm install ../cereale/cereale-0.4.0.tgz +npm install cereale ``` +Published with [provenance](https://www.npmjs.com/package/cereale), so the registry carries a +verified attestation linking the tarball to the commit it was built from. + Cereale uses **TC39 standard decorators** (since 0.2.0), so no `experimentalDecorators` flag: ```json diff --git a/docs/index.html b/docs/index.html index a2a42ba..ff19890 100644 --- a/docs/index.html +++ b/docs/index.html @@ -814,22 +814,15 @@ export default defineConfig({
-

not on npm yet installing it today

+

on npm installing it

shell
-
git clone https://github.com/avalon-vanguard/cereale
-cd cereale
-npm install && npm run build
-npm pack                    # → cereale-0.4.0.tgz
-
-# then, from your own project
-npm install ../cereale/cereale-0.4.0.tgz
+
npm install cereale

- npm install cereale does not resolve to - this library — the name is unclaimed on the registry. Installing straight from GitHub - will not work either: the build output is not committed, so the package would arrive - without its dist/. + Published from CI with provenance, so the registry carries a verified + attestation linking the tarball to the commit it was built from — visible on the + package page.

@@ -998,9 +991,11 @@ npm install ../cereale/cereale-0.4.0.tgz cannot reach. Both are errors rather than silent no-ops.

-

It is not on npm yet

-

0.4.0 lives in the repository. npm install cereale does not - resolve to this library — build it from source until it is published.

+

It is still 0.x

+

0.4.0 is published, and under semver a 0.x minor bump + is allowed to break you. Pin the version until 1.0; the + changelog + says what moved and why.

diff --git a/docs/page.js b/docs/page.js index e1bc546..c0a903f 100644 --- a/docs/page.js +++ b/docs/page.js @@ -43,12 +43,6 @@ Array.prototype.forEach.call(document.querySelectorAll('.js-version'), function (el) { el.textContent = meta.version; }); - // The install snippet names the tarball npm pack produces; keep it tied to the - // same package.json fact the badge uses instead of hand-bumping it each release. - var shell = document.getElementById('install-shell'); - if (shell) { - shell.textContent = shell.textContent.replace(/cereale-[\d.]+\.tgz/g, 'cereale-' + meta.version + '.tgz'); - } } if (decoratorCount) { Array.prototype.forEach.call(document.querySelectorAll('.js-dec-count'), function (el) { From 730e5902a5c3f7b9216be1c43428c1c12328a26a Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 20 Aug 2026 16:16:19 +0000 Subject: [PATCH 2/2] =?UTF-8?q?=F0=9F=94=8E=20ci:=20make=20the=20dry=20run?= =?UTF-8?q?=20prove=20trusted=20publishing=20engaged?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit npm calls oidc() before every dryRun branch in publish.js, so `npm publish --dry-run` performs the real token exchange and the existing step is already a trusted-publishing smoke test — it just could not be read. The exchange is non-throwing by design, so at the default log level a working OIDC exchange and a silent fallback to NPM_TOKEN look exactly the same. Raising that one step to verbose surfaces `oidc Successfully retrieved and set token`, which turns "did trusted publishing actually work?" into something a dry run answers without publishing anything. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK --- .github/workflows/release.yml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf80fe4..1b0812c 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -105,6 +105,16 @@ jobs: run: npm run verify - name: Show exactly what would ship + # `npm publish --dry-run` performs the OIDC token exchange before it short-circuits + # (publish.js calls oidc() ahead of every dryRun branch), so this step is also the + # trusted-publishing smoke test — a dry run proves the exchange without publishing. + # + # verbose, because npm's OIDC step is non-throwing: at the default log level a + # successful exchange and a silent fallback to token auth look identical. Success + # prints `oidc Successfully retrieved and set token`; if that line is missing, + # trusted publishing did not engage. (The reasons it skips are logged at silly.) + env: + NPM_CONFIG_LOGLEVEL: verbose run: npm publish --dry-run - name: Publish