v0.2.10
Run cargo with --locked in CI
Without it, cargo rewrites Cargo.lock in place when it has drifted from the
manifests — so CI resolves dependencies fresh and tests a graph nobody
committed, then the release is built from it. The workspace lock had drifted
by 382 lines before the same flag caught it locally.
Every package here commits a Cargo.lock, so --locked is meaningful: it fails
loudly instead of silently updating. Verified against the current lock before
the flag went in.
The Node side is deliberately left alone: these repositories ship no
pnpm-lock.yaml, so --frozen-lockfile has nothing to freeze against, and
resolving from the registry is what a consumer gets anyway.
Take the NAPI platform table from the vendored copy
The map from Node's platform/arch to napi-rs's suffix was repeated here as
well. Adding a target means adding it everywhere, and a package that misses
the edit fails only on that platform.
The table, the path and the require come from scripts/vendor/nativeBinary.ts.
The policy does not: what this package does when the binary is absent, and the
error it raises, stay here — they carry this package's code and its build
instruction, which a shared helper has no business inventing.
Run clippy as a gate, not a note
cargo fmt --check was gating the formatting while nothing gated the
lints that catch a real defect — the one place the compiler stays silent
and clippy does not. Nine of the ten crates in this cohort had the same
hole; only one ran it.
No version change: this gates what is already there, and every crate
passes it today.
Measure the grace, not how long node took to start
a_failing_worker_that_will_not_exit_is_not_made_to_wait_out_the_grace
timed the whole call — spawn included — against a 1500ms ceiling. Spawning
node costs whatever the machine costs, and on a two-core CI runner that is
seconds, so the assertion could not tell "the pool waited out the 2s
grace" from "node was slow to come up". It failed on the second reading.
It now starts its clock when the pool announces the file error, which is
the moment the behaviour under test begins.
Find the tsx loader the way Node finds it
Five integration suites resolved the tsx ESM loader by scanning four
directories up for pnpm's virtual store. That path exists in this
workspace and nowhere else, so in helix's own repository — which is where
CI builds it — the scan returned nothing, the spawned worker had no loader
for a .ts fixture, and sixteen tests read that as the orchestrator
returning no results.
Node's resolver finds tsx wherever the package is installed. One helper,
in tests/__helpers__, instead of five copies of a lookup that only ever
worked in one layout.
Run the coverage suite after the build it spawns
helix's integration tests spawn bin/helix.js, which runs out of dist:
before pnpm build there is nothing to spawn, and sixteen of them read
that as the orchestrator returning no results.
Align the config with the one every sibling now carries
Lint helix the way its own repository will
biome's config lived only at the workspace root, including the line that
excludes the fixture whose parse error is the point of the test that loads
it. helix is built from its own repository, where that file does not
exist and biome falls back to defaults — so lint there checked a
different set of rules against a file it was never meant to read.
And coverage belongs on the whole vitest suite: tests/unit is a subset —
the selftests run through helix's own binary — so thresholds measured on
everything could not be met there.
Declare what CI has to install
Each package is its own repository: pnpm install there sees only this
file, so a dependency the workspace happened to hoist locally is simply
absent in CI. --coverage needs @vitest/coverage-v8 named here, and an
optional peer a test imports has to be a devDependency as well — optional
is exactly what keeps it from being installed.
Stop rejecting a callback that returns something
A union containing void does not get the rule that makes
forEach(x => list.push(x)) legal: () => void | Promise<void> accepts a
body returning nothing and rejects one returning a number. Every one of
these is awaited and its value dropped, or read back through a runtime
typeof === "function" guard, so the honest return type is unknown:
CleanupFn, SuiteHookCleanup, ReporterHandler, Plugin,
configureSuite, importer, the dataset body and test.each's.
HookFn keeps its union: the second arm is what gives a returned
CleanupFn its parameter types, and collapsing it to unknown loses that
inference — the runtime does consume that return, so it is not the same
finding.
Plus the gates the package already declared: lint now covers tests/.
Changes since v0.2.9.