Merge pull request #289231 from adisbladis/fetchnpmlock
importNpmLock: init
This commit is contained in:
commit
434df3c94b
7 changed files with 316 additions and 3 deletions
|
@ -233,6 +233,37 @@ sha256-AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=
|
||||||
|
|
||||||
It returns a derivation with all `package-lock.json` dependencies downloaded into `$out/`, usable as an npm cache.
|
It returns a derivation with all `package-lock.json` dependencies downloaded into `$out/`, usable as an npm cache.
|
||||||
|
|
||||||
|
#### importNpmLock {#javascript-buildNpmPackage-importNpmLock}
|
||||||
|
|
||||||
|
`importNpmLock` is a Nix function that requires the following optional arguments:
|
||||||
|
|
||||||
|
- `npmRoot`: Path to package directory containing the source tree
|
||||||
|
- `package`: Parsed contents of `package.json`
|
||||||
|
- `packageLock`: Parsed contents of `package-lock.json`
|
||||||
|
- `pname`: Package name
|
||||||
|
- `version`: Package version
|
||||||
|
|
||||||
|
It returns a derivation with a patched `package.json` & `package-lock.json` with all dependencies resolved to Nix store paths.
|
||||||
|
|
||||||
|
This function is analogous to using `fetchNpmDeps`, but instead of specifying `hash` it uses metadata from `package.json` & `package-lock.json`.
|
||||||
|
|
||||||
|
Note that `npmHooks.npmConfigHook` cannot be used with `importNpmLock`. You will instead need to use `importNpmLock.npmConfigHook`:
|
||||||
|
|
||||||
|
```nix
|
||||||
|
{ buildNpmPackage, importNpmLock }:
|
||||||
|
|
||||||
|
buildNpmPackage {
|
||||||
|
pname = "hello";
|
||||||
|
version = "0.1.0";
|
||||||
|
|
||||||
|
npmDeps = importNpmLock {
|
||||||
|
npmRoot = ./.;
|
||||||
|
};
|
||||||
|
|
||||||
|
npmConfigHook = importNpmLock.npmConfigHook;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### corepack {#javascript-corepack}
|
### corepack {#javascript-corepack}
|
||||||
|
|
||||||
This package puts the corepack wrappers for pnpm and yarn in your PATH, and they will honor the `packageManager` setting in the `package.json`.
|
This package puts the corepack wrappers for pnpm and yarn in your PATH, and they will honor the `packageManager` setting in the `package.json`.
|
||||||
|
|
|
@ -49,6 +49,12 @@
|
||||||
name = "${name}-npm-deps";
|
name = "${name}-npm-deps";
|
||||||
hash = npmDepsHash;
|
hash = npmDepsHash;
|
||||||
}
|
}
|
||||||
|
# Custom npmConfigHook
|
||||||
|
, npmConfigHook ? null
|
||||||
|
# Custom npmBuildHook
|
||||||
|
, npmBuildHook ? null
|
||||||
|
# Custom npmInstallHook
|
||||||
|
, npmInstallHook ? null
|
||||||
, ...
|
, ...
|
||||||
} @ args:
|
} @ args:
|
||||||
|
|
||||||
|
@ -57,14 +63,19 @@ let
|
||||||
npmHooks = buildPackages.npmHooks.override {
|
npmHooks = buildPackages.npmHooks.override {
|
||||||
inherit nodejs;
|
inherit nodejs;
|
||||||
};
|
};
|
||||||
|
|
||||||
inherit (npmHooks) npmConfigHook npmBuildHook npmInstallHook;
|
|
||||||
in
|
in
|
||||||
stdenv.mkDerivation (args // {
|
stdenv.mkDerivation (args // {
|
||||||
inherit npmDeps npmBuildScript;
|
inherit npmDeps npmBuildScript;
|
||||||
|
|
||||||
nativeBuildInputs = nativeBuildInputs
|
nativeBuildInputs = nativeBuildInputs
|
||||||
++ [ nodejs npmConfigHook npmBuildHook npmInstallHook nodejs.python ]
|
++ [
|
||||||
|
nodejs
|
||||||
|
# Prefer passed hooks
|
||||||
|
(if npmConfigHook != null then npmConfigHook else npmHooks.npmConfigHook)
|
||||||
|
(if npmBuildHook != null then npmBuildHook else npmHooks.npmBuildHook)
|
||||||
|
(if npmInstallHook != null then npmInstallHook else npmHooks.npmInstallHook)
|
||||||
|
nodejs.python
|
||||||
|
]
|
||||||
++ lib.optionals stdenv.isDarwin [ darwin.cctools ];
|
++ lib.optionals stdenv.isDarwin [ darwin.cctools ];
|
||||||
buildInputs = buildInputs ++ [ nodejs ];
|
buildInputs = buildInputs ++ [ nodejs ];
|
||||||
|
|
||||||
|
|
134
pkgs/build-support/node/import-npm-lock/default.nix
Normal file
134
pkgs/build-support/node/import-npm-lock/default.nix
Normal file
|
@ -0,0 +1,134 @@
|
||||||
|
{ lib
|
||||||
|
, fetchurl
|
||||||
|
, stdenv
|
||||||
|
, callPackages
|
||||||
|
, runCommand
|
||||||
|
}:
|
||||||
|
|
||||||
|
let
|
||||||
|
inherit (builtins) match elemAt toJSON removeAttrs;
|
||||||
|
inherit (lib) importJSON mapAttrs;
|
||||||
|
|
||||||
|
matchGitHubReference = match "github(.com)?:.+";
|
||||||
|
getName = package: package.name or "unknown";
|
||||||
|
getVersion = package: package.version or "0.0.0";
|
||||||
|
|
||||||
|
# Fetch a module from package-lock.json -> packages
|
||||||
|
fetchModule =
|
||||||
|
{ module
|
||||||
|
, npmRoot ? null
|
||||||
|
}: (
|
||||||
|
if module ? "resolved" then
|
||||||
|
(
|
||||||
|
let
|
||||||
|
# Parse scheme from URL
|
||||||
|
mUrl = match "(.+)://(.+)" module.resolved;
|
||||||
|
scheme = elemAt mUrl 0;
|
||||||
|
in
|
||||||
|
(
|
||||||
|
if mUrl == null then
|
||||||
|
(
|
||||||
|
assert npmRoot != null; {
|
||||||
|
outPath = npmRoot + "/${module.resolved}";
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else if (scheme == "http" || scheme == "https") then
|
||||||
|
(
|
||||||
|
fetchurl {
|
||||||
|
url = module.resolved;
|
||||||
|
hash = module.integrity;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else if lib.hasPrefix "git" module.resolved then
|
||||||
|
(
|
||||||
|
builtins.fetchGit {
|
||||||
|
url = module.resolved;
|
||||||
|
}
|
||||||
|
)
|
||||||
|
else throw "Unsupported URL scheme: ${scheme}"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else null
|
||||||
|
);
|
||||||
|
|
||||||
|
# Manage node_modules outside of the store with hooks
|
||||||
|
hooks = callPackages ./hooks { };
|
||||||
|
|
||||||
|
in
|
||||||
|
{
|
||||||
|
importNpmLock =
|
||||||
|
{ npmRoot ? null
|
||||||
|
, package ? importJSON (npmRoot + "/package.json")
|
||||||
|
, packageLock ? importJSON (npmRoot + "/package-lock.json")
|
||||||
|
, pname ? getName package
|
||||||
|
, version ? getVersion package
|
||||||
|
}:
|
||||||
|
let
|
||||||
|
mapLockDependencies =
|
||||||
|
mapAttrs
|
||||||
|
(name: version: (
|
||||||
|
# Substitute the constraint with the version of the dependency from the top-level of package-lock.
|
||||||
|
if (
|
||||||
|
# if the version is `latest`
|
||||||
|
version == "latest"
|
||||||
|
||
|
||||||
|
# Or if it's a github reference
|
||||||
|
matchGitHubReference version != null
|
||||||
|
) then packageLock'.packages.${"node_modules/${name}"}.version
|
||||||
|
# But not a regular version constraint
|
||||||
|
else version
|
||||||
|
));
|
||||||
|
|
||||||
|
packageLock' = packageLock // {
|
||||||
|
packages =
|
||||||
|
mapAttrs
|
||||||
|
(_: module:
|
||||||
|
let
|
||||||
|
src = fetchModule {
|
||||||
|
inherit module npmRoot;
|
||||||
|
};
|
||||||
|
in
|
||||||
|
(removeAttrs module [
|
||||||
|
"link"
|
||||||
|
"funding"
|
||||||
|
]) // lib.optionalAttrs (src != null) {
|
||||||
|
resolved = "file:${src}";
|
||||||
|
} // lib.optionalAttrs (module ? dependencies) {
|
||||||
|
dependencies = mapLockDependencies module.dependencies;
|
||||||
|
} // lib.optionalAttrs (module ? optionalDependencies) {
|
||||||
|
optionalDependencies = mapLockDependencies module.optionalDependencies;
|
||||||
|
})
|
||||||
|
packageLock.packages;
|
||||||
|
};
|
||||||
|
|
||||||
|
mapPackageDependencies = mapAttrs (name: _: packageLock'.packages.${"node_modules/${name}"}.resolved);
|
||||||
|
|
||||||
|
# Substitute dependency references in package.json with Nix store paths
|
||||||
|
packageJSON' = package // lib.optionalAttrs (package ? dependencies) {
|
||||||
|
dependencies = mapPackageDependencies package.dependencies;
|
||||||
|
} // lib.optionalAttrs (package ? devDependencies) {
|
||||||
|
devDependencies = mapPackageDependencies package.devDependencies;
|
||||||
|
};
|
||||||
|
|
||||||
|
pname = package.name or "unknown";
|
||||||
|
|
||||||
|
in
|
||||||
|
runCommand "${pname}-${version}-sources"
|
||||||
|
{
|
||||||
|
inherit pname version;
|
||||||
|
|
||||||
|
passAsFile = [ "package" "packageLock" ];
|
||||||
|
|
||||||
|
package = toJSON packageJSON';
|
||||||
|
packageLock = toJSON packageLock';
|
||||||
|
} ''
|
||||||
|
mkdir $out
|
||||||
|
cp "$packagePath" $out/package.json
|
||||||
|
cp "$packageLockPath" $out/package-lock.json
|
||||||
|
'';
|
||||||
|
|
||||||
|
inherit hooks;
|
||||||
|
inherit (hooks) npmConfigHook;
|
||||||
|
|
||||||
|
__functor = self: self.importNpmLock;
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
const fs = require("fs");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
// When installing files rewritten to the Nix store with npm
|
||||||
|
// npm writes the symlinks relative to the build directory.
|
||||||
|
//
|
||||||
|
// This makes relocating node_modules tricky when refering to the store.
|
||||||
|
// This script walks node_modules and canonicalizes symlinks.
|
||||||
|
|
||||||
|
async function canonicalize(storePrefix, root) {
|
||||||
|
console.log(storePrefix, root)
|
||||||
|
const entries = await fs.promises.readdir(root);
|
||||||
|
const paths = entries.map((entry) => path.join(root, entry));
|
||||||
|
|
||||||
|
const stats = await Promise.all(
|
||||||
|
paths.map(async (path) => {
|
||||||
|
return {
|
||||||
|
path: path,
|
||||||
|
stat: await fs.promises.lstat(path),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const symlinks = stats.filter((stat) => stat.stat.isSymbolicLink());
|
||||||
|
const dirs = stats.filter((stat) => stat.stat.isDirectory());
|
||||||
|
|
||||||
|
// Canonicalize symlinks to their real path
|
||||||
|
await Promise.all(
|
||||||
|
symlinks.map(async (stat) => {
|
||||||
|
const target = await fs.promises.realpath(stat.path);
|
||||||
|
if (target.startsWith(storePrefix)) {
|
||||||
|
await fs.promises.unlink(stat.path);
|
||||||
|
await fs.promises.symlink(target, stat.path);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
// Recurse into directories
|
||||||
|
await Promise.all(dirs.map((dir) => canonicalize(storePrefix, dir.path)));
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
const storePrefix = args[0];
|
||||||
|
|
||||||
|
if (fs.existsSync("node_modules")) {
|
||||||
|
await canonicalize(storePrefix, "node_modules");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main();
|
13
pkgs/build-support/node/import-npm-lock/hooks/default.nix
Normal file
13
pkgs/build-support/node/import-npm-lock/hooks/default.nix
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
{ callPackage, lib, makeSetupHook, srcOnly, nodejs }:
|
||||||
|
{
|
||||||
|
npmConfigHook = makeSetupHook
|
||||||
|
{
|
||||||
|
name = "npm-config-hook";
|
||||||
|
substitutions = {
|
||||||
|
nodeSrc = srcOnly nodejs;
|
||||||
|
nodeGyp = "${nodejs}/lib/node_modules/npm/node_modules/node-gyp/bin/node-gyp.js";
|
||||||
|
canonicalizeSymlinksScript = ./canonicalize-symlinks.js;
|
||||||
|
storePrefix = builtins.storeDir;
|
||||||
|
};
|
||||||
|
} ./npm-config-hook.sh;
|
||||||
|
}
|
|
@ -0,0 +1,70 @@
|
||||||
|
# shellcheck shell=bash
|
||||||
|
|
||||||
|
npmConfigHook() {
|
||||||
|
echo "Executing npmConfigHook"
|
||||||
|
|
||||||
|
if [ -n "${npmRoot-}" ]; then
|
||||||
|
pushd "$npmRoot"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -z "${npmDeps-}" ]; then
|
||||||
|
echo "Error: 'npmDeps' should be set when using npmConfigHook."
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Configuring npm"
|
||||||
|
|
||||||
|
export HOME="$TMPDIR"
|
||||||
|
export npm_config_nodedir="@nodeSrc@"
|
||||||
|
export npm_config_node_gyp="@nodeGyp@"
|
||||||
|
npm config set offline true
|
||||||
|
npm config set progress false
|
||||||
|
npm config set fund false
|
||||||
|
|
||||||
|
echo "Installing patched package.json/package-lock.json"
|
||||||
|
|
||||||
|
# Save original package.json/package-lock.json for closure size reductions.
|
||||||
|
# The patched one contains store paths we don't want at runtime.
|
||||||
|
mv package.json .package.json.orig
|
||||||
|
if test -f package-lock.json; then # Not all packages have package-lock.json.
|
||||||
|
mv package-lock.json .package-lock.json.orig
|
||||||
|
fi
|
||||||
|
cp --no-preserve=mode "${npmDeps}/package.json" package.json
|
||||||
|
cp --no-preserve=mode "${npmDeps}/package-lock.json" package-lock.json
|
||||||
|
|
||||||
|
echo "Installing dependencies"
|
||||||
|
|
||||||
|
if ! npm install --ignore-scripts $npmInstallFlags "${npmInstallFlagsArray[@]}" $npmFlags "${npmFlagsArray[@]}"; then
|
||||||
|
echo
|
||||||
|
echo "ERROR: npm failed to install dependencies"
|
||||||
|
echo
|
||||||
|
echo "Here are a few things you can try, depending on the error:"
|
||||||
|
echo '1. Set `npmFlags = [ "--legacy-peer-deps" ]`'
|
||||||
|
echo
|
||||||
|
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
patchShebangs node_modules
|
||||||
|
|
||||||
|
npm rebuild $npmRebuildFlags "${npmRebuildFlagsArray[@]}" $npmFlags "${npmFlagsArray[@]}"
|
||||||
|
|
||||||
|
patchShebangs node_modules
|
||||||
|
|
||||||
|
# Canonicalize symlinks from relative paths to the Nix store.
|
||||||
|
node @canonicalizeSymlinksScript@ @storePrefix@
|
||||||
|
|
||||||
|
# Revert to pre-patched package.json/package-lock.json for closure size reductions
|
||||||
|
mv .package.json.orig package.json
|
||||||
|
if test -f ".package-lock.json.orig"; then
|
||||||
|
mv .package-lock.json.orig package-lock.json
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -n "${npmRoot-}" ]; then
|
||||||
|
popd
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Finished npmConfigHook"
|
||||||
|
}
|
||||||
|
|
||||||
|
postConfigureHooks+=(npmConfigHook)
|
|
@ -10262,6 +10262,8 @@ with pkgs;
|
||||||
inherit (callPackages ../build-support/node/fetch-npm-deps { })
|
inherit (callPackages ../build-support/node/fetch-npm-deps { })
|
||||||
fetchNpmDeps prefetch-npm-deps;
|
fetchNpmDeps prefetch-npm-deps;
|
||||||
|
|
||||||
|
importNpmLock = callPackages ../build-support/node/import-npm-lock { };
|
||||||
|
|
||||||
nodePackages_latest = dontRecurseIntoAttrs nodejs_latest.pkgs // { __attrsFailEvaluation = true; };
|
nodePackages_latest = dontRecurseIntoAttrs nodejs_latest.pkgs // { __attrsFailEvaluation = true; };
|
||||||
|
|
||||||
nodePackages = dontRecurseIntoAttrs nodejs.pkgs // { __attrsFailEvaluation = true; };
|
nodePackages = dontRecurseIntoAttrs nodejs.pkgs // { __attrsFailEvaluation = true; };
|
||||||
|
|
Loading…
Reference in a new issue