Suggestion to enrich skills/nix-typescript/SKILL.md with conventions that have crystallized around recent Bun-based projects (srid/anywhen, srid/drishti). The current skill is implicitly Node/tsx-shaped; the Bun path is now consistent enough across two real apps that the patterns belong as first-class guidance.
Conventions worth lifting
Flake + npins layout
- One flake input only:
juspay/bun2nix#rawflake. nixpkgs and any other Nix-store inputs (e.g. @kolu/surface source) are pinned via npins → npins/sources.json. Cold nix develop eval stays sub-second.
lib.mkBun2nix { pkgs } from the rawflake branch lets you feed your own npins-pinned pkgs, so bun2nix's transitive nixpkgs never enters your eval graph.
flake.lock and npins/sources.json end up as two independent pin surfaces (bun2nix's CLI build vs. the app's build). That partition is the property worth naming explicitly.
Workspace + hoisted linker
bunfig.toml: [install] linker = "hoisted" is load-bearing whenever you hydrate any package source into node_modules/ from outside Bun (the workspace pattern, the Nix-store-symlink pattern). Isolated linker leaves each workspace package's deps in its own node_modules, breaking transitive resolution from the hydrated source.
Build derivation
bun2nix.hook as nativeBuildInputs; bunDeps = bun2nix.fetchBunDeps { bunNix = ./bun.nix; }.
dontUseBunBuild = true to skip the hook's default bun build --compile (single-binary mode is wrong for server-plus-static-bundle apps).
- Custom
buildPhase that invokes a Bun script (e.g. bun src/server/build.ts dist) — same code path the dev server uses at startup, so dev and Nix share one bundler implementation.
dontFixup = true and dontPatchShebangs = true — Bun apps don't have shebangs we care about, and the fixupPhase walking node_modules is pure overhead.
Client bundler (when SolidJS / JSX is involved)
- Bun's default JSX transform emits
React.createElement, which breaks SolidJS. A ~20-line solidJsxPlugin driving babel-preset-solid + babel-preset-typescript via Bun.build's plugin array is the working pattern.
- Bun.serve's HTML-import bundler does not honor
bunfig preload plugins — so Bun.serve+HMR is unavailable for SolidJS today. Worth flagging.
- For Tailwind v4: invoke
@tailwindcss/cli via createRequire(import.meta.url).resolve("@tailwindcss/cli/package.json") (NOT bunx, which falls back to a network fetch and fails in the Nix sandbox). The transitive @parcel/watcher dlopen of libstdc++ requires LD_LIBRARY_PATH = ${stdenv.cc.cc.lib}/lib:$LD_LIBRARY_PATH in the buildPhase.
Vendoring Nix-store source into node_modules
- For workspace-style packages that aren't on npm (e.g.
@kolu/surface), expose them through a Nix overlay (one per leaf, or via a mkPackage factory if N > 1), set *_KOLU_* env vars in nix/env.nix, and hydrate via a shell script: cp -rL "$src" node_modules/<dest>; chmod -R u+w …. Three callers per project (shellHook, justfile install, postBunNodeModulesInstallPhase).
Common pitfalls worth a heading
bun.lock + bun.nix both committed; CI gates on freshness.
bun2nix builds via juspay/bun2nix Cargo crates — current bun2nix flake pins old nixpkgs that fails to download crates from crates.io (User-Agent now mandatory, fetchurl path stuck with SSL_CERT_FILE=/no-cert-file.crt). Workaround: pre-download with proper UA and nix store add-file until the upstream pin bumps.
- Multi-system flake outputs via
eachSystem over [x86_64-linux, aarch64-linux, aarch64-darwin] so nix run github:… works without --system flag.
Example projects
- srid/anywhen — single-package app, no workspace; canonical anywhen-pattern reference.
- srid/drishti — workspace, two
@kolu/* packages hydrated, SolidJS + Tailwind v4 client; mostly extends the anywhen pattern.
Happy to draft an actual PR against the skill if the structure above looks right.
Suggestion to enrich skills/nix-typescript/SKILL.md with conventions that have crystallized around recent Bun-based projects (srid/anywhen, srid/drishti). The current skill is implicitly Node/tsx-shaped; the Bun path is now consistent enough across two real apps that the patterns belong as first-class guidance.
Conventions worth lifting
Flake + npins layout
juspay/bun2nix#rawflake.nixpkgsand any other Nix-store inputs (e.g.@kolu/surfacesource) are pinned vianpins→npins/sources.json. Coldnix developeval stays sub-second.lib.mkBun2nix { pkgs }from therawflakebranch lets you feed your own npins-pinnedpkgs, so bun2nix's transitive nixpkgs never enters your eval graph.flake.lockandnpins/sources.jsonend up as two independent pin surfaces (bun2nix's CLI build vs. the app's build). That partition is the property worth naming explicitly.Workspace + hoisted linker
bunfig.toml:[install] linker = "hoisted"is load-bearing whenever you hydrate any package source intonode_modules/from outside Bun (the workspace pattern, the Nix-store-symlink pattern). Isolated linker leaves each workspace package's deps in its ownnode_modules, breaking transitive resolution from the hydrated source.Build derivation
bun2nix.hookasnativeBuildInputs;bunDeps = bun2nix.fetchBunDeps { bunNix = ./bun.nix; }.dontUseBunBuild = trueto skip the hook's defaultbun build --compile(single-binary mode is wrong for server-plus-static-bundle apps).buildPhasethat invokes a Bun script (e.g.bun src/server/build.ts dist) — same code path the dev server uses at startup, so dev and Nix share one bundler implementation.dontFixup = trueanddontPatchShebangs = true— Bun apps don't have shebangs we care about, and the fixupPhase walkingnode_modulesis pure overhead.Client bundler (when SolidJS / JSX is involved)
React.createElement, which breaks SolidJS. A ~20-linesolidJsxPlugindrivingbabel-preset-solid+babel-preset-typescriptviaBun.build's plugin array is the working pattern.bunfigpreload plugins — so Bun.serve+HMR is unavailable for SolidJS today. Worth flagging.@tailwindcss/cliviacreateRequire(import.meta.url).resolve("@tailwindcss/cli/package.json")(NOTbunx, which falls back to a network fetch and fails in the Nix sandbox). The transitive@parcel/watcherdlopen oflibstdc++requiresLD_LIBRARY_PATH = ${stdenv.cc.cc.lib}/lib:$LD_LIBRARY_PATHin thebuildPhase.Vendoring Nix-store source into node_modules
@kolu/surface), expose them through a Nix overlay (one per leaf, or via amkPackagefactory if N > 1), set*_KOLU_*env vars innix/env.nix, and hydrate via a shell script:cp -rL "$src" node_modules/<dest>; chmod -R u+w …. Three callers per project (shellHook, justfile install,postBunNodeModulesInstallPhase).Common pitfalls worth a heading
bun.lock+bun.nixboth committed; CI gates on freshness.bun2nixbuilds viajuspay/bun2nixCargo crates — current bun2nix flake pins old nixpkgs that fails to download crates fromcrates.io(User-Agent now mandatory, fetchurl path stuck withSSL_CERT_FILE=/no-cert-file.crt). Workaround: pre-download with proper UA andnix store add-fileuntil the upstream pin bumps.eachSystemover[x86_64-linux, aarch64-linux, aarch64-darwin]sonix run github:…works without--systemflag.Example projects
@kolu/*packages hydrated, SolidJS + Tailwind v4 client; mostly extends the anywhen pattern.Happy to draft an actual PR against the skill if the structure above looks right.