How to use importNpmLock?

I am trying to build padloc but nothing seems to work. node2nix seems to fail at random derivations, being impossible to debug, npmlock2nix seems abandoned, so I’m left with two options. Either fetchNpmDeps or importNpmLock. fetchNpmDeps doesn’t seem to be able to download the right dependencies, since when I start the build, it still misses some and I have no clue how to add them at that point as they’re not available in nixpkgs (e.g. ts-loader). importNpmLock, on the other hand, doesn’t seem to really work at all. I can found no documentation apart from here and it really doesn’t seem to be used anywhere in nixpkgs.

Here is what I have tried with importNpmLock:

{ pkgs, lib, buildNpmPackage, fetchNpmDeps, importNpmLock, npmHooks, ... }:

buildNpmPackage rec {
  pname = "padloc";
  version = "4.3.0";

  env.ELECTRON_SKIP_BINARY_DOWNLOAD = 1;
  # env.CYPRESS_INSTALL_BINARY = "0";

  env.PL_PWA_URL = "https://10.10.10.10:8429";

  src = pkgs.fetchzip {
    url = "https://github.com/padloc/padloc/archive/refs/tags/v${version}.zip";
    sha256 = "sha256-2G3uOmWKA6ZZJKyZhJu/B3jq9x+Bx/fb1zDSmzf0a0w=";
  };
  # npmFlags = [ "--ignore-scripts" "--prefer-offline" ];
  npmFlags = [ "--ignore-scripts" ];
  makeCacheWritable = true;

  npmDeps = importNpmLock {
    npmRoot = src;
    package = lib.importJSON "${src}/package.json";
    packageLock = lib.importJSON "${src}/package-lock.json";
  };
  npmConfigHook = importNpmLock.npmConfigHook;

  npmBuildScript = "electron:build";

  nativeBuildInputs = with pkgs; [
    nodePackages.webpack
    nodePackages.webpack-cli
  ];
}

What this will do is just spit an error saying:

in pure evaluation mode, 'fetchTree' requires a locked input, at «none»:0

So, is there any way in Nix to build a project like that?

I’ve researched a bit more and I’ve finally made importNpmLock work:

buildNpmPackage rec {
  pname = "padloc";
  version = "4.3.0";

  env.ELECTRON_SKIP_BINARY_DOWNLOAD = 1;
  # env.CYPRESS_INSTALL_BINARY = "0";

  env.PL_PWA_URL = "https://10.10.10.10:8429";

  src = pkgs.fetchzip {
    url = "https://github.com/padloc/padloc/archive/refs/tags/v${version}.zip";
    sha256 = "sha256-2G3uOmWKA6ZZJKyZhJu/B3jq9x+Bx/fb1zDSmzf0a0w=";
  };
  # npmFlags = [ "--ignore-scripts" "--prefer-offline" ];
  npmFlags = [ "--ignore-scripts" ];
  # makeCacheWritable = true;

  npmDeps = importNpmLock {
    npmRoot = src;
    fetcherOpts = {
      "node_modules/maildev" = {
        url = "https://git@github.com/padloc/maildev.git";
        rev = "a3ac6bf682dfb7cbb08b8b86e7b8afc9e5e41c66";
      };
      "maildev" = {
        url = "https://git@github.com/padloc/maildev.git";
        rev = "a3ac6bf682dfb7cbb08b8b86e7b8afc9e5e41c66";
      };
    };
  };
  npmConfigHook = importNpmLock.npmConfigHook;

  npmBuildScript = "electron:build";

  nativeBuildInputs = with pkgs; [
    nodePackages.webpack
    nodePackages.webpack-cli
  ];
}

Now the problem is, npm install will not work. It won’t because some packages, such as maildev, have binaries. And apparently npm automatically tries to chmod those binaries, but since they come from other derivations it can’t and it will just give Permission Denied:

npm error code EPERM
npm error syscall chmod
npm error path /build/source/node_modules/maildev/bin/maildev
npm error errno -1
npm error Error: EPERM: operation not permitted, chmod '/build/source/node_modules/maildev/bin/maildev'
npm error     at async chmod (node:internal/fs/promises:1085:10)
npm error     at async Promise.all (index 0)
npm error     at async Promise.all (index 0)
npm error     at async #createBinLinks (/nix/store/v14k93caffbf0xz2g7bqr964grxxqlmj-nodejs-20.15.1/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arbo>
npm error     at async Promise.allSettled (index 0)
npm error     at async #linkAllBins (/nix/store/v14k93caffbf0xz2g7bqr964grxxqlmj-nodejs-20.15.1/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arboris>
npm error     at async #build (/nix/store/v14k93caffbf0xz2g7bqr964grxxqlmj-nodejs-20.15.1/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arborist/rebu>
npm error     at async Arborist.rebuild (/nix/store/v14k93caffbf0xz2g7bqr964grxxqlmj-nodejs-20.15.1/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arb>
npm error     at async [reifyPackages] (/nix/store/v14k93caffbf0xz2g7bqr964grxxqlmj-nodejs-20.15.1/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arbo>
npm error     at async Arborist.reify (/nix/store/v14k93caffbf0xz2g7bqr964grxxqlmj-nodejs-20.15.1/lib/node_modules/npm/node_modules/@npmcli/arborist/lib/arbor>
npm error   errno: -1,
npm error   code: 'EPERM',
npm error   syscall: 'chmod',
npm error   path: '/build/source/node_modules/maildev/bin/maildev'
npm error }

If I use:

  npmFlags = [ "--ignore-scripts" "--no-bin-links" ];

then npm install will work but the actual build will fail because there are no binaries.

With --no-bin-links we can also find out that the dependency maildev is indeed basically a symlink to the derivation:

# inside the build
ls -ahl node_modules
lrwxrwxrwx   1 nixbld nixbld   58 Oct  4 00:13 maildev -> ../../../nix/store/rbi112hv5sf2kiqmq12bdzrzsb9xhar1-source
ls -ahl /nix/store/rbi112hv5sf2kiqmq12bdzrzsb9xhar1-source/bin/maildev 
-r-xr-xr-x 1 root root 237  1 gen  1970 /nix/store/rbi112hv5sf2kiqmq12bdzrzsb9xhar1-source/bin/maildev

So it tries to chmod a file in another derivation and fails. I have no idea whether this is the right path, but I’ll keep logging here what works and what doesn’t.