Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: nzbr/pnpm2nix-nzbr
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: main
Choose a base ref
...
head repository: mojotech/pnpm2nix-nzbr
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: main
Choose a head ref
Able to merge. These branches can be automatically merged.
  • 4 commits
  • 1 file changed
  • 2 contributors

Commits on Feb 27, 2024

  1. Add argument for build specific environment

    It is likely that a user may want to specify environment variables
    during the build phase as well as during pnpm install. Add an argument
    for a build specific environment. This allows the user to avoid
    duplicating the environment between a configure or prebuild step and
    installEnv, by using something like:
    
    let
      sharedEnv = { ... };
      buildEnvOverrides = { ... };
      installEnvOverrides = { ... };
    in
      mkPnpmPackage {
        buildEnv = sharedEnv // buildEnvOverrides;
        installEnv = sharedEnv // installEnvOverrides;
        ...
      }
    iteratee committed Feb 27, 2024
    Copy the full SHA
    ccbd38a View commit details
  2. Add support for pnpm workspaces as single derivation

    Allow a caller to specify a workspace and a list of components rather
    than a single source directory. This allows for the building of pnpm
    monorepo projects with dependency links between the various projects and
    a single pnpm-lock.yaml for the whole workspace. Requires that the
    script has the same name in all components.
    
    The location of all of the per-component package.json files is
    overridable. A workspace project can still specify the location of the
    root package.json via packageJSON, as well as the components'
    package.json files via componentPackageJSONs. The list defaults to
    c/package.json for c in components.
    
    Similarly, the list of distDirs for the components is overridable. For a
    workspace, the distDirs are all subdirs of $out rather than dist
    becoming $out. The default list is c/dist for c in components. Allowing
    for override handles the case where one component is built by a
    different tool like next and produces a different directory like ".next"
    
    NB: Any "link:" dependencies will need to be recreated as a preBuild
    step. pnpm will create the symlinks during install, and nix removes them
    because when the node_modules derivation is built, they are dangling.
    
    Attempts to leave the non-workspace pnpm package support intact.
    iteratee committed Feb 27, 2024
    Copy the full SHA
    a17fd10 View commit details
  3. Add support for placing additional artifacts into the result.

    A user may want the node_modules and package artifacts as a part of the
    output in order to have a runnable build artifact without needing the
    node-modules derivation or the sources available. Add options to control
    additional copies as a part of the install.
    
    Simplify the values used in the derivation so that the conditional logic
    is almost all in the let rather than in the derivation body.
    
    Some of this change should probably rebased into the first commit.
    iteratee committed Feb 27, 2024
    Copy the full SHA
    71a9968 View commit details

Commits on Mar 4, 2024

  1. Merge pull request #1 from mojotech/kb/pnpm-workspace

    Add support for pnpm workspaces as single derivation
    iteratee authored Mar 4, 2024
    Copy the full SHA
    c3cfff8 View commit details
Showing with 108 additions and 19 deletions.
  1. +108 −19 derivation.nix
