build: package the kitchen CLI as a Nix flake

Add a flake-parts flake whose default package Bun-compiles the kitchen
CLI into a self-contained native binary, built in the sandbox with
dependencies vendored by bun2nix.

Bun is pinned via a dedicated nixpkgs-bun input (Bun 1.3.13) that
bun2nix follows, so nixpkgs can track unstable without moving the
compile toolchain onto a release that emits empty binaries.

bun2nix is both a flake input and an npm devDependency, and a
postinstall hook keeps the checked-in bun.nix in sync with the lockfile.
This commit is contained in:
2026-07-25 22:28:10 -04:00
parent 1af090caa3
commit 8fd2e53c53
8 changed files with 369 additions and 2 deletions

View File

@@ -0,0 +1,53 @@
---
spec: nix-flake-packaging
---
## What to build
The tracer bullet for packaging the repository as a Nix flake: a flake-parts flake whose default package Bun-compiles the `kitchen` CLI into a self-contained native binary, built reproducibly in the sandbox with vendored dependencies.
This is the whole risk seam — a Nix build is sandboxed with no network, while `bun install` fetches from npm, so the dependency closure must be vendored before anything compiles.
Bun dependencies are vendored via bun2nix (a flake input) reading the workspace lockfile into a checked-in generated expression, kept in sync with the lockfile by a postinstall hook so a dependency change needs no separate manual regeneration.
A single pinned Bun version is shared by the build derivation (and later the dev shell), avoiding point releases known to produce empty binaries under sandboxed native compilation.
Once this compiles, the remaining outputs (`nix run`, `nix develop`, checks) hang off the working flake in the next slice.
## Acceptance criteria
- [x] The flake is structured with flake-parts, with `x86_64-linux` in the systems list and nixpkgs tracking `nixpkgs-unstable`.
- [x] bun2nix is a flake input, and its generated vendoring expression is checked in and regenerated by a postinstall hook on lockfile changes.
- [x] A single Bun version is pinned in one place and consumed by the build derivation.
- [x] `nix build` produces a runnable `kitchen` binary reproducibly with no network access during the build.
- [x] The compiled binary embeds its dependencies and runs on a machine with no Node.js or Bun runtime present.
- [x] The default package is consumable as a flake input from another configuration.
## Implementation Notes
The tracer bullet builds and all six criteria were verified end-to-end against a real `nix build`, not just by inspection.
### The Bun pin
bun2nix compiles the binary with the `bun` from *its own* nixpkgs, baked into its setup hook at bun2nix build time.
So pinning Bun means controlling the nixpkgs that bun2nix follows, not overlaying `bun` in this flake's package set.
The pin is a dedicated `nixpkgs-bun` input fixed to one revision (Bun 1.3.13), with `bun2nix.inputs.nixpkgs.follows = "nixpkgs-bun"`.
That revision is the one bun2nix 2.1.2 itself locks, so the compile toolchain matches what bun2nix was tested against.
`nixpkgs` still tracks `nixpkgs-unstable` for everything else, and updating it cannot move Bun.
The dev shell in task 0002 consumes the same `nixpkgs-bun`, keeping "develop with the version that compiles" true from one place.
### Vendoring and the postinstall hook
bun2nix is both a flake input (native builder) and an npm devDependency, so the `postinstall: bun2nix -o bun.nix` hook regenerates the checked-in `bun.nix` on any `bun install` even outside the Nix dev shell.
The build passes `dontRunLifecycleScripts = true` so that same postinstall does not fire redundantly inside the sandbox.
### Verification performed
`nix build` produced a 101 MB native ELF that runs to exit 0 under `env -i` (empty environment, no Node or Bun, `ldd` shows only glibc), confirming the embedded-runtime and no-network claims — the compile derivation is a normal sandboxed derivation with networking disabled.
A throwaway consumer flake built the package through `inputs.kitchen.packages.x86_64-linux.default`, confirming criterion 6.
### Deviations from the plan
- The nix-community cachix `nixConfig` block that the bun2nix templates ship was dropped, because the spec lists "any binary cache or substituter setup" as out of scope.
Consequence: a first build with a cold store compiles bun2nix from source.
A developer who wants the prebuilt bun2nix can add the substituter to their own Nix configuration.
- `packages/bin/src/index.ts` is still the placeholder entry point.
The CLI's behaviour belongs to the core-parser and cli-view specs, so the compiled binary is a no-op that exits 0 — enough to prove the packaging seam.