No description
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
natsukium 9d5559ef24
Build Node.js and the pinned package manager from package.json
Keeping the Node.js and package manager versions in package.json, .node-version
and Nix in sync by hand drifts. Deriving the Nix packages from the files the
JavaScript tooling already reads makes package.json the single source of truth.

Package managers are fetched at exactly the pinned version rather than mapped to
the nearest nixpkgs release, because pnpm and Corepack download the pinned release
themselves whenever the version on PATH differs, defeating the pin. The
+sha512.<hex> suffix written by `corepack use` is the digest of the same artifact
Corepack downloads, so it doubles as the fetch hash; specs without a hash Nix can
verify require an explicit one instead of falling back.

Node.js does not self-update, so a nixpkgs nodejs_<major> is the default and a
patch mismatch only warns; per-system hashes opt into the official binary for an
exact match.

The overlay adds pkgs.nodeToolchain instead of overriding nodejs or pnpm, which
would rebuild their nixpkgs dependents and shift pnpm dependency hashes.

Assisted-by: Claude Code:Opus 5.5
2026-10-23 15:20:59 +09:00
lib Build Node.js and the pinned package manager from package.json 2026-10-23 15:20:59 +09:00
pkgs Build Node.js and the pinned package manager from package.json 2026-10-23 15:20:59 +09:00
tests Build Node.js and the pinned package manager from package.json 2026-10-23 15:20:59 +09:00
flake.lock Build Node.js and the pinned package manager from package.json 2026-10-23 15:20:59 +09:00
flake.nix Build Node.js and the pinned package manager from package.json 2026-10-23 15:20:59 +09:00
README.md Build Node.js and the pinned package manager from package.json 2026-10-23 15:20:59 +09:00

node-toolchain.nix

Builds Node.js and the package manager pinned in package.json#packageManager with Nix, so the versions live in one place and nothing downloads them at run time.

Mismatched versions cannot fall back to whatever nixpkgs ships: pnpm (>= 9.7) and Corepack read packageManager and fetch the pinned release themselves when the version on PATH differs, so every artifact is fetched at the exact pinned version and verified by hash.

Usage

{
  inputs = {
    nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
    node-toolchain.url = "github:natsukium/node-toolchain.nix";
  };

  outputs =
    { nixpkgs, node-toolchain, ... }:
    let
      system = "aarch64-darwin";
      pkgs = import nixpkgs {
        inherit system;
        overlays = [ (node-toolchain.lib.mkOverlay { src = ./.; }) ];
      };
    in
    {
      devShells.${system}.default = pkgs.mkShell {
        packages = pkgs.nodeToolchain.packages;
      };
    };
}

pkgs.nodeToolchain provides:

Attribute Value
nodejs Node.js
packageManager the pinned package manager, or null
pnpm / yarn / npm the same package manager under its own name
packages [ packageManager nodejs ], ready for mkShell

The overlay adds nodeToolchain instead of replacing nodejs or pnpm, which would rebuild or change the dependency hashes of every nixpkgs package that uses them. lib.mkToolchain { pkgs; src; ... } returns the same set without an overlay.

The pnpm derivation has nodejs-slim and version, so it can be passed to fetchPnpmDeps.

Package manager

packageManager Fetched from
pnpm@<12 registry.npmjs.org/pnpm, run with Node.js
pnpm@>=12 the wrapper above plus the native @pnpm/exe.*
yarn@1 registry.yarnpkg.com/yarn
yarn@>=2 repo.yarnpkg.com/<version>/.../yarn.js
npm registry.npmjs.org/npm

These are the artifacts Corepack downloads, so the +sha512.<hex> suffix that corepack use writes is used as the hash directly. Without it (or with an algorithm Nix cannot verify, such as sha224), pass the hash yourself:

mkOverlay {
  src = ./.;
  hashes.packageManager = "sha512-...";
}

pnpm 12 and later is a native executable that its npm wrapper downloads on first run, so it also needs one hash per system, the dist.integrity of @pnpm/exe.<target> (darwin-arm64, linux-arm64-musl, linux-x64-musl):

hashes.pnpmExe = {
  aarch64-darwin = "sha512-...";
  x86_64-linux = "sha512-...";
};

Node.js

The version is read from the first of these that exists:

  1. package.json#devEngines.runtime (name = "node")
  2. .node-version
  3. .nvmrc
  4. package.json#volta.node

Only plain versions (22, 22.1, v22.1.0) are accepted. Pass nodeVersion to override. With none of them, pkgs.nodejs is used.

By default the version selects nodejs_<major> from nixpkgs, with a warning when its minor or patch version differs from the requested one. To match the version exactly, pass the sha256 of the official binary tarball for each system, as listed in https://nodejs.org/dist/v<version>/SHASUMS256.txt:

hashes.nodejs = {
  aarch64-darwin = "sha256-...";
  x86_64-linux = "sha256-...";
};