Commit Graph
11 Commits
Author SHA1 Message Date
Claude f4c5e214f9 🤖 ci: pin the Junie action to its newest release, v1.7.4
Checked against the action repo itself rather than assumed: git ls-remote
shows v1.7.4 is the newest release tag and the moving v1 tag points at the
same commit (c2ae82f), so this changes which ref is named, not which code
runs — today. What it buys is that the version running stays the version
that was reviewed here, instead of silently following wherever v1 moves.

There was never a v0 reference in this workflow, and there is no api-key
presence check to remove — the action is invoked directly and fails loudly
if JUNIE_API_KEY is absent, which is the intended behaviour.

For the record, since this commit will re-trigger the job on PR #12: the two
failures there today are neither the workflow nor the Junie balance. Both
died in under ten seconds at the action's GitHub permission pre-check with
GitHub's own 503 body ("No server is currently available to service your
request"), before any Junie API call was made.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-17 14:29:33 +00:00
Claude 60cb8303ed 🚀 ci: deploy Pages through Actions instead of serving the branch
Pages currently serves main /docs directly, which publishes whatever is
committed with no check between the push and the live site. This builds the
page from src/, asserts the committed bundle matches it, and asserts the page
still loads nothing from the network — then uploads. A stale or
network-dependent page fails the job instead of going live.

Shape is GitHub's own starter pairing: configure-pages@v5,
upload-pages-artifact@v3, deploy-pages@v5, verified to exist at those tags.
Deploy is a separate job with the pages/id-token permissions scoped to it.
`cancel-in-progress: false`, because a half-published site is worse than a
stale one — pushes queue rather than interrupt a deploy already going out.

Triggered on docs/ rather than src/: docs/ carries the generated bundle, so a
src/ change only reaches the site once it has been rebuilt into docs/, which
is what the CI sync check already enforces.

Needs a one-time setting before it can work: Settings → Pages → Source must
be "GitHub Actions" rather than "Deploy from a branch". Until then the deploy
job fails on permissions. That switch needs administration:write, which
GITHUB_TOKEN does not have, so it cannot be made from CI. It is reversible —
setting Source back to a branch restores the current behaviour.

actionlint clean across all four workflows; the build job's steps were run
locally in order and pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-08 12:21:00 +00:00
Claude fb84d82c84 🤖 ci: add a Junie code review workflow
Runs JetBrains' Junie agent on every PR into main or develop, using the
action's built-in `code-review` prompt rather than a free-form instruction.

Details worth keeping:

- `use_single_comment` plus a `concurrency` group keyed on the PR number, so a
  burst of pushes leaves one review of the final state rather than a queue of
  reviews of intermediate ones.
- Skipped explicitly on fork PRs. `pull_request` does not expose secrets to
  them, so the job would otherwise fail on an empty API key — a skipped job
  reads as "not applicable", a failed one as "broken".
- `contents: read`. This workflow reviews; it does not push.
- Not a required check, on purpose. CI gates merges; a review that can block
  one on a judgement call is a review that gets rubber-stamped.

Needs a JUNIE_API_KEY repository secret before it will do anything but fail.
Pinned to @v1, whose action.yml declares each of the three inputs used here.
actionlint clean across all three workflows.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-07 11:41:16 +00:00
Claude b4f0657d09 📦 feat: add cereale/min, and a verified framework guide
**cereale/min** — the library flattened into one minified ES module,
25.5 KB / 8.6 KB gzipped, for import maps, <script type="module">, Deno
and Workers. Built from dist/esm/index.js, so the decorator lowering and
the ES2025 target are tsc's; esbuild only flattens and minifies.

It is an addition rather than a replacement, and the measurement is the
reason. Bundled through esbuild the flat and per-module builds land within
2 bytes of each other; through rollup + terser the flat one is 165 bytes
smaller; unused decorators tree-shake out of both. With the size argument a
wash, per-module stays the default import for the one thing it does better
— readable stack traces without source maps. (My first pass at that
measurement reported "shaken" for every symbol because both rollup builds
had failed and grep was reading missing files as absence. The check now
asserts the bundle is non-empty and that a *used* symbol is present, so it
can tell a real result from a broken harness.)

**FRAMEWORKS.md** — a recipe per framework, each one run before it was
written, with the versions and date verified against.

