232 lines
6.5 KiB
Nix
232 lines
6.5 KiB
Nix
{
|
|
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,
|
|
fetcherOpts,
|
|
}:
|
|
(
|
|
if module ? "resolved" && module.resolved != null 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;
|
|
}
|
|
// fetcherOpts
|
|
))
|
|
else if lib.hasPrefix "git" module.resolved then
|
|
(builtins.fetchGit (
|
|
{
|
|
url = module.resolved;
|
|
}
|
|
// fetcherOpts
|
|
))
|
|
else
|
|
throw "Unsupported URL scheme: ${scheme}"
|
|
)
|
|
)
|
|
else
|
|
null
|
|
);
|
|
|
|
cleanModule = lib.flip removeAttrs [
|
|
"link" # Remove link not to symlink directories. These have been processed to store paths already.
|
|
"funding" # Remove funding to get rid sponsorship nag in build output
|
|
];
|
|
|
|
# Manage node_modules outside of the store with hooks
|
|
hooks = callPackages ./hooks { };
|
|
|
|
in
|
|
lib.fix (self: {
|
|
importNpmLock =
|
|
{
|
|
npmRoot ? null,
|
|
package ? importJSON (npmRoot + "/package.json"),
|
|
packageLock ? importJSON (npmRoot + "/package-lock.json"),
|
|
pname ? getName package,
|
|
version ? getVersion package,
|
|
# A map of additional fetcher options forwarded to the fetcher used to download the package.
|
|
# Example: { "node_modules/axios" = { curlOptsList = [ "--verbose" ]; }; }
|
|
# This will download the axios package with curl's verbose option.
|
|
fetcherOpts ? { },
|
|
# A map from node_module path to an alternative package to use instead of fetching the source in package-lock.json.
|
|
# Example: { "node_modules/axios" = stdenv.mkDerivation { ... }; }
|
|
# This is useful if you want to inject custom sources for a specific package.
|
|
packageSourceOverrides ? { },
|
|
}:
|
|
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 (
|
|
modulePath: module:
|
|
let
|
|
src =
|
|
packageSourceOverrides.${modulePath} or (fetchModule {
|
|
inherit module npmRoot;
|
|
fetcherOpts = fetcherOpts.${modulePath} or { };
|
|
});
|
|
in
|
|
cleanModule module
|
|
// 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
|
|
'';
|
|
|
|
# Build node modules from package.json & package-lock.json
|
|
buildNodeModules =
|
|
{
|
|
npmRoot ? null,
|
|
package ? importJSON (npmRoot + "/package.json"),
|
|
packageLock ? importJSON (npmRoot + "/package-lock.json"),
|
|
nodejs,
|
|
derivationArgs ? { },
|
|
}:
|
|
stdenv.mkDerivation (
|
|
{
|
|
pname = derivationArgs.pname or "${getName package}-node-modules";
|
|
version = derivationArgs.version or getVersion package;
|
|
|
|
dontUnpack = true;
|
|
|
|
npmDeps = self.importNpmLock {
|
|
inherit npmRoot package packageLock;
|
|
};
|
|
|
|
package = toJSON package;
|
|
packageLock = toJSON packageLock;
|
|
|
|
installPhase = ''
|
|
runHook preInstall
|
|
mkdir $out
|
|
cp package.json $out/
|
|
cp package-lock.json $out/
|
|
[[ -d node_modules ]] && mv node_modules $out/
|
|
runHook postInstall
|
|
'';
|
|
}
|
|
// derivationArgs
|
|
// {
|
|
nativeBuildInputs = [
|
|
nodejs
|
|
nodejs.passthru.python
|
|
hooks.npmConfigHook
|
|
] ++ derivationArgs.nativeBuildInputs or [ ];
|
|
|
|
passAsFile = [
|
|
"package"
|
|
"packageLock"
|
|
] ++ derivationArgs.passAsFile or [ ];
|
|
|
|
postPatch =
|
|
''
|
|
cp --no-preserve=mode "$packagePath" package.json
|
|
cp --no-preserve=mode "$packageLockPath" package-lock.json
|
|
''
|
|
+ derivationArgs.postPatch or "";
|
|
}
|
|
);
|
|
|
|
inherit hooks;
|
|
inherit (hooks) npmConfigHook linkNodeModulesHook;
|
|
|
|
__functor = self: self.importNpmLock;
|
|
})
|