pnpm.fetchDeps: init

Signed-off-by: Sefa Eyeoglu <contact@scrumplex.net>
This commit is contained in:
Sefa Eyeoglu 2024-02-22 23:22:44 +01:00
parent 53af969d62
commit 74f5ff78bf
No known key found for this signature in database
GPG Key ID: E13DFD4B47127951
4 changed files with 193 additions and 4 deletions

View File

@ -310,6 +310,69 @@ 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` 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}
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}
#### Preparation {#javascript-yarn2nix-preparation}

View File

@ -0,0 +1,83 @@
{
stdenvNoCC,
fetchurl,
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 (
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
'';
dontConfigure = true;
dontBuild = true;
outputHashMode = "recursive";
}
// hash'
);
configHook = makeSetupHook {
name = "pnpm-config-hook";
propagatedBuildInputs = [ pnpm ];
} ./pnpm-config-hook.sh;
}

View File

@ -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)

View File

@ -1,6 +1,7 @@
{
lib,
stdenvNoCC,
callPackages,
fetchurl,
nodejs,
testers,
@ -8,9 +9,7 @@
version,
hash,
}:
stdenvNoCC.mkDerivation (finalAttrs: {
}: stdenvNoCC.mkDerivation (finalAttrs: {
pname = "pnpm";
inherit version;
@ -32,7 +31,11 @@ stdenvNoCC.mkDerivation (finalAttrs: {
runHook postInstall
'';
passthru = {
passthru = let
fetchDepsAttrs = callPackages ./fetch-deps { pnpm = finalAttrs.finalPackage; };
in {
inherit (fetchDepsAttrs) fetchDeps configHook;
tests.version = lib.optionalAttrs withNode (
testers.testVersion { package = finalAttrs.finalPackage; }
);