Angular works, which was not obvious: the CLI scaffolds
experimentalDecorators: true, but ngtsc erases @Component and @Injectable
into static properties rather than leaning on TypeScript's decorator emit.
Flip the flag and both systems coexist. Verified with ngc on Angular 21.2
with strictTemplates — templates still type-check and a wrong cereale rule
is still TS1240 inside the Angular build.

Next.js cannot work inline, structurally: it derives both the SWC parser's
decorator support and the transform mode from the one flag, so on gives
legacy emit and off makes @ a syntax error. NestJS cannot either — its DI
needs design:type from emitDecoratorMetadata.

Both have the same answer: keep the cereale classes in a package compiled
by tsc and import the built output. Verified inside a program with BOTH
legacy flags on, alongside @Injectable() — mapping and validation work, and
the compile-time guarantee still holds where the rules are written.

Also verified: Bun 1.3 needs no configuration, and a real Vite 8 build with
the plugin works where the same build without it silently leaves decorator
syntax in the bundle.

Version 0.4.0: cereale/min is a new public entry point, and cutting a minor
keeps the existing v0.3.0 tag meaningful instead of force-moving it onto a
commit it was never cut from.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-05 16:00:26 +00:00
Claude 8bd770e343 🔧 ci: fetch tags in the release workflow
The version gate resolves `v$VERSION` with git, but actions/checkout does
not fetch tags at its default depth — so the guard would have failed every
release with "No tag v0.3.0" whether or not the tag existed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-05 15:05:36 +00:00
Claude 9796d599dc 📦 chore: make the published package actually complete
`files` was ["dist"], but dist carries 256KB of .js.map and .d.ts.map
whose `sources` point at ../../src/*.ts — which was not published. Every
shipped sourcemap resolved to nothing: 44% of the tarball, dead weight.

The source is 116KB and its comments are the most detailed explanation of
why the engine does what it does, so it now ships (tests and the demo
excluded) and the maps resolve. Verified from a real `npm pack` install:
both utils.js.map and utils.d.ts.map now resolve to a file that exists, so
stepping into cereale in a debugger and "go to definition" from a decorator
both land in the real TypeScript.

Also: CHANGELOG.md ships; the repository/homepage/bugs URLs said
Avalon-Vanguard and only worked via GitHub's redirect, now lowercase to
match the org; publishConfig.access is explicit so a later move to a scoped
name cannot quietly attempt a private publish.

Adds a Publish to npm workflow, deliberately manual — pushing a tag does
not publish, because a tag is a decision to cut a release and publishing is
a decision to make it public and immutable. It asserts the tag exists and
points at the commit being published, refuses a version already on the
registry, runs the full verify gate, prints the file list, and defaults to
a dry run.

Checked end to end against the tarball: a strict consumer (no skipLibCheck,
no DOM lib) compiles and runs against both `cereale` and `cereale/vite`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-05 15:03:28 +00:00
Claude c828f5cfe9 🌾 feat: rebuild the landing page, and stop it from rotting again
The old page had been quietly broken for some time. It loaded
@babel/standalone from an **unpinned** CDN URL, which rolled over to Babel
8 and dropped the `proposal-class-properties` plugin the page asked for, so
Babel.transform threw before it ever reached the decorators — and the
decorator config it passed was `{ legacy: true }`, which 0.2.0 had already
made wrong. Nothing on the page said so. The copy was still selling the
0.1.0 pitch ("Spring-like"), listed about half the decorators, showed
`npm install cereale` for a package the registry returns 404 for, and
claimed "Zero overhead" against a README that publishes the real
microsecond costs.

The rebuild is one self-contained page: hand-written CSS, no Tailwind CDN,
no CodeMirror, and a vendored compiler pinned by package.json. It loads
nothing from the network. The playground runs the real bundled library
across six examples, all verified in a headless browser. The reference
covers all 68 decorators and the full API, counted from the bundle at
runtime so it cannot drift.

The hero's compiler error is not typed into the HTML. scripts/build-docs.mjs
compiles the snippets with the real tsc and writes the verbatim diagnostics
into docs/diagnostics.js, failing the build if a snippet the page calls a
compile error ever compiles — and two snippets that must compile guard
against the harness passing vacuously.

Three guards keep it honest, all wired into CI:
- check:docs fails on any remote subresource
- build:docs + git diff fails if docs/ is stale against src/
- check:types compiles a consumer against dist/ with no DOM lib, no
  @types/node and no skipLibCheck

That last one found a real packaging defect: `fromRequest` was declared as
taking the global `Request`, so cereale's own published .d.ts raised
"Cannot find name 'Request'" in any project whose lib and types did not
happen to supply it — inside a dependency, in code they may never call, and
unfixable from the outside. It now takes a structural JsonBody, which a
Request still satisfies. The library's own type tests had been hiding it by
enabling both DOM and skipLibCheck.

An adversarial review of the finished page caught four more: the lede
claimed *every* rule is type-checked (@IsDefined and @IsNotIn deliberately
are not), the guarantee section was wrong about the mechanism (a legacy
decorator does get design:type under emitDecoratorMetadata — the real claim
is about its type signature), one sample called a Movie method on a Media[]
and did not compile, and "nested objects come back as real classes" omitted
that you have to declare them. WCAG contrast was measured rather than
eyeballed: seven real failures fixed in the two themes.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-05 10:03:16 +00:00
Claude 0938300477 🔊 feat!: make the three silent failures loud
Every change here answers one question: where does cereale currently fail
without saying so?

**Vite 8 / Vitest 4 drop decorators silently.** Both transform with oxc,
which does not implement the standard decorator transform and does not
report that. `vitest` prints "0 test" beside a bare SyntaxError, and
`vite build` reports success while emitting a bundle that throws on first
import. Ship the plugin that fixes it as `cereale/vite`, transforming with
esbuild and falling back to tsc — cereale depends on neither. The library's
own suite now runs through it, so it is exercised by every test.

**Legacy decorators died opaquely.** With `experimentalDecorators: true`,
still the default in most existing TypeScript projects, decorators are
invoked as (prototype, "name") and cereale raised "TypeError: Cannot convert
undefined or null to object". All decorators now resolve metadata through
one checkpoint that names the tsconfig setting instead, and reject
application to a method, getter or accessor field.

**Values JSON cannot carry were emptied.** A populated Map serialized to
{}, a Uint8Array to index-keyed noise, a bigint straight through so the
caller's own JSON.stringify threw somewhere unrelated. All now raise
JsonMappingError naming the property path and both ways out. Covers what a
@JsonSerialize serializer returns, sync or async. Circular-reference and
depth errors name the path too.

Also fixed: defineRule on a subclass with no decorators of its own wrote
the rule into its base class, because the base's metadata object is
inherited through the static prototype chain and `??=` found it non-nullish.

The README's toolchain table (tsc, esbuild, swc ✅, oxc ❌) is now executed
by a test rather than asserted, and the positioning leads with
class-validator + class-transformer, the stack cereale actually replaces,
rather than Zod, which it deliberately is not.

193 -> 249 tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-05 09:11:32 +00:00
Claude 2acdf3b360 🔧 ci: run the pipeline on develop as well as main
The PR now targets develop, which was branched from main. The workflow only
triggered on push and pull_request against main, so nothing would have run on
develop or on any future PR into it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-04 11:14:58 +00:00
Claude 66e19f690f 🔧 fix: repair test, build and CI infrastructure
The vitest suite silently ran zero tests: vitest 4 transpiles with oxc, which
does not pick up `experimentalDecorators` from a tsconfig that excludes the
files being transformed, so every decorator-using suite failed to parse and was
reported as "0 test". A vitest.config.ts enabling legacy decorators brings all
40 existing tests back to life.

- Add vitest.config.ts (oxc legacy decorators + v8 coverage config)
- Type-check test files: move the test/demo exclusions from the base tsconfig
  onto the two build configs, and fix the strict-mode errors this surfaced
- Stop shipping src/example.ts in dist (it invokes runExample() at import time,
  a side effect in a package declaring "sideEffects": false)
- CI: run lint, tests with coverage, build and entry-point smoke checks; drop
  EOL Node 18, add Node 24; commit package-lock.json so `npm ci` works
- Add `verify`, `test:watch` and `build:docs` scripts, and an engines field;
  `build:docs` regenerates the previously hand-maintained docs/cereale.js

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SAcqrz3FcadkYr3xG32CjK
2026-08-03 23:14:16 +00:00
Enzo Marioni 7fe7e8c7c3 ✨ feat: implement core Optimus library for JSON mapping and validation
- 🎨 add Spring-like decorators (@JsonSerialize, @JsonDeserialize, etc.)
- ⚙️ implement JsonMapper and metadata storage for transformations
- 🧪 add comprehensive test suite using Vitest
- 📝 add README, CONTRIBUTING, and API documentation
- 👷 setup GitHub Actions CI workflow
- 🔧 configure TypeScript and project settings
2026-04-11 16:58:12 +02:00