package-npm-nix について
package-npm-nix は、npmベースのコマンドラインツールをNixパッケージ化するスキルです。npmの公開済みビルドとソースからのビルドに対応し、依存関係、ハッシュ、メタデータ、実行ファイルの扱いを案内します。
主な機能
- 01ビルド済みとソースビルドの方式選択
- 02npmとソース依存関係のハッシュ管理
- 03Nixパッケージのメタデータと由来の設定
- 04shebang、ネイティブモジュール、実行ファイルの処理
活用シーン
- 01npm CLIツールのNixパッケージ作成
- 02Bun製ツールのNixパッケージ化
- 03依存ハッシュやビルドエラーの調査
- 04再現性と実行状態の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ ypares/ agent-skills --skill package-npm-nixインストール後、Claude Code で/package-npm-nixを入力して読み込まれたか確認します。
詳細
<quick_start> <pre_built_from_npm> npm でビルド済みの状態で公開されているツールの場合(最も速い方法):
{
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;
};
}
ハッシュを取得します:
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> 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>
**ビルド方法を判断する**:npm パッケージを確認します:
# 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/
ビルド済みファイルを含む dist/ ディレクトリがある場合:
→ ビルド済みパッケージの方法を使います(よりシンプルで高速です)
src/ しかない場合、または package.json にビルドスクリプトがある場合:
→ ソースビルドの方法を使います
package.json で次の項目を確認します:
"bin"フィールド:提供される実行ファイルを示します"type": "module":ES モジュールです(最近のパッケージでは一般的です)"scripts":ビルドコマンドです(ソースビルドが必要であることを示します)- ランタイム:bun、node、または特定のバージョン要件を確認します </step_1_identify_package_type>
<step_2_fetch_hashes> ソースと依存関係のハッシュを取得する:
ビルド済みパッケージの場合:
# 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...
ソースビルドの場合:
# 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> パッケージ構成を作成する:
mkdir -p packages/tool-name
完全な導出式を含む packages/tool-name/package.nix を作成します(quick_start を参照)。
packages/tool-name/default.nix を作成します:
{ pkgs }: pkgs.callPackage ./package.nix { }
この 2 ファイルのパターンにより、パッケージを単独で使用することも、flake に統合することもできます。 </step_3_create_package_files>
<step_4_handle_special_cases> よくある追加要件:
WASM ファイルやその他のアセット:
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"
'';
複数の実行ファイル:
# 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 command
プラットフォーム固有のバイナリ:
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
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>
<metadata_requirements> <essential_fields> すべてのパッケージに完全なメタデータを設定する必要があります:
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> パッケージ化するものに応じて選択します:
fromSource:導出式の実行中に TypeScript/ソースからビルドbinaryBytecode:npm レジストリから取得したコンパイル済み JSbinaryNativeCode:ネイティブバイナリ(Rust、Go、Bun でコンパイルされたもの)
これはセキュリティ監査と再現性に関する期待値に影響します。 </source_provenance_guide> </metadata_requirements>
<common_patterns> <shebang_replacement> 再現性のため、必ずシバンを置き換えます:
# 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" \;
--replace-quiet フラグを指定すると、パターンが見つからない場合の警告を抑制できます。
</shebang_replacement>
<native_dependencies> ネイティブモジュール(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
];
Linux では autoPatchelf が自動的に実行され、.so ファイルの RPATH を修正します。
</native_dependencies>
<bun_compiled_binaries> Bun でコンパイルされた実行ファイルは strip しないでください:
# Bun embeds JavaScript in the binary
dontStrip = true;
strip すると埋め込み JS が削除され、プログラムが動作しなくなります。 </bun_compiled_binaries>
<checking_tarball_contents> npm パッケージの構造を調べる:
# 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 modules
package.json を確認して、正しいエントリーポイントを見つけます。 </checking_tarball_contents> </common_patterns>
<anti_patterns> <avoid_these> 次のことはしないでください:
❌ node のパスをハードコードする:
# Bad
"#!/usr/bin/node" # Won't work on NixOS
✅ substituteInPlace を使う:
# Good
substituteInPlace $out/bin/tool \
--replace-quiet "#!/usr/bin/env node" "#!${nodejs}/bin/node"
❌ ハッシュ検証を省略する:
# Bad - insecure
hash = lib.fakeHash;
✅ 実際のハッシュを取得する:
# Good - reproducible and secure
hash = "sha256-actual-hash-here";
❌ 実行可能にするのを忘れる:
# Bad - won't run
cp $src/dist/cli.js $out/bin/tool
✅ 実行可能ビットを設定する:
# Good
cp $src/dist/cli.js $out/bin/tool
chmod +x $out/bin/tool
❌ Bun バイナリを strip する:
# Bad - breaks Bun-compiled executables
# (default behavior strips binaries)
✅ strip を無効にする:
# Good
dontStrip = true;
</avoid_these> </anti_patterns>
**エラー:「hash mismatch in fixed-output derivation」**指定したハッシュが、Nix が取得した内容と一致しません。
解決方法:
- Nix のエラーに「got: sha256-XYZ...」と表示されます
- そのハッシュを導出式にコピーします
- 再ビルドします
fetchBunDeps の場合、初回にこれが発生するのは想定どおりです。エラー出力から正しいハッシュを取得してください。
</hash_mismatch>
<missing_executable> エラー:ビルド後にバイナリが見つからない
次を確認します:
# 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.build
ビルドの出力先が想定と異なる場合があります。
<elf_interpreter_error> エラー:「No such file or directory」バイナリの実行時(Linux)
バイナリのネイティブ依存関係を解決するには、ELF のパッチ適用が必要です。
解決方法:
nativeBuildInputs = [
autoPatchelfHook
];
buildInputs = [
stdenv.cc.cc.lib
];
ネイティブアドオンを含む node_modules の場合:
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> エラー:「bun.lock mismatch」
ソース内のロックファイルが、キャッシュされた依存関係と一致しません。
次のような場合に発生します:
- ソースのバージョンを更新したものの、依存関係のハッシュを更新していない
- ソースリポジトリにコミットされていないロックファイルの変更がある
解決方法:
- 新しいバージョンに合わせてソースのハッシュを更新する
- 依存関係のハッシュに
lib.fakeHashを設定する - ビルドして正しい依存関係のハッシュを取得する
- 依存関係のハッシュを更新する
- 再ビルドする </bun_lock_mismatch>
-
nix build .#package-nameが成功する -
./result/bin/tool --versionが動作する -
./result/bin/tool --helpが動作する -
nix flake checkが成功する -
meta.descriptionが明確かつ簡潔である -
meta.homepageがプロジェクトのサイトを指している -
meta.licenseが正しい -
meta.sourceProvenanceがパッケージ化した内容と一致している -
meta.mainProgramが設定されている -
meta.platformsがツールに適切である - すべてのハッシュが実際の値である(
lib.fakeHashを使用していない) - シバンが /usr/bin ではなく Nix ストアパスを使用している
- ファイルが
nix fmtでフォーマットされている </build_checklist>
<testing_on_other_platforms>
Linux しか使えないにもかかわらず、パッケージの platforms.all を指定する場合:
macOS/ARM 環境のメンテナーにテストを依頼するか、次の対応を検討してください。
- テストできる範囲に基づいて、対応プラットフォームを控えめに指定する
- 他のプラットフォームでは未テストであることをパッケージに記載する
- CI や他のコントリビューターによるプラットフォーム対応の拡張に委ねる </testing_on_other_platforms>
<success_criteria> 適切にパッケージ化された npm ツールは、次の条件を満たします。
- 警告やエラーのないクリーンなビルド
result/bin/に実行可能ファイルがある- メタデータが完全かつ正確である
- ソースの来歴が適切に分類されている
- すべての依存関係が解決されている(ライブラリの不足がない)
- 再現可能なビルド(実際のハッシュを使用し、ビルド中にネットワークへアクセスしない)
- Nix のパッケージング規約に従っている(シバンのパッチ適用、適切なフェーズ) </success_criteria>