@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/parse.ts
CHANGED
|
@@ -12,13 +12,13 @@
|
|
|
12
12
|
* version. Once that is done, nothing downstream has to know which manager
|
|
13
13
|
* wrote the file.
|
|
14
14
|
*/
|
|
15
|
-
import { LockfileFormat } from "../enums/lockfile-format.js"
|
|
16
|
-
import { LockfileError } from "../errors.js"
|
|
17
|
-
import { parseBunLockfile } from "./bun.js"
|
|
18
|
-
import { detectLockfile } from "./detect.js"
|
|
19
|
-
import { parseNpmLockfile } from "./npm.js"
|
|
20
|
-
import { parsePnpmLockfile } from "./pnpm.js"
|
|
21
|
-
import type { LockedTree, LockfileParseOptions } from "./types.js"
|
|
15
|
+
import { LockfileFormat } from "../enums/lockfile-format.js";
|
|
16
|
+
import { LockfileError } from "../errors.js";
|
|
17
|
+
import { parseBunLockfile } from "./bun.js";
|
|
18
|
+
import { detectLockfile } from "./detect.js";
|
|
19
|
+
import { parseNpmLockfile } from "./npm.js";
|
|
20
|
+
import { parsePnpmLockfile } from "./pnpm.js";
|
|
21
|
+
import type { LockedTree, LockfileParseOptions } from "./types.js";
|
|
22
22
|
|
|
23
23
|
/**
|
|
24
24
|
* Parses lockfile content into a flat graph.
|
|
@@ -26,31 +26,31 @@ import type { LockedTree, LockfileParseOptions } from "./types.js"
|
|
|
26
26
|
* Throws `LockfileError`; the caller is expected to be inside an `Effect.try`.
|
|
27
27
|
*/
|
|
28
28
|
export const parseLockfile = (
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
path: string,
|
|
30
|
+
content: string,
|
|
31
|
+
format?: LockfileFormat,
|
|
32
|
+
options: LockfileParseOptions = {},
|
|
33
33
|
): LockedTree => {
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
}
|
|
34
|
+
const resolved = format ?? resolveFormat(path, content);
|
|
35
|
+
const importer = options.importer;
|
|
36
|
+
switch (resolved) {
|
|
37
|
+
case LockfileFormat.Npm:
|
|
38
|
+
return parseNpmLockfile(path, content, importer);
|
|
39
|
+
case LockfileFormat.Pnpm:
|
|
40
|
+
return parsePnpmLockfile(path, content, importer);
|
|
41
|
+
case LockfileFormat.Bun:
|
|
42
|
+
return parseBunLockfile(path, content, importer);
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
45
|
|
|
46
46
|
const resolveFormat = (path: string, content: string): LockfileFormat => {
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
}
|
|
47
|
+
const detected = detectLockfile(content);
|
|
48
|
+
if (detected._tag === "Supported") return detected.format;
|
|
49
|
+
if (detected._tag === "Unsupported") {
|
|
50
|
+
throw new LockfileError(path, `${detected.label} — ${detected.hint}`);
|
|
51
|
+
}
|
|
52
|
+
throw new LockfileError(
|
|
53
|
+
path,
|
|
54
|
+
"content does not look like a package-lock.json, pnpm-lock.yaml or bun.lock",
|
|
55
|
+
);
|
|
56
|
+
};
|
package/src/lockfile/pnpm.ts
CHANGED
|
@@ -1,225 +1,230 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `pnpm-lock.yaml`.
|
|
3
|
-
*
|
|
4
|
-
* The easy one: pnpm's snapshots already name exact versions, so there is no
|
|
5
|
-
* nesting walk-up to reimplement. The fiddly parts are the three package-key
|
|
6
|
-
* encodings still in the wild and the parenthesised peer-resolution suffix.
|
|
7
|
-
*/
|
|
8
|
-
import semver from "semver"
|
|
9
|
-
import { parse as parseYaml } from "yaml"
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { isRecord } from "../utils/is-record.js"
|
|
16
|
-
import {
|
|
17
|
-
import
|
|
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
|
-
|
|
1
|
+
/**
|
|
2
|
+
* `pnpm-lock.yaml`.
|
|
3
|
+
*
|
|
4
|
+
* The easy one: pnpm's snapshots already name exact versions, so there is no
|
|
5
|
+
* nesting walk-up to reimplement. The fiddly parts are the three package-key
|
|
6
|
+
* encodings still in the wild and the parenthesised peer-resolution suffix.
|
|
7
|
+
*/
|
|
8
|
+
import semver from "semver";
|
|
9
|
+
import { parse as parseYaml } from "yaml";
|
|
10
|
+
|
|
11
|
+
import { parseDependencyTarget } from "../dependency-range.js";
|
|
12
|
+
import { EdgeKind, LockedRootKind } from "../enums/edge-kind.js";
|
|
13
|
+
import { LockfileFormat } from "../enums/lockfile-format.js";
|
|
14
|
+
import { LockfileError } from "../errors.js";
|
|
15
|
+
import { isRecord } from "../utils/is-record.js";
|
|
16
|
+
import { describeError } from "./json.js";
|
|
17
|
+
import { fieldByRootKind, notCovered, TreeBuilder } from "./tree-builder.js";
|
|
18
|
+
import type { LockedEdge, LockedTree } from "./types.js";
|
|
19
|
+
|
|
20
|
+
/** `name@version`, as a snapshot or importer entry resolves to. */
|
|
21
|
+
type PnpmReference = {
|
|
22
|
+
readonly name: string;
|
|
23
|
+
readonly version: string;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/** pnpm never records peer dependencies against an importer. */
|
|
27
|
+
const ROOT_KINDS = [LockedRootKind.Prod, LockedRootKind.Optional, LockedRootKind.Dev] as const;
|
|
28
|
+
|
|
29
|
+
export const parsePnpmLockfile = (
|
|
30
|
+
path: string,
|
|
31
|
+
content: string,
|
|
32
|
+
importer: string | undefined,
|
|
33
|
+
): LockedTree => {
|
|
34
|
+
let root: unknown;
|
|
35
|
+
try {
|
|
36
|
+
root = parseYaml(content);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
throw new LockfileError(path, `not valid YAML — ${describeError(error)}`, { cause: error });
|
|
39
|
+
}
|
|
40
|
+
if (!isRecord(root)) {
|
|
41
|
+
throw new LockfileError(path, "top level is not a mapping");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const version = String(root["lockfileVersion"] ?? "?");
|
|
45
|
+
const builder = new TreeBuilder();
|
|
46
|
+
|
|
47
|
+
// v9 split the graph in two: `packages` keeps resolution metadata, and
|
|
48
|
+
// `snapshots` keeps the edges. Before that both lived in `packages`.
|
|
49
|
+
const packages = isRecord(root["packages"]) ? root["packages"] : {};
|
|
50
|
+
const snapshots = isRecord(root["snapshots"]) ? root["snapshots"] : packages;
|
|
51
|
+
|
|
52
|
+
const known = new Map<string, PnpmReference>();
|
|
53
|
+
for (const key of Object.keys(packages)) {
|
|
54
|
+
const parsed = parsePnpmKey(key);
|
|
55
|
+
if (parsed === null) continue;
|
|
56
|
+
known.set(key, parsed);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
for (const [key, raw] of Object.entries(snapshots)) {
|
|
60
|
+
const parsed = parsePnpmKey(key) ?? known.get(key);
|
|
61
|
+
if (parsed === undefined || parsed === null) {
|
|
62
|
+
const described = describeKey(key);
|
|
63
|
+
if (described !== null) builder.skip(described.name, described.spec, described.reason);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
builder.add({
|
|
67
|
+
name: parsed.name,
|
|
68
|
+
version: parsed.version,
|
|
69
|
+
dependencies: edgesOf(builder, parsed, isRecord(raw) ? raw : {}),
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
collectRoots(builder, root, path, importer);
|
|
74
|
+
|
|
75
|
+
return builder.finish({
|
|
76
|
+
format: LockfileFormat.Pnpm,
|
|
77
|
+
lockfileVersion: version,
|
|
78
|
+
path,
|
|
79
|
+
importer,
|
|
80
|
+
});
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
const edgesOf = (
|
|
84
|
+
builder: TreeBuilder,
|
|
85
|
+
from: PnpmReference,
|
|
86
|
+
snapshot: Record<string, unknown>,
|
|
87
|
+
): ReadonlyArray<LockedEdge> => {
|
|
88
|
+
const edges: Array<LockedEdge> = [];
|
|
89
|
+
|
|
90
|
+
const add = (field: string, kind: EdgeKind): void => {
|
|
91
|
+
const entries = snapshot[field];
|
|
92
|
+
if (!isRecord(entries)) return;
|
|
93
|
+
for (const [name, raw] of Object.entries(entries)) {
|
|
94
|
+
if (typeof raw !== "string") continue;
|
|
95
|
+
const target = parsePnpmReference(name, raw);
|
|
96
|
+
if (target === null) {
|
|
97
|
+
if (kind === EdgeKind.Prod) builder.dangling(`${from.name}@${from.version}`, name);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
edges.push({ ...target, kind });
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
add(fieldByRootKind[LockedRootKind.Prod], EdgeKind.Prod);
|
|
105
|
+
add(fieldByRootKind[LockedRootKind.Optional], EdgeKind.Optional);
|
|
106
|
+
add(fieldByRootKind[LockedRootKind.Peer], EdgeKind.Peer);
|
|
107
|
+
|
|
108
|
+
return edges;
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* The importers' direct dependencies become the roots.
|
|
113
|
+
*
|
|
114
|
+
* pnpm is the format that makes this easy: `importers` is already keyed by the
|
|
115
|
+
* path each package.json sits at, so scoping to one member is a lookup rather
|
|
116
|
+
* than a reconstruction.
|
|
117
|
+
*/
|
|
118
|
+
const collectRoots = (
|
|
119
|
+
builder: TreeBuilder,
|
|
120
|
+
root: Record<string, unknown>,
|
|
121
|
+
path: string,
|
|
122
|
+
member: string | undefined,
|
|
123
|
+
): void => {
|
|
124
|
+
// A single-project lockfile has no `importers` block; its dependencies sit at
|
|
125
|
+
// the top level, which is the same thing as an importer called ".".
|
|
126
|
+
const importers = isRecord(root["importers"]) ? root["importers"] : { ".": root };
|
|
127
|
+
|
|
128
|
+
const selected =
|
|
129
|
+
member === undefined
|
|
130
|
+
? Object.values(importers)
|
|
131
|
+
: Object.hasOwn(importers, member)
|
|
132
|
+
? [importers[member]]
|
|
133
|
+
: notCovered(path, member, Object.keys(importers));
|
|
134
|
+
|
|
135
|
+
for (const importer of selected) {
|
|
136
|
+
if (!isRecord(importer)) continue;
|
|
137
|
+
for (const kind of ROOT_KINDS) {
|
|
138
|
+
const entries = importer[fieldByRootKind[kind]];
|
|
139
|
+
if (!isRecord(entries)) continue;
|
|
140
|
+
|
|
141
|
+
for (const [name, raw] of Object.entries(entries)) {
|
|
142
|
+
// v5 wrote a bare version string; v6 and v9 write
|
|
143
|
+
// `{specifier, version}`.
|
|
144
|
+
const value =
|
|
145
|
+
typeof raw === "string" ? raw : isRecord(raw) ? raw["version"] : undefined;
|
|
146
|
+
if (typeof value !== "string") continue;
|
|
147
|
+
|
|
148
|
+
const target = parsePnpmReference(name, value);
|
|
149
|
+
if (target === null) continue;
|
|
150
|
+
|
|
151
|
+
const specifier =
|
|
152
|
+
isRecord(raw) && typeof raw["specifier"] === "string"
|
|
153
|
+
? raw["specifier"]
|
|
154
|
+
: undefined;
|
|
155
|
+
builder.addRoot({ ...target, kind, specifier });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Reads a dependency value from a snapshot or importer.
|
|
163
|
+
*
|
|
164
|
+
* The value is either a bare version (`1.4.0`) or a full package key
|
|
165
|
+
* (`/lodash@4.17.21`, used when the install name is an alias of another
|
|
166
|
+
* package). Both may carry a peer-resolution suffix in parentheses.
|
|
167
|
+
*/
|
|
168
|
+
const parsePnpmReference = (name: string, raw: string): PnpmReference | null => {
|
|
169
|
+
const value = stripSuffix(raw);
|
|
170
|
+
|
|
171
|
+
const exact = semver.valid(value, { loose: true });
|
|
172
|
+
if (exact !== null) return { name, version: exact };
|
|
173
|
+
|
|
174
|
+
return parsePnpmKey(raw);
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Splits a package key into name and version.
|
|
179
|
+
*
|
|
180
|
+
* Three encodings across the versions still in the wild:
|
|
181
|
+
* v5 `/@babel/core/7.0.0`
|
|
182
|
+
* v6 `/@babel/core@7.0.0`
|
|
183
|
+
* v9 `@babel/core@7.0.0`
|
|
184
|
+
*
|
|
185
|
+
* plus an optional `(peer@1.0.0)` or `(patch_hash=…)` suffix on any of them.
|
|
186
|
+
*/
|
|
187
|
+
export const parsePnpmKey = (key: string): PnpmReference | null => {
|
|
188
|
+
const body = stripSuffix(key.startsWith("/") ? key.slice(1) : key);
|
|
189
|
+
|
|
190
|
+
// A scoped name's own `@` sits at index 0, so only a later one separates the
|
|
191
|
+
// version. When there is none, this is the v5 form and the last `/` does it.
|
|
192
|
+
const at = body.lastIndexOf("@");
|
|
193
|
+
const cut = at > 0 ? at : body.lastIndexOf("/");
|
|
194
|
+
if (cut <= 0) return null;
|
|
195
|
+
|
|
196
|
+
const name = body.slice(0, cut);
|
|
197
|
+
const version = semver.valid(body.slice(cut + 1), { loose: true });
|
|
198
|
+
if (version === null || name.length === 0) return null;
|
|
199
|
+
|
|
200
|
+
return { name, version };
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/** Describes a key we cannot pin, so the skip warning can say why. */
|
|
204
|
+
const describeKey = (
|
|
205
|
+
key: string,
|
|
206
|
+
): { readonly name: string; readonly spec: string; readonly reason: string } | null => {
|
|
207
|
+
const body = key.startsWith("/") ? key.slice(1) : key;
|
|
208
|
+
const at = body.lastIndexOf("@");
|
|
209
|
+
if (at <= 0) return null;
|
|
210
|
+
const name = body.slice(0, at);
|
|
211
|
+
const spec = body.slice(at + 1);
|
|
212
|
+
const target = parseDependencyTarget(name, spec);
|
|
213
|
+
return {
|
|
214
|
+
name,
|
|
215
|
+
spec,
|
|
216
|
+
reason: target._tag === "Unsupported" ? target.reason : "not an exact version",
|
|
217
|
+
};
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Drops the parenthesised peer-resolution suffix.
|
|
222
|
+
*
|
|
223
|
+
* pnpm distinguishes the same version installed against different peers —
|
|
224
|
+
* `react-dom@18.3.1(react@18.3.1)`. Those are the same published tarball, which
|
|
225
|
+
* is all this tool downloads, so the suffix is noise here.
|
|
226
|
+
*/
|
|
227
|
+
const stripSuffix = (value: string): string => {
|
|
228
|
+
const open = value.indexOf("(");
|
|
229
|
+
return open === -1 ? value : value.slice(0, open);
|
|
230
|
+
};
|