monkeypatchlist 0.0.0-stage → 0.1.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 +202 -0
- package/NOTICE +61 -0
- package/README.md +183 -2
- package/catalog/catalog.json +552211 -0
- package/catalog/catalog.schema.json +787 -0
- package/catalog/changelog.json +3 -0
- package/dist/bin/monkeypatchlist.d.ts +2 -0
- package/dist/bin/monkeypatchlist.js +3 -0
- package/dist/catalog.d.ts +561 -0
- package/dist/catalog.js +58 -0
- package/dist/cli.d.ts +84 -0
- package/dist/cli.js +470 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/lockfile-input.d.ts +9 -0
- package/dist/lockfile-input.js +91 -0
- package/dist/packages/analysis/binding.d.ts +2 -0
- package/dist/packages/analysis/binding.js +5 -0
- package/dist/packages/analysis/index.d.ts +10 -0
- package/dist/packages/analysis/index.js +13 -0
- package/dist/packages/analysis/job.d.ts +33 -0
- package/dist/packages/analysis/job.js +85 -0
- package/dist/packages/analysis/lib/absence-tags.d.ts +34 -0
- package/dist/packages/analysis/lib/absence-tags.js +340 -0
- package/dist/packages/analysis/lib/ast.d.ts +28 -0
- package/dist/packages/analysis/lib/ast.js +64 -0
- package/dist/packages/analysis/lib/binding.d.ts +36 -0
- package/dist/packages/analysis/lib/binding.js +66 -0
- package/dist/packages/analysis/lib/budget.d.ts +80 -0
- package/dist/packages/analysis/lib/budget.js +31 -0
- package/dist/packages/analysis/lib/catch-arms.d.ts +19 -0
- package/dist/packages/analysis/lib/catch-arms.js +351 -0
- package/dist/packages/analysis/lib/chunk-loads.d.ts +160 -0
- package/dist/packages/analysis/lib/chunk-loads.js +882 -0
- package/dist/packages/analysis/lib/chunk-registry.d.ts +45 -0
- package/dist/packages/analysis/lib/chunk-registry.js +334 -0
- package/dist/packages/analysis/lib/code-routes.d.ts +52 -0
- package/dist/packages/analysis/lib/code-routes.js +921 -0
- package/dist/packages/analysis/lib/collisions.d.ts +22 -0
- package/dist/packages/analysis/lib/collisions.js +103 -0
- package/dist/packages/analysis/lib/definitions.d.ts +76 -0
- package/dist/packages/analysis/lib/definitions.js +782 -0
- package/dist/packages/analysis/lib/early-exits.d.ts +39 -0
- package/dist/packages/analysis/lib/early-exits.js +161 -0
- package/dist/packages/analysis/lib/effect.d.ts +138 -0
- package/dist/packages/analysis/lib/effect.js +800 -0
- package/dist/packages/analysis/lib/global-object.d.ts +581 -0
- package/dist/packages/analysis/lib/global-object.js +8240 -0
- package/dist/packages/analysis/lib/keys.d.ts +33 -0
- package/dist/packages/analysis/lib/keys.js +82 -0
- package/dist/packages/analysis/lib/line-starts.d.ts +41 -0
- package/dist/packages/analysis/lib/line-starts.js +79 -0
- package/dist/packages/analysis/lib/link-group.d.ts +51 -0
- package/dist/packages/analysis/lib/link-group.js +487 -0
- package/dist/packages/analysis/lib/module-links.d.ts +117 -0
- package/dist/packages/analysis/lib/module-links.js +1060 -0
- package/dist/packages/analysis/lib/nested-code.d.ts +38 -0
- package/dist/packages/analysis/lib/nested-code.js +200 -0
- package/dist/packages/analysis/lib/numeric.d.ts +16 -0
- package/dist/packages/analysis/lib/numeric.js +216 -0
- package/dist/packages/analysis/lib/once-flags.d.ts +12 -0
- package/dist/packages/analysis/lib/once-flags.js +190 -0
- package/dist/packages/analysis/lib/parameter-arguments.d.ts +12 -0
- package/dist/packages/analysis/lib/parameter-arguments.js +34 -0
- package/dist/packages/analysis/lib/parse.d.ts +10 -0
- package/dist/packages/analysis/lib/parse.js +39 -0
- package/dist/packages/analysis/lib/partial.d.ts +60 -0
- package/dist/packages/analysis/lib/partial.js +518 -0
- package/dist/packages/analysis/lib/print.d.ts +3 -0
- package/dist/packages/analysis/lib/print.js +150 -0
- package/dist/packages/analysis/lib/reachability.d.ts +137 -0
- package/dist/packages/analysis/lib/reachability.js +1295 -0
- package/dist/packages/analysis/lib/resolver.d.ts +13 -0
- package/dist/packages/analysis/lib/resolver.js +2886 -0
- package/dist/packages/analysis/lib/restores.d.ts +42 -0
- package/dist/packages/analysis/lib/restores.js +482 -0
- package/dist/packages/analysis/lib/rewritten-deletes.d.ts +6 -0
- package/dist/packages/analysis/lib/rewritten-deletes.js +142 -0
- package/dist/packages/analysis/lib/scope.d.ts +110 -0
- package/dist/packages/analysis/lib/scope.js +443 -0
- package/dist/packages/analysis/lib/script-loads.d.ts +56 -0
- package/dist/packages/analysis/lib/script-loads.js +359 -0
- package/dist/packages/analysis/lib/summaries.d.ts +177 -0
- package/dist/packages/analysis/lib/summaries.js +498 -0
- package/dist/packages/analysis/lib/types.d.ts +632 -0
- package/dist/packages/analysis/lib/types.js +128 -0
- package/dist/packages/analysis/lib/wrap.d.ts +34 -0
- package/dist/packages/analysis/lib/wrap.js +407 -0
- package/dist/packages/analysis/lib/writes.d.ts +358 -0
- package/dist/packages/analysis/lib/writes.js +1612 -0
- package/dist/packages/analysis/visit-order.d.ts +3 -0
- package/dist/packages/analysis/visit-order.js +23 -0
- package/dist/packages/attribution/index.d.ts +233 -0
- package/dist/packages/attribution/index.js +444 -0
- package/dist/packages/attribution/lib/containing-package.d.ts +76 -0
- package/dist/packages/attribution/lib/containing-package.js +304 -0
- package/dist/packages/attribution/lib/first-party.d.ts +37 -0
- package/dist/packages/attribution/lib/first-party.js +153 -0
- package/dist/packages/attribution/lib/lockfile-graph.d.ts +99 -0
- package/dist/packages/attribution/lib/lockfile-graph.js +158 -0
- package/dist/packages/attribution/lib/lockfile.d.ts +33 -0
- package/dist/packages/attribution/lib/lockfile.js +330 -0
- package/dist/packages/attribution/lib/path-rules.d.ts +29 -0
- package/dist/packages/attribution/lib/path-rules.js +84 -0
- package/dist/packages/attribution/lib/pnpm.d.ts +4 -0
- package/dist/packages/attribution/lib/pnpm.js +133 -0
- package/dist/packages/attribution/lib/remote-url.d.ts +7 -0
- package/dist/packages/attribution/lib/remote-url.js +24 -0
- package/dist/packages/attribution/lib/source-map.d.ts +51 -0
- package/dist/packages/attribution/lib/source-map.js +125 -0
- package/dist/packages/attribution/lib/yaml.d.ts +9 -0
- package/dist/packages/attribution/lib/yaml.js +277 -0
- package/dist/packages/attribution/lib/yarn.d.ts +7 -0
- package/dist/packages/attribution/lib/yarn.js +227 -0
- package/dist/packages/baseline/index.d.ts +45 -0
- package/dist/packages/baseline/index.js +41 -0
- package/dist/packages/baseline/lib/error.d.ts +15 -0
- package/dist/packages/baseline/lib/error.js +25 -0
- package/dist/packages/baseline/lib/format.d.ts +49 -0
- package/dist/packages/baseline/lib/format.js +22 -0
- package/dist/packages/baseline/lib/migrations.d.ts +19 -0
- package/dist/packages/baseline/lib/migrations.js +7 -0
- package/dist/packages/baseline/lib/previous.d.ts +23 -0
- package/dist/packages/baseline/lib/previous.js +64 -0
- package/dist/packages/baseline/lib/read.d.ts +39 -0
- package/dist/packages/baseline/lib/read.js +219 -0
- package/dist/packages/baseline/lib/report.d.ts +25 -0
- package/dist/packages/baseline/lib/report.js +104 -0
- package/dist/packages/baseline/lib/skew.d.ts +51 -0
- package/dist/packages/baseline/lib/skew.js +75 -0
- package/dist/packages/compare/index.d.ts +120 -0
- package/dist/packages/compare/index.js +274 -0
- package/dist/packages/compare/lib/catalog-notes.d.ts +73 -0
- package/dist/packages/compare/lib/catalog-notes.js +224 -0
- package/dist/packages/completeness/index.d.ts +246 -0
- package/dist/packages/completeness/index.js +249 -0
- package/dist/packages/confidence/index.d.ts +54 -0
- package/dist/packages/confidence/index.js +71 -0
- package/dist/packages/config/index.d.ts +107 -0
- package/dist/packages/config/index.js +95 -0
- package/dist/packages/config/lib/command-line.d.ts +23 -0
- package/dist/packages/config/lib/command-line.js +239 -0
- package/dist/packages/config/lib/config-file.d.ts +11 -0
- package/dist/packages/config/lib/config-file.js +119 -0
- package/dist/packages/config/lib/scan-config-id.d.ts +28 -0
- package/dist/packages/config/lib/scan-config-id.js +54 -0
- package/dist/packages/config/lib/schema.d.ts +2 -0
- package/dist/packages/config/lib/schema.js +31 -0
- package/dist/packages/config/lib/types.d.ts +64 -0
- package/dist/packages/config/lib/types.js +6 -0
- package/dist/packages/config/lib/urls.d.ts +6 -0
- package/dist/packages/config/lib/urls.js +9 -0
- package/dist/packages/discovery/index.d.ts +277 -0
- package/dist/packages/discovery/index.js +1950 -0
- package/dist/packages/discovery/lib/import-map.d.ts +68 -0
- package/dist/packages/discovery/lib/import-map.js +180 -0
- package/dist/packages/discovery/lib/requests.d.ts +58 -0
- package/dist/packages/discovery/lib/requests.js +591 -0
- package/dist/packages/discovery/lib/scopes.d.ts +6 -0
- package/dist/packages/discovery/lib/scopes.js +286 -0
- package/dist/packages/discovery/lib/source-maps.d.ts +56 -0
- package/dist/packages/discovery/lib/source-maps.js +209 -0
- package/dist/packages/discovery/lib/swept-code.d.ts +27 -0
- package/dist/packages/discovery/lib/swept-code.js +107 -0
- package/dist/packages/discovery/lib/syntax.d.ts +17 -0
- package/dist/packages/discovery/lib/syntax.js +39 -0
- package/dist/packages/findings/index.d.ts +106 -0
- package/dist/packages/findings/index.js +254 -0
- package/dist/packages/findings/lib/scope.d.ts +38 -0
- package/dist/packages/findings/lib/scope.js +110 -0
- package/dist/packages/parallel/index.d.ts +2 -0
- package/dist/packages/parallel/index.js +5 -0
- package/dist/packages/parallel/lib/protocol.d.ts +15 -0
- package/dist/packages/parallel/lib/protocol.js +2 -0
- package/dist/packages/parallel/lib/runner.d.ts +36 -0
- package/dist/packages/parallel/lib/runner.js +96 -0
- package/dist/packages/parallel/lib/schedule.d.ts +16 -0
- package/dist/packages/parallel/lib/schedule.js +52 -0
- package/dist/packages/parallel/lib/worker.d.ts +1 -0
- package/dist/packages/parallel/lib/worker.js +18 -0
- package/dist/packages/policy/index.d.ts +36 -0
- package/dist/packages/policy/index.js +22 -0
- package/dist/packages/report/explain.d.ts +26 -0
- package/dist/packages/report/explain.js +99 -0
- package/dist/packages/report/index.d.ts +210 -0
- package/dist/packages/report/index.js +388 -0
- package/dist/packages/report/terminal.d.ts +11 -0
- package/dist/packages/report/terminal.js +331 -0
- package/dist/packages/source-store/fixture.d.ts +22 -0
- package/dist/packages/source-store/fixture.js +58 -0
- package/dist/packages/source-store/index.d.ts +278 -0
- package/dist/packages/source-store/index.js +322 -0
- package/dist/packages/source-store/lib/addresses.d.ts +6 -0
- package/dist/packages/source-store/lib/addresses.js +91 -0
- package/dist/packages/source-store/lib/connection.d.ts +15 -0
- package/dist/packages/source-store/lib/connection.js +135 -0
- package/dist/packages/source-store/lib/content-types.d.ts +16 -0
- package/dist/packages/source-store/lib/content-types.js +75 -0
- package/dist/packages/source-store/lib/data-urls.d.ts +10 -0
- package/dist/packages/source-store/lib/data-urls.js +85 -0
- package/dist/packages/source-store/lib/deadline.d.ts +18 -0
- package/dist/packages/source-store/lib/deadline.js +44 -0
- package/dist/packages/source-store/lib/exchange.d.ts +29 -0
- package/dist/packages/source-store/lib/exchange.js +112 -0
- package/dist/packages/source-store/lib/integrity.d.ts +19 -0
- package/dist/packages/source-store/lib/integrity.js +86 -0
- package/dist/packages/source-store/lib/outcomes.d.ts +12 -0
- package/dist/packages/source-store/lib/outcomes.js +22 -0
- package/dist/packages/source-store/lib/proxy.d.ts +11 -0
- package/dist/packages/source-store/lib/proxy.js +53 -0
- package/dist/packages/source-store/lib/session.d.ts +12 -0
- package/dist/packages/source-store/lib/session.js +139 -0
- package/dist/packages/source-store/lib/urls.d.ts +6 -0
- package/dist/packages/source-store/lib/urls.js +15 -0
- package/dist/packages/source-store/resolve.d.ts +59 -0
- package/dist/packages/source-store/resolve.js +45 -0
- package/dist/scan.d.ts +65 -0
- package/dist/scan.js +592 -0
- package/package.json +95 -4
- package/schema/config.schema.json +133 -0
- package/schema/report.schema.json +1905 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// The installed-package graph every lockfile format reads into (§10 Introducing dependencies), and the parts the
|
|
2
|
+
// formats share. Pure.
|
|
3
|
+
/** A node's `name@version`, or its name alone when the lockfile records no version. */
|
|
4
|
+
export const nameAtVersion = ({ name, version }) => (version === '' ? name : `${name}@${version}`);
|
|
5
|
+
const compareText = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
6
|
+
/** A lockfile over `nodes`, whose dependency edges and workspaces' direct dependencies are resolved by the format. */
|
|
7
|
+
export function graph(parts) {
|
|
8
|
+
const nodes = [...parts.nodes];
|
|
9
|
+
const { edges, direct } = parts;
|
|
10
|
+
let reverse;
|
|
11
|
+
const dependents = () => {
|
|
12
|
+
if (reverse !== undefined)
|
|
13
|
+
return reverse;
|
|
14
|
+
reverse = new Map();
|
|
15
|
+
for (const node of nodes) {
|
|
16
|
+
for (const target of edges(node)) {
|
|
17
|
+
const list = reverse.get(target);
|
|
18
|
+
if (list === undefined)
|
|
19
|
+
reverse.set(target, [node]);
|
|
20
|
+
else if (!list.includes(node))
|
|
21
|
+
list.push(node);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return reverse;
|
|
25
|
+
};
|
|
26
|
+
return {
|
|
27
|
+
format: parts.format,
|
|
28
|
+
keyedByInstallPath: parts.keyedByInstallPath,
|
|
29
|
+
confidence: parts.confidence,
|
|
30
|
+
workspaces: parts.workspaces,
|
|
31
|
+
unresolved: parts.unresolved ?? null,
|
|
32
|
+
place: parts.place,
|
|
33
|
+
named: (name, version) => nodes.filter((node) => node.name === name && node.version === version),
|
|
34
|
+
introducers(from, workspace) {
|
|
35
|
+
const directs = new Set(direct(workspace));
|
|
36
|
+
const seen = new Set(from);
|
|
37
|
+
const queue = [...from];
|
|
38
|
+
for (let next = 0; next < queue.length; next++) {
|
|
39
|
+
for (const dependent of dependents().get(queue[next]) ?? []) {
|
|
40
|
+
if (seen.has(dependent))
|
|
41
|
+
continue;
|
|
42
|
+
seen.add(dependent);
|
|
43
|
+
queue.push(dependent);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const found = [...seen].filter((node) => directs.has(node)).map(nameAtVersion);
|
|
47
|
+
return [...new Set(found)].sort(compareText);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** The package names of a `node_modules/<a>/node_modules/<b>` chain, or null when it is no such chain. */
|
|
52
|
+
export function installChain(segments) {
|
|
53
|
+
const out = [];
|
|
54
|
+
for (let i = 0; i < segments.length;) {
|
|
55
|
+
if (segments[i] !== 'node_modules')
|
|
56
|
+
return null;
|
|
57
|
+
const next = segments[i + 1];
|
|
58
|
+
if (next === undefined || next.startsWith('.'))
|
|
59
|
+
return null;
|
|
60
|
+
if (next.startsWith('@')) {
|
|
61
|
+
if (segments[i + 2] === undefined)
|
|
62
|
+
return null;
|
|
63
|
+
out.push(`${next}/${segments[i + 2]}`);
|
|
64
|
+
i += 3;
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
out.push(next);
|
|
68
|
+
i += 2;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return out.length === 0 ? null : out;
|
|
72
|
+
}
|
|
73
|
+
/** The nodes when they are all one version, or null: several versions leave the copy unknown. */
|
|
74
|
+
function oneVersion(nodes) {
|
|
75
|
+
if (nodes.length === 0)
|
|
76
|
+
return null;
|
|
77
|
+
return nodes.every((node) => node.version === nodes[0].version) ? [...new Set(nodes)] : null;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Where a lockfile that isn't keyed by install path (`pnpm-lock.yaml`, `yarn.lock`) puts a package directory: by the
|
|
81
|
+
* names of its `node_modules` chain. The first name is the workspace's (the folder before the chain) or else the root's
|
|
82
|
+
* direct dependency of that name, or else the lockfile's only version of it; each further name is what the package
|
|
83
|
+
* before it depends on by that name. A name the lockfile doesn't install at all is vendored, in the package before it.
|
|
84
|
+
* Anything else is unplaced: the lockfile doesn't record where its copies are installed.
|
|
85
|
+
*/
|
|
86
|
+
export function placeByName(directory, byName, workspaces, edges, direct) {
|
|
87
|
+
const top = (name, holders) => {
|
|
88
|
+
for (const holder of holders) {
|
|
89
|
+
const one = holder === undefined ? null : oneVersion(direct(holder).filter((node) => node.name === name));
|
|
90
|
+
if (one !== null)
|
|
91
|
+
return one;
|
|
92
|
+
}
|
|
93
|
+
return oneVersion(byName.get(name));
|
|
94
|
+
};
|
|
95
|
+
for (let i = 0; i < directory.length; i++) {
|
|
96
|
+
if (directory[i] !== 'node_modules')
|
|
97
|
+
continue;
|
|
98
|
+
const chain = installChain(directory.slice(i));
|
|
99
|
+
if (chain === null)
|
|
100
|
+
continue;
|
|
101
|
+
const before = directory.slice(0, i).join('/');
|
|
102
|
+
const root = workspaces.find(({ folder }) => folder === '');
|
|
103
|
+
const workspace = workspaces
|
|
104
|
+
.filter(({ folder }) => folder !== '' && (before === folder || before.endsWith(`/${folder}`)))
|
|
105
|
+
.sort((a, b) => b.folder.length - a.folder.length)[0];
|
|
106
|
+
let current = [];
|
|
107
|
+
for (let k = 0; k < chain.length; k++) {
|
|
108
|
+
const name = chain[k];
|
|
109
|
+
if ((byName.get(name) ?? []).length === 0)
|
|
110
|
+
return { kind: 'vendored', around: current };
|
|
111
|
+
const candidates = k === 0
|
|
112
|
+
? top(name, [workspace, root])
|
|
113
|
+
: oneVersion(current.flatMap(edges).filter((node) => node.name === name));
|
|
114
|
+
if (candidates === null)
|
|
115
|
+
return { kind: 'unplaced' };
|
|
116
|
+
current = candidates;
|
|
117
|
+
}
|
|
118
|
+
return { kind: 'installed', nodes: current };
|
|
119
|
+
}
|
|
120
|
+
return { kind: 'unplaced' };
|
|
121
|
+
}
|
|
122
|
+
/** A manifest's text read, or null when it is missing or isn't a manifest. */
|
|
123
|
+
export function readManifest(text) {
|
|
124
|
+
if (text === null || text === undefined)
|
|
125
|
+
return null;
|
|
126
|
+
let value;
|
|
127
|
+
try {
|
|
128
|
+
value = JSON.parse(text.startsWith('') ? text.slice(1) : text);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
if (!isRecord(value))
|
|
134
|
+
return null;
|
|
135
|
+
const list = (field) => isRecord(field) ? Object.entries(field).flatMap(([name, range]) => (typeof range === 'string' ? [[name, range]] : [])) : [];
|
|
136
|
+
return {
|
|
137
|
+
name: typeof value.name === 'string' ? value.name : null,
|
|
138
|
+
version: typeof value.version === 'string' ? value.version : null,
|
|
139
|
+
production: [...list(value.dependencies), ...list(value.optionalDependencies), ...list(value.peerDependencies)],
|
|
140
|
+
development: list(value.devDependencies),
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
export function isRecord(value) {
|
|
144
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
145
|
+
}
|
|
146
|
+
/** A folder relative to the lockfile's directory, normalized: `.` is `''`, and `./`, `x/..` and a trailing `/` go. */
|
|
147
|
+
export function normalizeFolder(folder) {
|
|
148
|
+
const out = [];
|
|
149
|
+
for (const segment of folder.replace(/\\/g, '/').split('/')) {
|
|
150
|
+
if (segment === '' || segment === '.')
|
|
151
|
+
continue;
|
|
152
|
+
if (segment === '..' && out.length > 0 && out.at(-1) !== '..')
|
|
153
|
+
out.pop();
|
|
154
|
+
else
|
|
155
|
+
out.push(segment);
|
|
156
|
+
}
|
|
157
|
+
return out.join('/');
|
|
158
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Why introducers are unknown (§10 Unknown is not empty, §13 Attribution block): a closed list of three. */
|
|
2
|
+
export declare const introducerReasons: readonly ["no-lockfile", "importer-unresolved", "lockfile-stale"];
|
|
3
|
+
export type IntroducerReason = (typeof introducerReasons)[number];
|
|
4
|
+
import { nameAtVersion, type Lockfile, type Node, type Placed, type ReadLockfile, type Workspace } from './lockfile-graph.ts';
|
|
5
|
+
export { nameAtVersion };
|
|
6
|
+
export type { Lockfile, Placed, ReadLockfile, Workspace };
|
|
7
|
+
export type { Node as LockfileNode };
|
|
8
|
+
/** A lockfile's format, by its file name, and for a `.lock` file by its text: Bun's is JSON, Yarn's isn't. */
|
|
9
|
+
export declare function lockfileFormat(path: string, text: string | null): Lockfile['format'];
|
|
10
|
+
/**
|
|
11
|
+
* Reads a lockfile's text by its format (`lockfileFormat`). Null text is a file that couldn't be read. `manifests`
|
|
12
|
+
* holds the `package.json` texts beside it, by folder relative to its directory (`''` for its own), null for one that
|
|
13
|
+
* couldn't be read: Yarn's direct dependencies come from them, and pnpm's linked workspaces' names and versions.
|
|
14
|
+
*/
|
|
15
|
+
export declare function readLockfile(path: string, text: string | null, manifests?: Readonly<Record<string, string | null>>): ReadLockfile;
|
|
16
|
+
/**
|
|
17
|
+
* The importer (§10 Importer): the `lockfile.importer` workspace, by folder (`.` for the root) or name; else the one
|
|
18
|
+
* workspace that holds every first-party source path, taken relative to the lockfile's directory, each in its deepest
|
|
19
|
+
* workspace. With no first-party path, a lockfile with only a root has the root. Null when none decides.
|
|
20
|
+
*/
|
|
21
|
+
export declare function importerOf(lockfile: Lockfile, configured: string | null, firstParty: readonly string[]): Workspace | null;
|
|
22
|
+
/** The deepest workspace whose folder holds `path`, relative to the lockfile's directory: the root if no other does. */
|
|
23
|
+
export declare function workspaceOf(lockfile: Lockfile, path: string): Workspace | undefined;
|
|
24
|
+
/**
|
|
25
|
+
* The `package.json` manifests a lockfile needs beside it (§2 Inputs), besides the one in its own directory, which
|
|
26
|
+
* pnpm and Yarn always read: the workspace folders it names (pnpm's importers, Yarn Berry's workspace entries), or for
|
|
27
|
+
* Yarn v1 the `workspaces` globs of its own manifest (`root`), relative to its directory. Null for npm and Bun, which
|
|
28
|
+
* read none.
|
|
29
|
+
*/
|
|
30
|
+
export declare function lockfileManifests(path: string, text: string | null, root: string | null): {
|
|
31
|
+
folders: string[];
|
|
32
|
+
globs: string[];
|
|
33
|
+
} | null;
|
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
// Lockfiles (§10 Introducing dependencies): `package-lock.json` v2/v3 and `bun.lock`, read into one installed-package
|
|
2
|
+
// graph keyed by install path. Pure: the CLI reads the file's text.
|
|
3
|
+
/** Why introducers are unknown (§10 Unknown is not empty, §13 Attribution block): a closed list of three. */
|
|
4
|
+
export const introducerReasons = ['no-lockfile', 'importer-unresolved', 'lockfile-stale'];
|
|
5
|
+
import { graph, installChain, isRecord, nameAtVersion, } from "./lockfile-graph.js";
|
|
6
|
+
import { pnpmFolders, readPnpm } from "./pnpm.js";
|
|
7
|
+
import { readYarn, yarnManifests } from "./yarn.js";
|
|
8
|
+
export { nameAtVersion };
|
|
9
|
+
/** A lockfile's format, by its file name, and for a `.lock` file by its text: Bun's is JSON, Yarn's isn't. */
|
|
10
|
+
export function lockfileFormat(path, text) {
|
|
11
|
+
const name = path.split(/[/\\]/).at(-1) ?? path;
|
|
12
|
+
if (/\.ya?ml$/.test(name))
|
|
13
|
+
return 'pnpm';
|
|
14
|
+
if (name === 'yarn.lock')
|
|
15
|
+
return 'yarn';
|
|
16
|
+
if (name.endsWith('.lock'))
|
|
17
|
+
return text !== null && !stripBom(text).trimStart().startsWith('{') ? 'yarn' : 'bun';
|
|
18
|
+
return 'npm';
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Reads a lockfile's text by its format (`lockfileFormat`). Null text is a file that couldn't be read. `manifests`
|
|
22
|
+
* holds the `package.json` texts beside it, by folder relative to its directory (`''` for its own), null for one that
|
|
23
|
+
* couldn't be read: Yarn's direct dependencies come from them, and pnpm's linked workspaces' names and versions.
|
|
24
|
+
*/
|
|
25
|
+
export function readLockfile(path, text, manifests = {}) {
|
|
26
|
+
if (text === null)
|
|
27
|
+
return { unusable: `${path} can't be read` };
|
|
28
|
+
const name = path.split(/[/\\]/).at(-1) ?? path;
|
|
29
|
+
const format = lockfileFormat(path, text);
|
|
30
|
+
if (format === 'pnpm')
|
|
31
|
+
return readPnpm(text, name, manifests);
|
|
32
|
+
if (format === 'yarn')
|
|
33
|
+
return readYarn(text, name, manifests);
|
|
34
|
+
const bun = format === 'bun';
|
|
35
|
+
let value;
|
|
36
|
+
try {
|
|
37
|
+
value = JSON.parse(bun ? withoutJsonc(text) : stripBom(text));
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return { unusable: `${name} is not valid JSON` };
|
|
41
|
+
}
|
|
42
|
+
if (!isRecord(value))
|
|
43
|
+
return { unusable: `${name} is not a lockfile` };
|
|
44
|
+
return bun ? readBun(value, name) : readNpm(value, name);
|
|
45
|
+
}
|
|
46
|
+
const stripBom = (text) => (text.startsWith('') ? text.slice(1) : text);
|
|
47
|
+
/** JSON with comments and trailing commas (`bun.lock`), as plain JSON. */
|
|
48
|
+
function withoutJsonc(input) {
|
|
49
|
+
const text = stripBom(input);
|
|
50
|
+
let out = '';
|
|
51
|
+
for (let i = 0; i < text.length; i++) {
|
|
52
|
+
const c = text[i];
|
|
53
|
+
if (c === '"') {
|
|
54
|
+
const start = i;
|
|
55
|
+
for (i++; i < text.length && text[i] !== '"'; i++)
|
|
56
|
+
if (text[i] === '\\')
|
|
57
|
+
i++;
|
|
58
|
+
out += text.slice(start, i + 1);
|
|
59
|
+
}
|
|
60
|
+
else if (c === '/' && text[i + 1] === '/') {
|
|
61
|
+
while (i < text.length && text[i] !== '\n')
|
|
62
|
+
i++;
|
|
63
|
+
out += '\n';
|
|
64
|
+
}
|
|
65
|
+
else if (c === '/' && text[i + 1] === '*') {
|
|
66
|
+
const end = text.indexOf('*/', i + 2);
|
|
67
|
+
i = end < 0 ? text.length : end + 1;
|
|
68
|
+
}
|
|
69
|
+
else if (c === ',') {
|
|
70
|
+
let j = i + 1;
|
|
71
|
+
while (j < text.length && /\s/.test(text[j]))
|
|
72
|
+
j++;
|
|
73
|
+
if (text[j] !== '}' && text[j] !== ']')
|
|
74
|
+
out += c;
|
|
75
|
+
}
|
|
76
|
+
else
|
|
77
|
+
out += c;
|
|
78
|
+
}
|
|
79
|
+
return out;
|
|
80
|
+
}
|
|
81
|
+
/** A record's string-keyed names: the keys of a dependency map. */
|
|
82
|
+
function names(value) {
|
|
83
|
+
return isRecord(value) ? Object.keys(value) : [];
|
|
84
|
+
}
|
|
85
|
+
const str = (value) => (typeof value === 'string' ? value : null);
|
|
86
|
+
/** The package name an npm install key ends in: the segment after its last `node_modules`, two for a scope. */
|
|
87
|
+
function nameInKey(segments) {
|
|
88
|
+
const at = segments.lastIndexOf('node_modules');
|
|
89
|
+
if (at < 0)
|
|
90
|
+
return null;
|
|
91
|
+
const next = segments[at + 1];
|
|
92
|
+
if (next === undefined)
|
|
93
|
+
return null;
|
|
94
|
+
return next.startsWith('@') ? (segments[at + 2] === undefined ? null : `${next}/${segments[at + 2]}`) : next;
|
|
95
|
+
}
|
|
96
|
+
/** `package-lock.json` v2/v3: `packages` keyed by install path. v1 and anything else is unusable. */
|
|
97
|
+
function readNpm(value, file) {
|
|
98
|
+
const version = value.lockfileVersion;
|
|
99
|
+
if ((version !== 2 && version !== 3) || !isRecord(value.packages)) {
|
|
100
|
+
return { unusable: `${file} lockfileVersion ${JSON.stringify(version ?? null)}` };
|
|
101
|
+
}
|
|
102
|
+
const entries = value.packages;
|
|
103
|
+
const nodes = new Map();
|
|
104
|
+
/** A link entry's target folder, by its key. */
|
|
105
|
+
const links = new Map();
|
|
106
|
+
const workspaces = [];
|
|
107
|
+
for (const [key, entry] of Object.entries(entries)) {
|
|
108
|
+
if (!isRecord(entry))
|
|
109
|
+
continue;
|
|
110
|
+
const segments = key === '' ? [] : key.split('/');
|
|
111
|
+
if (entry.link === true && typeof entry.resolved === 'string') {
|
|
112
|
+
links.set(key, entry.resolved);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const dependencies = [...names(entry.dependencies), ...names(entry.optionalDependencies), ...names(entry.peerDependencies)];
|
|
116
|
+
const name = str(entry.name) ?? nameInKey(segments);
|
|
117
|
+
const own = str(entry.version);
|
|
118
|
+
if (!segments.includes('node_modules')) {
|
|
119
|
+
workspaces.push({
|
|
120
|
+
folder: key,
|
|
121
|
+
name,
|
|
122
|
+
version: own,
|
|
123
|
+
key,
|
|
124
|
+
direct: [...dependencies, ...names(entry.devDependencies)],
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
if (name !== null && key !== '')
|
|
128
|
+
nodes.set(key, { key, name, version: own ?? '', dependencies });
|
|
129
|
+
}
|
|
130
|
+
const lookup = (key) => {
|
|
131
|
+
const target = links.get(key);
|
|
132
|
+
return nodes.get(target ?? key) ?? null;
|
|
133
|
+
};
|
|
134
|
+
/** Node's resolution of `name` from the package at `key` (§10 Walk): up its directories' `node_modules`. */
|
|
135
|
+
const resolve = (key, name) => {
|
|
136
|
+
const dir = key === '' ? [] : key.split('/');
|
|
137
|
+
for (;;) {
|
|
138
|
+
const found = lookup([...dir, 'node_modules', name].join('/'));
|
|
139
|
+
if (found !== null)
|
|
140
|
+
return found;
|
|
141
|
+
if (dir.length === 0)
|
|
142
|
+
return null;
|
|
143
|
+
dir.pop();
|
|
144
|
+
if (dir.at(-1) === 'node_modules')
|
|
145
|
+
dir.pop();
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
return {
|
|
149
|
+
lockfile: namedGraph('npm', nodes, workspaces, resolve, (directory) => {
|
|
150
|
+
for (let i = 0; i < directory.length; i++) {
|
|
151
|
+
const found = lookup(directory.slice(i).join('/'));
|
|
152
|
+
if (found !== null && !links.has(found.key))
|
|
153
|
+
return found;
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
}),
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
/** `bun.lock`: `packages` keyed by hoisted path (`a/b` is `node_modules/a/node_modules/b`), and `workspaces`. */
|
|
160
|
+
function readBun(value, file) {
|
|
161
|
+
const version = value.lockfileVersion;
|
|
162
|
+
if ((version !== 0 && version !== 1) || !isRecord(value.packages) || !isRecord(value.workspaces)) {
|
|
163
|
+
return { unusable: `${file} lockfileVersion ${JSON.stringify(version ?? null)}` };
|
|
164
|
+
}
|
|
165
|
+
const declared = value.workspaces;
|
|
166
|
+
const workspaces = [];
|
|
167
|
+
const byName = new Map();
|
|
168
|
+
for (const [folder, entry] of Object.entries(declared)) {
|
|
169
|
+
if (!isRecord(entry))
|
|
170
|
+
continue;
|
|
171
|
+
const name = str(entry.name);
|
|
172
|
+
const workspace = {
|
|
173
|
+
folder,
|
|
174
|
+
name,
|
|
175
|
+
version: str(entry.version),
|
|
176
|
+
// The root resolves from the top; another workspace from its own entry, where Bun nests what it doesn't hoist.
|
|
177
|
+
key: folder === '' || name === null ? '' : name,
|
|
178
|
+
direct: [
|
|
179
|
+
...names(entry.dependencies),
|
|
180
|
+
...names(entry.optionalDependencies),
|
|
181
|
+
...names(entry.peerDependencies),
|
|
182
|
+
...names(entry.devDependencies),
|
|
183
|
+
],
|
|
184
|
+
};
|
|
185
|
+
workspaces.push(workspace);
|
|
186
|
+
if (name !== null)
|
|
187
|
+
byName.set(name, workspace);
|
|
188
|
+
}
|
|
189
|
+
const nodes = new Map();
|
|
190
|
+
for (const [key, entry] of Object.entries(value.packages)) {
|
|
191
|
+
if (!Array.isArray(entry) || typeof entry[0] !== 'string')
|
|
192
|
+
continue;
|
|
193
|
+
const ident = entry[0];
|
|
194
|
+
const split = ident.indexOf('@', 1);
|
|
195
|
+
if (split <= 0)
|
|
196
|
+
continue;
|
|
197
|
+
const name = ident.slice(0, split);
|
|
198
|
+
let own = ident.slice(split + 1);
|
|
199
|
+
let dependencies;
|
|
200
|
+
const info = entry.find((part, i) => i > 0 && isRecord(part));
|
|
201
|
+
if (own.startsWith('workspace:')) {
|
|
202
|
+
// A workspace's dependencies are its `workspaces` entry's, without its dev dependencies.
|
|
203
|
+
const workspace = workspaces.find(({ folder }) => folder === own.slice('workspace:'.length)) ?? byName.get(name);
|
|
204
|
+
own = workspace?.version ?? own;
|
|
205
|
+
const entry = workspace === undefined ? undefined : declared[workspace.folder];
|
|
206
|
+
dependencies = isRecord(entry)
|
|
207
|
+
? [...names(entry.dependencies), ...names(entry.optionalDependencies), ...names(entry.peerDependencies)]
|
|
208
|
+
: [];
|
|
209
|
+
}
|
|
210
|
+
else {
|
|
211
|
+
dependencies = [
|
|
212
|
+
...names(info?.dependencies),
|
|
213
|
+
...names(info?.optionalDependencies),
|
|
214
|
+
...names(info?.peerDependencies),
|
|
215
|
+
];
|
|
216
|
+
}
|
|
217
|
+
nodes.set(key, { key, name, version: own, dependencies });
|
|
218
|
+
}
|
|
219
|
+
/** The package names of a hoisted key, a scope and its name together. */
|
|
220
|
+
const parts = (key) => {
|
|
221
|
+
const out = [];
|
|
222
|
+
const segments = key === '' ? [] : key.split('/');
|
|
223
|
+
for (let i = 0; i < segments.length; i++) {
|
|
224
|
+
out.push(segments[i].startsWith('@') && i + 1 < segments.length ? `${segments[i]}/${segments[++i]}` : segments[i]);
|
|
225
|
+
}
|
|
226
|
+
return out;
|
|
227
|
+
};
|
|
228
|
+
const resolve = (key, name) => {
|
|
229
|
+
const path = parts(key);
|
|
230
|
+
for (;;) {
|
|
231
|
+
const found = nodes.get([...path, name].join('/'));
|
|
232
|
+
if (found !== undefined)
|
|
233
|
+
return found;
|
|
234
|
+
if (path.length === 0)
|
|
235
|
+
return null;
|
|
236
|
+
path.pop();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
const folders = workspaces.filter(({ folder, name }) => folder !== '' && name !== null);
|
|
240
|
+
return {
|
|
241
|
+
lockfile: namedGraph('bun', nodes, workspaces, resolve, (directory) => {
|
|
242
|
+
for (let i = 0; i < directory.length; i++) {
|
|
243
|
+
if (directory[i] !== 'node_modules')
|
|
244
|
+
continue;
|
|
245
|
+
const chain = installChain(directory.slice(i));
|
|
246
|
+
if (chain === null)
|
|
247
|
+
continue;
|
|
248
|
+
const before = directory.slice(0, i).join('/');
|
|
249
|
+
// A workspace's own `node_modules` is keyed under the workspace's name.
|
|
250
|
+
const workspace = folders.find(({ folder }) => before === folder || before.endsWith(`/${folder}`));
|
|
251
|
+
const found = (workspace === undefined ? undefined : nodes.get([workspace.name, ...chain].join('/'))) ??
|
|
252
|
+
nodes.get(chain.join('/'));
|
|
253
|
+
if (found !== undefined)
|
|
254
|
+
return found;
|
|
255
|
+
}
|
|
256
|
+
return null;
|
|
257
|
+
}),
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* A lockfile keyed by install path (npm, Bun), over nodes whose dependencies `resolve` resolves by name. A package
|
|
262
|
+
* directory is the node its install path is; a path the lockfile doesn't install is vendored, in the installed package
|
|
263
|
+
* whose directory holds it (the directory before its package's `node_modules` segment), or in none the lockfile names.
|
|
264
|
+
*/
|
|
265
|
+
function namedGraph(format, nodes, workspaces, resolve, atInstallPath) {
|
|
266
|
+
const byFolder = new Map(workspaces.map((workspace) => [workspace.folder, workspace]));
|
|
267
|
+
return graph({
|
|
268
|
+
format,
|
|
269
|
+
keyedByInstallPath: true,
|
|
270
|
+
confidence: 'high',
|
|
271
|
+
nodes: nodes.values(),
|
|
272
|
+
workspaces: workspaces.map(({ folder, name, version }) => ({ folder, name, version })),
|
|
273
|
+
edges: (node) => node.dependencies.flatMap((name) => resolve(node.key, name) ?? []),
|
|
274
|
+
direct: (workspace) => {
|
|
275
|
+
const named = byFolder.get(workspace.folder);
|
|
276
|
+
return named === undefined ? [] : named.direct.flatMap((name) => resolve(named.key, name) ?? []);
|
|
277
|
+
},
|
|
278
|
+
place(directory) {
|
|
279
|
+
const node = atInstallPath(directory);
|
|
280
|
+
if (node !== null)
|
|
281
|
+
return { kind: 'installed', nodes: [node] };
|
|
282
|
+
const around = directory.slice(0, directory.lastIndexOf('node_modules'));
|
|
283
|
+
const vendoring = around.includes('node_modules') ? atInstallPath(around) : null;
|
|
284
|
+
return { kind: 'vendored', around: vendoring === null ? [] : [vendoring] };
|
|
285
|
+
},
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
/**
|
|
289
|
+
* The importer (§10 Importer): the `lockfile.importer` workspace, by folder (`.` for the root) or name; else the one
|
|
290
|
+
* workspace that holds every first-party source path, taken relative to the lockfile's directory, each in its deepest
|
|
291
|
+
* workspace. With no first-party path, a lockfile with only a root has the root. Null when none decides.
|
|
292
|
+
*/
|
|
293
|
+
export function importerOf(lockfile, configured, firstParty) {
|
|
294
|
+
const { workspaces } = lockfile;
|
|
295
|
+
if (configured !== null) {
|
|
296
|
+
const folder = configured === '.' || configured === './' ? '' : configured.replace(/^\.\//, '').replace(/\/$/, '');
|
|
297
|
+
return workspaces.find((each) => each.folder === folder) ?? workspaces.find((each) => each.name === configured) ?? null;
|
|
298
|
+
}
|
|
299
|
+
if (firstParty.length === 0)
|
|
300
|
+
return workspaces.length === 1 ? workspaces[0] : null;
|
|
301
|
+
const holding = new Set(firstParty.map((path) => workspaceOf(lockfile, path)));
|
|
302
|
+
const [only] = holding;
|
|
303
|
+
return holding.size === 1 && only !== undefined ? only : null;
|
|
304
|
+
}
|
|
305
|
+
/** The deepest workspace whose folder holds `path`, relative to the lockfile's directory: the root if no other does. */
|
|
306
|
+
export function workspaceOf(lockfile, path) {
|
|
307
|
+
let best;
|
|
308
|
+
for (const workspace of lockfile.workspaces) {
|
|
309
|
+
const { folder } = workspace;
|
|
310
|
+
if (folder !== '' && !path.startsWith(`${folder}/`))
|
|
311
|
+
continue;
|
|
312
|
+
if (best === undefined || folder.length > best.folder.length)
|
|
313
|
+
best = workspace;
|
|
314
|
+
}
|
|
315
|
+
return best;
|
|
316
|
+
}
|
|
317
|
+
/**
|
|
318
|
+
* The `package.json` manifests a lockfile needs beside it (§2 Inputs), besides the one in its own directory, which
|
|
319
|
+
* pnpm and Yarn always read: the workspace folders it names (pnpm's importers, Yarn Berry's workspace entries), or for
|
|
320
|
+
* Yarn v1 the `workspaces` globs of its own manifest (`root`), relative to its directory. Null for npm and Bun, which
|
|
321
|
+
* read none.
|
|
322
|
+
*/
|
|
323
|
+
export function lockfileManifests(path, text, root) {
|
|
324
|
+
const format = lockfileFormat(path, text);
|
|
325
|
+
if (format === 'npm' || format === 'bun')
|
|
326
|
+
return null;
|
|
327
|
+
if (text === null)
|
|
328
|
+
return { folders: [], globs: [] };
|
|
329
|
+
return format === 'pnpm' ? { folders: pnpmFolders(text), globs: [] } : yarnManifests(text, root);
|
|
330
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { EscapeOnlyShape, FrameworkPathRule } from '../../../catalog.ts';
|
|
2
|
+
import type { SourcePath } from './containing-package.ts';
|
|
3
|
+
/** The curated path data attribution reads from the catalog (§4 Catalog versions). */
|
|
4
|
+
export interface PathRules {
|
|
5
|
+
frameworkPathRules: Readonly<Record<string, FrameworkPathRule>>;
|
|
6
|
+
escapeOnlyShapes: Readonly<Record<string, EscapeOnlyShape>>;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* What the curated path data says of a source with no `node_modules` segment (§10 Escaped sources):
|
|
10
|
+
* - `rule`: a framework path rule matched it, and names `package` (the rule with the lowest id when several match)
|
|
11
|
+
* - `escaped`: no rule matched, but an escape-only shape did
|
|
12
|
+
* - `none`: neither did, so the inferred root decides
|
|
13
|
+
*/
|
|
14
|
+
export type PathRuling = {
|
|
15
|
+
kind: 'rule';
|
|
16
|
+
rule: string;
|
|
17
|
+
package: string;
|
|
18
|
+
} | {
|
|
19
|
+
kind: 'escaped';
|
|
20
|
+
shape: string;
|
|
21
|
+
} | {
|
|
22
|
+
kind: 'none';
|
|
23
|
+
};
|
|
24
|
+
/**
|
|
25
|
+
* Tries the framework path rules first, then the escape-only shapes, against a source's normalized path (§10 Escaped
|
|
26
|
+
* sources: "Framework path rules are tried first"). Each is tried in id order, so the answer never depends on the
|
|
27
|
+
* catalog's key order.
|
|
28
|
+
*/
|
|
29
|
+
export declare function pathRuling(path: SourcePath, rules: PathRules): PathRuling;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// Framework path rules and escape-only shapes (§10 Escaped sources, Framework path rules): curated catalog data (§4)
|
|
2
|
+
// matched against a source's normalized path.
|
|
3
|
+
/**
|
|
4
|
+
* Tries the framework path rules first, then the escape-only shapes, against a source's normalized path (§10 Escaped
|
|
5
|
+
* sources: "Framework path rules are tried first"). Each is tried in id order, so the answer never depends on the
|
|
6
|
+
* catalog's key order.
|
|
7
|
+
*/
|
|
8
|
+
export function pathRuling(path, rules) {
|
|
9
|
+
if (path.segments.includes('node_modules'))
|
|
10
|
+
return { kind: 'none' };
|
|
11
|
+
for (const id of sortedIds(rules.frameworkPathRules)) {
|
|
12
|
+
const rule = rules.frameworkPathRules[id];
|
|
13
|
+
const captured = matches(rule, path);
|
|
14
|
+
if (captured !== null) {
|
|
15
|
+
return { kind: 'rule', rule: id, package: rule.package.replace(placeholder, (_, name) => captured[name]) };
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
for (const id of sortedIds(rules.escapeOnlyShapes)) {
|
|
19
|
+
if (matches(rules.escapeOnlyShapes[id], path) !== null)
|
|
20
|
+
return { kind: 'escaped', shape: id };
|
|
21
|
+
}
|
|
22
|
+
return { kind: 'none' };
|
|
23
|
+
}
|
|
24
|
+
const placeholder = /<([A-Za-z][A-Za-z0-9_-]*)>/g;
|
|
25
|
+
function sortedIds(record) {
|
|
26
|
+
return Object.keys(record).sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* What a shape captures from a path it matches, or null when it doesn't match: the path is in the shape's namespace,
|
|
30
|
+
* and its segments hold the shape's run of whole segments, with at least one segment after it, and each placeholder
|
|
31
|
+
* captured a value the shape's `captures` allows. With `up`, the run comes right after the path's leading `..`
|
|
32
|
+
* segments, of which there are at least `up`. Without it, the run may start at any segment, and the first place it
|
|
33
|
+
* matches counts.
|
|
34
|
+
*/
|
|
35
|
+
function matches(shape, path) {
|
|
36
|
+
if (shape.namespace !== undefined && shape.namespace !== path.namespace)
|
|
37
|
+
return null;
|
|
38
|
+
const { segments } = path;
|
|
39
|
+
let leading = 0;
|
|
40
|
+
while (segments[leading] === '..')
|
|
41
|
+
leading++;
|
|
42
|
+
const starts = shape.up === undefined
|
|
43
|
+
? segments.map((_, at) => at)
|
|
44
|
+
: leading >= shape.up
|
|
45
|
+
? [leading]
|
|
46
|
+
: [];
|
|
47
|
+
for (const start of starts) {
|
|
48
|
+
if (start + shape.segments.length >= segments.length)
|
|
49
|
+
continue;
|
|
50
|
+
const captured = {};
|
|
51
|
+
const all = shape.segments.every((pattern, i) => segmentMatches(pattern, segments[start + i], captured));
|
|
52
|
+
if (all && capturesAllowed(shape, captured))
|
|
53
|
+
return captured;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
/** Whether every placeholder captured a value its shape's `captures` allows, when it limits that placeholder. */
|
|
58
|
+
function capturesAllowed({ captures }, captured) {
|
|
59
|
+
if (captures === undefined)
|
|
60
|
+
return true;
|
|
61
|
+
return Object.entries(captured).every(([name, value]) => captures[name]?.includes(value) ?? true);
|
|
62
|
+
}
|
|
63
|
+
/** Whether one path segment matches a segment pattern, adding what its placeholders capture to `captured`. */
|
|
64
|
+
function segmentMatches(pattern, segment, captured) {
|
|
65
|
+
const names = [];
|
|
66
|
+
const source = pattern
|
|
67
|
+
.split(/(<[A-Za-z][A-Za-z0-9_-]*>|\*)/)
|
|
68
|
+
.map((part) => {
|
|
69
|
+
if (part === '*')
|
|
70
|
+
return '.*';
|
|
71
|
+
const name = /^<(.+)>$/.exec(part)?.[1];
|
|
72
|
+
if (name !== undefined) {
|
|
73
|
+
names.push(name);
|
|
74
|
+
return '(.+)';
|
|
75
|
+
}
|
|
76
|
+
return part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
77
|
+
})
|
|
78
|
+
.join('');
|
|
79
|
+
const found = new RegExp(`^${source}$`, 's').exec(segment);
|
|
80
|
+
if (found === null)
|
|
81
|
+
return false;
|
|
82
|
+
names.forEach((name, i) => (captured[name] = found[i + 1]));
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import { type ReadLockfile } from './lockfile-graph.ts';
|
|
2
|
+
export declare function readPnpm(text: string, file: string, manifests: Readonly<Record<string, string | null>>): ReadLockfile;
|
|
3
|
+
/** The importer folders a `pnpm-lock.yaml` names, besides its own directory, whose manifests name linked workspaces. */
|
|
4
|
+
export declare function pnpmFolders(text: string): string[];
|