docs: add nix flake packaging spec and ADR
Replace the stale Bun-only project-scaffold spec with a Nix-aware packaging spec, and record the flake-parts + bun2nix + pinned-Bun decision as ADR 0007.
This commit is contained in:
82
.claude/spec/nix-flake-packaging.md
Normal file
82
.claude/spec/nix-flake-packaging.md
Normal file
@@ -0,0 +1,82 @@
|
||||
## Problem Statement
|
||||
|
||||
The repository was scaffolded as a Bun workspaces monorepo before the developer adopted Nix.
|
||||
There is no declarative, reproducible way to build, run, or install the `kitchen` CLI, and no reproducible development environment.
|
||||
A contributor must install the right Bun version and tooling by hand, and the "no Node.js runtime on target machines" promise is trusted rather than enforced.
|
||||
The developer wants to install the CLI declaratively — as a flake input in a system or home configuration — and to enter a fully specified dev shell with a single command.
|
||||
|
||||
## Solution
|
||||
|
||||
Package the whole repository as a Nix flake, structured with flake-parts, exposing the `kitchen` CLI as an installable package plus a development shell, a runnable app, and a set of checks.
|
||||
Bun dependencies are vendored reproducibly with bun2nix so the sandboxed build needs no network.
|
||||
Bun itself is pinned so the version used to develop is the version used to compile.
|
||||
The flake is the single declarative entry point: build it, run it, install it, or develop in it.
|
||||
|
||||
## User Stories
|
||||
|
||||
1. As the developer, I want to install the `kitchen` CLI declaratively via a flake input, so that it lands in my system or home configuration reproducibly without manual build steps.
|
||||
2. As the developer, I want `nix build` to produce the compiled `kitchen` binary, so that the artifact is built the same way on any machine.
|
||||
3. As the developer, I want `nix run` to execute the CLI without a prior install, so that I can try it ad hoc.
|
||||
4. As a contributor, I want `nix develop` to drop me into a shell with the exact Bun and tooling the project expects, so that I do not have to install or match versions by hand.
|
||||
5. As a contributor using direnv, I want the dev shell to load automatically on entering the directory, so that the environment is present without a manual command.
|
||||
6. As the developer, I want `nix flake check` to run the test suites and the linter, so that a single command verifies the project reproducibly and can gate CI later.
|
||||
7. As the developer, I want the compiled binary to embed its dependencies, so that no Node.js or Bun runtime is required on the target machine.
|
||||
8. As a maintainer, I want dependency vendoring to stay in sync with the lockfile automatically, so that adding or removing a dependency does not require a separate manual regeneration step I might forget.
|
||||
9. As a maintainer, I want the Bun version pinned and shared between the dev shell and the build, so that a Bun release that breaks sandboxed compilation cannot silently diverge dev from build.
|
||||
|
||||
## Implementation Decisions
|
||||
|
||||
- **Flake structure: flake-parts.**
|
||||
Per-system outputs (package, dev shell, app, checks) are declared through flake-parts' `perSystem` module, and system-independent configuration sits at the top level.
|
||||
This keeps per-system and top-level outputs separate and lets tooling integrations compose as modules.
|
||||
- **Outputs.**
|
||||
The flake exposes a default package building the `kitchen` binary, a default app pointing at that binary, a default dev shell, and a checks set aggregating tests and lint.
|
||||
`nix build`, `nix run`, `nix develop`, and `nix flake check` are the supported entry points.
|
||||
- **Dependency vendoring: bun2nix.**
|
||||
Bun workspace dependencies are vendored via bun2nix, which reads the lockfile and produces a generated Nix expression the build consumes.
|
||||
A postinstall hook regenerates that expression on any dependency change, keeping it in sync with the lockfile without a manual step.
|
||||
bun2nix is added as a flake input.
|
||||
- **Binary build via Bun's native compiler.**
|
||||
The package derivation compiles the CLI to a self-contained native binary that embeds its dependencies, so the runtime closure on the target has no Node.js or Bun.
|
||||
The vendored dependencies are consumed at build time only.
|
||||
- **Bun pinned.**
|
||||
A single pinned Bun version is shared by the dev shell and the build derivation.
|
||||
The pin avoids Bun releases known to break native compilation inside the Nix sandbox, where specific point releases have produced empty binaries.
|
||||
- **Systems: `x86_64-linux` only** for now, declared in the flake-parts systems list.
|
||||
Additional targets are a one-line addition when a real target appears; vendoring and compilation are per-platform, so each added system carries its own build.
|
||||
- **nixpkgs tracks `nixpkgs-unstable`**, to follow a recent Bun.
|
||||
- **Lint as a check: treefmt-nix.**
|
||||
Biome runs as a `nix flake check` through the treefmt-nix flake-parts module, so lint verification is reproducible and shares one configuration with the existing biome setup.
|
||||
- **direnv auto-load.**
|
||||
A direnv configuration using `use flake` loads the dev shell automatically for direnv users.
|
||||
- **The monorepo it packages is unchanged.**
|
||||
The existing Bun workspaces layout (`@kitchen-md/core` and `@kitchen-md/bin`) and the `kitchen` binary target are the inputs to the flake; the flake wraps them without restructuring the packages.
|
||||
|
||||
## Testing Decisions
|
||||
|
||||
- The flake's verification surface is its outputs, exercised as commands rather than unit tests.
|
||||
The highest seam is `nix flake check`: it composes the package build, the Bun test suites, and the biome lint into one reproducible gate.
|
||||
- **Build seam.**
|
||||
`nix build` must produce a runnable `kitchen` binary; a minimal check invokes the built binary (for example `--help`) and asserts a zero exit, mirroring the existing smoke-test approach in `@kitchen-md/bin` but against the Nix artifact.
|
||||
- **Dev-shell seam.**
|
||||
Entering the dev shell must expose the pinned Bun and biome; this is verified by the presence and versions of those tools in the shell.
|
||||
- **Application behaviour is not re-tested here.**
|
||||
The parser and CLI behaviour are covered by their own specs' unit, integration, and smoke tiers, which run under the Bun test runner and are invoked by `nix flake check`.
|
||||
This spec's tests concern packaging and environment reproducibility only.
|
||||
- Prior art: the existing smoke tests in `@kitchen-md/bin` invoke the CLI as a black box; the build-seam check reuses that black-box style against the Nix-built binary.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- The parser and CLI implementation and their behaviour, covered by the core-parser and cli-view specs.
|
||||
- Publishing either package to a registry, and the future Obsidian plugin's own repository and packaging.
|
||||
- CI/CD wiring; `nix flake check` is designed to be CI-callable, but the CI configuration itself is separate.
|
||||
- Multi-system and cross-compilation beyond `x86_64-linux`, and any binary cache or substituter setup.
|
||||
- A NixOS or home-manager module wrapping the package; the package output is directly consumable without one.
|
||||
- git-hooks integration; treefmt-nix covers the lint check, and pre-commit hooks can be added later as another flake-parts module.
|
||||
|
||||
## Further Notes
|
||||
|
||||
- Because Bun's native compiler bundles dependencies into the binary, the vendoring problem is confined to build time; nothing about the dependency closure reaches consumers of the installed CLI.
|
||||
- The Bun-version pin is a correctness constraint, not just hygiene: native compilation has produced empty binaries in the sandbox on specific Bun point releases, so the dev shell and build must never drift apart on Bun version.
|
||||
- The decision to package with a flake, vendor via bun2nix, and pin Bun is recorded in ADR 0007.
|
||||
- This spec replaces the removed project-scaffold spec's role for anything Nix-related; the underlying Bun monorepo layout that spec documented is retained in the codebase.
|
||||
@@ -1,63 +0,0 @@
|
||||
## Problem Statement
|
||||
|
||||
The `kitchen-md` repository is a fresh git init with no files.
|
||||
Before any implementation can begin, the project needs a complete structural scaffold: a monorepo layout, two packages, TypeScript configuration, and a testing infrastructure organised by tier.
|
||||
|
||||
## Solution
|
||||
|
||||
A Bun-workspaces monorepo containing two packages (`@kitchen-md/core` and `@kitchen-md/bin`), a shared TypeScript base configuration, and per-package test scaffolding organised into unit, integration, and smoke tiers.
|
||||
Synthetic test fixtures are shared at the workspace root so both packages can reference the same canonical recipe files.
|
||||
|
||||
## User Stories
|
||||
|
||||
1. As a developer, I want a Bun workspace root so that I can manage both packages with a single toolchain and no additional runtime.
|
||||
2. As a developer, I want `@kitchen-md/core` isolated as its own package so that it can be consumed by a future Obsidian plugin without bundling the CLI.
|
||||
3. As a developer, I want `@kitchen-md/bin` isolated as its own package so that the CLI binary can be compiled and distributed independently of the library.
|
||||
4. As a developer, I want a shared TypeScript base configuration at the workspace root so that both packages inherit consistent compiler settings without duplication.
|
||||
5. As a developer, I want unit test files co-located with their source module so that the test for a module is always findable next to the module itself.
|
||||
6. As a developer, I want integration test files co-located with the code they exercise so that cross-module test coverage is discoverable alongside the relevant source.
|
||||
7. As a developer, I want smoke tests in `@kitchen-md/bin` that invoke the CLI directly so that end-to-end user-story coverage is separated from unit-level coverage.
|
||||
8. As a developer, I want synthetic recipe fixture files at the workspace root so that both packages can reference the same canonical test inputs without duplication.
|
||||
|
||||
## Implementation Decisions
|
||||
|
||||
- Bun is the single toolchain for the entire project: package manager, runtime, test runner, and native binary compiler (`bun --compile`).
|
||||
No Node.js runtime is required on target machines.
|
||||
- The workspace root `package.json` declares Bun workspaces pointing to `packages/core` and `packages/bin`.
|
||||
- `@kitchen-md/core` lives in `packages/core/` and is structured as a publishable library with a single entry point.
|
||||
- `@kitchen-md/bin` lives in `packages/bin/`, declares `@kitchen-md/core` as a workspace dependency, and declares a `bin` entry named `kitchen` pointing at the CLI entry point.
|
||||
- A `tsconfig.json` at the workspace root defines shared compiler settings (strict mode, ESNext target and module, bundler module resolution, Bun types).
|
||||
Each package's `tsconfig.json` extends the root config.
|
||||
- A `fixtures/` directory at the workspace root holds synthetic `.md` recipe files.
|
||||
The primary fixture covers every annotation type (ingredient, cookware, timer) and all format features.
|
||||
Additional fixture files are acceptable for regression cases or edge cases that cannot fit cleanly into the primary file.
|
||||
|
||||
## Testing Decisions
|
||||
|
||||
- Tests assert external behaviour only — given an input, assert the output — never implementation internals.
|
||||
- **Unit tests**: co-located with the source file they test, named `{module}_test.ts`.
|
||||
All test data is inline raw strings within the test file; no filesystem access.
|
||||
Both packages have unit tests.
|
||||
- **Integration tests**: co-located with the source they exercise, naming is flexible.
|
||||
Integration tests read from the shared `fixtures/` directory.
|
||||
Both packages have integration tests where they cross file boundaries.
|
||||
- **Smoke tests**: present only in `@kitchen-md/bin`.
|
||||
Each smoke test invokes the CLI directly (via subprocess or equivalent) and asserts its output against expected results.
|
||||
Smoke tests use the shared `fixtures/` directory as input.
|
||||
Smoke test coverage maps directly to the CLI user stories in the recipe format spec.
|
||||
- The Bun test runner discovers test files via `*_test.ts` and `*.test.ts` patterns; all test files must match one of these patterns to be picked up automatically.
|
||||
|
||||
## Out of Scope
|
||||
|
||||
- Parser implementation (covered by the recipe format spec).
|
||||
- CLI command design and subcommand structure.
|
||||
- Aisle mapping file format.
|
||||
- Shopping list generator.
|
||||
- Publishing either package to a registry.
|
||||
- CI/CD configuration.
|
||||
|
||||
## Further Notes
|
||||
|
||||
- The `$` sigil for cookware and the choice of Bun over other runtimes are decisions from the recipe format spec; this scaffold spec assumes them and does not re-litigate them.
|
||||
- The binary is named `kitchen` (the `bin` key in `@kitchen-md/bin`'s `package.json`), not `kitchen-md` or `kmd`.
|
||||
- Smoke tests differ from integration tests in degree, not kind: they test the CLI as a black box from the outside rather than testing module interactions from the inside.
|
||||
Reference in New Issue
Block a user