feat(nix): flake.nix exposing lib.mkWebapp #98

Merged
padreug merged 3 commits from feat/flake-mkwebapp into dev 2026-06-10 13:52:29 +00:00
Owner

Closes #97 — adds the nix build path so deploy/server-deploy hosts can call inputs.webapp.lib.mkWebapp { brandDir, app } per-host instead of running their own derivation. Follow-up to #96 (brand kit Phase 1, merged into dev).

What ships

flake.nix exposes:

  • lib.mkWebapp { pkgs, brandDir ? ./branding/default, app ? "main" } — system-agnostic builder returning a stdenv.mkDerivation.
  • packages.<system>.<app> for each of main / events / wallet / chat / market / forum / tasks / restaurant / libra, all using the aiolabs default brand (CI / local sanity).
  • packages.<system>.default = packages.<system>.main.

flake.lock pins nixpkgs (nixos-unstable) and flake-utils.

How it builds

  1. pkgs.fetchPnpmDeps (with fetcherVersion = 3 + pkgs.pnpm_10 explicit) snapshots all NPM packages from pnpm-lock.yaml into a hash-pinned fixed-output derivation.
  2. pkgs.pnpmConfigHook installs node_modules from the snapshot offline.
  3. pkgs.autoPatchelfHook + stdenv.cc.cc.lib patch the prebuilt @img/sharp-libvips-linux-* binaries so they run under the nix sandbox.
  4. BRAND_DIR env points at the brandDir arg (a /nix/store/... path).
  5. BRAND_APP env carries the standalone name (empty for the hub).
  6. CI=true bypasses pnpm 10's interactive modules-purge prompt.
  7. pnpm run build (hub) or pnpm run build:<app> (standalone) produces dist/ or dist-<app>/, which gets copied to $out.

Design notes

pkgs.pnpm_10 is pinned explicitly (not pkgs.pnpm) so the pnpmDeps hash stays stable across downstream nixpkgs versions. The flake's nixos-unstable currently has pnpm floating at 11.5.1, but pnpm_10 stays at the 10.x major series that matches packageManager: pnpm@10.33.0 in package.json. Downstream consumers using a different nixpkgs still get a working build as long as their nixpkgs also has pnpm_10 (it has been in nixpkgs for a long time).

Sharp under nix worked first try with autoPatchelfHook — modern sharp 0.33+ ships prebuilt libvips as a separate npm package (@img/sharp-libvips-linux-x64) which fetchPnpmDeps captures as a tarball. The .so files inside get patched at install time. No need to build libvips from source.

Test plan

  • nix build .#maindist/index.html with <title>AIO Hub</title> + 6 icons in dist/icons/
  • nix build .#eventsdist-events/ with manifest.name = "AIO"
  • End-to-end branded build via lib.mkWebapp { pkgs = <external>; brandDir = /tmp/fixture; app = "events"; } — fixture's brand.json { name: "Sortir", themeColor: "#dc2626", ... } propagates to manifest + HTML title; fixture's logo.png resolves via @brand/ and gets hashed into dist-events/assets/logo-<hash>.png.
  • Downstream consumer test (server-deploy) — happens in aiolabs/server-deploy#8.

Cross-references

🤖 Generated with Claude Code

