Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/node-pkg-v2' into devShells
Browse files Browse the repository at this point in the history
  • Loading branch information
wmertens committed Aug 23, 2022
2 parents 68f2211 + a3c0e5e commit 0a68574
Show file tree
Hide file tree
Showing 4 changed files with 275 additions and 8 deletions.
21 changes: 16 additions & 5 deletions src/subsystems/nodejs/discoverers/default/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@

getTranslatorNames = path: let
nodes = l.readDir path;
packageJson = l.fromJSON (l.readFile "${path}/package.json");
packageJson = getPackageJson path;
translators =
# if the package has no dependencies we use the
# package-lock translator with `packageLock = null`
Expand All @@ -46,16 +46,27 @@
&& (packageJson.devDependencies or {} == {})
&& (packageJson.workspaces or [] == [])
then ["package-lock"]
else if nodes ? "package-lock.json"
then let
# Wish there was a way to get the version without reading a 2MB file
lockJson = getLockJson path;
lockVersion = lockJson.lockfileVersion or 0;
in
if lockVersion == 1
then ["package-lock"]
else if lockVersion == 2
then ["package-lock-v2"]
else ["package-json"]
else
l.optionals (nodes ? "package-lock.json") ["package-lock"]
++ l.optionals (nodes ? "yarn.lock") ["yarn-lock"]
l.optionals (nodes ? "yarn.lock") ["yarn-lock"]
++ ["package-json"];
in
translators;

# returns the parsed package.json of a given directory
getPackageJson = dirPath:
l.fromJSON (l.readFile "${dirPath}/package.json");
getJson = jsonPath: l.fromJSON (l.readFile jsonPath);
getPackageJson = dirPath: getJson "${dirPath}/package.json";
getLockJson = dirPath: getJson "${dirPath}/package-lock.json";

