-
-
Notifications
You must be signed in to change notification settings - Fork 132
/
Copy pathnode-modules-tree.nix
153 lines (135 loc) · 4.17 KB
/
node-modules-tree.nix
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
{
lib,
getDependencies,
packageVersions,
name,
version,
# resolveChildren,
pkgs,
nodeModulesBuilder,
}: let
l = lib // builtins;
b = builtins;
/*
Function that resolves local vs global dependencies.
We copy dependencies into the global node_modules scope, if they don't have
conflicts there.
Otherwise we need to declare the package as 'private'.
type:
resolveChildren :: {
name :: String,
version :: String,
ancestorCandidates :: {
${pname} :: String
}
}
-> Dependencies
Dependencies :: {
${pname} :: {
version :: String,
dependencies :: Dependencies,
}
}
*/
resolveChildren = {
name,
version,
ancestorCandidates,
}: let
directDeps = getDependencies name version;
/*
Determine if a dependency needs to be installed as a local dep.
Node modules automatically inherits all ancestors and their siblings as
dependencies.
Therefore, installation of a local dep can be omitted, if the same dep
is already present as an ancestor or ancestor sibling.
*/
installLocally = name: version:
!(ancestorCandidates ? ${name})
|| (ancestorCandidates.${name} != version);
locallyRequiredDeps =
b.filter (d: installLocally d.name d.version) directDeps;
localDepsAttrs = b.listToAttrs (
l.map (dep: l.nameValuePair dep.name dep.version) locallyRequiredDeps
);
newAncestorCandidates = ancestorCandidates // localDepsAttrs;
# creates entry for single dependency.
mkDependency = name: version: {
inherit version;
dependencies = resolveChildren {
inherit name version;
ancestorCandidates = newAncestorCandidates;
};
};
# attrset of locally installed dependencies
dependencies = l.mapAttrs mkDependency localDepsAttrs;
in
dependencies;
# in case of a conflict pick the highest semantic version as root. All other version must then be private if used.
# TODO: pick the version that minimizes the tree
pickVersion = name: versions: directDepsAttrs.${name} or (l.head (l.sort (a: b: l.compareVersions a b == 1) versions));
rootPackages = l.mapAttrs (name: versions: pickVersion name versions) packageVersions;
# direct dependencies are all direct dependencies parsed from the lockfile at root level.
directDeps = getDependencies name version;
# type: { ${name} :: String } # e.g { "prettier" = "1.2.3"; }
# set with all direct dependencies contains every 'root' package with only one version
directDepsAttrs = l.listToAttrs (b.map (dep: l.nameValuePair dep.name dep.version) directDeps);
# build the node_modules tree from all known rootPackages
# type: NodeModulesTree :: { ${name} :: { version :: String, dependencies :: NodeModulesTree } }
nodeModulesTree =
l.mapAttrs (
name: version: let
dependencies = resolveChildren {
inherit name version;
ancestorCandidates = rootPackages;
};
in {
inherit version dependencies;
}
)
# filter out the 'self' package (e.g. "my-app")
(l.filterAttrs (n: v: n != name) rootPackages);
/*
Type:
mkNodeModules :: {
pname :: String,
version :: String,
isMain :: Bool,
installMethod :: "copy" | "symlink",
depsTree :: DependencyTree,
nodeModulesTree :: NodeModulesTree,
packageJSON :: Path
}
*/
mkNodeModules = {
isMain,
installMethod,
pname,
version,
depsTree,
packageJSON,
}:
# dependency tree as JSON needed to build node_modules
let
depsTreeJSON = b.toJSON depsTree;
nmTreeJSON = b.toJSON nodeModulesTree;
in
pkgs.runCommandLocal "node-modules" {
pname = "node_modules-${pname}";
inherit version;
buildInputs = with pkgs; [python3];
inherit nmTreeJSON depsTreeJSON;
passAsFile = ["nmTreeJSON" "depsTreeJSON"];
} ''
export isMain=${b.toString isMain}
export installMethod=${installMethod}
export packageJSON=${packageJSON}
${nodeModulesBuilder}
# make sure $out gets created every time, even if it is empty
if [ ! -d "$out" ]; then
mkdir -p $out
fi
'';
in {
inherit mkNodeModules;
}