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 all 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
8 changes: 7 additions & 1 deletion lib/internal/nodejsLockUtils.nix
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@
in
lib.removeSuffix "/" nextPath;

sanitizeLockfile = lock:
# This lockfile module only supports lockfileVersion 2 and 3
if ! lock ? lockfileVersion || lock.lockfileVersion <= 1
then throw "This lockfile module only supports lockfileVersion 2 and 3"
else lock;

findEntry =
# = "attrs"
packageLock:
Expand All @@ -24,5 +30,5 @@
then throw "${search} not found in package-lock.json."
else findEntry packageLock (stripPath currentPath) search;
in {
inherit findEntry stripPath;
inherit findEntry stripPath sanitizeLockfile;
Copy link
Member

Choose a reason for hiding this comment

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

sanitizeLockFile -> ensureLockFileVersion?

}
56 changes: 35 additions & 21 deletions modules/dream2nix/nodejs-package-lock-v3/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,31 +11,42 @@

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
}
> We should mention that docs are imcomplete on npmjs.com
pent is one entry of 'packages'
*/
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,18 +81,21 @@
};
};

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

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

pdefs = parse config.nodejs-package-lock-v3.packageLock;
pdefs = parse (nodejsLockUtils.sanitizeLockfile config.nodejs-package-lock-v3.packageLock);
in {
imports = [
./interface.nix
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.
# '';
# };
};
}
43 changes: 19 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,21 @@ 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 {
};
};
config = evaled.config;
in {
expr = config.nodejs-package-lock-v3.pdefs;
expectedError = {
type = "ThrownError";
msg = "Invalid lockfile";
};
};
}
56 changes: 56 additions & 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,60 @@
expr = path;
expected = "node_modules/underscore";
};

# test the lock
test_nodejsLockUtils_lockfile_v3 = let
plock = {
name = "foo";
version = "1.0.0";
lockfileVersion = 3;
packages = {};
};
in {
expr = nodejsLockUtils.sanitizeLockfile plock;
expected = plock;
};

test_nodejsLockUtils_lockfile_v2 = let
plock = {
name = "foo";
version = "1.0.0";
lockfileVersion = 2;
packages = {};
dependencies = {};
};
in {
expr = nodejsLockUtils.sanitizeLockfile plock;
expected = plock;
};

test_nodejsLockUtils_lockfile_v1 = let
plock = {
name = "foo";
version = "1.0.0";
lockfileVersion = 1;
dependencies = {};
};
in {
expr = nodejsLockUtils.sanitizeLockfile plock;
expectedError = {
type = "ThrownError";
msg = "This lockfile module only supports lockfileVersion 2 and 3";
};
};

test_nodejsLockUtils_lockfile_missing_lockfileVersion = let
plock = {
name = "foo";
version = "1.0.0";
# lockfileVersion = 3;
packages = {};
};
in {
expr = nodejsLockUtils.sanitizeLockfile plock;
expectedError = {
type = "ThrownError";
msg = "lockfileVersion";
};
};
}