test(nix): drive the installed binary through the shared tier (task 0038)
The Nix build now runs the installed-binary facet of the packaging tier against the binary it has just produced, via an `installCheckPhase` that sets `GITEA_AXI_INSTALLED_BIN` to `$out/bin/gitea-axi` and runs the new `test:installed` script. Running after `fixupPhase` means the binary under test is the wrapped one an operator actually gets, and naming it is all the phase does — the assertions stay in the shared tier, so the npm and Nix installation paths cannot drift apart in what they guarantee. `npmInstallHook` prunes dev dependencies out of the build tree before the check runs, so `preInstall` snapshots `node_modules` with `cp -al` and the check restores it by copying, leaving the snapshot intact for a replayed phase. Two defects fixed in passing, both surfaced by review: `test:installed` pins a test file by path while the packaging runner sets `passWithNoTests: true`, so moving that file would have taken the build green having asserted nothing — the same silently-inert trap `doCheck` sprang in task 0037. The script now passes `--passWithNoTests=false`. `checkPhase`'s vitest left a timestamped run cache under `node_modules/.vite`, which `npmInstallHook` copied into `$out`, shipping a stray cache and making the derivation non-reproducible. It is now removed before the install phase; `nix build --rebuild` passes.
This commit was merged in pull request #47.
This commit is contained in:
60
package.nix
60
package.nix
@@ -10,6 +10,11 @@
|
||||
}:
|
||||
|
||||
let
|
||||
# Where the install check finds the dev dependencies that `npmInstallHook`
|
||||
# prunes out of the build tree. Named once: it is a contract between
|
||||
# `preInstall`, which writes it, and `installCheckPhase`, which reads it.
|
||||
devNodeModules = "$NIX_BUILD_TOP/node_modules-dev";
|
||||
|
||||
# The manifest is the canonical version: the release flow bumps it, and
|
||||
# reading it here means a store path and a released version cannot disagree.
|
||||
manifest = lib.importJSON ./package.json;
|
||||
@@ -37,6 +42,7 @@ let
|
||||
./tsconfig.json
|
||||
./tsconfig.build.json
|
||||
./vitest.config.ts
|
||||
./vitest.packaging.config.ts
|
||||
];
|
||||
};
|
||||
in
|
||||
@@ -89,9 +95,25 @@ buildNpmPackage {
|
||||
export HOME=$(mktemp -d)
|
||||
npm run test
|
||||
|
||||
# vitest leaves a run cache under node_modules/.vite whose results.json
|
||||
# records durations and timestamps. `npmInstallHook` copies node_modules
|
||||
# into $out wholesale, so leaving it there both ships a stray cache in the
|
||||
# closure and makes the output non-reproducible — `nix build --rebuild`
|
||||
# reports the derivation "may not be deterministic" on that one file.
|
||||
rm -rf node_modules/.vite
|
||||
|
||||
runHook postCheck
|
||||
'';
|
||||
|
||||
# `npmInstallHook` prunes dev dependencies out of the build tree's
|
||||
# node_modules on its way to assembling $out, which would take vitest with it
|
||||
# and leave the install check with nothing to run. Snapshot the tree first —
|
||||
# as hardlinks, so it costs neither time nor space, and so the prune's
|
||||
# deletions do not follow through to the copy.
|
||||
preInstall = ''
|
||||
cp -al node_modules "${devNodeModules}"
|
||||
'';
|
||||
|
||||
# ADR 0018: append, never prepend. The operator's own `tea` owns the
|
||||
# credential store it refreshes in place, so the closure's copy is a
|
||||
# fresh-machine fallback rather than an override.
|
||||
@@ -100,6 +122,44 @@ buildNpmPackage {
|
||||
--suffix PATH : ${lib.makeBinPath [ git tea ]}
|
||||
'';
|
||||
|
||||
# Drive the binary that was just installed through the shared installed-binary
|
||||
# tier, which the npm distribution path drives too — so the two cannot drift
|
||||
# apart in what they guarantee about an installed gitea-axi.
|
||||
#
|
||||
# This guards a class of failure `checkPhase` structurally cannot reach,
|
||||
# because it runs against the source tree rather than an installation. The
|
||||
# one that bites here is Skill resolution: `setup` locates the bundled Agent
|
||||
# Skill relative to its own module location, so the built output's position
|
||||
# relative to that Skill is load-bearing — an arrangement that exists only
|
||||
# once installed. A probe moving the installed `skills` aside does fail this
|
||||
# phase.
|
||||
#
|
||||
# The tier's executable-bit assertion carries less weight under Nix than
|
||||
# under npm, and deliberately so: `nodejsInstallExecutables` generates a
|
||||
# wrapper invoking `node <path>` rather than symlinking the entrypoint, so
|
||||
# the bit that matters is the one on `$out/bin/gitea-axi`, which makeWrapper
|
||||
# always sets. That assertion earns its keep on the npm path, where npm sets
|
||||
# the bit from the manifest's `bin` entry and `tsc` does not. Sharing one
|
||||
# tier means neither path picks which guarantees it feels like offering.
|
||||
#
|
||||
# `installCheckPhase` runs after `fixupPhase`, so the binary named here is the
|
||||
# wrapped one an operator would actually get. Naming it is all this phase
|
||||
# does: the assertions live in the tier, not in shell script here.
|
||||
doInstallCheck = true;
|
||||
installCheckPhase = ''
|
||||
runHook preInstallCheck
|
||||
|
||||
# Restore by copying, not moving, so the snapshot survives for a replayed
|
||||
# phase — `--keep-failed` debugging, or `genericBuild` re-entered by hand.
|
||||
rm -rf node_modules
|
||||
cp -al "${devNodeModules}" node_modules
|
||||
|
||||
export HOME=$(mktemp -d)
|
||||
GITEA_AXI_INSTALLED_BIN=$out/bin/gitea-axi npm run test:installed
|
||||
|
||||
runHook postInstallCheck
|
||||
'';
|
||||
|
||||
meta = {
|
||||
inherit (manifest) description homepage;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user