v0.2.5
Build the cross-platform matrix only when the version moves
The five-runner matrix exists to produce the prebuilt binaries a release
ships. It ran on every push to main, rebuilding artefacts nobody
downloads — five runners, every time, for a comment fix.
A version-gate job now compares the package version at HEAD^ with
the one at HEAD and the matrix runs only when they differ. Anything
that is not a push passes the gate unconditionally, so workflow_dispatch
— how a release is actually cut — is unaffected, and so is publish,
which still waits on the full matrix. A missing HEAD^ reads as a bump:
erring towards building is the safe direction.
The test signal deliberately does NOT move with it. quality and the
cargo/integration jobs were already independent of the matrix, but
vitest ran INSIDE it, so gating the matrix alone would have quietly
taken the TypeScript suite off every ordinary push. A ts-tests job now
runs it on ubuntu, building its own napi binary rather than waiting on a
gated artefact. On a push without a bump that leaves typecheck, lint,
cargo and vitest — one runner instead of five.
Make the native type generation reach the platforms that run it
Two gaps in the generation added for 0.2.6, both found while extending
it to the other native packages.
The driver was a bash script, but build:napi also runs on the Windows
prebuild runner. There, mktemp hands cargo a Git Bash /tmp/... path
the native proc-macro cannot write to: the type-def file comes back
empty, the "emitted nothing" guard fires, and the build fails with an
explanation that points at the wrong thing. It is now Node, which has
one idea of what a path is on every runner.
The declarations were a .d.ts under src/, which tsc does not
carry into dist/. The emitted dist/events/native.d.ts referenced
./native/generated.js and nothing was there to resolve — a consumer
type-checking against the published package would have hit it, though
nothing in this repo did. It is now a .ts holding only ambient
declarations, so the compiler emits it alongside everything else.
Folded into 0.2.6, which is tagged but not published.
Derive the native TypeScript surface from the Rust
The interfaces describing the NAPI modules were written by hand, so nothing
connected them to the code they described. They drifted, and this session
proved it: making Bus::on_request synchronous changed the Rust with nothing on
the TypeScript side to notice.
napi-derive can emit those definitions while cargo compiles — they are derived
from the #[napi] items themselves. pnpm build:napi-types turns them into
src/native/generated.d.ts, and build:napi runs it, so a signature change cannot
ship without the declarations following.
One thing napi-rs genuinely cannot infer is the shape of a JsFunction
callback; it emits (...args: any[]) => any. Those four are refined by name in
the generator, and it FAILS when a refinement stops matching — so a hand
annotation cannot quietly stop describing the Rust either. There is no any
left in the generated surface.
Three details the generator had to handle: type-def APPENDS to its output
file, so crates are built one at a time or a parallel build interleaves the
writes and definitions go missing; a Rust doc example holding a cron
expression closes the comment block early and has to be escaped, but the
closer itself must not be; and napi-derive emits whole declarations for
functions while giving only members for structs.
Use real private fields, not the TypeScript keyword
private is erased at compile time: at runtime the field is public,
enumerable, and shows up in Object.keys and JSON.stringify. # is enforced by
the engine. The difference is not cosmetic, and one test proved it — photon's
renderer test reached into a private ssrModule through an
as unknown as { ssrModule } cast, which the keyword never prevented. It now
goes through useSsrModule(), a real seam, and the cast is gone.
Constructor parameter properties are expanded into a field plus an assignment,
since # cannot be declared in a parameter list.
Three private CONSTRUCTORS stay as they are, annotated: that form has no native
equivalent, and it is the one place the keyword expresses something # cannot.
Give Application the lifecycle surface an app reads
Application was the least covered class of the AdonisJS surface, and the gaps
were the ones an app actually touches: it reads app.getEnvironment() in a
provider, branches on app.isReady, and reports app.toJSON() from a health
endpoint. None of it existed.
The environment moves onto the application, where AdonisJS keeps it and where a
provider reads it — the Ignitor held the only copy, so a provider had no way to
ask. The Ignitor keeps the setter, because that is where an entry point
declares it, and now delegates. One declaration of the type, not two that
happen to look alike.
Lifecycle state is a value, not a pair of booleans: "not booted" covers both
"starting up" and "already shut down", and code guarding on the wrong one runs
at exactly the wrong moment. Ready is reached AFTER the booted hooks, since
those are where an app wires what a request needs — reporting ready before them
greens a health check on an application that cannot serve. Terminated is
reached even when a hook throws, because the application IS down by then.
Plus listenOnce/listenOnceIf, notify, importDefault (which names the file with
no default export, instead of failing later as "undefined is not a
constructor"), appName, version and nodeEnvironment.
The private fields were private _booted and friends — against both the
native-privates and the underscore conventions.
Create the GitHub release from the publish workflow
A published version arrived with no notes: npm showed a number, GitHub showed
nothing, and the only way to learn what changed was to read a diff. The commit
messages already carry the reasoning, so the release is built from the commits
the tag contains rather than written twice.
Skips a pure version bump, leaves an existing release alone, and does nothing
when the run was not built from a tag. The job takes contents:write for this;
the workflow default stays read.
Changes since v0.2.4.