Merge pull request #290715 from Scrumplex/pkgs/build-support/fetchPnpmDeps

pnpm.fetchDeps: init
This commit is contained in:
Doron Behar 2024-06-06 09:41:22 +03:00 committed by GitHub
commit 01d57709ec
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
14 changed files with 396 additions and 330 deletions

@ -310,6 +310,71 @@ See `node2nix` [docs](https://github.com/svanderburg/node2nix) for more info.
- `node2nix` has some [bugs](https://github.com/svanderburg/node2nix/issues/238) related to working with lock files from npm distributed with `nodejs_16`. - `node2nix` has some [bugs](https://github.com/svanderburg/node2nix/issues/238) related to working with lock files from npm distributed with `nodejs_16`.
- `node2nix` does not like missing packages from npm. If you see something like `Cannot resolve version: vue-loader-v16@undefined` then you might want to try another tool. The package might have been pulled off of npm. - `node2nix` does not like missing packages from npm. If you see something like `Cannot resolve version: vue-loader-v16@undefined` then you might want to try another tool. The package might have been pulled off of npm.
### pnpm {#javascript-pnpm}
Pnpm is available as the top-level package `pnpm`. Additionally, there are variants pinned to certain major versions, like `pnpm_8` and `pnpm_9`, which support different sets of lock file versions.
When packaging an application that includes a `pnpm-lock.yaml`, you need to fetch the pnpm store for that project using a fixed-output-derivation. The functions `pnpm_8.fetchDeps` and `pnpm_9.fetchDeps` can create this pnpm store derivation. In conjunction, the setup hooks `pnpm_8.configHook` and `pnpm_9.configHook` will prepare the build environment to install the prefetched dependencies store. Here is an example for a package that contains a `package.json` and a `pnpm-lock.yaml` files using the above `pnpm_` attributes:
```nix
{
stdenv,
nodejs,
# This is pinned as { pnpm = pnpm_9; }
pnpm
}:
stdenv.mkDerivation (finalAttrs: {
pname = "foo";
version = "0-unstable-1980-01-01";
src = ...;
nativeBuildInputs = [
nodejs
pnpm.configHook
];
pnpmDeps = pnpm.fetchDeps {
inherit (finalAttrs) pname version src;
hash = "...";
};
})
```
NOTE: It is highly recommended to use a pinned version of pnpm (i.e. `pnpm_8` or `pnpm_9`), to increase future reproducibility. It might also be required to use an older version, if the package needs support for a certain lock file version.
In case you are patching `package.json` or `pnpm-lock.yaml`, make sure to pass `finalAttrs.patches` to the function as well (i.e. `inherit (finalAttrs) patches`.
#### Dealing with `sourceRoot` {#javascript-pnpm-sourceRoot}
NOTE: Nixpkgs pnpm tooling doesn't support building projects with a `pnpm-workspace.yaml`, or building monorepos. It maybe possible to use `pnpm.fetchDeps` for these projects, but it may be hard or impossible to produce a binary from such projects ([an example attempt](https://github.com/NixOS/nixpkgs/pull/290715#issuecomment-2144543728)).
If the pnpm project is in a subdirectory, you can just define `sourceRoot` or `setSourceRoot` for `fetchDeps`. Note, that projects using `pnpm-workspace.yaml` are currently not supported, and will probably not work using this approach.
If `sourceRoot` is different between the parent derivation and `fetchDeps`, you will have to set `pnpmRoot` to effectively be the same location as it is in `fetchDeps`.
Assuming the following directory structure, we can define `sourceRoot` and `pnpmRoot` as follows:
```
.
├── frontend
│   ├── ...
│   ├── package.json
│   └── pnpm-lock.yaml
└── ...
```
```nix
...
pnpmDeps = pnpm.fetchDeps {
...
sourceRoot = "${finalAttrs.src.name}/frontend";
};
# by default the working directory is the extracted source
pnpmRoot = "frontend";
```
### yarn2nix {#javascript-yarn2nix} ### yarn2nix {#javascript-yarn2nix}
#### Preparation {#javascript-yarn2nix-preparation} #### Preparation {#javascript-yarn2nix-preparation}

@ -4,12 +4,9 @@
, electron , electron
, python3 , python3
, stdenv , stdenv
, stdenvNoCC
, copyDesktopItems , copyDesktopItems
, moreutils , nodejs
, cacert , pnpm
, jq
, nodePackages
, makeDesktopItem , makeDesktopItem
}: }:
@ -24,54 +21,17 @@ stdenv.mkDerivation (finalAttrs: {
hash = "sha256-nxpctEG4XoxW6jOAxGdgTEYr6YnhFRR8+5HUQLxRJB0="; hash = "sha256-nxpctEG4XoxW6jOAxGdgTEYr6YnhFRR8+5HUQLxRJB0=";
}; };
pnpmDeps = stdenvNoCC.mkDerivation { pnpmDeps = pnpm.fetchDeps {
pname = "${finalAttrs.pname}-pnpm-deps"; inherit (finalAttrs) pname version src;
inherit (finalAttrs) src version ELECTRON_SKIP_BINARY_DOWNLOAD; hash = "sha256-8oeloQYiwUy+GDG4R+XtiynT+8Fad4WYFWTO1KANZKQ=";
nativeBuildInputs = [ jq moreutils nodePackages.pnpm cacert ];
installPhase = ''
export HOME=$(mktemp -d)
pnpm config set store-dir $out
pnpm install --frozen-lockfile --ignore-script
rm -rf $out/v3/tmp
for f in $(find $out -name "*.json"); do
sed -i -E -e 's/"checkedAt":[0-9]+,//g' $f
jq --sort-keys . $f | sponge $f
done
'';
dontBuild = true;
dontFixup = true;
outputHashMode = "recursive";
outputHash = {
x86_64-linux = "sha256-bujlQxP6Lr3qPUDxYXKyb702ZJY/xbuCsu3wVDhcb+8=";
aarch64-linux = "sha256-0kyjjttpXpFVhdza5NAjGrRn++qc/N5/u2dQl7VufLE=";
x86_64-darwin = "sha256-Q37QJt/mhfpSguOlkJGKFTCrIOrpbG3OBwaD/Bg09Us=";
aarch64-darwin = "sha256-wbfjzoGa/6vIlOOVX3bKNQ2uxzph3WSofo3MGXqA6yQ=";
}.${stdenv.system} or (throw "Unsupported system: ${stdenv.system}");
}; };
nativeBuildInputs = [ makeWrapper python3 nodePackages.pnpm nodePackages.nodejs ] nativeBuildInputs = [ makeWrapper python3 nodejs pnpm.configHook ]
++ lib.optionals (!stdenv.isDarwin) [ copyDesktopItems ]; ++ lib.optionals (!stdenv.isDarwin) [ copyDesktopItems ];
ELECTRON_SKIP_BINARY_DOWNLOAD = 1; ELECTRON_SKIP_BINARY_DOWNLOAD = 1;
preBuild = ''
export HOME=$(mktemp -d)
export STORE_PATH=$(mktemp -d)
cp -Tr "$pnpmDeps" "$STORE_PATH"
chmod -R +w "$STORE_PATH"
pnpm config set store-dir "$STORE_PATH"
pnpm install --offline --frozen-lockfile --ignore-script
patchShebangs node_modules/{*,.*}
'';
postBuild = lib.optionalString stdenv.isDarwin '' postBuild = lib.optionalString stdenv.isDarwin ''
cp -R ${electron}/Applications/Electron.app Electron.app cp -R ${electron}/Applications/Electron.app Electron.app
chmod -R u+w Electron.app chmod -R u+w Electron.app

@ -4,7 +4,8 @@
, fetchFromGitHub , fetchFromGitHub
, buildGoModule , buildGoModule
, makeWrapper , makeWrapper
, nodePackages , nodejs
, pnpm
, cacert , cacert
, esbuild , esbuild
, jq , jq
@ -48,7 +49,7 @@ in
}; };
}; };
gui = stdenvNoCC.mkDerivation rec { gui = stdenvNoCC.mkDerivation (finalAttrs: {
pname = "geph-gui"; pname = "geph-gui";
inherit version; inherit version;
@ -60,42 +61,11 @@ in
fetchSubmodules = true; fetchSubmodules = true;
}; };
pnpm-deps = stdenvNoCC.mkDerivation {
pname = "${pname}-pnpm-deps";
inherit src version;
sourceRoot = "${src.name}/gephgui-wry/gephgui";
nativeBuildInputs = [
jq
moreutils
nodePackages.pnpm
cacert
];
installPhase = ''
export HOME=$(mktemp -d)
pnpm config set store-dir $out
pnpm install --ignore-scripts
# Remove timestamp and sort the json files
rm -rf $out/v3/tmp
for f in $(find $out -name "*.json"); do
sed -i -E -e 's/"checkedAt":[0-9]+,//g' $f
jq --sort-keys . $f | sponge $f
done
'';
dontFixup = true;
outputHashMode = "recursive";
outputHash = "sha256-OKPx5xRI7DWd6m31nYx1biP0k6pcZ7fq7dfVlHda4O0=";
};
gephgui-wry = rustPlatform.buildRustPackage { gephgui-wry = rustPlatform.buildRustPackage {
pname = "gephgui-wry"; pname = "gephgui-wry";
inherit version src; inherit (finalAttrs) version src;
sourceRoot = "${src.name}/gephgui-wry"; sourceRoot = "${finalAttrs.src.name}/gephgui-wry";
cargoLock = { cargoLock = {
lockFile = ./Cargo.lock; lockFile = ./Cargo.lock;
@ -105,10 +75,17 @@ in
}; };
}; };
pnpmDeps = pnpm.fetchDeps {
inherit (finalAttrs) pname version src;
sourceRoot = "${finalAttrs.src.name}/gephgui-wry/gephgui";
hash = "sha256-0MGlsLEgugQ1wEz07ROIwkanTa8PSKwIaxNahyS1014=";
};
nativeBuildInputs = [ nativeBuildInputs = [
pkg-config pkg-config
nodePackages.pnpm pnpm.configHook
makeWrapper makeWrapper
nodejs
]; ];
buildInputs = [ buildInputs = [
@ -132,22 +109,19 @@ in
}); });
})}"; })}";
pnpmRoot = "gephgui";
preBuild = '' preBuild = ''
cd gephgui pushd gephgui
export HOME=$(mktemp -d)
pnpm config set store-dir ${pnpm-deps}
pnpm install --ignore-scripts --offline
chmod -R +w node_modules
pnpm rebuild
pnpm build pnpm build
cd .. popd
''; '';
}; };
dontBuild = true; dontBuild = true;
installPhase = '' installPhase = ''
install -Dt $out/bin ${gephgui-wry}/bin/gephgui-wry install -Dt $out/bin ${finalAttrs.gephgui-wry}/bin/gephgui-wry
install -d $out/share/icons/hicolor install -d $out/share/icons/hicolor
for i in '16' '32' '64' '128' '256' for i in '16' '32' '64' '128' '256'
do do
@ -163,5 +137,5 @@ in
meta = geph-meta // { meta = geph-meta // {
license = with lib.licenses; [ unfree ]; license = with lib.licenses; [ unfree ];
}; };
}; });
} }

