build: package the kitchen CLI as a Nix flake #1

Merged
alexion merged 3 commits from task-0001-flake-build into main 2026-07-25 22:38:14 -04:00
6 changed files with 151 additions and 7 deletions
Showing only changes of commit fdede89e0c - Show all commits

View File

@@ -0,0 +1,52 @@
---
spec: nix-flake-packaging
blocked-by: 0001-flake-build
---
## What to build
The remaining flake outputs layered onto the working build: a runnable app, a development shell, direnv auto-loading, and a checks set that gates the project reproducibly.
The default app points at the compiled binary so the CLI runs without a prior install.
The default dev shell exposes the same pinned Bun as the build plus biome, and a `use flake` direnv configuration loads it automatically on entering the directory.
The checks set aggregates the Bun test tiers, a build-seam smoke check that invokes the built binary (for example `--help`) and asserts a zero exit, and biome lint run through the treefmt-nix flake-parts module sharing the existing biome configuration.
## Acceptance criteria
- [x] `nix run` executes the CLI without a prior install.
- [x] `nix develop` drops into a shell exposing the pinned Bun and biome at their expected versions.
- [x] The dev shell's Bun version is the same pin the build derivation uses.
- [x] A `use flake` `.envrc` auto-loads the dev shell for direnv users.
- [x] `nix flake check` runs the Bun test tiers, the build-seam smoke check against the Nix-built binary, and biome lint via treefmt-nix.
## Implementation Notes
All five criteria were verified end-to-end: `nix run` exits 0, `nix develop` exposes Bun 1.3.13, biome 2.5.4, and bun2nix, and `nix flake check` runs all three checks green.
### Outputs
The default app points at the compiled binary via its store path.
The dev shell carries the pinned Bun, biome, and bun2nix — bun2nix so the repo's `postinstall` regeneration of `bun.nix` works inside the shell.
`.envrc` is a one-line `use flake`, and `.direnv/` is gitignored.
### Bun pin sameness
The dev shell takes Bun from `inputs.nixpkgs-bun` directly, while the build takes it through bun2nix, whose nixpkgs `follows` the same `nixpkgs-bun`.
Both therefore resolve to the one pinned input, which is the single source of the Bun version.
The value is not threaded through a shared binding because bun2nix bakes its own `pkgs.bun` into its build hook, so the compile-side Bun cannot be passed in — the shared `nixpkgs-bun` input is the seam instead.
### Checks
`tests` runs `bun test` across the workspace through bun2nix's check phase.
`smoke` runs the Nix-built binary with `--help` and asserts a zero exit; while the CLI is still a placeholder this passes for any argument, and it will exercise real `--help` once the CLI lands.
Biome runs through the treefmt-nix flake-parts module, which auto-adds its own check.
### Biome via treefmt-nix
The treefmt biome program reads the repo's `biome.json` (via `importJSON`) so there is one configuration, but with `vcs.enabled` overridden off: the reproducible check has no git tree, and biome's `useIgnoreFile` lookup errors without one, while treefmt already selects the files.
Schema validation is skipped because nixpkgs' biome is newer than the schema treefmt-nix would validate against, and the config is the one biome itself runs with.
### Deviation
`packages/core/src/index_test.ts` was reformatted by biome (two `test.todo` lines exceeded the repo's `lineWidth`).
This is a pre-existing file, formatted only to make the new lint gate green — a necessary consequence of adding the gate, not a change of its behaviour.

1
.envrc Normal file
View File

@@ -0,0 +1 @@
use flake

3
.gitignore vendored
View File

@@ -7,6 +7,9 @@ packages/bin/kitchen
result
result-*
# direnv / nix-direnv cache
.direnv/
# local environment overrides
.env
.env.local

23
flake.lock generated
View File

@@ -118,7 +118,8 @@
"flake-parts": "flake-parts_2",
"nixpkgs": "nixpkgs",
"nixpkgs-bun": "nixpkgs-bun",
"systems": "systems"
"systems": "systems",
"treefmt-nix": "treefmt-nix_2"
}
},
"systems": {
@@ -156,6 +157,26 @@
"repo": "treefmt-nix",
"type": "github"
}
},
"treefmt-nix_2": {
"inputs": {
"nixpkgs": [
"nixpkgs"
]
},
"locked": {
"lastModified": 1784369104,
"narHash": "sha256-47cxbcZODibHv3rELFQ9vZly0vUNkND/atn/U7HLeb0=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "df3c0640565d04a0261253cdd89fce78ec50168a",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",

View File

@@ -4,9 +4,9 @@
inputs = {
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
# Provides the single pinned Bun used to compile the binary, held apart
# from `nixpkgs` so a `nixpkgs` update cannot drag Bun to a release that
# emits empty binaries under sandboxed native compilation.
# The single pinned Bun, used to compile the binary and to develop against.
# It is held apart from `nixpkgs` so a `nixpkgs` update cannot pull in a Bun
# release that emits empty binaries under sandboxed native compilation.
nixpkgs-bun.url = "github:nixos/nixpkgs/241313f4e8e508cb9b13278c2b0fa25b9ca27163";
flake-parts.url = "github:hercules-ci/flake-parts";
@@ -19,6 +19,11 @@
inputs.nixpkgs.follows = "nixpkgs-bun";
inputs.systems.follows = "systems";
};
treefmt-nix = {
url = "github:numtide/treefmt-nix";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs =
@@ -26,6 +31,8 @@
inputs.flake-parts.lib.mkFlake { inherit inputs; } {
systems = import inputs.systems;
imports = [ inputs.treefmt-nix.flakeModule ];
perSystem =
{ system, ... }:
let
@@ -33,9 +40,65 @@
inherit system;
overlays = [ inputs.bun2nix.overlays.default ];
};
# The Bun from the same pinned `nixpkgs-bun` the build compiles with.
bun = inputs.nixpkgs-bun.legacyPackages.${system}.bun;
kitchen = pkgs.callPackage ./package.nix { };
bunDeps = pkgs.bun2nix.fetchBunDeps { bunNix = ./bun.nix; };
in
{
packages.default = pkgs.callPackage ./package.nix { };
# Share the overlaid package set with the imported modules.
_module.args.pkgs = pkgs;
packages.default = kitchen;
apps.default = {
type = "app";
program = "${kitchen}/bin/kitchen";
};
devShells.default = pkgs.mkShell {
packages = [
bun
pkgs.biome
pkgs.bun2nix
];
};
treefmt = {
projectRootFile = "flake.nix";
programs.biome = {
enable = true;
# Reuse the repo's biome configuration as the single source.
# The sandboxed check has no git tree, so biome's VCS-ignore
# lookup must be off.
settings = pkgs.lib.recursiveUpdate (pkgs.lib.importJSON ./biome.json) {
vcs.enabled = false;
};
# Skip re-validating the config; biome consumes it directly.
validate.enable = false;
};
};
checks = {
tests = pkgs.bun2nix.mkDerivation {
pname = "kitchen-tests";
inherit (kitchen) version;
src = ./.;
inherit bunDeps;
dontRunLifecycleScripts = true;
dontUseBunBuild = true;
doCheck = true;
installPhase = "touch $out";
};
smoke = pkgs.runCommand "kitchen-smoke" { } ''
${kitchen}/bin/kitchen --help
touch $out
'';
};
};
};
}

View File

@@ -36,8 +36,12 @@ describe("parser", () => {
});
describe("raw fallbacks", () => {
test.todo("preserves an unmodelled block (e.g. a GFM table) as a RawBlock with verbatim source");
test.todo("preserves an unmodelled inline (e.g. strikethrough) as a RawInline with verbatim source");
test.todo(
"preserves an unmodelled block (e.g. a GFM table) as a RawBlock with verbatim source",
);
test.todo(
"preserves an unmodelled inline (e.g. strikethrough) as a RawInline with verbatim source",
);
});
describe("ingredient annotations", () => {