mcpField
P
package-npm-nix

package-npm-nix

github.com/ypares開発3052 回インストール1 日前に更新
リポジトリ

package-npm-nix について

package-npm-nix は、npmベースのコマンドラインツールをNixパッケージ化するスキルです。npmの公開済みビルドとソースからのビルドに対応し、依存関係、ハッシュ、メタデータ、実行ファイルの扱いを案内します。

主な機能

  1. 01ビルド済みとソースビルドの方式選択
  2. 02npmとソース依存関係のハッシュ管理
  3. 03Nixパッケージのメタデータと由来の設定
  4. 04shebang、ネイティブモジュール、実行ファイルの処理

活用シーン

  1. 01npm CLIツールのNixパッケージ作成
  2. 02Bun製ツールのNixパッケージ化
  3. 03依存ハッシュやビルドエラーの調査
  4. 04再現性と実行状態の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/ypares/agent-skills --skill package-npm-nix

インストール後、Claude Code で/package-npm-nixを入力して読み込まれたか確認します。

詳細

npm ベースの CLI ツール向けに Nix パッケージを作成します。npm レジストリから取得したビルド済みパッケージと、適切な依存関係管理を伴うソースビルドの両方を扱います。このスキルでは、Nix 環境で JavaScript/TypeScript/Bun ツールを取得、ビルド、パッケージ化するためのパターンを紹介します。

<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 レジストリから取得したコンパイル済み JS
  • binaryNativeCode:ネイティブバイナリ(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 が取得した内容と一致しません。

解決方法:

  1. Nix のエラーに「got: sha256-XYZ...」と表示されます
  2. そのハッシュを導出式にコピーします
  3. 再ビルドします

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」

ソース内のロックファイルが、キャッシュされた依存関係と一致しません。

次のような場合に発生します:

  • ソースのバージョンを更新したものの、依存関係のハッシュを更新していない
  • ソースリポジトリにコミットされていないロックファイルの変更がある

解決方法:

  1. 新しいバージョンに合わせてソースのハッシュを更新する
  2. 依存関係のハッシュに lib.fakeHash を設定する
  3. ビルドして正しい依存関係のハッシュを取得する
  4. 依存関係のハッシュを更新する
  5. 再ビルドする </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>

コメント