{ lib, stdenvNoCC, callPackage, jq, moreutils, cacert, makeSetupHook, pnpm, pnpm-fixup-state-db, sqlite, writableTmpDirAsHomeHook, yq, zstd, }: let pnpmLatest = pnpm; supportedFetcherVersions = [ 3 # Build a reproducible tarball. See https://github.com/NixOS/nixpkgs/pull/469950 4 # Dump SQLite database to an SQL file. See https://github.com/NixOS/nixpkgs/pull/522703 ]; in { fetchPnpmDeps = lib.makeOverridable ( { hash ? "", pname, pnpm ? pnpmLatest, pnpmWorkspaces ? [ ], prePnpmInstall ? "", pnpmInstallFlags ? [ ], fetcherVersion ? null, ... }@args: let args' = removeAttrs args [ "hash" "pname" ]; hash' = if hash != "" then { outputHash = hash; } else { outputHash = ""; outputHashAlgo = "sha256"; }; filterFlags = lib.map (package: "--filter=${package}") pnpmWorkspaces; pnpm-fixup-state-db' = pnpm-fixup-state-db.override { inherit (pnpm) nodejs-slim; }; in assert fetcherVersion != null || throw "fetchPnpmDeps: `fetcherVersion` is not set, see https://nixos.org/manual/nixpkgs/stable/#javascript-pnpm-fetcherVersion."; assert !(fetcherVersion == 1 || fetcherVersion == 2) || throw "fetchPnpmDeps: `fetcherVersion = ${toString fetcherVersion}` was removed in the 26.11 release. Please migrate `${pname}` to `fetcherVersion = 4` and regenerate the hash. See https://nixos.org/manual/nixpkgs/stable/#javascript-pnpm-fetcherVersion."; assert builtins.elem fetcherVersion supportedFetcherVersions || throw "fetchPnpmDeps `fetcherVersion` is not set to a supported value (${lib.concatStringsSep ", " (map toString supportedFetcherVersions)}), see https://nixos.org/manual/nixpkgs/stable/#javascript-pnpm-fetcherVersion."; assert !(fetcherVersion == 3 && lib.versionAtLeast pnpm.version "11.0.0") || throw "fetchPnpmDeps `fetcherVersion = 3` is no longer supported for `pnpm_11` or newer. Please upgrade to the latest, see https://nixos.org/manual/nixpkgs/stable/#javascript-pnpm-fetcherVersion."; stdenvNoCC.mkDerivation ( finalAttrs: ( args' // { name = "${pname}-pnpm-deps"; nativeBuildInputs = [ cacert jq moreutils pnpm # from args pnpm.nodejs-slim pnpm-fixup-state-db' sqlite writableTmpDirAsHomeHook yq zstd ] ++ args.nativeBuildInputs or [ ]; impureEnvVars = lib.fetchers.proxyImpureEnvVars ++ [ "NIX_NPM_REGISTRY" ] ++ args.impureEnvVars or [ ]; installPhase = '' runHook preInstall versionAtLeast () { local cur_version=$1 min_version=$2 printf "%s\0%s" "$min_version" "$cur_version" | sort -zVC } lockfileVersion="$(yq -r .lockfileVersion pnpm-lock.yaml)" if [[ ''${lockfileVersion:0:1} -gt ${lib.versions.major pnpm.version} ]]; then echo "ERROR: lockfileVersion $lockfileVersion in pnpm-lock.yaml is too new for the provided pnpm version ${lib.versions.major pnpm.version}!" exit 1 fi # The pnpm store is bundled into a compressed tarball within $out, # without distributing the uncompressed store files. mkdir $out storePath=$(mktemp -d) pushd "$HOME" pnpmVersion=$(pnpm --version) if versionAtLeast "$pnpmVersion" "11"; then # pnpm 11 uses a different mechanism to manage package manager versions export pnpm_config_pm_on_fail=ignore # Some packages produce platform dependent outputs. We do not want to cache those in the global store export pnpm_config_side_effects_cache=false export pnpm_config_update_notifier=false else pnpm config set manage-package-manager-versions false pnpm config set side-effects-cache false pnpm config set update-notifier false fi popd pnpm config set reporter append-only pnpm config set store-dir $storePath # Run any additional pnpm configuration commands that users provide. ${prePnpmInstall} echo "Final pnpm config:" pnpm config list echo # pnpm is going to warn us about using --force # --force allows us to fetch all dependencies including ones that aren't meant for our host platform local installFlagsArray=( "--force" "--ignore-scripts" "--frozen-lockfile" ) if [[ -n "$NIX_NPM_REGISTRY" ]]; then installFlagsArray+=("--registry=$NIX_NPM_REGISTRY") fi pnpm install \ ${lib.escapeShellArgs filterFlags} \ ${lib.escapeShellArgs pnpmInstallFlags} \ "''${installFlagsArray[@]}" # Record the fetcherVersion in the output for introspection. echo ${toString fetcherVersion} > $out/.fetcher-version runHook postInstall ''; fixupPhase = '' runHook preFixup # Remove timestamp and sort the json files rm -rf $storePath/{v3,v10,v11}/tmp for f in $(find $storePath -name "*.json"); do jq --sort-keys "del(.. | .checkedAt?)" $f | sponge $f done if [ -f "$storePath/v11/index.db" ]; then pnpm-fixup-state-db "$storePath/v11"; # Dump the SQLite database to a SQL text file for reproducibility. # SQLite's binary format is non-deterministic (version-valid-for number, etc), # so we store the logical contents as SQL statements and reconstruct during build. if [[ ${toString fetcherVersion} -ge 4 ]]; then sqlite3 "$storePath/v11/index.db" .dump > "$storePath/v11/index.db.sql" rm "$storePath/v11/index.db" ${lib.optionalString (lib.versionAtLeast sqlite.version "3.53.0") '' # SQLite 3.53.0 refactored code around the dump generation, that changed # BLOB data type representations from `X'...'` to `x'...'`, breaking existing hashes. # This change was made in http://github.com/sqlite/sqlite/commit/d2424338b05f2766034f91c03009c8f0d78bc937 # and the following command reverts the dump to the old format: sed -i "s/,x'/,X'/g" "$storePath/v11/index.db.sql" ''} fi fi # This folder contains symlinks to /build/source which we don't need # since https://github.com/pnpm/pnpm/releases/tag/v10.27.0 rm -rf $storePath/{v3,v10,v11}/projects # Ensure consistent permissions # NOTE: For reasons not yet fully understood, pnpm might create files with # inconsistent permissions, for example inside the ubuntu-24.04 # github actions runner. # To ensure stable derivations, we need to set permissions # consistently, namely: # * All files with `-exec` suffix have 555. # * All other files have 444. # * All folders have 555. # See https://github.com/NixOS/nixpkgs/pull/350063 # See https://github.com/NixOS/nixpkgs/issues/422889 find $storePath -type f -name "*-exec" -print0 | xargs --no-run-if-empty -0 chmod 555 find $storePath -type f -not -name "*-exec" -print0 | xargs --no-run-if-empty -0 chmod 444 find $storePath -type d -print0 | xargs --no-run-if-empty -0 chmod 555 ( cd $storePath # Build a reproducible tarball, per instructions at https://reproducible-builds.org/docs/archives/ tar --sort=name \ --mtime="@$SOURCE_DATE_EPOCH" \ --owner=0 --group=0 --numeric-owner \ --pax-option=exthdr.name=%d/PaxHeaders/%f,delete=atime,delete=ctime \ --zstd -cf $out/pnpm-store.tar.zst . ) runHook postFixup ''; passthru = args.passthru or { } // { inherit fetcherVersion; serve = throw "fetchPnpmDeps: `serve` has been deprecated as it was removed in pnpm 11 and only had a niche use case."; # Added 2026-06-04 }; dontConfigure = true; dontBuild = true; outputHashMode = "recursive"; } // hash' ) ) ); pnpmConfigHook = makeSetupHook { name = "pnpm-config-hook"; propagatedBuildInputs = [ sqlite writableTmpDirAsHomeHook zstd ]; substitutions = { npmArch = stdenvNoCC.targetPlatform.node.arch; npmPlatform = stdenvNoCC.targetPlatform.node.platform; }; meta.license = lib.licenses.mit; } ./pnpm-config-hook.sh; }