@ -2,7 +2,6 @@
, fetchFromGitHub , fetchFromGitHub
, copyDesktopItems , copyDesktopItems
, stdenv , stdenv
, stdenvNoCC
, rustc , rustc
, rustPlatform , rustPlatform
, cargo , cargo
@ -12,20 +11,18 @@
, webkitgtk , webkitgtk
, pkg-config , pkg-config
, makeDesktopItem , makeDesktopItem
, jq , pnpm
, moreutils , nodejs
, nodePackages
, cacert
}: }:
stdenv.mkDerivation rec { stdenv.mkDerivation (finalAttrs: {
pname = "kiwitalk"; pname = "kiwitalk";
version = "0.5.1"; version = "0.5.1";
src = fetchFromGitHub { src = fetchFromGitHub {
owner = "KiwiTalk"; owner = "KiwiTalk";
repo = "KiwiTalk"; repo = "KiwiTalk";
rev = "v${version}"; rev = "v${finalAttrs.version}";
hash = "sha256-Th8q+Zbc102fIk2v7O3OOeSriUV/ydz60QwxzmS7AY8="; hash = "sha256-Th8q+Zbc102fIk2v7O3OOeSriUV/ydz60QwxzmS7AY8=";
}; };
@ -34,43 +31,9 @@ stdenv.mkDerivation rec {
--replace "libayatana-appindicator3.so.1" "${libayatana-appindicator}/lib/libayatana-appindicator3.so.1" --replace "libayatana-appindicator3.so.1" "${libayatana-appindicator}/lib/libayatana-appindicator3.so.1"
''; '';
pnpm-deps = stdenvNoCC.mkDerivation { pnpmDeps = pnpm.fetchDeps {
pname = "${pname}-pnpm-deps"; inherit (finalAttrs) pname version src;
inherit src version; hash = "sha256-gf3vmKUta8KksUOxyhQS4UO6ycAJDfEicyXVGMW8+4c=";
nativeBuildInputs = [
jq
moreutils
nodePackages.pnpm
cacert
];
installPhase = ''
export HOME=$(mktemp -d)
pnpm config set store-dir $out
# This version of the package has different versions of esbuild as a dependency.
# You can use the command below to get esbuild binaries for a specific platform and calculate hashes for that platforms. (linux, darwin for os, and x86, arm64, ia32 for cpu)
# cat package.json | jq '.pnpm.supportedArchitectures += { "os": ["linux"], "cpu": ["arm64"] }' | sponge package.json
pnpm install --frozen-lockfile --ignore-script
# Remove timestamp and sort the json files.
rm -rf $out/v3/tmp
for f in $(find $out -name "*.json"); do
sed -i -E -e 's/"checkedAt":[0-9]+,//g' $f
jq --sort-keys . $f | sponge $f
done
'';
dontBuild = true;
dontFixup = true;
outputHashMode = "recursive";
outputHash = {
x86_64-linux = "sha256-LJPjWNpVfdUu8F5BMhAzpTo/h6ax7lxY2EESHj5P390=";
aarch64-linux = "sha256-N1K4pV5rbWmO/KonvYegzBoWa6TYQIqhQyxH/sWjOJQ=";
i686-linux = "sha256-/Q7VZahYhLdKVFB25CanROYxD2etQOcRg+4bXZUMqTc=";
x86_64-darwin = "sha256-9biFAbFD7Bva7KPKztgCvcaoX8E6AlJBKkjlDQdP6Zw=";
aarch64-darwin = "sha256-to5Y0R9tm9b7jUQAK3eBylLhpu+w5oDd63FbBCBAvd8=";
}.${stdenv.system} or (throw "Unsupported system: ${stdenv.system}");
}; };
cargoDeps = rustPlatform.importCargoLock { cargoDeps = rustPlatform.importCargoLock {
@ -86,7 +49,8 @@ stdenv.mkDerivation rec {
cargo cargo
rustc rustc
cargo-tauri cargo-tauri
nodePackages.pnpm nodejs
pnpm.configHook
copyDesktopItems copyDesktopItems
pkg-config pkg-config
]; ];
@ -98,10 +62,6 @@ stdenv.mkDerivation rec {
]; ];
preBuild = '' preBuild = ''
export HOME=$(mktemp -d)
pnpm config set store-dir ${pnpm-deps}
pnpm install --offline --frozen-lockfile --ignore-script
pnpm rebuild
cargo tauri build -b deb cargo tauri build -b deb
''; '';
@ -131,4 +91,4 @@ stdenv.mkDerivation rec {
platforms = platforms.linux ++ platforms.darwin; platforms = platforms.linux ++ platforms.darwin;
mainProgram = "kiwi-talk"; mainProgram = "kiwi-talk";
}; };
} })