# returns all relative paths to workspaces defined by a glob
getWorkspacePaths = glob: tree:
Expand Down
9 changes: 6 additions & 3 deletions src/subsystems/nodejs/translators/package-json/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
openssh
]
''
# accroding to the spec, the translator reads the input from a json file
# according to the spec, the translator reads the input from a json file
jsonInput=$1
# read the json input
Expand All @@ -40,14 +40,17 @@
relPath=$(jq '.project.relPath' -c -r $jsonInput)
npmArgs=$(jq '.project.subsystemInfo.npmArgs' -c -r $jsonInput)
# TODO: Do we really need to copy everything? Just package.json + .npmrc
# is enough, no? And then pass the lock file to translate separately?
cp -r $source/* ./
chmod -R +w ./
newSource=$(pwd)
cd ./$relPath
rm -rf package-lock.json yarn.lock
echo "translating in temp dir: $(pwd)"
echo "Translating with npm in temp dir: $(pwd)"
echo "You can avoid this by adding your own package-lock.json file"
if [ "$(jq '.project.subsystemInfo.noDev' -c -r $jsonInput)" == "true" ]; then
echo "excluding dev dependencies"
Expand All @@ -61,7 +64,7 @@
jq ".source = \"$newSource\"" -c -r $jsonInput > $TMPDIR/newJsonInput
cd $WORKDIR
${subsystems.nodejs.translators.package-lock.translateBin} $TMPDIR/newJsonInput
${subsystems.nodejs.translators.package-lock-v2.translateBin} $TMPDIR/newJsonInput
'';

# inherit options from package-lock translator
Expand Down
149 changes: 149 additions & 0 deletions src/subsystems/nodejs/translators/package-lock-v2/default.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# TODO use translate2
# TODO use package.json for v1 lock files
{
dlib,
lib,
...
}: let
b = builtins;
l = lib // builtins;
nodejsUtils = import ../utils.nix {inherit lib;};

translate = {
translatorName,
utils,
pkgs,
...
}: {
project,
source,
tree,
# translator args
# name
# nodejs
...
} @ args: let
b = builtins;

name =
if (args.name or "{automatic}") != "{automatic}"
then args.name
else project.name;
tree = args.tree.getNodeFromPath project.relPath;
relPath = project.relPath;
source = "${args.source}/${relPath}";
workspaces = project.subsystemInfo.workspaces or [];

getResolved = tree: project: let
lock =
(nodejsUtils.getWorkspaceLockFile tree project "package-lock.json").jsonContent;
resolved = import ./v2-parse.nix {inherit lib lock source;};
in
resolved;

resolved = getResolved args.tree project;

packageVersion = resolved.self.version or "unknown";

rootDependencies = resolved.self.deps;

identifyGitSource = dependencyObject:
# TODO: when integrity is there, and git url is github then use tarball instead
# ! (dependencyObject ? integrity) &&
dlib.identifyGitUrl dependencyObject.url;

getVersion = dependencyObject: dependencyObject.version;

getPath = dependencyObject:
lib.removePrefix "file:" dependencyObject.url;

stripDep = dep: l.removeAttrs dep ["pname" "version" "deps"];
in
utils.simpleTranslate
({
getDepByNameVer,
dependenciesByOriginalID,
...
}: rec {
inherit translatorName;
location = relPath;

# values
inputData = resolved.allDeps;

defaultPackage = name;

packages =
{"${defaultPackage}" = packageVersion;}
// (nodejsUtils.getWorkspacePackages tree workspaces);

mainPackageDependencies = resolved.self.deps;

subsystemName = "nodejs";

subsystemAttrs = {nodejsVersion = args.nodejs;};

# functions
serializePackages = inputData: inputData;

getName = dependencyObject: dependencyObject.pname;

inherit getVersion;

# TODO handle npm link maybe?
getSourceType = dependencyObject:
if identifyGitSource dependencyObject
then "git"
else if lib.hasPrefix "file:" dependencyObject.url
then "path"
else "http";

sourceConstructors = {
git = dependencyObject:
(stripDep dependencyObject)
// (dlib.parseGitUrl dependencyObject.url);

http = dependencyObject: (stripDep dependencyObject);

path = dependencyObject:
(stripDep dependencyObject)
// (dlib.construct.pathSource {
path = getPath dependencyObject;
rootName = project.name;
rootVersion = packageVersion;
});
};

getDependencies = dependencyObject: dependencyObject.deps;
});
in rec {
version = 2;

type = "pure";

inherit translate;

extraArgs = {
name = {
description = "The name of the main package";
examples = [
"react"
"@babel/code-frame"
];
default = "{automatic}";
type = "argument";
};

# TODO: this should either be removed or only used to select
# the nodejs version for translating, not for building.
nodejs = {
description = "nodejs version to use for building";
default = "16";
examples = [
"14"
"16"
];
type = "argument";
};
};
}
104 changes: 104 additions & 0 deletions src/subsystems/nodejs/translators/package-lock-v2/v2-parse.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# This parses a v2 package-lock.json file. This format includes all information
# to get correct dependencies, including peer dependencies and multiple
# versions. lock.packages is a set that includes the path of each dep, and
# this function teases it apart to know exactly which dep is being resolved.
# The format of the lockfile is documented at
# https://docs.npmjs.com/cli/v8/configuring-npm/package-lock-json/
{
lib,
lock,
source,
}:
assert (lib.elem lock.lockfileVersion [2 3]); let
b = builtins;
# { "node_modules/@foo/bar/node_modules/meep": pkg; ... }
pkgs = lock.packages;
lockName = lock.name or "unnamed";
lockVersion = lock.version or "unknown";

# First part is always "" and path doesn't start with /
toPath = parts: let
joined = b.concatStringsSep "/node_modules/" parts;
len = b.stringLength joined;
sliced = b.substring 1 len joined;
in
sliced;
toParts = path: b.filter b.isString (b.split "/?node_modules/" path);

getDep = name: parts:
if b.length parts == 0
then null
else pkgs.${toPath (parts ++ [name])} or (getDep name (lib.init parts));
resolveDep = name: parts: isOptional: let
dep = getDep name parts;
in
if dep == null
then
if !isOptional
then b.abort "Cannot resolve dependency ${name} from ${parts}"
else null
else {
inherit name;
inherit (dep) version;
};
resolveDeps = nameSet: parts: isOptional:
if nameSet == null
then []
else let
depNames = b.attrNames nameSet;
resolved = b.map (n: resolveDep n parts isOptional) depNames;
in
b.filter (d: d != null) resolved;

mapPkg = path: let
parts = toParts path;
pname = let
n = lib.last parts;
in
if n == ""
then lockName
else n;
in
{
version ? "unknown",
# URL to content - only main package is not defined
resolved ? "file://${source}",
# hash for content
integrity ? null,
# platforms this package works on
os ? null,
# this is a dev dependency
dev ? false,
# this is an optional dependency
optional ? false,
# this is an optional dev dependency
devOptional ? false,
# set of binary scripts { name = relativePath }
bin ? {},
# pkg needs to run install scripts
hasInstallScript ? false,
dependencies ? null,
devDependencies ? null,
peerDependencies ? null,
optionalDependencies ? null,
peerDependenciesMeta ? null,
...
}: let
deps =
lib.unique
((resolveDeps dependencies parts false)
++ (resolveDeps devDependencies parts true)
++ (resolveDeps optionalDependencies parts true)
++ (resolveDeps peerDependencies parts true)
++ (resolveDeps peerDependenciesMeta parts true));
in {
inherit pname version deps os dev optional devOptional bin;
url = resolved;
hash = integrity;
# Storing negation so other translators don't have to have this feature
noInstall = !hasInstallScript;
};

allDeps = lib.mapAttrsToList mapPkg pkgs;
self = lib.findFirst (d: d.pname == lockName && d.version == lockVersion) (b.abort "Could not find main package") allDeps;
in {inherit allDeps self;}

0 comments on commit 0a68574

Please sign in to comment.