diff --git a/derivation.nix b/derivation.nix index a032422..556cca0 100644 --- a/derivation.nix +++ b/derivation.nix @@ -1,6 +1,7 @@ { lib , stdenv , nodejs +, rsync , pkg-config , callPackage , writeText @@ -15,15 +16,23 @@ 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)) , installInPlace ? false , installEnv ? { } , buildEnv ? { } @@ -38,17 +47,65 @@ 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 + 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 distDirs == 1 && !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} @@ -57,12 +114,11 @@ 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 @@ -77,7 +133,7 @@ in runHook preBuild - pnpm run ${script} + ${buildScripts} runHook postBuild ''; @@ -85,7 +141,17 @@ in 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 + '') distDirs + } + '' + } runHook postInstall ''; @@ -119,18 +185,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 = '' @@ -147,7 +214,7 @@ 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 @@ -155,12 +222,16 @@ in 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" + '')} ''; }; };