Wrapping node packages

I needed to wrap an npm package that doesn’t exist in nixpkgs. After looking up on how to do it, I ended up with this solution:

{pkgs}:
pkgs.buildNpmPackage {
  pname = "nx";
  version = "22.3.1";
  src = ./.;
  npmDepsHash = "sha256-bF4lcaeBW0E4YPkqDsIOZCG3jvlOGVDTdiZ4oN4vgC8=";
  dontNpmBuild = true;

  postInstall = ''
    mkdir -p $out/bin
    ln -s $out/lib/node_modules/nx-wrapper/node_modules/.bin/nx $out/bin/nx
  '';
}

while having package.json and package-lock.json in the same directory.

And it works in a devShell. Is there more to it? I feel like I’m missiing something. Many packages seem to have a more complex setup.

Only if you want to do the actual build with nix. Currently you’re relying on the fact that node_modules was pre-created by npm, and you’re completely skipping the build.

The package seems pretty useless to me as-is, might as well just use npm install -g at that point, but it depends on what you want to achieve I guess.

Sorry I forgot to mention that I’m pretty new to nixos ecosystem.

I want primarily to be able to have an npm package available in my devShell, in a reproducible manner of course. My secondary objective is to try and identify what do I need to do when building a npm package the nix way. I have a feeling I will need to do this in the future, so I want to have the right mental model.

So, what is the right way? Any pointers/feedback is valuable :folded_hands:

Sadly that’s often a bit complex; npm packages are rarely written to be easily buildable in a reproducible context. npm isn’t a build system as much as it is a place people upload arbitrary files with some metadata to.

This particular package uses pnpm, which you’d start with as follows:

{
  stdenv,
  pnpm,
  fetchFromGitHub,
  ...
}:
stdenv.mkDerivation (drv: {
  pname = "nx";
  version = "22.3.3";

  src = fetchFromGitHub {
    owner = "nrwl";
    repo = "nx";
    tag = drv.version;
    hash = "sha256-fWaVicQb8QTGhSSrU8dA972G9n2G2pUlro/mCGJXOY4=";
  };

  pnpmDeps = pnpm.fetchDeps {
    inherit (drv) pname version src;
    fetcherVersion = 3;
    hash = "sha256-AGaVqYv5JCl0b/tPGlmow3XgPDma56wv1ziZ58m1ZTg=";
  };
})

Actually building it is likely going to be a lot more complex, though, as you’ll have to write the whole set of build instructions by hand. This also isn’t just a pnpm project, it also includes at least gradle and some other build systems; At a glance it looks like a 4 stage build, each involving various different tools which are all notoriously difficult to build reproducibly with.

If you really want to reverse engineer their build scripts, you can look at their GitHub workflow. I’d expect to spend at least a few weeks on this, though, and it might involve writing completely new integrations (gradle currently is especially poorly integrated), at which point it might be months to years.