Closes #97 — adds the nix build path so deploy/server-deploy hosts can call `inputs.webapp.lib.mkWebapp { brandDir, app }` per-host instead of running their own derivation. Follow-up to #96 (brand kit Phase 1, merged into dev). ## What ships `flake.nix` exposes: - `lib.mkWebapp { pkgs, brandDir ? ./branding/default, app ? "main" }` — system-agnostic builder returning a `stdenv.mkDerivation`. - `packages.<system>.<app>` for each of `main / events / wallet / chat / market / forum / tasks / restaurant / libra`, all using the aiolabs default brand (CI / local sanity). - `packages.<system>.default = packages.<system>.main`. `flake.lock` pins `nixpkgs` (nixos-unstable) and `flake-utils`. ## How it builds 1. `pkgs.fetchPnpmDeps` (with `fetcherVersion = 3` + `pkgs.pnpm_10` explicit) snapshots all NPM packages from `pnpm-lock.yaml` into a hash-pinned fixed-output derivation. 2. `pkgs.pnpmConfigHook` installs `node_modules` from the snapshot offline. 3. `pkgs.autoPatchelfHook` + `stdenv.cc.cc.lib` patch the prebuilt `@img/sharp-libvips-linux-*` binaries so they run under the nix sandbox. 4. `BRAND_DIR` env points at the `brandDir` arg (a `/nix/store/...` path). 5. `BRAND_APP` env carries the standalone name (empty for the hub). 6. `CI=true` bypasses pnpm 10's interactive modules-purge prompt. 7. `pnpm run build` (hub) or `pnpm run build:<app>` (standalone) produces `dist/` or `dist-<app>/`, which gets copied to `$out`. ## Design notes **`pkgs.pnpm_10` is pinned explicitly** (not `pkgs.pnpm`) so the pnpmDeps hash stays stable across downstream nixpkgs versions. The flake's nixos-unstable currently has `pnpm` floating at 11.5.1, but `pnpm_10` stays at the 10.x major series that matches `packageManager: pnpm@10.33.0` in `package.json`. Downstream consumers using a different nixpkgs still get a working build as long as their nixpkgs also has `pnpm_10` (it has been in nixpkgs for a long time). **Sharp under nix worked first try with autoPatchelfHook** — modern sharp 0.33+ ships prebuilt libvips as a separate npm package (`@img/sharp-libvips-linux-x64`) which fetchPnpmDeps captures as a tarball. The .so files inside get patched at install time. No need to build libvips from source. ## Test plan - [x] `nix build .#main` → `dist/index.html` with `<title>AIO Hub</title>` + 6 icons in `dist/icons/` - [x] `nix build .#events` → `dist-events/` with `manifest.name = "AIO"` - [x] End-to-end branded build via `lib.mkWebapp { pkgs = <external>; brandDir = /tmp/fixture; app = "events"; }` — fixture's `brand.json { name: "Sortir", themeColor: "#dc2626", ... }` propagates to manifest + HTML title; fixture's `logo.png` resolves via `@brand/` and gets hashed into `dist-events/assets/logo-<hash>.png`. - [ ] Downstream consumer test (server-deploy) — happens in aiolabs/server-deploy#8. ## Cross-references - aiolabs/webapp#95 — brand kit architecture parent design - aiolabs/webapp#96 — Phase 1 brand kit (merged into dev) - aiolabs/server-deploy#8 — Phase 2 host migration (this PR unblocks it) 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Establishes the nix build path so deploy/server-deploy can call
inputs.webapp.lib.mkWebapp { brandDir, app } per-host instead of
running its own derivation.

lib.mkWebapp { pkgs, brandDir ? ./branding/default, app ? "main" }
returns a stdenv.mkDerivation that:
- Uses pnpmConfigHook + fetchPnpmDeps (fetcherVersion = 3) to install
  node_modules from a hash-pinned snapshot, offline.
- Wires brandDir into the BRAND_DIR env var so vite-branding.ts and
  pwa-assets.config.ts resolve the right brand.
- Sets BRAND_APP from `app` so per-standalone overrides
  (branding/<dep>/icons/<app>/logo.*) work.
- autoPatchelfHook + stdenv.cc.cc.lib patch the prebuilt
  @img/sharp-libvips-linux-x64 binaries to run under the nix sandbox.
- Runs `pnpm run build` for the hub or `pnpm run build:<app>` for a
  standalone, then copies the resulting dist/ or dist-<app>/ into $out.

Per-system exposure:
- packages.<app> for each of main/events/wallet/chat/market/forum/
  tasks/restaurant/libra — exercises the builder under CI.
- packages.default = packages.main.

Closes aiolabs/webapp#97. Server-deploy hosts can now migrate via
aiolabs/server-deploy#8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two issues found when calling lib.mkWebapp from an external nixpkgs
(server-deploy's scenario):

- pnpm 10 in the sandbox aborts with
  ERR_PNPM_ABORTED_REMOVE_MODULES_DIR_NO_TTY when it sees a
  modules-purge prompt without a TTY. CI=true is pnpm's documented
  bypass; harmless on builds that don't need it.

- Pinning pkgs.pnpm leaves it floating with the consumer's nixpkgs
  (flake's nixos-unstable has pnpm 11.5.1, system nixpkgs has 10.33,
  etc.). pnpmDeps hash is per-pnpm-version so a floating pnpm means
  consumers hit hash mismatches. Pinning pkgs.pnpm_10 locks to the
  same major series that produced the lockfile (package.json's
  packageManager: pnpm@10.33.0) while still allowing minor drift
  inside major-10.

New pnpmDeps hash reflects pnpm_10's snapshot format.

Verified end-to-end: `nix build --impure --expr '...lib.mkWebapp {
brandDir = /tmp/fixture; app = "events"; }'` with an external pkgs
produces a Sortir-branded dist-events (manifest name "Sortir", theme
#dc2626, bg #fff5f5, HTML title "Sortir").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
branding/README's "Integration with NixOS deployment" section now
describes the actual lib.mkWebapp API + the per-host call site, with
a ready-to-paste server-deploy snippet. Also documents the pnpm_10
pin, sharp/autoPatchelfHook handling, and CI=true bypass — anchors
that surface in error logs and benefit from being grep-able.

CLAUDE.md's NixOS deployment paragraph stops calling lib.mkWebapp a
future TODO and points at the API directly.

Adds a `nix build` recipe (default + impure brand override) for local
sanity-checking.

Part of aiolabs/webapp#97.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
padreug deleted branch feat/flake-mkwebapp 2026-06-10 13:52:30 +00:00
Sign in to join this conversation.
No description provided.