-
Parse the GitHub URL: Extract the owner and repo name from the provided GitHub URL (e.g., https://github.com/owner/repo).
-
Fetch repo information: Use gh repo view owner/repo --json description,licenseInfo,latestRelease,name to get:
- Repository description
- License information
- Latest release version
- Repo name (used as pname)
-
Inspect package.json: Fetch the package.json from the repo to determine:
- The package name
- Whether it has a CLI entry point (
bin field)
- Dependencies vs devDependencies
- The
type field (module vs commonjs)
gh api repos/<owner>/<repo>/contents/package.json --jq '.content' | base64 -d
-
Determine if a CLI wrapper is needed: If the package is a library (no bin field in package.json), you need to create a cli.ts wrapper. If it already has a CLI entry point, note the entry file path.
-
Get the source hash:
nix-prefetch-url --unpack "https://github.com/<owner>/<repo>/archive/refs/tags/<rev>.tar.gz"
Then convert to SRI format:
nix hash to-sri --type sha256 <hash>
Check the repo's tags to determine if versions are prefixed with v (e.g., v1.0.0 vs 1.0.0).
-
Generate bun.nix: Clone the repo at the target version, install production dependencies only, and generate the Nix lockfile:
cd $TMPDIR
git clone --branch <tag> --depth 1 https://github.com/<owner>/<repo>.git
cd <repo>
IMPORTANT: Remove devDependencies from package.json before installing. DevDependencies (linters, type checkers, test frameworks, etc.) are not needed at runtime, and packages like @biomejs/biome have platform-specific postinstall scripts that will hang indefinitely in the Nix sandbox with no network access.
rm -f package-lock.json bun.lock
bun install
nix run github:nix-community/bun2nix -- -o bun.nix
Verify the generated bun.nix does NOT contain devDependencies (e.g., grep biome bun.nix should return nothing if biome was a devDep). Also verify no empty hashes (hash = "") exist — empty hashes indicate the lockfile was migrated from package-lock.json without integrity data.
Copy the generated bun.nix to derivations/<pname>/bun.nix.
-
Create CLI wrapper (if needed): Create derivations/<pname>/cli.ts that wraps the library's API. Key considerations:
- Import from relative source paths (e.g.,
"./src/index.ts") since we build from within the source tree
- Wrap all async code in an
async function main() — top-level await does not work with bun build --compile
- Call
main() at the end of the file
- Handle stdin input and file arguments
- Add
--help flag support
-
Create the derivation: Create derivations/<pname>/default.nix using bun2nix.writeBunApplication:
{ lib
, bun2nix
, fetchFromGitHub
}:
let
version = "<version>";
src = fetchFromGitHub {
owner = "<owner>";
repo = "<repo>";
rev = "v${version}";
hash = "<source-hash>";
};
in
bun2nix.writeBunApplication {
pname = "<pname>";
inherit version src;
bunDeps = bun2nix.fetchBunDeps {
bunNix = ./bun.nix;
};
postUnpack = ''
cp ${./cli.ts} $sourceRoot/cli.ts
'';
dontUseBunBuild = true;
dontRunLifecycleScripts = true;
startScript = ''
bun run cli.ts "$@"
'';
doCheck = false;
meta = with lib; {
description = "<description>";
homepage = "https://github.com/<owner>/<repo>";
license = licenses.<license>;
maintainers = with maintainers; [ ];
platforms = platforms.unix;
mainProgram = "<pname>";
};
}
Important notes on the template:
- Always use
bun2nix.writeBunApplication, NOT bun2nix.mkDerivation — bun build --compile produces 0-byte binaries in the Nix sandbox
- Always set
dontUseBunBuild = true — we run the TypeScript source directly with Bun at runtime
- Always set
dontRunLifecycleScripts = true — lifecycle scripts (postinstall) can hang in the Nix sandbox when packages try to download platform binaries with no network access
bunDeps must use bun2nix.fetchBunDeps { bunNix = ./bun.nix; } — do NOT pass bunNix directly
postUnpack copies the CLI wrapper into the source tree before bun install runs
startScript is the shell command that runs the entry point; Bun is automatically on the PATH
- If the package already has a CLI (a
bin field in package.json), adjust startScript to run that entry point instead and omit postUnpack/cli.ts
-
Stage new files: New files must be git added before Nix can see them:
git add derivations/<pname>
-
Build and verify:
nix build .#<pname>
Ensure the build completes and the binary works.
-
Add to flake.nix: Add the package to the my-packages overlay:
<pname> = final.callPackage (self + "/derivations/<pname>") { };
Also add to the packages output:
<pname> = pkgs.<pname>;
-
Add to home.packages (if requested): Add <pname> to the packages list in home/default.nix.
-
Summary: Report what was created (package name, version, file path) and remind to run sudo darwin-rebuild switch --flake . to apply changes.