feat(nix): add a dev shell and a checks output (task 0039) #48
@@ -16,8 +16,38 @@ The full typecheck stays in continuous integration, where it already runs.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- [ ] `nix develop` yields a shell with Node, `git`, and `tea` available.
|
||||
- [ ] The build, the fast tier, and the benchmark harness's runner all work from inside that shell.
|
||||
- [ ] The shell's Node and the package's Node come from one reference — changing it moves both, and they cannot be set independently.
|
||||
- [ ] `nix flake check` builds the package and runs its tests, and fails when the package fails.
|
||||
- [ ] No per-stage check derivations are added.
|
||||
- [x] `nix develop` yields a shell with Node, `git`, and `tea` available.
|
||||
- [x] The build, the fast tier, and the benchmark harness's runner all work from inside that shell.
|
||||
- [x] The shell's Node and the package's Node come from one reference — changing it moves both, and they cannot be set independently.
|
||||
- [x] `nix flake check` builds the package and runs its tests, and fails when the package fails.
|
||||
- [x] No per-stage check derivations are added.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
### The single Node reference is a `passthru`, not a second `pkgs.nodejs`
|
||||
|
||||
The shell takes `self.packages.${system}.gitea-axi.nodejs` rather than naming `pkgs.nodejs` again.
|
||||
Naming it twice would satisfy the criterion's letter while leaving two places to edit, which is the drift the criterion exists to prevent; reading it back off the derivation means there is genuinely one reference.
|
||||
|
||||
The first cut relied on `buildNpmPackage` incidentally surfacing its `nodejs` argument as a derivation attribute — which works, but only as a side effect of `inherit src nodejs`, with nothing marking it load-bearing.
|
||||
Review caught that: moving or dropping that `inherit` would have broken the shell silently at a distance.
|
||||
`package.nix` now declares `passthru = { inherit nodejs; };`, making it an interface with a comment saying what depends on it.
|
||||
`passthru` does not enter the derivation, so the store path is unchanged and the change costs no rebuild — verified: the drv hash before and after is identical.
|
||||
|
||||
### `curl` was a missing part of the toolchain
|
||||
|
||||
The benchmark's `raw-api` arm shells out to `curl` (`ARM_BINARY` in `bench/guard.ts`), so the shell carries it alongside Node, `git`, and `tea`.
|
||||
|
||||
### The shell deliberately does not supply `gitea-axi`
|
||||
|
||||
The criterion asks that the benchmark harness's runner work from inside the shell, and it does.
|
||||
A *live* arm run is a further step the shell cannot take: `provisionArmBin` resolves each arm's binary by name off `PATH`, and the `gitea-axi` arm's binary must be the locally built `dist/main.js` so a run measures the working tree rather than whatever the flake last packaged.
|
||||
Putting the packaged binary on `PATH` would satisfy the lookup with the wrong artifact — a silently misleading benchmark, worse than a missing one.
|
||||
|
||||
Exposing the *built* one was considered and rejected as well: `tsc` does not set an executable bit on `dist/main.js` (npm sets it at install time from the manifest's `bin` entry, which is what the packaging tier's bit assertion guards), so a `shellHook` would have had to `chmod +x` the build output on every shell entry — mutating build artifacts to work around a lookup that is the benchmark's own concern.
|
||||
Both the flake and the CLAUDE.md gotcha now state the boundary rather than implying the shell covers it.
|
||||
|
||||
### CLAUDE.md's toolchain gotcha was stale by construction
|
||||
|
||||
It read "the repository has no dev shell yet (task 0039 adds one)".
|
||||
This slice is that task, so the entry was rewritten to point at `nix develop --command`, keeping `nix shell nixpkgs#nodejs -c ...` only as the one-off outside the repository.
|
||||
|
||||
@@ -36,8 +36,10 @@ Per [ADR 0001](.claude/adr/0001-diff-auth-via-tea-login-list.md) as amended, `sr
|
||||
The only bypass is the test hook requiring `GITEA_AXI_API_URL` + `GITEA_AXI_TOKEN` + `GITEA_AXI_REPO` together; there is no user-facing path that avoids `tea`, and `TEA_NOT_INSTALLED` exists for its absence.
|
||||
|
||||
Neither `node`/`npm` nor `tea` is on the `PATH` in a non-interactive shell on this machine, and there is no `~/.gitconfig`.
|
||||
This is a NixOS host with no global Node install, and the repository has no dev shell yet (task 0039 adds one).
|
||||
Until then, prefix commands with `nix shell nixpkgs#nodejs -c ...`, adding `nixpkgs#tea` for anything that resolves credentials — including `gitea-axi pr create`.
|
||||
This is a NixOS host with no global Node install, so run work through the flake's dev shell: `nix develop --command <cmd>`, which carries Node, `git`, `tea`, and `curl` — enough for the build, the fast tier, the live end-to-end tier, and the benchmark harness's own runner.
|
||||
That covers anything resolving credentials, including `gitea-axi pr create`.
|
||||
The shell deliberately does *not* put `gitea-axi` on the `PATH`, so a live `bench:run` of the `gitea-axi` arm still needs the built binary exposed under that name itself — see the benchmark gotcha above.
|
||||
`nix shell nixpkgs#nodejs -c ...` still works for a one-off outside the repository, but inside it the dev shell is the declarative answer and pins the same Node the package is built against.
|
||||
`node_modules/` may be absent too, so `npm ci` first.
|
||||
Commits need an explicit identity: `git -c user.name=alexion -c user.email=contact@alexion.dev commit ...`, matching the existing history.
|
||||
|
||||
|
||||
58
flake.nix
58
flake.nix
@@ -19,13 +19,65 @@
|
||||
"aarch64-darwin"
|
||||
];
|
||||
|
||||
# Hands each output both the package set and the system name — the latter
|
||||
# because the shell and the checks reach back into `self.packages` for the
|
||||
# system being evaluated, and `pkgs.system` is discouraged in favour of a
|
||||
# considerably wordier spelling.
|
||||
forAllSystems =
|
||||
f: nixpkgs.lib.genAttrs systems (system: f nixpkgs.legacyPackages.${system});
|
||||
f:
|
||||
nixpkgs.lib.genAttrs systems (
|
||||
system:
|
||||
f {
|
||||
inherit system;
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
}
|
||||
);
|
||||
in
|
||||
{
|
||||
packages = forAllSystems (pkgs: rec {
|
||||
packages = forAllSystems (
|
||||
{ pkgs, ... }: rec {
|
||||
gitea-axi = pkgs.callPackage ./package.nix { };
|
||||
default = gitea-axi;
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
# The toolchain the repository actually needs: the build and the fast tier
|
||||
# want Node, the live end-to-end tier and the benchmark harness additionally
|
||||
# shell out to `git`, `tea`, and `curl` — none of which the repository
|
||||
# specifies anywhere else.
|
||||
#
|
||||
# Not `gitea-axi` itself, which the benchmark's own arm resolves by name off
|
||||
# PATH: that has to be the locally built `dist/main.js`, so that a bench run
|
||||
# measures the working tree rather than whatever the flake last packaged.
|
||||
# Supplying it here would silently substitute the wrong binary.
|
||||
devShells = forAllSystems (
|
||||
{ pkgs, system }: {
|
||||
default = pkgs.mkShell {
|
||||
packages = [
|
||||
# The package's own Node, taken from its passthru rather than named
|
||||
# a second time here. There is one reference, so development and
|
||||
# the shipped artifact cannot drift onto different majors — and
|
||||
# they cannot be set independently even by mistake.
|
||||
self.packages.${system}.gitea-axi.nodejs
|
||||
pkgs.git
|
||||
pkgs.tea
|
||||
# The benchmark's raw-api arm shells out to curl.
|
||||
pkgs.curl
|
||||
];
|
||||
};
|
||||
}
|
||||
);
|
||||
|
||||
# An alias for the package, so `nix flake check` builds it and thereby runs
|
||||
# both its verification phases — the fast tier in `checkPhase`, the
|
||||
# installed-binary tier in `installCheckPhase`.
|
||||
#
|
||||
# No granular per-stage checks: the one stage that would add coverage the
|
||||
# package build does not already have is the full typecheck, which spans
|
||||
# `test/` and `bench/` and would therefore drag the benchmark harness into
|
||||
# the derivation's inputs — undoing the source filtering that keeps
|
||||
# benchmark churn from forcing a rebuild. That typecheck stays in
|
||||
# continuous integration, where it already runs.
|
||||
checks = forAllSystems ({ system, ... }: { inherit (self.packages.${system}) gitea-axi; });
|
||||
};
|
||||
}
|
||||
|
||||
@@ -160,6 +160,12 @@ buildNpmPackage {
|
||||
runHook postInstallCheck
|
||||
'';
|
||||
|
||||
# The Node the package is built against, published as a declared interface
|
||||
# rather than left to be read off the build environment. The flake's dev shell
|
||||
# consumes exactly this, so the two cannot drift onto different majors — and
|
||||
# this attribute is why that holds, so removing it breaks the shell.
|
||||
passthru = { inherit nodejs; };
|
||||
|
||||
meta = {
|
||||
inherit (manifest) description homepage;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user