7 Commits
Author SHA1 Message Date
Senrokai 5f2e310f61 Merge pull request #16 from avalon-vanguard/develop
🔖 0.4.1 — the published README says the package is not on npm
2026-08-20 18:36:07 +02:00
Claude 057162e45b 🔖 fix: sync package-lock, and stamp the version fallbacks from the build
Junie's review on #16 caught the lock, and it was worse than reported:
package-lock.json said 0.3.0, so it had already missed the 0.4.0 release. It
does not ship (npm excludes it from tarballs, and `files` lists only dist, src,
FRAMEWORKS.md and CHANGELOG.md) and `npm ci` never complained because it only
diffs dependencies, not the project's own version — which is exactly why it
drifted two releases without anyone noticing. Regenerated with
`npm install --package-lock-only`.

The other half was the two version strings in docs/index.html: the brand badge
and the "It is still 0.x" line. Both are no-JavaScript fallbacks — page.js
overwrites them from meta.js — so they are right in a browser and stale in a
text reader or a scraper. Bumping them by hand is what failed on 0.4.0 and
again here, and it is the "release facts hand-bumped beside their generator"
finding from the code review.

So build-docs.mjs now stamps them, next to the meta.js it already writes. The
docs-sync gate turns a forgotten bump into a CI failure instead of a review
comment. Both regexes are asserted: renaming the markup fails the build with
the pattern that stopped matching, rather than silently stamping nothing —
verified by renaming the id and watching it exit.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-20 16:34:49 +00:00
Claude 46e30909c7 🔖 fix: regenerate docs/meta.js for 0.4.1
The version bump left meta.js at 0.4.0, so CI's docs-sync gate failed on all
three Node versions — the gate doing exactly its job.

Worth naming why it got through locally: `npm run verify` does not chain
check:docs-sync, so a green verify says nothing about docs/ being current. I
briefly added it, then took it back out. check:docs-sync is built on
`git status`, so inside verify it would fail during any in-progress docs edit,
and docs/ does not ship in the tarball — the package `files` list is dist,
src, FRAMEWORKS.md and CHANGELOG.md. Publishing correctness never depended on
it. CI and the Pages workflow already gate the site, which is the only thing
that stale docs affect.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-20 16:31:11 +00:00
Claude b2f2de2bd4 🔖 chore: 0.4.1 — the published README says the package is not on npm
The README inside a tarball is what npmjs.com renders, so the cereale package
page currently tells visitors: "npm install cereale does not resolve to this
library — the name is unclaimed on the registry." True when it was written,
nonsense on the page of the thing it describes.

Nothing could have prevented it. 0.4.0 was the first publish, so the docs could
only be corrected after the registry proved the claim wrong; the fix has to
ride a second version.

No code changes. Every emitted file is byte-identical to 0.4.0 except the
banner line of dist/cereale.min.js, which carries the version string — checked
by unpacking the published 0.4.0 tarball and diffing it against a fresh pack,
which is also why the changelog no longer claims dist/ is untouched. It said
that first, and it was wrong.

