diff --git a/lib/internal/nodejsLockUtils.nix b/lib/internal/nodejsLockUtils.nix index a82cdfea6e..ba96e3e325 100644 --- a/lib/internal/nodejsLockUtils.nix +++ b/lib/internal/nodejsLockUtils.nix @@ -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: @@ -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; } diff --git a/modules/dream2nix/nodejs-package-lock-v3/default.nix b/modules/dream2nix/nodejs-package-lock-v3/default.nix index 07859899fd..e8828c7708 100644 --- a/modules/dream2nix/nodejs-package-lock-v3/default.nix +++ b/modules/dream2nix/nodejs-package-lock-v3/default.nix @@ -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 {}; + + /* + 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: @@ -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 diff --git a/modules/dream2nix/nodejs-package-lock-v3/interface.nix b/modules/dream2nix/nodejs-package-lock-v3/interface.nix index e045273dba..ab87720b73 100644 --- a/modules/dream2nix/nodejs-package-lock-v3/interface.nix +++ b/modules/dream2nix/nodejs-package-lock-v3/interface.nix @@ -1,7 +1,3 @@ -# subsystemAttrs :: { -# meta? :: { -# } -# } { config, options, @@ -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 { @@ -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 { @@ -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. - # ''; - # }; }; } diff --git a/tests/nix-unit/test_nodejs_lock_v3/default.nix b/tests/nix-unit/test_nodejs_lock_v3/default.nix index 9e938604e4..f51976e7cd 100644 --- a/tests/nix-unit/test_nodejs_lock_v3/default.nix +++ b/tests/nix-unit/test_nodejs_lock_v3/default.nix @@ -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 @@ -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"; + }; + }; } diff --git a/tests/nix-unit/test_nodejs_lockutils/default.nix b/tests/nix-unit/test_nodejs_lockutils/default.nix index 9c5ce37ba8..f5de80f163 100644 --- a/tests/nix-unit/test_nodejs_lockutils/default.nix +++ b/tests/nix-unit/test_nodejs_lockutils/default.nix @@ -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"; + }; + }; }