v0.2.19
List every package, and the static files the core serves
Three packages were missing from the table -- eclipse, parsec and
prism, the three most recent. They were already in the docs site, its
index, its navigation and the root README; only this table lagged.
The feature list also never mentioned static files, although the core
has always carried the middleware and now wires it through a provider.
Satisfy the lint the CI actually runs
A template literal in the containment test and an export sort in the
barrel. Both came from edits made AFTER I ran biome mid-way through the
work, over a narrower set of paths than biome check src/ tests/ --
so the last changes were never checked. Lint at the end, over the paths
CI uses.
Take back the version number that was burnt
0.2.19 was never published: npm runs 0.2.0 to 0.2.18 with no gap. It got
skipped because I could not move its tag and incremented instead, which
solved a git problem by spending a version number. Publishing 0.2.20
next would leave a hole in the registry that nothing explains.
The work that was in 0.2.20 is the same work; only the number changes.
Serve static files the way AdonisJS does
Nothing served public/. StaticMiddleware existed in the core and was
exported; no application mounted it, the CLI scaffold did not add it,
and the docs never mentioned it. app.publicPath() resolved a directory
nobody read.
What it did have diverged from upstream in ways that mattered:
- An extension ALLOWLIST, under the name serve-static gives to a
FALLBACK list. Porting an AdonisJS config across would silently
restrict the server; and the list had no .avif, so the framework
refused a format its own image package now writes. - No Last-Modified and no If-Modified-Since, though upstream defaults
lastModified to true. A client that revalidates by date refetched
everything. - No Range support, while the allowlist advertised .mp4, .webm, .mp3,
.zip and .pdf. A browser cannot seek in a video without it. - A /static prefix, where upstream mounts at the URL root.
NAMED DEVIATION (NAPI): @adonisjs/static delegates to serve-static,
which writes to a Node ServerResponse. Ream's response crosses the NAPI
boundary as a complete object and there is none, so the behaviour is
reproduced rather than the code -- same option names, same defaults,
same headers. The prefix option is kept as an optional narrowing, defaulting to
upstream's behaviour of none.
Kept deliberately, being stronger than upstream: the file is opened
with O_NOFOLLOW and its metadata read from the descriptor, so the bytes
served are the ones that passed the containment checks, and a symlink
swapped in after the check fails the open instead.
Containment is now pinned by 16 tests covering encoded traversal,
symlinked files AND directories, sibling directories with a shared
prefix, malformed percent sequences (which made decodeURIComponent
throw, and would have been a 500 on a path anyone can send), and NUL.
Six of them compare against the real content of a system file rather
than only asserting fallthrough. Neutering either containment check
makes four of them fail, so they pin the guard rather than passing
vacuously.
Accept quasar 0.2 in the peer range
The session store's connection contract is unchanged in quasar 0.2.0, but
the range still said ^0.1.0, so every install reported an unmet peer
against the version actually on disk.
Folded into the unpublished 0.2.20.
Release 0.2.20
Add usingInker, so a package can find the template engine without the container
A package that contributes to templates — rosetta pushing its i18n globals —
needs to know whether an engine is installed. Asking the container meant
resolving a binding whose lifecycle has to have reached the right phase,
which is how that push ended up failing the whole application boot.
The flag is set by InkerProvider's constructor, so it is already true
before any provider boots. Same shape as upstream's usingEdgeJS.
Bring the Rust pins up to date and release 0.2.19
ream-http-napi was still pinned to blackhole v0.1.4, warden v0.1.4 and
sigil v0.1.4. The first of those cost a day: text/plain responses came back
entity-escaped (a b=c as a b=c), the fix had long since landed
in blackhole, and reading the local checkout said so while Cargo.lock
said otherwise. The lock is what builds.
blackhole v0.1.20, warden v0.2.1, sigil v0.1.15. Only sigil moved its API:
Argon2Options gained secret/variant/hash_length/salt_length.
Narrow the cookie deviation to the case that can happen, and test it
The note cited 0 and false alongside "". Neither can reach a signed
cookie: Response.cookie takes value: string. Claiming them made a sound
argument look like a stretch, so they are gone — the empty string is the whole
of it, and enough: a cleared preference or an emptied field is something
someone wrote, and || hands back the default for it.
And it had no test at the Request level, which is where it matters. Four now,
including a tampered cookie still falling back — an absence with a lie attached
is not a value. Flipping ?? to || fells the empty-string one.
Cite the upstream line the cookie fallback differs from
The deviation note asserted that upstream uses || without saying where, so it
could not be checked and an audit rightly called the claim unproven. It is
@adonisjs/http-server@9.3.0: return this.#cookieParser.unsign(key) || defaultValue.
Which settles it in an unexpected direction: the doc-comment on that very line
says the default is returned "when actual value is undefined" — what ?? does,
and what || does not. So this follows the contract upstream documents and
differs only from what it happens to execute, the same shape as rune's
accepted() returning a value its own declared type says it cannot.
Say one thing about the cipher, and name no framework version here
algorithm still described GCM as a divergence from upstream while the file
header explains it is upstream's own default now, with CBC surviving there as
the legacy driver. Two answers to the same question, twenty lines apart.
The router header claimed v6 compatibility; the target is declared once in the
guide now, so a per-file version number can only drift out of step with it.
Read the artifact where cargo actually wrote it
The NAPI copy script looked under <package>/target unconditionally. Cargo
writes elsewhere whenever CARGO_TARGET_DIR is set — a shared cache, a CI mount,
a read-only external directory — so the build produced the library and then
failed to find it, or silently copied a stale one from a previous run.
CARGO_TARGET_DIR is honoured now, resolved against the package root when it
is relative, as cargo resolves it. All fourteen scripts had the same line; an
audit reported it in ream-mcp alone.
Verified end to end, not by reading: a real cargo build redirected to a
temporary directory, the artifact copied out of it, and the package suite green
on that binary.
Keep the dev-dependency alignment, drop the workspace: protocol
The internal ranges had been rewritten to workspace:^. That resolves inside
this monorepo and nowhere else: every package CI checks out its own repository
alone and runs pnpm install, where the protocol has no workspace to point at
and fails with ERR_PNPM_WORKSPACE_PKG_NOT_FOUND before a single test runs. The
concrete ranges are back; the dev-dependency bumps that came with the same edit
are kept, and now match what the lockfile already resolved.
Changes since v0.2.18.