Failed to set sourceRoot for buildNpmPackage

When I tried build npm package in a subdirectory of the current project, it couldn’t find the directory:

Source:

pkgs.buildNpmPackage {
  name = "xxx";
  src = ./.;
  sourceRoot = "source/cli";

  npmDepsHash = "...";
};

Error:

Running phase: unpackPhase
unpacking source archive /nix/store/1ym91argw0z2f1wlcm410ydk4fkmhil9-rr7fi5fifihfxsddxh46bwmrcs1kayqi-source
source root is source/cli
chmod: cannot access 'source/cli': No such file or directory

However, if I build the package from another flake and add this project to its input, it can build without error:

pkgs.buildNpmPackage {
  name = "xxx";
  src = inputs.my-project;
  sourceRoot = "source/cli";

  npmDepsHash = "...";
};

Is there a way to build an npm package in a subdirectory within the same project?

The src needs to refer to the dir that contains the cli directory.
Also I suggest using "${src.name}/cli" rather than hardcoding "source/cli" in the string (just in case the name changes for whichever reason).

If you’re still stuck, please share your code or at least a minimal reproducible example.

source is the the place Nix unpacking the source code. src.name won’t be available as src is a path. Besides, using src.name is a bad idea as it will refer to a read-only path.

I’ll come up with an example soon.

A minimal example is straight forward:

{
  inputs = {
    nixpkgs.url = "github:nixos/nixpkgs?ref=nixos-unstable";
    flake-parts.url = "github:hercules-ci/flake-parts";
  };

  outputs = inputs@{ nixpkgs, flake-parts, ... }:
    flake-parts.lib.mkFlake { inherit inputs; } {
      systems = [ "x86_64-linux" "aarch64-linux" ];

      perSystem = { self', system, pkgs, lib, ... }: {
        packages.default = pkgs.buildNpmPackage {
          name = "test";
          src = ./.;
          sourceRoot = "source/cli";

          npmDepsHash = lib.fakeHash;
        };
      };
    };
}

Then just create a directory cli and run npm init -y && npm i inside it.
The nix build . will fail with the same error.

Using $src would be the readonly store path, that’s different from my suggestion though.

Also I missed that you were using a path, not a fetcher in the first example.
If you simply provide a path, then the path will get unpacked to a dir called [hash]-source where [hash] could be anything if you’re using flakes, and if you’re not using flakes, then it takes the name of the current directory (even more impure!)

To fix your issue you probably want to implement something like Working with local files — nix.dev documentation to avoid dependency on the current directory’s name and ensure only the build-relevant files are included.

For example, if you want to exclude result (if present), default.nix, flake.nix, and flake.lock from the build:

let
  fs = lib.fileset;
  sourceFiles = fs.difference ./. (
    fs.unions [
      (fs.maybeMissing ./result)
      ./default.nix
      ./flake.nix
      ./flake.lock
    ]
  );
in
buildNpmPackage rec {
  src = fs.toSource {
    root = ./.;
    fileset = sourceFiles;
  };

  sourceRoot = "${src.name}/cli";
  
  # other code...
}

Using toSrouce works perfectly. Thanks for the solution!