Patch rather than minor: no API surface moves.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-20 16:23:09 +00:00
Senrokai 1b9202edf1 Merge pull request #15 from avalon-vanguard/develop
📦 cereale is on npm — docs, badge, and trusted publishing
2026-08-20 18:18:05 +02:00
Claude 730e5902a5 🔎 ci: make the dry run prove trusted publishing engaged
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-20 16:16:19 +00:00
Claude f56d2811cb 📦 docs: cereale is on npm, and release.yml moves to trusted publishing
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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-20 16:10:42 +00:00
9 changed files with 109 additions and 42 deletions
+54 -5
View File
@@ -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
@@ -72,12 +105,28 @@ 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
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
+14
View File
@@ -5,6 +5,20 @@ All notable changes to this project are documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [0.4.1] - 2026-08-20
No code changes. Every emitted file is byte-identical to 0.4.0 except the banner line of
`dist/cereale.min.js`, which carries the version string. This exists because the README
inside the 0.4.0 tarball is the one npmjs.com renders, and it said the package was not on
npm: *"`npm install cereale` does not resolve to this library — the name is unclaimed on
the registry."* True when it was written, nonsense on the package page of the thing it
describes.
0.4.0 was the first publish, so nothing could have carried the corrected text: the docs
could only be fixed after the registry proved the claim wrong. The install instructions,
the landing page panel, and the "what cereale is not" entry now say `npm install cereale`,
and the README carries an npm version badge.
## [0.4.0] - 2026-08-05
### `cereale/min` — one file, no bundler
+5 -11
View File
@@ -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
+11 -16
View File
@@ -410,7 +410,7 @@ footer { border-top: 1px solid var(--border); padding-block: 2.5rem; color: var(
<header class="nav">
<div class="wrap nav-inner">
<a class="brand" href="#top"><svg class="mark" width="10" height="18" viewBox="0 0 10 18" aria-hidden="true" focusable="false"><rect x="4" y="0" width="2" height="18" fill="currentColor" opacity=".3"/><rect x="0" y="2" width="10" height="2" fill="currentColor"/><rect x="0" y="6" width="10" height="2" fill="currentColor"/><rect x="0" y="10" width="10" height="2" fill="currentColor"/><rect x="0" y="14" width="10" height="2" fill="currentColor"/></svg>cereale <span class="badge" id="version-badge">v0.4.0</span></a>
<a class="brand" href="#top"><svg class="mark" width="10" height="18" viewBox="0 0 10 18" aria-hidden="true" focusable="false"><rect x="4" y="0" width="2" height="18" fill="currentColor" opacity=".3"/><rect x="0" y="2" width="10" height="2" fill="currentColor"/><rect x="0" y="6" width="10" height="2" fill="currentColor"/><rect x="0" y="10" width="10" height="2" fill="currentColor"/><rect x="0" y="14" width="10" height="2" fill="currentColor"/></svg>cereale <span class="badge" id="version-badge">v0.4.1</span></a>
<nav class="nav-links" aria-label="Primary">
<a class="nav-hide" href="#guarantee">Guarantee</a>
<a class="nav-hide" href="#playground">Playground</a>
@@ -814,22 +814,15 @@ export default defineConfig({
</div>
<div>
<p class="compare-label"><span class="pill pill-bad">not on npm yet</span> installing it today</p>
<p class="compare-label"><span class="pill pill-ok">on npm</span> installing it</p>
<div class="code">
<div class="code-head"><span class="name">shell</span></div>
<pre><code data-lang="text" id="install-shell">git clone https://github.com/avalon-vanguard/cereale
cd cereale
npm install &amp;&amp; npm run build
npm pack # → cereale-0.4.0.tgz
# then, from your own project
npm install ../cereale/cereale-0.4.0.tgz</code></pre>
<pre><code data-lang="text">npm install cereale</code></pre>
</div>
<p class="pg-note">
<code class="inline-code">npm install cereale</code> does <strong>not</strong> 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 <code class="inline-code">dist/</code>.
Published from CI with <strong>provenance</strong>, so the registry carries a verified
attestation linking the tarball to the commit it was built from — visible on the
<a href="https://www.npmjs.com/package/cereale">package page</a>.
</p>
</div>
</div>
@@ -998,9 +991,11 @@ npm install ../cereale/cereale-0.4.0.tgz</code></pre>
cannot reach. Both are errors rather than silent no-ops.</p>
</div>
<div class="note-item">
<h3>It is not on npm yet</h3>
<p><span class="js-version">0.4.0</span> lives in the repository. <code class="inline-code">npm install cereale</code> does not
resolve to this library — build it from source until it is published.</p>
<h3>It is still 0.x</h3>
<p><span class="js-version">0.4.1</span> is published, and under semver a 0.x minor bump
is allowed to break you. Pin the version until 1.0; the
<a href="https://github.com/avalon-vanguard/cereale/blob/main/CHANGELOG.md">changelog</a>
says what moved and why.</p>
</div>
</div>
</div>
+1 -1
View File
@@ -1,5 +1,5 @@
// Generated by scripts/build-docs.mjs — do not edit.
window.CEREALE_META = {
"version": "0.4.0",
"version": "0.4.1",
"node": ">=20.0.0"
};
-6
View File
@@ -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) {
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "cereale",
"version": "0.3.0",
"version": "0.4.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "cereale",
"version": "0.3.0",
"version": "0.4.1",
"license": "MIT",
"devDependencies": {
"@babel/standalone": "^8.0.4",
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "cereale",
"version": "0.4.0",
"version": "0.4.1",
"description": "Strongly typed JSON mapping and validation for TypeScript classes \u2014 validated domain objects, not validated data. A zero-dependency replacement for class-validator + class-transformer, on TC39 standard decorators.",
"type": "module",
"main": "./dist/cjs/index.js",
+21
View File
@@ -49,6 +49,27 @@ await writeFile(
`window.CEREALE_META = ${JSON.stringify({ version: pkg.version, node: pkg.engines.node }, null, 2)};\n`
);
// 2b. The same version, stamped into the two spots in index.html that page.js later
// overwrites from meta.js. Those are the no-JavaScript fallbacks: correct in a browser,
// stale in a text reader or a scraper, and hand-bumped until now — they went stale on
// 0.4.0 and again on 0.4.1. Stamping them here means the docs-sync gate catches the
// drift instead of a reviewer. The regexes are asserted, so if the markup is renamed
// the build fails loudly rather than silently stamping nothing.
const indexPath = path.join(docs, 'index.html');
let index = await readFile(indexPath, 'utf8');
const stamps = [
[/(<span class="badge" id="version-badge">)v[\d.]+(<\/span>)/, `$1v${pkg.version}$2`],
[/(<span class="js-version">)[\d.]+(<\/span>)/g, `$1${pkg.version}$2`],
];
for (const [re, replacement] of stamps) {
if (!re.test(index)) {
console.error(`build-docs: nothing in docs/index.html matched ${re} — the version fallback markup moved.`);
process.exit(1);
}
index = index.replace(re, replacement);
}
await writeFile(indexPath, index);
// 3. The compiler errors the page quotes, produced by actually running the compiler. If a
// snippet the page calls a compile error ever compiles, this fails the build.
const { byCase, problems } = await collectDiagnostics(root);