@ -1,76 +1,49 @@
{ lib { lib
, stdenv , stdenv
, stdenvNoCC
, rustPlatform , rustPlatform
, fetchFromGitHub , fetchFromGitHub
, nodejs
, pnpm
, wrapGAppsHook3 , wrapGAppsHook3
, cargo , cargo
, rustc , rustc
, cargo-tauri , cargo-tauri
, pkg-config , pkg-config
, nodePackages
, esbuild , esbuild
, buildGoModule , buildGoModule
, jq
, moreutils
, libayatana-appindicator , libayatana-appindicator
, gtk3 , gtk3
, webkitgtk , webkitgtk
, libsoup , libsoup
, openssl , openssl
, xdotool , xdotool
, cacert
}: }:
stdenv.mkDerivation rec { stdenv.mkDerivation (finalAttrs: {
pname = "pot"; pname = "pot";
version = "2.7.9"; version = "2.7.9";
src = fetchFromGitHub { src = fetchFromGitHub {
owner = "pot-app"; owner = "pot-app";
repo = "pot-desktop"; repo = "pot-desktop";
rev = version; rev = finalAttrs.version;
hash = "sha256-Y2gFLvRNBjOGxdpIeoY1CXEip0Ht73aymWIP5wuc9kU="; hash = "sha256-Y2gFLvRNBjOGxdpIeoY1CXEip0Ht73aymWIP5wuc9kU=";
}; };
sourceRoot = "${src.name}/src-tauri"; sourceRoot = "${finalAttrs.src.name}/src-tauri";
postPatch = '' postPatch = ''
substituteInPlace $cargoDepsCopy/libappindicator-sys-*/src/lib.rs \ substituteInPlace $cargoDepsCopy/libappindicator-sys-*/src/lib.rs \
--replace "libayatana-appindicator3.so.1" "${libayatana-appindicator}/lib/libayatana-appindicator3.so.1" --replace "libayatana-appindicator3.so.1" "${libayatana-appindicator}/lib/libayatana-appindicator3.so.1"
''; '';
pnpm-deps = stdenvNoCC.mkDerivation { pnpmDeps = pnpm.fetchDeps {
pname = "${pname}-pnpm-deps"; inherit (finalAttrs) pname version src;
inherit src version; hash = "sha256-nRRUX6CH3s1cEoI80gtRmu0ovXpIwS+h1rFJo8kw60E=";
nativeBuildInputs = [
jq
moreutils
nodePackages.pnpm
cacert
];
installPhase = ''
export HOME=$(mktemp -d)
pnpm config set store-dir $out
# use --ignore-script and --no-optional to avoid downloading binaries
# use --frozen-lockfile to avoid checking git deps
pnpm install --frozen-lockfile --no-optional --ignore-script
# Remove timestamp and sort the json files
rm -rf $out/v3/tmp
for f in $(find $out -name "*.json"); do
sed -i -E -e 's/"checkedAt":[0-9]+,//g' $f
jq --sort-keys . $f | sponge $f
done
'';
dontFixup = true;
outputHashMode = "recursive";
outputHash = "sha256-LuY5vh642DgSa91eUcA/AT+ovDcP9tZFE2dKyicCOeQ=";
}; };
pnpmRoot = "..";
cargoDeps = rustPlatform.importCargoLock { cargoDeps = rustPlatform.importCargoLock {
lockFile = ./Cargo.lock; lockFile = ./Cargo.lock;
outputHashes = { outputHashes = {
@ -84,8 +57,9 @@ stdenv.mkDerivation rec {
cargo cargo
rustc rustc
cargo-tauri cargo-tauri
nodejs
pnpm.configHook
wrapGAppsHook3 wrapGAppsHook3
nodePackages.pnpm
pkg-config pkg-config
]; ];
@ -111,13 +85,13 @@ stdenv.mkDerivation rec {
}); });
})}"; })}";
preBuild = '' preConfigure = ''
export HOME=$(mktemp -d) # pnpm.configHook has to write to .., as our sourceRoot is set to src-tauri
pnpm config set store-dir ${pnpm-deps} # TODO: move frontend into its own drv
chmod +w .. chmod +w ..
pnpm install --offline --frozen-lockfile --no-optional --ignore-script '';
chmod -R +w ../node_modules
pnpm rebuild preBuild = ''
# Use cargo-tauri from nixpkgs instead of pnpm tauri from npm # Use cargo-tauri from nixpkgs instead of pnpm tauri from npm
cargo tauri build -b deb cargo tauri build -b deb
''; '';
@ -134,5 +108,4 @@ stdenv.mkDerivation rec {
license = licenses.gpl3Only; license = licenses.gpl3Only;
maintainers = with maintainers; [ linsui ]; maintainers = with maintainers; [ linsui ];
}; };
} })

