Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat/nodejs_sanitize_lockfile #665

Open
wants to merge 7 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion lib/internal/nodejsLockUtils.nix
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,33 @@
else if currentPath == ""
then throw "${search} not found in package-lock.json."
else findEntry packageLock (stripPath currentPath) search;

# Returns the names of all "bundledDependencies".
# People depend on different types and different names. Unfortunatly those fields are not part of the offical npm documentation.
# Which may also be the reason for the mess.
#
# TODO: define unit tests.
# Adopted from https://github.com/aakropotkin/floco/blob/708c4ffa0c05033c29fe6886a238cb20c3ba3fb4/modules/plock/implementation.nix#L139
#
# getBundledDependencies :: Pent -> {}
getBundledDependencies = pent: let
hsjobeki marked this conversation as resolved.
Show resolved Hide resolved
# b :: bool | []
b = pent.bundledDependencies or pent.bundleDependencies or [];
in
# The following asserts is the XOR logic.
# "bundle" and "bundled" dependencies are both valid but invalid if both or none keys exist
assert ( pent ? bundledDependencies ) ->
( ! ( pent ? bundleDependencies ) );
assert ( pent ? bundleDependencies ) ->
( ! ( pent ? bundledDependencies ) );
if b == [] then {} else
if builtins.isList b then { bundledDependencies = b; } else
if ! b then {} else {
# b :: true
bundledDependencies = builtins.attrNames (
( pent.dependencies or {} ) // ( pent.requires or {} )
);
};
in {
inherit findEntry stripPath;
inherit findEntry stripPath getBundledDependencies;
}
62 changes: 40 additions & 22 deletions modules/dream2nix/nodejs-package-lock-v3/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,47 @@

inherit (config.deps) fetchurl;

nodejsLockUtils = import ../../../lib/internal/nodejsLockUtils.nix {inherit lib;};
nodejsLockUtils = import ../../../lib/internal/nodejsLockUtils.nix { inherit lib; };

isLink = plent: plent ? link && plent.link;
# Collection of sanitized functions that always return the same type
isLink = pent: pent.link or false;

parseSource = plent:
if isLink plent
# isDev = pent: pent.dev or false;
# isOptional = pent: pent.optional or false;
# isInBundle = pent: pent.inBundle or false;
# hasInstallScript = pent: pent.hasInstallScript or false;
# getBin = pent: pent.bin or {};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can these be removed?


/*
Pent :: {
See: https://docs.npmjs.com/cli/v9/configuring-npm/package-lock-json#packages
}
pent is one entry of 'packages'
hsjobeki marked this conversation as resolved.
Show resolved Hide resolved
*/
parseSource = pent:
if isLink pent
then
# entry is local file
(builtins.dirOf config.nodejs-package-lock-v3.packageLockFile) + "/${plent.resolved}"
(builtins.dirOf config.nodejs-package-lock-v3.packageLockFile) + "/${pent.resolved}"
else
fetchurl {
url = plent.resolved;
hash = plent.integrity;
url = pent.resolved;
hash = pent.integrity;
};