127 changes: 108 additions & 19 deletions derivation.nix
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{ lib
, stdenv
, nodejs
, rsync
, pkg-config
, callPackage
, writeText
@@ -15,17 +16,29 @@ let
in
{
mkPnpmPackage =
{ src
{ workspace ? null
, components ? []
, src ? if (workspace != null && components != []) then workspace else null
, packageJSON ? src + "/package.json"
, componentPackageJSONs ? map (c: {
name = "${c}/package.json";
value = src + "/${c}/package.json";
}) components
, pnpmLockYaml ? src + "/pnpm-lock.yaml"
, pnpmWorkspaceYaml ? (if workspace == null then null else workspace + "/pnpm-workspace.yaml")
, pname ? (fromJSON (readFile packageJSON)).name
, version ? (fromJSON (readFile packageJSON)).version or null
, name ? if version != null then "${pname}-${version}" else pname
, registry ? "https://registry.npmjs.org"
, script ? "build"
, distDir ? "dist"
, distDirs ? (if workspace == null then [distDir] else (map (c: "${c}/dist") components))
, distDirIsOut ? true
, installNodeModules ? false
, installPackageFiles ? false
, installInPlace ? false
, installEnv ? { }
, buildEnv ? { }
, noDevDependencies ? false
, extraNodeModuleSources ? [ ]
, copyPnpmStore ? true
@@ -37,17 +50,73 @@ in
, ...
}@attrs:
let
# Flag that can be computed from arguments, indicating a workspace was
# supplied. Only used in these let bindings.
isWorkspace = workspace != null && components != [];
# Utility functions
forEachConcat = f: xs: concatStringsSep "\n" (map f xs);
forEachComponent = f: forEachConcat f components;
# Computed values used below that don't loop
nativeBuildInputs = [
nodejs
pnpm
pkg-config
] ++ extraBuildInputs;
] ++ extraBuildInputs ++ (optional copyNodeModules rsync);
copyLink =
if copyNodeModules
then "rsync -a --chmod=u+w"
else "ln -s";
rsyncSlash = optionalString copyNodeModules "/";
packageFilesWithoutLockfile =
[
{ name = "package.json"; value = packageJSON; }
] ++ componentPackageJSONs ++ computedNodeModuleSources;
computedNodeModuleSources =
(if pnpmWorkspaceYaml == null
then []
else [
{name = "pnpm-workspace.yaml"; value = pnpmWorkspaceYaml;}
]
) ++ extraNodeModuleSources;
# Computed values that loop over something
computedDistFiles =
let
packageFileNames = ["pnpm-lock.yaml"] ++
map ({ name, ... }: name) packageFilesWithoutLockfile;
in
distDirs ++
optionals installNodeModules nodeModulesDirs ++
optionals installPackageFiles packageFileNames;
nodeModulesDirs =
if isWorkspace then
["node_modules"] ++ (map (c: "${c}/node_modules") components)
else ["node_modules"];
filterString = concatStringsSep " " (
["--recursive" "--stream"] ++
map (c: "--filter ./${c}") components
) + " ";
buildScripts = ''
pnpm run ${optionalString isWorkspace filterString}${script}
'';
# Flag derived from value computed above, indicating the single dist
# should be copied as $out directly, rather than $out/${distDir}
computedDistDirIsOut =
length computedDistFiles == 1 && distDirIsOut && !isWorkspace;
in
stdenv.mkDerivation (
recursiveUpdate
(rec {
inherit src name nativeBuildInputs;

postUnpack = ''
${optionalString (pnpmWorkspaceYaml != null) ''
cp ${pnpmWorkspaceYaml} pnpm-workspace.yaml
''}
${forEachComponent (component:
''mkdir -p "${component}"'')
}
'';

configurePhase = ''
export HOME=$NIX_BUILD_TOP # Some packages need a writable HOME
export npm_config_nodedir=${nodejs}
@@ -56,29 +125,44 @@ in
${if installInPlace
then passthru.nodeModules.buildPhase
else ''
${if !copyNodeModules
then "ln -s"
else "cp -r"
} ${passthru.nodeModules}/node_modules node_modules
''
else
forEachConcat (
nodeModulesDir: ''
${copyLink} ${passthru.nodeModules}/${nodeModulesDir}${rsyncSlash} ${nodeModulesDir}
'') nodeModulesDirs
}
runHook postConfigure
'';

buildPhase = ''
${concatStringsSep "\n" (
mapAttrsToList
(n: v: ''export ${n}="${v}"'')
buildEnv
)}
runHook preBuild
pnpm run ${script}
${buildScripts}
runHook postBuild
'';

installPhase = ''
runHook preInstall
${if distDir == "." then "cp -r" else "mv"} ${distDir} $out
${if computedDistDirIsOut then ''
${if distDir == "." then "cp -r" else "mv"} ${distDir} $out
''
else ''
mkdir -p $out
${forEachConcat (dDir: ''
cp -r --parents ${dDir} $out
'') computedDistFiles
}
''
}
runHook postInstall
'';
@@ -112,18 +196,19 @@ in
inherit nativeBuildInputs;

unpackPhase = concatStringsSep "\n"
(
( [ # components is an empty list for non workspace builds
(forEachComponent (component: ''
mkdir -p "${component}"
'')) ] ++
map
(v:
let
nv = if isAttrs v then v else { name = "."; value = v; };
in
"cp -vr ${nv.value} ${nv.name}"
"cp -vr \"${nv.value}\" \"${nv.name}\""
)
([
{ name = "package.json"; value = packageJSON; }
{ name = "pnpm-lock.yaml"; value = passthru.patchedLockfileYaml; }
] ++ extraNodeModuleSources)
([{ name = "pnpm-lock.yaml"; value = passthru.patchedLockfileYaml; }]
++ packageFilesWithoutLockfile)
);

buildPhase = ''
@@ -140,25 +225,29 @@ in
else "cp -RL"
} ${passthru.pnpmStore} $(pnpm store path)
${lib.optionalString copyPnpmStore "chmod -R +w $(pnpm store path)"}
${optionalString copyPnpmStore "chmod -R +w $(pnpm store path)"}
${concatStringsSep "\n" (
mapAttrsToList
(n: v: ''export ${n}="${v}"'')
installEnv
)}
pnpm install ${optionalString noDevDependencies "--prod "}--frozen-lockfile --offline
pnpm install --stream ${optionalString noDevDependencies "--prod "}--frozen-lockfile --offline
'';

installPhase = ''
mkdir -p $out
cp -r node_modules/. $out/node_modules
${forEachComponent (component: ''
mkdir -p $out/"${component}"
cp -r "${component}/node_modules" $out/"${component}/node_modules"
'')}
'';
};
};

})
(attrs // { extraNodeModuleSources = null; installEnv = null; })
(attrs // { extraNodeModuleSources = null; installEnv = null; buildEnv = null;})
);
}