@ -1,7 +1,6 @@
{ {
lib, lib,
stdenv, stdenv,
stdenvNoCC,
fetchFromGitHub, fetchFromGitHub,
substituteAll, substituteAll,
makeWrapper, makeWrapper,
@ -10,13 +9,11 @@
vencord, vencord,
electron, electron,
libicns, libicns,
jq,
moreutils,
cacert,
nodePackages,
pipewire, pipewire,
libpulseaudio, libpulseaudio,
autoPatchelfHook, autoPatchelfHook,
pnpm,
nodejs,
withTTS ? true, withTTS ? true,
# Enables the use of vencord from nixpkgs instead of # Enables the use of vencord from nixpkgs instead of
# letting vesktop manage it's own version # letting vesktop manage it's own version
@ -33,66 +30,17 @@ stdenv.mkDerivation (finalAttrs: {
hash = "sha256-cZOyydwpIW9Xq716KVi1RGtSlgVnOP3w8vXDwouS70E="; hash = "sha256-cZOyydwpIW9Xq716KVi1RGtSlgVnOP3w8vXDwouS70E=";
}; };
# NOTE: This requires pnpm 8.10.0 or newer pnpmDeps = pnpm.fetchDeps {
# https://github.com/pnpm/pnpm/pull/7214 inherit (finalAttrs) pname version src patches;
pnpmDeps = hash = "sha256-PogE8uf3W5cKSCqFHMz7FOvT7ONUP4FiFWGBgtk3UC8=";
assert lib.versionAtLeast nodePackages.pnpm.version "8.10.0"; };
stdenvNoCC.mkDerivation {
pname = "${finalAttrs.pname}-pnpm-deps";
inherit (finalAttrs)
src
version
patches
ELECTRON_SKIP_BINARY_DOWNLOAD
;
nativeBuildInputs = [
cacert
jq
moreutils
nodePackages.pnpm
];
# inspired by https://github.com/NixOS/nixpkgs/blob/763e59ffedb5c25774387bf99bc725df5df82d10/pkgs/applications/misc/pot/default.nix#L56
# and based on https://github.com/NixOS/nixpkgs/pull/290715
installPhase = ''
runHook preInstall
export HOME=$(mktemp -d)
pnpm config set store-dir $out
# Some packages produce platform dependent outputs. We do not want to cache those in the global store
pnpm config set side-effects-cache false
# 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
pnpm install --force --frozen-lockfile --ignore-script
'';
fixupPhase = ''
runHook preFixup
# Remove timestamp and sort the json files
rm -rf $out/v3/tmp
for f in $(find $out -name "*.json"); do
sed -i -E -e 's/"checkedAt":[0-9]+,//g' $f
jq --sort-keys . $f | sponge $f
done
runHook postFixup
'';
dontConfigure = true;
dontBuild = true;
outputHashMode = "recursive";
outputHash = "sha256-PogE8uf3W5cKSCqFHMz7FOvT7ONUP4FiFWGBgtk3UC8=";
};
nativeBuildInputs = [ nativeBuildInputs = [
autoPatchelfHook autoPatchelfHook
copyDesktopItems copyDesktopItems
makeWrapper makeWrapper
nodePackages.pnpm nodejs
nodePackages.nodejs pnpm.configHook
]; ];
buildInputs = [ buildInputs = [
@ -110,22 +58,6 @@ stdenv.mkDerivation (finalAttrs: {
ELECTRON_SKIP_BINARY_DOWNLOAD = 1; ELECTRON_SKIP_BINARY_DOWNLOAD = 1;
configurePhase = ''
runHook preConfigure
export HOME=$(mktemp -d)
export STORE_PATH=$(mktemp -d)
cp -Tr "$pnpmDeps" "$STORE_PATH"
chmod -R +w "$STORE_PATH"
pnpm config set store-dir "$STORE_PATH"
pnpm install --frozen-lockfile --ignore-script --offline
patchShebangs node_modules/{*,.*}
runHook postConfigure
'';
buildPhase = '' buildPhase = ''
runHook preBuild runHook preBuild

@ -1,4 +1,4 @@
{ lib, fetchFromGitHub, stdenv, stdenvNoCC, nodePackages, buildGoModule, jq, mage, writeShellScriptBin, nixosTests, buildNpmPackage, moreutils, cacert }: { lib, fetchFromGitHub, stdenv, nodejs, pnpm, buildGoModule, mage, writeShellScriptBin, nixosTests }:
let let
version = "0.23.0"; version = "0.23.0";
@ -13,72 +13,20 @@ let
pname = "vikunja-frontend"; pname = "vikunja-frontend";
inherit version src; inherit version src;
postPatch = '' sourceRoot = "${finalAttrs.src.name}/frontend";
cd frontend
'';
pnpmDeps = stdenvNoCC.mkDerivation { pnpmDeps = pnpm.fetchDeps {
pname = "${finalAttrs.pname}-pnpm-deps"; inherit (finalAttrs) pname version src sourceRoot;
inherit (finalAttrs) src version; hash = "sha256-awQgOLkb46v2aWfw6yv+zGPoOnczalkzg02tBgMTyMY=";
nativeBuildInputs = [
jq
nodePackages.pnpm
moreutils
cacert
];
pnpmPatch = builtins.toJSON {
pnpm.supportedArchitectures = {
os = [ "linux" ];
cpu = [ "x64" "arm64" ];
};
};
postPatch = ''
cd frontend
mv package.json package.json.orig
jq --raw-output ". * $pnpmPatch" package.json.orig > package.json
'';
# https://github.com/NixOS/nixpkgs/blob/763e59ffedb5c25774387bf99bc725df5df82d10/pkgs/applications/misc/pot/default.nix#L56
installPhase = ''
export HOME=$(mktemp -d)
pnpm config set store-dir $out
pnpm install --frozen-lockfile --ignore-script
rm -rf $out/v3/tmp
for f in $(find $out -name "*.json"); do
sed -i -E -e 's/"checkedAt":[0-9]+,//g' $f
jq --sort-keys . $f | sponge $f
done
'';
dontBuild = true;
dontFixup = true;
outputHashMode = "recursive";
outputHash = {
x86_64-linux = "sha256-ybAkXe2/VhGZhr59ZQOcQ+SI2a204e8uPjyE40xUVwU=";
aarch64-linux = "sha256-2iURs6JtI/b2+CnLwhog1X5hSFFO6OmmgFRuTbMjH+k=";
}.${stdenv.system} or (throw "Unsupported system: ${stdenv.system}");
}; };
nativeBuildInputs = [ nativeBuildInputs = [
nodePackages.pnpm nodejs
nodePackages.nodejs pnpm.configHook
]; ];
doCheck = true; doCheck = true;
preBuild = ''
export HOME=$(mktemp -d)
pnpm config set store-dir ${finalAttrs.pnpmDeps}
pnpm install --offline --frozen-lockfile --ignore-script
patchShebangs node_modules/{*,.*}
'';
postBuild = '' postBuild = ''
pnpm run build pnpm run build
''; '';
@ -121,8 +69,10 @@ buildGoModule {
vendorHash = "sha256-d4AeQEAtPqMDe5a5aKhCe3i3pDXAMZJkJXxfcAFTx7A="; vendorHash = "sha256-d4AeQEAtPqMDe5a5aKhCe3i3pDXAMZJkJXxfcAFTx7A=";
inherit frontend;
prePatch = '' prePatch = ''
cp -r ${frontend} frontend/dist cp -r $frontend frontend/dist
''; '';
postConfigure = '' postConfigure = ''

@ -109,7 +109,7 @@ let
); );
# Recursively composes the dependencies of a package # Recursively composes the dependencies of a package
composePackage = { name, packageName, src, dependencies ? [], ... }@args: composePackage = { name, packageName, src, dependencies ? [], ... }:
builtins.addErrorContext "while evaluating node package '${packageName}'" '' builtins.addErrorContext "while evaluating node package '${packageName}'" ''
installPackage "${packageName}" "${src}" installPackage "${packageName}" "${src}"
${includeDependencies { inherit dependencies; }} ${includeDependencies { inherit dependencies; }}
@ -194,7 +194,7 @@ let
# dependencies in the package.json file to the versions that are actually # dependencies in the package.json file to the versions that are actually
# being used. # being used.
pinpointDependenciesOfPackage = { packageName, dependencies ? [], production ? true, ... }@args: pinpointDependenciesOfPackage = { packageName, dependencies ? [], production ? true, ... }:
'' ''
if [ -d "${packageName}" ] if [ -d "${packageName}" ]
then then
@ -496,7 +496,7 @@ let
let let
extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" "dontStrip" "dontNpmInstall" "preRebuild" "unpackPhase" "buildPhase" "meta" ]; extraArgs = removeAttrs args [ "name" "dependencies" "buildInputs" "dontStrip" "dontNpmInstall" "preRebuild" "unpackPhase" "buildPhase" "meta" ];
in in
stdenv.mkDerivation ({ stdenv.mkDerivation (finalAttrs: {
name = "${name}${if version == null then "" else "-${version}"}"; name = "${name}${if version == null then "" else "-${version}"}";
buildInputs = [ tarWrapper python nodejs ] buildInputs = [ tarWrapper python nodejs ]
++ lib.optional (stdenv.isLinux) utillinux ++ lib.optional (stdenv.isLinux) utillinux
@ -508,8 +508,9 @@ let
inherit dontStrip; # Stripping may fail a build for some package deployments inherit dontStrip; # Stripping may fail a build for some package deployments
inherit dontNpmInstall preRebuild unpackPhase buildPhase; inherit dontNpmInstall preRebuild unpackPhase buildPhase;
compositionScript = composePackage args; # TODO: enable overriding dependencies too?
pinpointDependenciesScript = pinpointDependenciesOfPackage args; compositionScript = composePackage { inherit packageName dependencies; inherit (finalAttrs) name src; };
pinpointDependenciesScript = pinpointDependenciesOfPackage { inherit packageName dependencies production; };
passAsFile = [ "compositionScript" "pinpointDependenciesScript" ]; passAsFile = [ "compositionScript" "pinpointDependenciesScript" ];

@ -0,0 +1,20 @@
{ lib, callPackage }:
let
inherit (lib) mapAttrs' nameValuePair;
variants = {
"8" = {
version = "8.15.8";
hash = "sha256-aR/hdu6pqKgN8g5JdvPftEoEhBzriFY4/iomF0+B5l4=";
};
"9" = {
version = "9.1.1";
hash = "sha256-lVHoA9y3oYOf31QWFTqEQGDHvOATIYzoI0EFMlBKwQs=";
};
};
callPnpm = variant: callPackage ./generic.nix {inherit (variant) version hash;};
mkPnpm = versionSuffix: variant: nameValuePair "pnpm_${versionSuffix}" (callPnpm variant);
in
mapAttrs' mkPnpm variants

@ -0,0 +1,91 @@
{
stdenvNoCC,
fetchurl,
callPackage,
jq,
moreutils,
cacert,
makeSetupHook,
pnpm,
}:
{
fetchDeps =
{
src,
hash ? "",
pname,
...
}@args:
let
args' = builtins.removeAttrs args [
"hash"
"pname"
];
hash' =
if hash != "" then
{ outputHash = hash; }
else
{
outputHash = "";
outputHashAlgo = "sha256";
};
in
stdenvNoCC.mkDerivation (finalAttrs: (
args'
// {
name = "${pname}-pnpm-deps";
nativeBuildInputs = [
jq
moreutils
pnpm
cacert
];
installPhase = ''
runHook preInstall
export HOME=$(mktemp -d)
pnpm config set store-dir $out
# Some packages produce platform dependent outputs. We do not want to cache those in the global store
pnpm config set side-effects-cache false
# As we pin pnpm versions, we don't really care about updates
pnpm config set update-notifier false
# 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
pnpm install --frozen-lockfile --ignore-script --force
runHook postInstall
'';
fixupPhase = ''
runHook preFixup
# Remove timestamp and sort the json files
rm -rf $out/v3/tmp
for f in $(find $out -name "*.json"); do
jq --sort-keys "del(.. | .checkedAt?)" $f | sponge $f
done
runHook postFixup
'';
passthru = {
serve = callPackage ./serve.nix {
inherit pnpm;
pnpmDeps = finalAttrs.finalPackage;
};
};
dontConfigure = true;
dontBuild = true;
outputHashMode = "recursive";
}
// hash'
));
configHook = makeSetupHook {
name = "pnpm-config-hook";
propagatedBuildInputs = [ pnpm ];
} ./pnpm-config-hook.sh;
}

@ -0,0 +1,40 @@
# shellcheck shell=bash
pnpmConfigHook() {
echo "Executing pnpmConfigHook"
if [ -n "${pnpmRoot-}" ]; then
pushd "$pnpmRoot"
fi
if [ -z "${pnpmDeps-}" ]; then
echo "Error: 'pnpmDeps' must be set when using pnpmConfigHook."
exit 1
fi
echo "Configuring pnpm store"
export HOME=$(mktemp -d)
export STORE_PATH=$(mktemp -d)
cp -Tr "$pnpmDeps" "$STORE_PATH"
chmod -R +w "$STORE_PATH"
pnpm config set store-dir "$STORE_PATH"
echo "Installing dependencies"
pnpm install --offline --frozen-lockfile --ignore-script
echo "Patching scripts"
patchShebangs node_modules/{*,.*}
if [ -n "${pnpmRoot-}" ]; then
popd
fi
echo "Finished pnpmConfigHook"
}
postConfigureHooks+=(pnpmConfigHook)

@ -0,0 +1,30 @@
{ writeShellApplication, pnpm, pnpmDeps }:
writeShellApplication {
name = "serve-pnpm-store";
runtimeInputs = [
pnpm
];
text = ''
storePath=$(mktemp -d)
clean() {
echo "Cleaning up temporary store at '$storePath'..."
rm -rf "$storePath"
}
echo "Copying pnpm store '${pnpmDeps}' to temporary store..."
cp -Tr "${pnpmDeps}" "$storePath"
chmod -R +w "$storePath"
echo "Run 'pnpm install --store-dir \"$storePath\"' to install packages from this store."
trap clean EXIT
pnpm server start \
--store-dir "$storePath"
'';
}

@ -0,0 +1,58 @@
{
lib,
stdenvNoCC,
callPackages,
fetchurl,
nodejs,
testers,
withNode ? true,
version,
hash,
}: stdenvNoCC.mkDerivation (finalAttrs: {
pname = "pnpm";
inherit version;
src = fetchurl {
url = "https://registry.npmjs.org/pnpm/-/pnpm-${finalAttrs.version}.tgz";
inherit hash;
};
# Remove binary files from src, we don't need them, and this way we make sure
# our distribution is free of binaryNativeCode
preConfigure = ''
rm -r dist/reflink.*node dist/vendor
'';
buildInputs = lib.optionals withNode [ nodejs ];
installPhase = ''
runHook preInstall
install -d $out/{bin,libexec}
cp -R . $out/libexec/pnpm
ln -s $out/libexec/pnpm/bin/pnpm.cjs $out/bin/pnpm
ln -s $out/libexec/pnpm/bin/pnpx.cjs $out/bin/pnpx
runHook postInstall
'';
passthru = let
fetchDepsAttrs = callPackages ./fetch-deps { pnpm = finalAttrs.finalPackage; };
in {
inherit (fetchDepsAttrs) fetchDeps configHook;
tests.version = lib.optionalAttrs withNode (
testers.testVersion { package = finalAttrs.finalPackage; }
);
};
meta = with lib; {
description = "Fast, disk space efficient package manager for JavaScript";
homepage = "https://pnpm.io/";
changelog = "https://github.com/pnpm/pnpm/releases/tag/v${finalAttrs.version}";
license = licenses.mit;
maintainers = with maintainers; [ Scrumplex ];
platforms = platforms.all;
mainProgram = "pnpm";
};
})

@ -2909,6 +2909,8 @@ with pkgs;
portfolio-filemanager = callPackage ../applications/file-managers/portfolio-filemanager { }; portfolio-filemanager = callPackage ../applications/file-managers/portfolio-filemanager { };
pot = callPackage ../by-name/po/pot/package.nix { pnpm = pnpm_8; };
potreeconverter = callPackage ../applications/graphics/potreeconverter { }; potreeconverter = callPackage ../applications/graphics/potreeconverter { };
ranger = callPackage ../applications/file-managers/ranger { }; ranger = callPackage ../applications/file-managers/ranger { };
@ -11895,6 +11897,10 @@ with pkgs;
pngquant = callPackage ../tools/graphics/pngquant { }; pngquant = callPackage ../tools/graphics/pngquant { };
inherit (callPackages ../development/tools/pnpm { })
pnpm_8 pnpm_9;
pnpm = pnpm_9;
po4a = perlPackages.Po4a; po4a = perlPackages.Po4a;
poac = callPackage ../development/tools/poac { poac = callPackage ../development/tools/poac {
@ -14078,6 +14084,8 @@ with pkgs;
viking = callPackage ../applications/misc/viking { }; viking = callPackage ../applications/misc/viking { };
vikunja = callPackage ../by-name/vi/vikunja/package.nix { pnpm = pnpm_8; };
vim-vint = callPackage ../development/tools/vim-vint { }; vim-vint = callPackage ../development/tools/vim-vint { };
vimer = callPackage ../tools/misc/vimer { }; vimer = callPackage ../tools/misc/vimer { };
@ -20823,7 +20831,7 @@ with pkgs;
gecode_6 = qt5.callPackage ../development/libraries/gecode { }; gecode_6 = qt5.callPackage ../development/libraries/gecode { };
gecode = gecode_6; gecode = gecode_6;
geph = recurseIntoAttrs (callPackages ../applications/networking/geph { }); geph = recurseIntoAttrs (callPackages ../applications/networking/geph { pnpm = pnpm_8; });
gephi = callPackage ../applications/science/misc/gephi { }; gephi = callPackage ../applications/science/misc/gephi { };
@ -24773,6 +24781,8 @@ with pkgs;
vencord-web-extension = callPackage ../by-name/ve/vencord/package.nix { buildWebExtension = true; }; vencord-web-extension = callPackage ../by-name/ve/vencord/package.nix { buildWebExtension = true; };
vesktop = callPackage ../by-name/ve/vesktop/package.nix { pnpm = pnpm_8; };
vid-stab = callPackage ../development/libraries/vid-stab { vid-stab = callPackage ../development/libraries/vid-stab {
inherit (llvmPackages) openmp; inherit (llvmPackages) openmp;
}; };
@ -32112,6 +32122,8 @@ with pkgs;
kitsas = libsForQt5.callPackage ../applications/office/kitsas { }; kitsas = libsForQt5.callPackage ../applications/office/kitsas { };
kiwitalk = callPackage ../by-name/ki/kiwitalk/package.nix { pnpm = pnpm_8; };
kiwix = libsForQt5.callPackage ../applications/misc/kiwix { }; kiwix = libsForQt5.callPackage ../applications/misc/kiwix { };
kiwix-tools = callPackage ../applications/misc/kiwix/tools.nix { }; kiwix-tools = callPackage ../applications/misc/kiwix/tools.nix { };
@ -35811,7 +35823,7 @@ with pkgs;
youtube-dl-light = with python3Packages; toPythonApplication youtube-dl-light; youtube-dl-light = with python3Packages; toPythonApplication youtube-dl-light;
youtube-music = callPackage ../applications/audio/youtube-music { }; youtube-music = callPackage ../applications/audio/youtube-music { pnpm = pnpm_8; };
youtube-tui = callPackage ../applications/video/youtube-tui { youtube-tui = callPackage ../applications/video/youtube-tui {
inherit (darwin.apple_sdk.frameworks) CoreFoundation Security AppKit; inherit (darwin.apple_sdk.frameworks) CoreFoundation Security AppKit;