getDependencies = lock: path: plent:
l.mapAttrs (name: _descriptor: {
dev = plent.dev or false;
version = let
# Need this util as dependencies no explizitly locked version
# This findEntry is needed to find the exact locked version
packageIdent = nodejsLockUtils.findEntry lock path name;
in
# Read version from package-lock entry for the resolved package
lock.packages.${packageIdent}.version;

getDependencies = lock: path: pent:
l.mapAttrs (depName: _semverConstraint:
let
packageIdent = nodejsLockUtils.findEntry lock path depName;
depPent = lock.packages.${packageIdent};
in
{
dev = pent.dev or false;
version = depPent.version;
})
(plent.dependencies or {} // plent.devDependencies or {} // plent.optionalDependencies or {});
(pent.dependencies or {} // pent.devDependencies or {} // pent.optionalDependencies or {});


# Takes one entry of "package" from package-lock.json
parseEntry = lock: path: entry:
Expand Down Expand Up @@ -70,15 +84,19 @@
};
};

parse = lock:
builtins.foldl'
mergePdefs = builtins.foldl'
(acc: entry:
acc
// {
${entry.name} = acc.${entry.name} or {} // entry.value;
})
{}
# [{name=; value=;} ...]
{};

parse = lock:
assert lock.lockfileVersion != 1;
assert lock ? packages;
mergePdefs
# type: [ { name :: String; value :: {...}; } ]
(l.mapAttrsToList (parseEntry lock) lock.packages);

pdefs = parse config.nodejs-package-lock-v3.packageLock;
Expand Down
71 changes: 20 additions & 51 deletions modules/dream2nix/nodejs-package-lock-v3/interface.nix
Original file line number Diff line number Diff line change
@@ -1,7 +1,3 @@
# subsystemAttrs :: {
# meta? :: {
# }
# }
{
config,
options,
Expand All @@ -16,7 +12,7 @@

derivationType = t.oneOf [t.str t.path t.package];

# A stricteer submodule type that prevents derivations from being
# A stricter submodule type that prevents derivations from being
# detected as modules by accident. (derivations are attrs as well as modules)
drvPart = let
type = t.submoduleWith {
Expand Down Expand Up @@ -72,20 +68,25 @@ in {
description = "The content of the package-lock.json";
};

# pdefs.${name}.${version} :: {
# // all dependency entries of that package.
# // each dependency is guaranteed to have its own entry in 'pdef'
# // A package without dependencies has `dependencies = {}` (So dependencies has a constant type)
# dependencies = {
# ${name} = {
# dev = boolean;
# version :: string;
# }
# }
# // Pointing to the source of the package.
# // in most cases this is a tarball (tar.gz) which needs to be unpacked by e.g. unpackPhase
# source :: Derivation | Path
# }
/*

type: pdefs.${name}.${version} :: {

// Pointing to the source of the package.
// in most cases this is a tarball (tar.gz) which needs to be unpacked by e.g. unpackPhase
source :: Derivation | Path

// all dependency entries of that package.
// each dependency is guaranteed to have its own entry in 'pdef'
// A package without dependencies has `dependencies = {}` (Empty set)
dependencies = {
${name} = {
dev = boolean;
version :: string;
}
}
}
*/
pdefs = {
type = t.attrsOf (t.attrsOf (t.submodule {
options.dependencies = l.mkOption {
Expand All @@ -94,37 +95,5 @@ in {
options.source = optPackage;
}));
};

# packageJsonFile = {
# type = t.path;
# description = ''
# The package.json file to use.
# '';
# default = cfg.source + "/package.json";
# };
# packageJson = {
# type = t.attrs;
# description = "The content of the package.json";
# };
# source = {
# type = t.either t.path t.package;
# description = "Source of the package";
# default = config.mkDerivation.src;
# };
# withDevDependencies = {
# type = t.bool;
# default = true;
# description = ''
# Whether to include development dependencies.
# Usually it's a bad idea to disable this, as development dependencies can contain important build time dependencies.
# '';
# };
# workspaces = {
# type = t.listOf t.str;
# description = ''
# Workspaces to include.
# Defaults to the ones defined in package.json.
# '';
# };
};
}
45 changes: 21 additions & 24 deletions tests/nix-unit/test_nodejs_lock_v3/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -68,18 +68,13 @@ in {
"resolved" = "https://registry.npmjs.org/async/-/async-0.2.10.tgz";
"integrity" = "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==";
};
# "node_modules/@org/async" = {
# "version" = "0.2.10";
# "resolved" = "https://registry.npmjs.org/async/-/async-0.2.10.tgz";
# "integrity" = "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==";
# };
};
};
};
config = evaled.config;
in {
expr = config.nodejs-package-lock-v3.pdefs."async"."0.2.10".source.type;
expected = "derivation";
expr = "${config.nodejs-package-lock-v3.pdefs."async"."0.2.10".source}";
expected = "/nix/store/sm4v0qaynkjf704lrcqxhlssp003y9h8-async-0.2.10.tgz";
};

# test if dependencies are ignored successfully in pip.rootDependencies
Expand Down Expand Up @@ -297,21 +292,23 @@ in {
];
};

# TODO: some infinite recursion occurs when accessing pdef.{name}.{version}.source
# test_nodejs_parse_lockfile = let
# evaled = eval {
# imports = [
# dream2nix.modules.dream2nix.nodejs-package-lock-v3
# ];
# nodejs-package-lock-v3.packageLockFile = ./package-lock.json;
# nodejs-package-lock-v3.packageLock = lib.mkForce (builtins.fromJSON (builtins.readFile ./package-lock.json));
# # set the root package source
# nodejs-package-lock-v3.pdefs."minimal"."1.0.0".source = "";
# };
# config = evaled.config;
# in {
# expr = config.nodejs-package-lock-v3.pdefs."argparse"."0.1.16";
# expected = {
# };
# };
test_nodejs_wrong_lockfile_version = let
evaled = eval {
imports = [
dream2nix.modules.dream2nix.nodejs-package-lock-v3
];
nodejs-package-lock-v3.packageLock = lib.mkForce {
# Example content of lockfile
"lockfileVersion" = 1;
};
};
config = evaled.config;
in {
expr = builtins.tryEval (config.nodejs-package-lock-v3.pdefs);
expected = {
success = false;
value = false;
};
};

}
1 change: 1 addition & 0 deletions tests/nix-unit/test_nodejs_lockutils/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,5 @@
expr = path;
expected = "node_modules/underscore";
};

}