package-npm-nix
Skill30 starsChanged 10 months agoMITView on GitHub
Package npm/TypeScript/Bun CLI tools for Nix. Use when creating Nix derivations for JavaScript/TypeScript tools from npm registry or GitHub sources, handling pre-built packages or source builds with dependency management.
In this repository13 other files it gives its agents.
- AGENTS.mdAGENTS.md
- cursor-agent-supervisorSkill
- github-pr-workflowSkill
- jj-todo-workflowSkill
- nix-profile-managerSkill
- nushell-plugin-builderSkill
- nushell-usageSkill
- read-bin-docsSkill
- research-assistantSkill
- searxng-searchSkill
- textual-builderSkill
- typst-writerSkill
- working-with-jjSkill
The file
We split this skill into the rules it gives an agent
12 rules. About 0 are common, 7 are distinctive, and 5 are very specific to this project.
≈0
Common · estimate
Many files like this have a rule of this kind.
≈7
Distinctive · estimate
This author's own choices. Worth a look.
5
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 7
Create Nix packages for npm-based CLI tools, covering both pre-built packages from npm registry and source builds with proper dependency …
- Distinctive: line 180
Check the npm package:
- Very specific: line 187
If dist/ directory exists with built files: → Use pre-built approach (simpler, faster)
- Very specific: line 190
If only src/ exists or package.json has build scripts: → Use source build approach
- Very specific: line 234
Create packages/tool-name/package.nix with full derivation (see quick_start).
- Distinctive: line 316
Every package must have complete metadata:
- Very specific: line 415
Check package.json to find the correct entry point.
- Distinctive: line 543
Update source hash to match new version
- Distinctive: line 553
Before considering the package done:
- Very specific: line 571
If you only have Linux but package claims platforms.all:
- Distinctive: line 573
Consider asking maintainers with macOS/ARM to test, or:
- Distinctive: line 587
All dependencies resolved (no missing libraries)
≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
<objective>
Create Nix packages for npm-based CLI tools, covering both pre-built packages from npm registry and source builds with proper dependency management. This skill provides patterns for fetching, building, and packaging JavaScript/TypeScript/Bun tools in Nix environments.
</objective><quick_start> <pre_built_from_npm> For tools already built and published to npm (fastest approach):
{
lib,
stdenv,
fetchzip,
nodejs,
}:
stdenv.mkDerivation rec {
pname = "tool-name";
version = "1.0.0";
src = fetchzip {
url = "https://registry.npmjs.org/${pname}/-/${pname}-${version}.tgz";
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
};
nativeBuildInputs = [ nodejs ];
installPhase = ''
runHook preInstall
mkdir -p $out/bin
cp $src/dist/cli.js $out/bin/tool-name
chmod +x $out/bin/tool-name
# Fix shebang
substituteInPlace $out/bin/tool-name \
--replace-quiet "#!/usr/bin/env node" "#!${nodejs}/bin/node"
runHook postInstall
'';
meta = with lib; {
description = "Tool description";
homepage = "https://github.com/org/repo";
license = licenses.mit;
sourceProvenance = with lib.sourceTypes; [ binaryBytecode ];
maintainers = with maintainers; [ ];
mainProgram = "tool-name";
platforms = platforms.all;
};
}Get the hash:
nix-prefetch-url --unpack https://registry.npmjs.org/tool-name/-/tool-name-1.0.0.tgz
# Convert to SRI format:
nix hash convert --to sri --hash-algo sha256 <hash-output></pre_built_from_npm>
<source_build_with_bun> For tools that need to be built from source using Bun:
{
lib,
stdenv,
stdenvNoCC,
fetchFromGitHub,
bun,
makeBinaryWrapper,
nodejs,
autoPatchelfHook,
}:
let
fetchBunDeps =
{ src, hash, ... }@args:
stdenvNoCC.mkDerivation {
pname = args.pname or "${src.name or "source"}-bun-deps";
version = args.version or src.version or "unknown";
inherit src;
nativeBuildInputs = [ bun ];
buildPhase = ''
export HOME=$TMPDIR
export npm_config_ignore_scripts=true
bun install --no-progress --frozen-lockfile --ignore-scripts
'';
installPhase = ''
mkdir -p $out
cp -R ./node_modules $out
cp ./bun.lock $out/
'';
dontFixup = true;
outputHash = hash;
outputHashAlgo = "sha256";
outputHashMode = "recursive";
};
version = "1.0.0";
src = fetchFromGitHub {
owner = "org";
repo = "repo";
rev = "v${version}";
hash = "sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=";
};
node_modules = fetchBunDeps {
pname = "tool-name-bun-deps";
inherit version src;
hash = "sha256-BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=";
};
in
stdenv.mkDerivation rec {
pname = "tool-name";
inherit version src;
nativeBuildInputs = [
bun
nodejs
makeBinaryWrapper
autoPatchelfHook
];
buildInputs = [
stdenv.cc.cc.lib
];
buildPhase = ''
# Verify lockfile match
diff -q ./bun.lock ${node_modules}/bun.lock || exit 1
# Copy and patch node_modules
cp -R ${node_modules}/node_modules .
chmod -R u+w node_modules
patchShebangs node_modules
autoPatchelf node_modules
export HOME=$TMPDIR
export npm_config_ignore_scripts=true
bun run build
'';
installPhase = ''
mkdir -p $out/bin
cp dist/tool-name $out/bin/tool-name
chmod +x $out/bin/tool-name
'';
dontStrip = true;
meta = with lib; {
description = "Tool description";
homepage = "https://github.com/org/repo";
license = licenses.mit;
sourceProvenance = with lib.sourceTypes; [ fromSource ];
maintainers = with maintainers; [ ];
mainProgram = "tool-name";
platforms = [ "x86_64-linux" ];
};
}</source_build_with_bun> </quick_start>
<workflow>
<step_1_identify_package_type>
**Determine build approach**:Check the npm package:
# Download and inspect
nix-prefetch-url --unpack https://registry.npmjs.org/package/-/package-1.0.0.tgz
ls -la /nix/store/<hash>-package-1.0.0.tgz/If dist/ directory exists with built files:
→ Use pre-built approach (simpler, faster)
If only src/ exists or package.json has build scripts:
→ Use source build approach
Check package.json for:
"bin"field: Shows what executables are provided"type": "module": ES modules (common in modern packages)"scripts": Build commands (indicates source build needed)- Runtime: Look for bun, node, or specific version requirements </step_1_identify_package_type>
<step_2_fetch_hashes> Get source and dependency hashes:
For pre-built packages:
# Fetch npm tarball
nix-prefetch-url --unpack https://registry.npmjs.org/pkg/-/pkg-1.0.0.tgz
# Output: 1abc... (base32 format)
# Convert to SRI format
nix hash convert --to sri --hash-algo sha256 1abc...
# Output: sha256-xyz...For source builds:
# Get GitHub source hash
nix-prefetch-url --unpack https://github.com/org/repo/archive/v1.0.0.tar.gz
# Get dependencies hash (requires iteration):
# 1. Use lib.fakeHash in fetchBunDeps
# 2. Try to build
# 3. Nix will show expected hash in error
# 4. Update hash and rebuild</step_2_fetch_hashes>
<step_3_create_package_files> Create package structure:
mkdir -p packages/tool-nameCreate packages/tool-name/package.nix with full derivation (see quick_start).
Create packages/tool-name/default.nix:
{ pkgs }: pkgs.callPackage ./package.nix { }This two-file pattern allows the package to be used standalone or integrated into a flake. </step_3_create_package_files>
<step_4_handle_special_cases> Common additional requirements:
WASM files or other assets:
installPhase = ''
mkdir -p $out/bin
cp $src/dist/cli.js $out/bin/tool
cp $src/dist/*.wasm $out/bin/ # Copy WASM alongside
chmod +x $out/bin/tool
substituteInPlace $out/bin/tool \
--replace-quiet "#!/usr/bin/env node" "#!${nodejs}/bin/node"
'';Multiple executables:
# package.json might have:
# "bin": {
# "tool": "dist/cli.js",
# "tool-admin": "dist/admin.js"
# }
installPhase = ''
mkdir -p $out/bin
for exe in tool tool-admin; do
cp $src/dist/$exe.js $out/bin/$exe
chmod +x $out/bin/$exe
substituteInPlace $out/bin/$exe \
--replace-quiet "#!/usr/bin/env node" "#!${nodejs}/bin/node"
done
'';
meta.mainProgram = "tool"; # Primary commandPlatform-specific binaries:
meta = {
platforms = [ "x86_64-linux" ]; # Bun-compiled binaries often Linux-only
# or
platforms = platforms.all; # Pure JS works everywhere
};</step_4_handle_special_cases>
<step_5_test_build> Build and test:
# Build
nix build .#tool-name
# Test the binary
./result/bin/tool-name --version
./result/bin/tool-name --help
# Check dependencies (Linux)
ldd ./result/bin/tool-name # Should show all deps resolved
# Format
nix fmt
# Run flake checks
nix flake check</step_5_test_build>
</workflow>
<metadata_requirements> <essential_fields> Every package must have complete metadata:
meta = with lib; {
description = "Clear, concise description";
homepage = "https://project-homepage.com";
changelog = "https://github.com/org/repo/releases"; # Optional but nice
license = licenses.mit; # or licenses.unfree for proprietary
sourceProvenance = with lib.sourceTypes; [
fromSource # Built from source
# or
binaryBytecode # Pre-built JS/TS (npm dist/)
# or
binaryNativeCode # Compiled binaries
];
maintainers = with maintainers; [ ]; # Empty OK for community packages
mainProgram = "binary-name";
platforms = platforms.all; # or specific: [ "x86_64-linux" ]
};</essential_fields>
<source_provenance_guide> Choose based on what you're packaging:
fromSource: Built from TypeScript/source during derivationbinaryBytecode: Pre-compiled JS from npm registrybinaryNativeCode: Native binaries (Rust, Go, Bun-compiled)
This affects security auditing and reproducibility expectations. </source_provenance_guide> </metadata_requirements>
<common_patterns> <shebang_replacement> Always replace shebangs for reproducibility:
# Single file
substituteInPlace $out/bin/tool \
--replace-quiet "#!/usr/bin/env node" "#!${nodejs}/bin/node"
# Multiple files
find $out/bin -type f -exec substituteInPlace {} \
--replace-quiet "#!/usr/bin/env node" "#!${nodejs}/bin/node" \;The --replace-quiet flag suppresses warnings if pattern not found.
</shebang_replacement>
<native_dependencies> Handle native modules (like sqlite, sharp):
nativeBuildInputs = [
bun
nodejs
makeBinaryWrapper
autoPatchelfHook # Linux: patches ELF binaries
];
buildInputs = [
stdenv.cc.cc.lib # Provides libgcc_s.so.1, libstdc++.so.6
];
autoPatchelfIgnoreMissingDeps = [
"libc.musl-x86_64.so.1" # Ignore musl if not available
];autoPatchelf runs automatically on Linux, fixing RPATH for .so files.
</native_dependencies>
<bun_compiled_binaries> Don't strip Bun-compiled executables:
# Bun embeds JavaScript in the binary
dontStrip = true;Stripping would remove the embedded JS, breaking the program. </bun_compiled_binaries>
<checking_tarball_contents> Inspect npm package structure:
# After nix-prefetch-url
ls -la /nix/store/*-pkg-1.0.0.tgz/
# Common layouts:
# dist/cli.js → Pre-built, use directly
# dist/index.js → Main entry, check package.json "bin"
# src/index.ts → Source only, need to build
# lib/ → Built CommonJS
# esm/ → Built ES modulesCheck package.json to find the correct entry point. </checking_tarball_contents> </common_patterns>
<anti_patterns> <avoid_these> Don't do this:
❌ Hardcode node paths:
# Bad
"#!/usr/bin/node" # Won't work on NixOS✅ Use substituteInPlace:
# Good
substituteInPlace $out/bin/tool \
--replace-quiet "#!/usr/bin/env node" "#!${nodejs}/bin/node"❌ Skip hash verification:
# Bad - insecure
hash = lib.fakeHash;✅ Get real hash:
# Good - reproducible and secure
hash = "sha256-actual-hash-here";❌ Forget to make executable:
# Bad - won't run
cp $src/dist/cli.js $out/bin/tool✅ Set executable bit:
# Good
cp $src/dist/cli.js $out/bin/tool
chmod +x $out/bin/tool❌ Strip Bun binaries:
# Bad - breaks Bun-compiled executables
# (default behavior strips binaries)✅ Disable stripping:
# Good
dontStrip = true;</avoid_these> </anti_patterns>
<troubleshooting>
<hash_mismatch>
**Error: "hash mismatch in fixed-output derivation"**The hash you provided doesn't match what Nix fetched.
Solution:
- Nix error shows "got: sha256-XYZ..."
- Copy that hash into your derivation
- Rebuild
For fetchBunDeps, this is expected the first time—use the error output to get the correct hash.
</hash_mismatch>
<missing_executable> Error: Binary not found after build
Check:
# List what was actually built
ls -R result/
# Check package.json "bin" field
cat /nix/store/*-source/package.json | jq .bin
# Check build output location
cat /nix/store/*-source/package.json | jq .scripts.buildThe build might output to a different directory than expected. </missing_executable>
<elf_interpreter_error> Error: "No such file or directory" when running binary (Linux)
The binary needs ELF patching for native dependencies.
Solution:
nativeBuildInputs = [
autoPatchelfHook
];
buildInputs = [
stdenv.cc.cc.lib
];For node_modules with native addons:
buildPhase = ''
cp -R ${node_modules}/node_modules .
chmod -R u+w node_modules
autoPatchelf node_modules # Patch .node files
'';</elf_interpreter_error>
<bun_lock_mismatch> Error: "bun.lock mismatch"
The lockfile in your source doesn't match the cached dependencies.
This happens when:
- Source version updated but dependency hash not updated
- Source repo has uncommitted lockfile changes
Solution:
- Update source hash to match new version
- Set dependency hash to
lib.fakeHash - Build to get correct dependency hash
- Update dependency hash
- Rebuild
</bun_lock_mismatch>
</troubleshooting>
<validation>
<build_checklist>
Before considering the package done:nix build .#package-namesucceeds./result/bin/tool --versionworks./result/bin/tool --helpworksnix flake checkpassesmeta.descriptionis clear and concisemeta.homepagepoints to project sitemeta.licenseis correctmeta.sourceProvenancematches what you packagedmeta.mainProgramis setmeta.platformsis appropriate for the tool- All hashes are real (no
lib.fakeHash) - Shebangs use Nix store paths, not /usr/bin
- File is formatted with
nix fmt</build_checklist>
<testing_on_other_platforms>
If you only have Linux but package claims platforms.all:
Consider asking maintainers with macOS/ARM to test, or:
- Mark platforms conservatively based on what you can test
- Note in package that other platforms are untested
- Let CI or other contributors expand platform support
</testing_on_other_platforms>
</validation>
<success_criteria> A well-packaged npm tool has:
- Clean build with no warnings or errors
- Working executable in
result/bin/ - Complete and accurate metadata
- Proper source provenance classification
- All dependencies resolved (no missing libraries)
- Reproducible builds (real hashes, no network access during build)
- Follows Nix packaging conventions (shebang patching, proper phases) </success_criteria>
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

