@packall/core 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +58 -0
- package/dist/archive-DmF9XNl7.js +284 -0
- package/dist/archive-DmF9XNl7.js.map +1 -0
- package/dist/archive.d.ts +47 -15
- package/dist/archive.d.ts.map +1 -1
- package/dist/bundle.d.ts +6 -3
- package/dist/bundle.d.ts.map +1 -1
- package/dist/dependency-range.d.ts.map +1 -1
- package/dist/download.d.ts +2 -1
- package/dist/download.d.ts.map +1 -1
- package/dist/enums/artifact-kind.d.ts.map +1 -1
- package/dist/enums/edge-kind.d.ts.map +1 -1
- package/dist/enums/input-file-kind.d.ts.map +1 -1
- package/dist/enums/layout.d.ts.map +1 -1
- package/dist/enums/lockfile-format.d.ts.map +1 -1
- package/dist/enums/phase.d.ts.map +1 -1
- package/dist/errors.d.ts +11 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/index.d.ts +12 -10
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +169 -344
- package/dist/index.js.map +1 -1
- package/dist/input-file.d.ts.map +1 -1
- package/dist/integrity.d.ts +37 -5
- package/dist/integrity.d.ts.map +1 -1
- package/dist/layout.d.ts +41 -7
- package/dist/layout.d.ts.map +1 -1
- package/dist/locked-resolve.d.ts.map +1 -1
- package/dist/lockfile/bun.d.ts.map +1 -1
- package/dist/lockfile/detect.d.ts.map +1 -1
- package/dist/lockfile/json.d.ts.map +1 -1
- package/dist/lockfile/names.d.ts.map +1 -1
- package/dist/lockfile/npm.d.ts.map +1 -1
- package/dist/lockfile/parse.d.ts.map +1 -1
- package/dist/lockfile/pnpm.d.ts.map +1 -1
- package/dist/lockfile/tree-builder.d.ts.map +1 -1
- package/dist/lockfile/types.d.ts.map +1 -1
- package/dist/manifest.d.ts.map +1 -1
- package/dist/node/archiver.d.ts +5 -0
- package/dist/node/archiver.d.ts.map +1 -0
- package/dist/node/index.d.ts +13 -0
- package/dist/node/index.d.ts.map +1 -0
- package/dist/node/index.js +55 -0
- package/dist/node/index.js.map +1 -0
- package/dist/options.d.ts +3 -3
- package/dist/options.d.ts.map +1 -1
- package/dist/platform.d.ts +5 -10
- package/dist/platform.d.ts.map +1 -1
- package/dist/progress.d.ts.map +1 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/resolve.d.ts.map +1 -1
- package/dist/schemas/lenient.d.ts.map +1 -1
- package/dist/schemas/package-json.d.ts.map +1 -1
- package/dist/spec.d.ts.map +1 -1
- package/dist/types/value-of.d.ts.map +1 -1
- package/dist/utils/is-record.d.ts.map +1 -1
- package/package.json +11 -10
- package/src/archive.ts +95 -92
- package/src/bundle.ts +736 -616
- package/src/dependency-range.ts +121 -115
- package/src/download.ts +155 -133
- package/src/enums/artifact-kind.ts +5 -5
- package/src/enums/edge-kind.ts +13 -13
- package/src/enums/input-file-kind.ts +9 -9
- package/src/enums/layout.ts +20 -20
- package/src/enums/lockfile-format.ts +6 -6
- package/src/enums/phase.ts +8 -8
- package/src/errors.ts +211 -199
- package/src/index.ts +126 -113
- package/src/input-file.ts +246 -234
- package/src/integrity.ts +165 -121
- package/src/layout.ts +63 -29
- package/src/locked-resolve.ts +471 -461
- package/src/lockfile/bun.ts +214 -207
- package/src/lockfile/detect.ts +80 -80
- package/src/lockfile/json.ts +91 -92
- package/src/lockfile/names.ts +9 -9
- package/src/lockfile/npm.ts +328 -308
- package/src/lockfile/parse.ts +32 -32
- package/src/lockfile/pnpm.ts +230 -225
- package/src/lockfile/tree-builder.ts +116 -116
- package/src/lockfile/types.ts +52 -52
- package/src/manifest.ts +129 -133
- package/src/node/archiver.ts +85 -0
- package/src/node/index.ts +13 -0
- package/src/options.ts +84 -84
- package/src/platform.ts +97 -101
- package/src/progress.ts +105 -106
- package/src/registry.ts +116 -115
- package/src/resolve.ts +560 -537
- package/src/schemas/lenient.ts +67 -60
- package/src/schemas/package-json.ts +11 -10
- package/src/spec.ts +123 -119
- package/src/types/value-of.ts +1 -1
- package/src/utils/is-record.ts +1 -1
package/src/lockfile/npm.ts
CHANGED
|
@@ -1,308 +1,328 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `package-lock.json` / `npm-shrinkwrap.json`.
|
|
3
|
-
*
|
|
4
|
-
* npm records *ranges* on its edges and relies on the directory nesting to say
|
|
5
|
-
* which copy of a package a range resolved to, so the node resolution walk-up —
|
|
6
|
-
* look beside me, then in my parent, then my grandparent — is reimplemented
|
|
7
|
-
* here.
|
|
8
|
-
*/
|
|
9
|
-
import { EdgeKind, LockedRootKind } from "../enums/edge-kind.js"
|
|
10
|
-
import { LockfileFormat } from "../enums/lockfile-format.js"
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `package-lock.json` / `npm-shrinkwrap.json`.
|
|
3
|
+
*
|
|
4
|
+
* npm records *ranges* on its edges and relies on the directory nesting to say
|
|
5
|
+
* which copy of a package a range resolved to, so the node resolution walk-up —
|
|
6
|
+
* look beside me, then in my parent, then my grandparent — is reimplemented
|
|
7
|
+
* here.
|
|
8
|
+
*/
|
|
9
|
+
import { EdgeKind, LockedRootKind } from "../enums/edge-kind.js";
|
|
10
|
+
import { LockfileFormat } from "../enums/lockfile-format.js";
|
|
11
|
+
import { isRecord } from "../utils/is-record.js";
|
|
12
|
+
import { optionalPeerNames, parseJsonObject, stringOr, stringRecord } from "./json.js";
|
|
13
|
+
import {
|
|
14
|
+
asExactVersion,
|
|
15
|
+
fieldByRootKind,
|
|
16
|
+
importerKey,
|
|
17
|
+
notCovered,
|
|
18
|
+
TreeBuilder,
|
|
19
|
+
} from "./tree-builder.js";
|
|
20
|
+
import type { LockedEdge, LockedTree } from "./types.js";
|
|
21
|
+
|
|
22
|
+
/** One entry of the v2/v3 `packages` map, after narrowing. */
|
|
23
|
+
type NpmEntry = {
|
|
24
|
+
readonly path: string;
|
|
25
|
+
readonly version: string;
|
|
26
|
+
/** Set when the install name differs from the real one — an `npm:` alias. */
|
|
27
|
+
readonly realName: string;
|
|
28
|
+
readonly dependencies: Readonly<Record<string, string>>;
|
|
29
|
+
readonly optionalDependencies: Readonly<Record<string, string>>;
|
|
30
|
+
readonly peerDependencies: Readonly<Record<string, string>>;
|
|
31
|
+
readonly optionalPeers: ReadonlySet<string>;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/** Root blocks in npm's precedence order — the first mention of a name wins. */
|
|
35
|
+
const ROOT_KINDS = [
|
|
36
|
+
LockedRootKind.Prod,
|
|
37
|
+
LockedRootKind.Optional,
|
|
38
|
+
LockedRootKind.Dev,
|
|
39
|
+
LockedRootKind.Peer,
|
|
40
|
+
] as const;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* The node resolution walk-up, over a flat map of install paths.
|
|
44
|
+
*
|
|
45
|
+
* A package at `node_modules/a/node_modules/b` depending on `c` gets, in order,
|
|
46
|
+
* `node_modules/a/node_modules/b/node_modules/c`, then
|
|
47
|
+
* `node_modules/a/node_modules/c`, then `node_modules/c`. This is what makes a
|
|
48
|
+
* range on an edge unambiguous: the nesting already recorded which copy won.
|
|
49
|
+
*/
|
|
50
|
+
const NESTING = "/node_modules/";
|
|
51
|
+
|
|
52
|
+
export const parseNpmLockfile = (
|
|
53
|
+
path: string,
|
|
54
|
+
content: string,
|
|
55
|
+
importer: string | undefined,
|
|
56
|
+
): LockedTree => {
|
|
57
|
+
const root = parseJsonObject(path, content);
|
|
58
|
+
const version = String(root["lockfileVersion"] ?? "?");
|
|
59
|
+
const builder = new TreeBuilder();
|
|
60
|
+
|
|
61
|
+
// v2 carries both shapes for backwards compatibility; `packages` is the
|
|
62
|
+
// accurate one, so it wins whenever it is there.
|
|
63
|
+
const packages = root["packages"];
|
|
64
|
+
const entries = isRecord(packages)
|
|
65
|
+
? collectEntries(builder, packages)
|
|
66
|
+
: collectLegacyEntries(builder, root);
|
|
67
|
+
|
|
68
|
+
for (const entry of entries.values()) {
|
|
69
|
+
builder.add({
|
|
70
|
+
name: entry.realName,
|
|
71
|
+
version: entry.version,
|
|
72
|
+
dependencies: edgesOf(builder, entries, entry),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
collectRoots(builder, entries, isRecord(packages) ? packages : root, path, importer);
|
|
77
|
+
|
|
78
|
+
return builder.finish({ format: LockfileFormat.Npm, lockfileVersion: version, path, importer });
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/** Narrows the v2/v3 `packages` map, dropping what cannot be bundled. */
|
|
82
|
+
const collectEntries = (
|
|
83
|
+
builder: TreeBuilder,
|
|
84
|
+
packages: Record<string, unknown>,
|
|
85
|
+
): Map<string, NpmEntry> => {
|
|
86
|
+
const entries = new Map<string, NpmEntry>();
|
|
87
|
+
|
|
88
|
+
for (const [entryPath, raw] of Object.entries(packages)) {
|
|
89
|
+
// The root project itself, and workspace members, are not things to fetch.
|
|
90
|
+
const installName = installNameFromPath(entryPath);
|
|
91
|
+
if (installName === null) continue;
|
|
92
|
+
if (!isRecord(raw)) continue;
|
|
93
|
+
|
|
94
|
+
// A symlinked workspace member. Nothing to fetch, but an edge into it has
|
|
95
|
+
// to be explained rather than counted as a hole in the file.
|
|
96
|
+
if (raw["link"] === true) {
|
|
97
|
+
builder.skip(installName, stringOr(raw["resolved"], "link"), "workspace link");
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const rawVersion = raw["version"];
|
|
102
|
+
if (typeof rawVersion !== "string") {
|
|
103
|
+
builder.warn(
|
|
104
|
+
`skipped ${installName} — its lockfile entry records no version, so there is nothing to pin`,
|
|
105
|
+
);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const resolvedName = typeof raw["name"] === "string" ? raw["name"] : installName;
|
|
110
|
+
const version = asExactVersion(resolvedName, rawVersion);
|
|
111
|
+
if (version._tag === "Unsupported") {
|
|
112
|
+
builder.skip(resolvedName, rawVersion, version.reason);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// `resolved` names a git or tarball URL for anything not from a registry,
|
|
117
|
+
// even when `version` looks ordinary.
|
|
118
|
+
const resolvedUrl = raw["resolved"];
|
|
119
|
+
if (typeof resolvedUrl === "string" && !isRegistryUrl(resolvedUrl)) {
|
|
120
|
+
builder.skip(resolvedName, resolvedUrl, "not a registry tarball");
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
entries.set(entryPath, {
|
|
125
|
+
path: entryPath,
|
|
126
|
+
version: version.version,
|
|
127
|
+
realName: resolvedName,
|
|
128
|
+
dependencies: stringRecord(raw["dependencies"]),
|
|
129
|
+
optionalDependencies: stringRecord(raw["optionalDependencies"]),
|
|
130
|
+
peerDependencies: stringRecord(raw["peerDependencies"]),
|
|
131
|
+
optionalPeers: optionalPeerNames(raw["peerDependenciesMeta"]),
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
return entries;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Flattens a v1 lockfile's nested `dependencies` tree into the same
|
|
140
|
+
* path-keyed shape v2 uses.
|
|
141
|
+
*
|
|
142
|
+
* v1 is old — npm 6 — but it is exactly the vintage still pinned inside a lot
|
|
143
|
+
* of the locked-down environments this tool exists for, so it is worth the
|
|
144
|
+
* thirty lines rather than an error telling somebody to upgrade npm.
|
|
145
|
+
*/
|
|
146
|
+
const collectLegacyEntries = (
|
|
147
|
+
builder: TreeBuilder,
|
|
148
|
+
root: Record<string, unknown>,
|
|
149
|
+
): Map<string, NpmEntry> => {
|
|
150
|
+
const entries = new Map<string, NpmEntry>();
|
|
151
|
+
|
|
152
|
+
const visit = (tree: unknown, prefix: string): void => {
|
|
153
|
+
if (!isRecord(tree)) return;
|
|
154
|
+
for (const [name, raw] of Object.entries(tree)) {
|
|
155
|
+
if (!isRecord(raw)) continue;
|
|
156
|
+
const entryPath =
|
|
157
|
+
prefix === "" ? `node_modules/${name}` : `${prefix}/node_modules/${name}`;
|
|
158
|
+
|
|
159
|
+
const rawVersion = raw["version"];
|
|
160
|
+
if (typeof rawVersion === "string") {
|
|
161
|
+
const version = asExactVersion(name, rawVersion);
|
|
162
|
+
if (version._tag === "Unsupported") {
|
|
163
|
+
builder.skip(name, rawVersion, version.reason);
|
|
164
|
+
} else {
|
|
165
|
+
entries.set(entryPath, {
|
|
166
|
+
path: entryPath,
|
|
167
|
+
version: version.version,
|
|
168
|
+
realName: name,
|
|
169
|
+
// v1 calls the edge set `requires`.
|
|
170
|
+
dependencies: stringRecord(raw["requires"]),
|
|
171
|
+
optionalDependencies: {},
|
|
172
|
+
peerDependencies: {},
|
|
173
|
+
optionalPeers: new Set(),
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
visit(raw["dependencies"], entryPath);
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
visit(root["dependencies"], "");
|
|
183
|
+
return entries;
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
/** Turns one entry's ranges into edges, resolved through the directory nesting. */
|
|
187
|
+
const edgesOf = (
|
|
188
|
+
builder: TreeBuilder,
|
|
189
|
+
entries: ReadonlyMap<string, NpmEntry>,
|
|
190
|
+
entry: NpmEntry,
|
|
191
|
+
): ReadonlyArray<LockedEdge> => {
|
|
192
|
+
const edges: Array<LockedEdge> = [];
|
|
193
|
+
|
|
194
|
+
const add = (names: ReadonlyArray<string>, kind: EdgeKind): void => {
|
|
195
|
+
for (const name of names) {
|
|
196
|
+
const targetPath = resolveNestedEdge(entries, entry.path, name);
|
|
197
|
+
const target = targetPath === null ? undefined : entries.get(targetPath);
|
|
198
|
+
if (target === undefined) {
|
|
199
|
+
// Only required edges are worth raising: an optional or peer edge that
|
|
200
|
+
// resolves to nothing is how a lockfile records "not installed here",
|
|
201
|
+
// which npm itself tolerates.
|
|
202
|
+
if (kind === EdgeKind.Prod)
|
|
203
|
+
builder.dangling(`${entry.realName}@${entry.version}`, name);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
edges.push({ name: target.realName, version: target.version, kind });
|
|
207
|
+
}
|
|
208
|
+
};
|
|
209
|
+
|
|
210
|
+
const optionalNames = Object.keys(entry.optionalDependencies);
|
|
211
|
+
const optional = new Set(optionalNames);
|
|
212
|
+
|
|
213
|
+
// An entry listed in both is optional: that is how npm records "wanted, but
|
|
214
|
+
// the install may proceed without it".
|
|
215
|
+
add(
|
|
216
|
+
Object.keys(entry.dependencies).filter((name) => !optional.has(name)),
|
|
217
|
+
EdgeKind.Prod,
|
|
218
|
+
);
|
|
219
|
+
add(optionalNames, EdgeKind.Optional);
|
|
220
|
+
add(
|
|
221
|
+
Object.keys(entry.peerDependencies).filter((name) => !entry.optionalPeers.has(name)),
|
|
222
|
+
EdgeKind.Peer,
|
|
223
|
+
);
|
|
224
|
+
|
|
225
|
+
return edges;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Reads the root project's direct dependencies.
|
|
230
|
+
*
|
|
231
|
+
* The lockfile records these under `packages[""]` for v2/v3. v1 has no such
|
|
232
|
+
* entry, so every top-level `node_modules/x` is treated as direct — which
|
|
233
|
+
* over-counts roots slightly on v1 and is the best that file can support.
|
|
234
|
+
*/
|
|
235
|
+
const collectRoots = (
|
|
236
|
+
builder: TreeBuilder,
|
|
237
|
+
entries: ReadonlyMap<string, NpmEntry>,
|
|
238
|
+
source: Record<string, unknown>,
|
|
239
|
+
path: string,
|
|
240
|
+
importer: string | undefined,
|
|
241
|
+
): void => {
|
|
242
|
+
// Resolution starts from the member's own directory, so a dependency it
|
|
243
|
+
// nests privately wins over the hoisted copy — the same rule node applies,
|
|
244
|
+
// which is why the walk-up is shared with ordinary edges.
|
|
245
|
+
const collectFrom = (from: string, project: Record<string, unknown>): void => {
|
|
246
|
+
for (const kind of ROOT_KINDS) {
|
|
247
|
+
const field = fieldByRootKind[kind];
|
|
248
|
+
for (const [name, specifier] of Object.entries(stringRecord(project[field]))) {
|
|
249
|
+
const targetPath = resolveNestedEdge(entries, from, name);
|
|
250
|
+
const target = targetPath === null ? undefined : entries.get(targetPath);
|
|
251
|
+
if (target === undefined) continue;
|
|
252
|
+
builder.addRoot({
|
|
253
|
+
name: target.realName,
|
|
254
|
+
version: target.version,
|
|
255
|
+
kind,
|
|
256
|
+
specifier,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
if (importer !== undefined) {
|
|
263
|
+
const key = importerKey(importer);
|
|
264
|
+
const project = source[key];
|
|
265
|
+
if (!isRecord(project)) notCovered(path, importer, workspacePaths(source));
|
|
266
|
+
else collectFrom(key, project);
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// No member named: every project the file records contributes, which is what
|
|
271
|
+
// pointing --file straight at a monorepo's lockfile should mean.
|
|
272
|
+
const projects = Object.entries(source).flatMap(([key, value]) =>
|
|
273
|
+
isRecord(value) && !key.includes("node_modules/") ? [[key, value] as const] : [],
|
|
274
|
+
);
|
|
275
|
+
|
|
276
|
+
if (projects.length > 0) {
|
|
277
|
+
for (const [key, project] of projects) collectFrom(key, project);
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// v1 records no project entry at all, so every top-level install is direct.
|
|
282
|
+
for (const entry of entries.values()) {
|
|
283
|
+
if (entry.path.includes(NESTING)) continue;
|
|
284
|
+
builder.addRoot({
|
|
285
|
+
name: entry.realName,
|
|
286
|
+
version: entry.version,
|
|
287
|
+
kind: LockedRootKind.Prod,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
|
|
292
|
+
/** The workspace paths a v2/v3 lockfile records, for a "does not cover" message. */
|
|
293
|
+
const workspacePaths = (source: Record<string, unknown>): ReadonlyArray<string> =>
|
|
294
|
+
Object.keys(source).flatMap((key) =>
|
|
295
|
+
key === "" ? "." : key.includes("node_modules/") ? [] : key,
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* npm records non-registry sources as a URL in `resolved`. Registry tarballs
|
|
300
|
+
* are ordinary http(s); anything with a `git+`, `file:` or bare path form is
|
|
301
|
+
* something we cannot fetch.
|
|
302
|
+
*/
|
|
303
|
+
const isRegistryUrl = (url: string): boolean =>
|
|
304
|
+
url.startsWith("http://") || url.startsWith("https://");
|
|
305
|
+
|
|
306
|
+
const resolveNestedEdge = (
|
|
307
|
+
entries: ReadonlyMap<string, NpmEntry>,
|
|
308
|
+
fromPath: string,
|
|
309
|
+
name: string,
|
|
310
|
+
): string | null => {
|
|
311
|
+
let scope = fromPath;
|
|
312
|
+
for (;;) {
|
|
313
|
+
const candidate = scope === "" ? `node_modules/${name}` : `${scope}${NESTING}${name}`;
|
|
314
|
+
if (entries.has(candidate)) return candidate;
|
|
315
|
+
if (scope === "") return null;
|
|
316
|
+
const at = scope.lastIndexOf(NESTING);
|
|
317
|
+
scope = at === -1 ? "" : scope.slice(0, at);
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
|
|
321
|
+
/** `node_modules/@babel/core` -> `@babel/core`; a workspace path -> `null`. */
|
|
322
|
+
const installNameFromPath = (entryPath: string): string | null => {
|
|
323
|
+
const marker = "node_modules/";
|
|
324
|
+
const at = entryPath.lastIndexOf(marker);
|
|
325
|
+
if (at === -1) return null;
|
|
326
|
+
const name = entryPath.slice(at + marker.length);
|
|
327
|
+
return name.length === 0 ? null : name;
|
|
328
|
+
};
|