dsh-plugin-compat-check 0.1.6 → 0.1.7
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/README.md +17 -30
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/index.js +1295 -550
- package/kernel/lib/api-semantics.mjs +223 -0
- package/kernel/lib/author-ci-budget.mjs +25 -0
- package/kernel/lib/author-ci-output.mjs +22 -0
- package/kernel/lib/author-ci-policy.mjs +6 -1
- package/kernel/lib/baseline.mjs +109 -38
- package/kernel/lib/catalog.mjs +11 -3
- package/kernel/lib/contracts.mjs +16 -1
- package/kernel/lib/execution-environment.mjs +52 -0
- package/kernel/lib/extract.mjs +57 -13
- package/kernel/lib/finding-guidance.mjs +6 -0
- package/kernel/lib/finding-review-store.mjs +11 -23
- package/kernel/lib/issue-report.mjs +2 -1
- package/kernel/lib/patch-mounts.mjs +232 -0
- package/kernel/lib/report-identity-worker.mjs +47 -26
- package/kernel/lib/report-identity.mjs +4 -3
- package/kernel/lib/report-service.mjs +118 -38
- package/kernel/lib/report.mjs +13 -17
- package/kernel/lib/source-analysis.mjs +258 -42
- package/kernel/lib/source-graph.mjs +353 -75
- package/kernel/lib/state-lock.mjs +206 -0
- package/kernel/lib/state-store.mjs +41 -30
- package/kernel/lib/static-input.mjs +111 -0
- package/kernel/lib/syntax.mjs +242 -20
- package/kernel/lib/upgrade-contract.mjs +1 -0
- package/kernel/lib/upgrade-input.mjs +9 -4
- package/kernel/lib/upgrade-preview.mjs +11 -15
- package/kernel/lib/upgrade-worker.mjs +10 -1
- package/kernel/lib/verdict.mjs +28 -5
- package/kernel/lib/version.mjs +2 -34
- package/kernel/lib/worker-analysis.mjs +74 -3
- package/kernel/rules/README.md +30 -208
- package/kernel/rules/dsh-0.2.1-rc.3-review.json +63 -0
- package/kernel/rules/dsh-api-catalog-0.2.1-rc.3.json +743 -0
- package/kernel/rules/severity.json +77 -0
- package/lib/client.js +4668 -3423
- package/lib/finding-guidance.js +6 -0
- package/lib/local-request.js +44 -0
- package/lib/report-contract.js +16 -1
- package/lib/upgrade-contract.js +1 -0
- package/package.json +3 -2
- package/scripts/prepublish-check.mjs +1 -22
- package/scripts/stage-contract.mjs +7 -0
- package/skill/SKILL.md +13 -12
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { patchMounts } from "./patch-mounts.mjs";
|
|
3
4
|
import { localImports } from "./syntax.mjs";
|
|
5
|
+
import {
|
|
6
|
+
readStaticFile,
|
|
7
|
+
readStaticDirectory,
|
|
8
|
+
inputError,
|
|
9
|
+
lowerInputLimits,
|
|
10
|
+
} from "./static-input.mjs";
|
|
4
11
|
|
|
5
12
|
const sourceFile = (file) =>
|
|
6
13
|
/\.[cm]?[jt]sx?$/.test(file) && !file.endsWith(".d.ts");
|
|
@@ -8,25 +15,98 @@ const portable = (file) => file.split(path.sep).join("/");
|
|
|
8
15
|
const inside = (root, file) =>
|
|
9
16
|
file === root || file.startsWith(root + path.sep);
|
|
10
17
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
/** DSH imports host plugins with Node's normal ESM conditions, never types. */
|
|
19
|
+
function hostEntry(meta, uncertain) {
|
|
20
|
+
if (meta.exports == null)
|
|
21
|
+
return typeof meta.main === "string" ? meta.main : "index.js";
|
|
22
|
+
let steps = 0;
|
|
23
|
+
const active = new Set([
|
|
24
|
+
"node",
|
|
25
|
+
"import",
|
|
26
|
+
"node-addons",
|
|
27
|
+
"module-sync",
|
|
28
|
+
"default",
|
|
29
|
+
]);
|
|
30
|
+
const inactive = new Set(["types", "browser", "require"]);
|
|
31
|
+
const invalid = (message, code) => {
|
|
32
|
+
throw Object.assign(new Error(message), { code });
|
|
33
|
+
};
|
|
34
|
+
const select = (value, depth = 0) => {
|
|
35
|
+
if (++steps > 128 || depth > 32)
|
|
36
|
+
return invalid("Export resolution budget exceeded");
|
|
37
|
+
if (value === null) return null;
|
|
38
|
+
if (typeof value === "string") {
|
|
39
|
+
if (
|
|
40
|
+
!value.startsWith("./") ||
|
|
41
|
+
value
|
|
42
|
+
.slice(2)
|
|
43
|
+
.split("/")
|
|
44
|
+
.some((p) => p === "." || p === ".." || p === "node_modules")
|
|
45
|
+
)
|
|
46
|
+
return invalid(
|
|
47
|
+
"Unsupported or invalid export target",
|
|
48
|
+
"INVALID_EXPORT_TARGET",
|
|
49
|
+
);
|
|
50
|
+
if (/[%?#\\]/.test(value))
|
|
51
|
+
return invalid(
|
|
52
|
+
"URL-style export target cannot be mapped to source with certainty",
|
|
53
|
+
);
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
if (Array.isArray(value)) {
|
|
57
|
+
let error;
|
|
58
|
+
for (const entry of value) {
|
|
59
|
+
try {
|
|
60
|
+
const target = select(entry, depth + 1);
|
|
61
|
+
if (target != null) return target;
|
|
62
|
+
} catch (cause) {
|
|
63
|
+
if (cause.code !== "INVALID_EXPORT_TARGET") throw cause;
|
|
64
|
+
error = cause;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
if (error) throw error;
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
if (!value || typeof value !== "object")
|
|
71
|
+
return invalid("Unsupported export value", "INVALID_EXPORT_TARGET");
|
|
72
|
+
for (const [condition, target] of Object.entries(value)) {
|
|
73
|
+
if (/^\d+$/.test(condition) || condition.startsWith("."))
|
|
74
|
+
return invalid("Invalid export condition");
|
|
75
|
+
if (active.has(condition)) {
|
|
76
|
+
const result = select(target, depth + 1);
|
|
77
|
+
if (result !== undefined) return result;
|
|
78
|
+
} else if (!inactive.has(condition))
|
|
79
|
+
return invalid(
|
|
80
|
+
"Custom export conditions are not established for this host",
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return undefined;
|
|
84
|
+
};
|
|
85
|
+
try {
|
|
86
|
+
let value = meta.exports;
|
|
87
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
88
|
+
const keys = Object.keys(value);
|
|
89
|
+
if (keys.some((k) => k.startsWith("."))) {
|
|
90
|
+
if (keys.some((k) => !k.startsWith(".")) || !Object.hasOwn(value, "."))
|
|
91
|
+
return invalid("Package root export is unavailable or invalid");
|
|
92
|
+
value = value["."];
|
|
93
|
+
}
|
|
21
94
|
}
|
|
22
|
-
|
|
95
|
+
const target = select(value);
|
|
96
|
+
if (!target) return invalid("No host import target can be established");
|
|
97
|
+
return target;
|
|
98
|
+
} catch (error) {
|
|
99
|
+
uncertain(error.message);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
23
102
|
}
|
|
24
103
|
export function clientEntry(meta) {
|
|
104
|
+
// client-modules at all three reviewed tags uses its own one-level loader
|
|
105
|
+
// contract, not Node/browser conditional export resolution.
|
|
25
106
|
const value = meta.exports?.["./client"];
|
|
26
107
|
if (typeof value === "string") return value;
|
|
27
|
-
if (value && typeof value === "object")
|
|
28
|
-
|
|
29
|
-
if (typeof value[key] === "string") return value[key];
|
|
108
|
+
if (value && typeof value === "object" && typeof value.default === "string")
|
|
109
|
+
return value.default;
|
|
30
110
|
return null;
|
|
31
111
|
}
|
|
32
112
|
|
|
@@ -39,14 +119,21 @@ export function collectPackageSources(pkg, meta, options = {}) {
|
|
|
39
119
|
files = [],
|
|
40
120
|
buckets = new Map(),
|
|
41
121
|
fileSides = {};
|
|
42
|
-
const limits =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
122
|
+
const limits = lowerInputLimits(
|
|
123
|
+
{
|
|
124
|
+
files: 2000,
|
|
125
|
+
bytes: 32 * 1024 * 1024,
|
|
126
|
+
fileBytes: 2 * 1024 * 1024,
|
|
127
|
+
directories: 10000,
|
|
128
|
+
entries: 10000,
|
|
129
|
+
timeMs: 5000,
|
|
130
|
+
yamlNodes: 10000,
|
|
131
|
+
yamlDepth: 32,
|
|
132
|
+
yamlFiles: 64,
|
|
133
|
+
yamlBytes: 65536,
|
|
134
|
+
},
|
|
135
|
+
options.limits,
|
|
136
|
+
);
|
|
50
137
|
const texts = new Map(),
|
|
51
138
|
resolvedFiles = new Map(),
|
|
52
139
|
moduleLinks = new Map();
|
|
@@ -54,7 +141,30 @@ export function collectPackageSources(pkg, meta, options = {}) {
|
|
|
54
141
|
const note = (kind, file, message) => {
|
|
55
142
|
if (diagnostics.length < 100) diagnostics.push({ kind, file, message });
|
|
56
143
|
};
|
|
144
|
+
let hostResolved = false,
|
|
145
|
+
hostTarget;
|
|
146
|
+
const hostEntryFor = () => {
|
|
147
|
+
if (!hostResolved) {
|
|
148
|
+
hostResolved = true;
|
|
149
|
+
hostTarget = hostEntry(meta, (message) =>
|
|
150
|
+
note("entry-resolution-uncertain", "package.json", message),
|
|
151
|
+
);
|
|
152
|
+
if (hostTarget) {
|
|
153
|
+
// Only legacy package main resolution has extension/index fallbacks.
|
|
154
|
+
// An exports target, like an explicit import URL, is an exact file.
|
|
155
|
+
hostTarget =
|
|
156
|
+
meta.exports == null
|
|
157
|
+
? resolveLegacyMain(hostTarget)
|
|
158
|
+
: resolveFile(hostTarget, true);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return hostTarget;
|
|
162
|
+
};
|
|
57
163
|
const expired = () => Date.now() - started > limits.timeMs;
|
|
164
|
+
const checkTime = () => {
|
|
165
|
+
if (expired())
|
|
166
|
+
throw inputError("STATIC_INPUT_BUDGET", "Source time budget exceeded");
|
|
167
|
+
};
|
|
58
168
|
const resolveFile = (rel, required = false) => {
|
|
59
169
|
if (typeof rel !== "string") return null;
|
|
60
170
|
const abs = path.resolve(root, rel);
|
|
@@ -66,24 +176,9 @@ export function collectPackageSources(pkg, meta, options = {}) {
|
|
|
66
176
|
);
|
|
67
177
|
return null;
|
|
68
178
|
}
|
|
69
|
-
|
|
70
|
-
abs,
|
|
71
|
-
...[
|
|
72
|
-
".js",
|
|
73
|
-
".mjs",
|
|
74
|
-
".cjs",
|
|
75
|
-
".ts",
|
|
76
|
-
".tsx",
|
|
77
|
-
".jsx",
|
|
78
|
-
".mts",
|
|
79
|
-
".cts",
|
|
80
|
-
"/index.js",
|
|
81
|
-
"/index.mjs",
|
|
82
|
-
"/index.ts",
|
|
83
|
-
].map((e) => abs + e),
|
|
84
|
-
]) {
|
|
179
|
+
if (!rel.endsWith("/") && !rel.endsWith(path.sep)) {
|
|
85
180
|
try {
|
|
86
|
-
const canonical = fs.realpathSync(
|
|
181
|
+
const canonical = fs.realpathSync(abs);
|
|
87
182
|
if (!inside(root, canonical)) {
|
|
88
183
|
note(
|
|
89
184
|
"path-outside-package",
|
|
@@ -95,7 +190,7 @@ export function collectPackageSources(pkg, meta, options = {}) {
|
|
|
95
190
|
if (fs.statSync(canonical).isFile())
|
|
96
191
|
return portable(path.relative(root, canonical));
|
|
97
192
|
} catch {
|
|
98
|
-
/*
|
|
193
|
+
/* The caller decides whether a different resolution mode may try more. */
|
|
99
194
|
}
|
|
100
195
|
}
|
|
101
196
|
if (required)
|
|
@@ -106,24 +201,117 @@ export function collectPackageSources(pkg, meta, options = {}) {
|
|
|
106
201
|
);
|
|
107
202
|
return null;
|
|
108
203
|
};
|
|
109
|
-
const
|
|
204
|
+
const nodeFile = (rel) => {
|
|
205
|
+
if (rel.endsWith("/") || rel.endsWith(path.sep)) return null;
|
|
206
|
+
for (const suffix of ["", ".js", ".json", ".node"]) {
|
|
207
|
+
const found = resolveFile(rel + suffix);
|
|
208
|
+
if (found) return found;
|
|
209
|
+
}
|
|
210
|
+
return null;
|
|
211
|
+
};
|
|
212
|
+
const directoryIndex = (rel) => {
|
|
213
|
+
for (const suffix of [".js", ".json", ".node"]) {
|
|
214
|
+
const found = resolveFile(path.join(rel, "index" + suffix));
|
|
215
|
+
if (found) return found;
|
|
216
|
+
}
|
|
217
|
+
return null;
|
|
218
|
+
};
|
|
219
|
+
const unresolved = (rel) => {
|
|
220
|
+
note(
|
|
221
|
+
"source-unresolved",
|
|
222
|
+
portable(rel),
|
|
223
|
+
"Node entry or relative module cannot be resolved",
|
|
224
|
+
);
|
|
225
|
+
return null;
|
|
226
|
+
};
|
|
227
|
+
const resolveLegacyMain = (rel) =>
|
|
228
|
+
nodeFile(rel) ||
|
|
229
|
+
directoryIndex(rel) ||
|
|
230
|
+
directoryIndex(".") ||
|
|
231
|
+
unresolved(rel);
|
|
232
|
+
const resolveRequire = (rel) => {
|
|
233
|
+
checkTime();
|
|
234
|
+
const found = nodeFile(rel);
|
|
235
|
+
if (found) return found;
|
|
236
|
+
const abs = path.resolve(root, rel);
|
|
237
|
+
if (!inside(root, abs)) return resolveFile(rel, true);
|
|
238
|
+
let directory;
|
|
239
|
+
try {
|
|
240
|
+
directory = fs.realpathSync(abs);
|
|
241
|
+
if (!inside(root, directory)) return resolveFile(rel, true);
|
|
242
|
+
if (!fs.statSync(directory).isDirectory()) return null;
|
|
243
|
+
} catch {
|
|
244
|
+
return null;
|
|
245
|
+
}
|
|
246
|
+
let manifest;
|
|
247
|
+
try {
|
|
248
|
+
// A present but unsafe manifest is not equivalent to a missing one.
|
|
249
|
+
const candidate = path.join(rel, "package.json");
|
|
250
|
+
fs.lstatSync(path.join(root, candidate));
|
|
251
|
+
manifest = portable(candidate);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (!["ENOENT", "ENOTDIR"].includes(error.code)) {
|
|
254
|
+
note(
|
|
255
|
+
"source-unreadable",
|
|
256
|
+
portable(rel),
|
|
257
|
+
"CommonJS package metadata cannot be inspected",
|
|
258
|
+
);
|
|
259
|
+
return null;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
if (manifest) {
|
|
263
|
+
const text = read(manifest, 65536);
|
|
264
|
+
if (text === null) return null;
|
|
265
|
+
try {
|
|
266
|
+
const metadata = JSON.parse(text);
|
|
267
|
+
if (
|
|
268
|
+
!metadata ||
|
|
269
|
+
typeof metadata !== "object" ||
|
|
270
|
+
Array.isArray(metadata)
|
|
271
|
+
)
|
|
272
|
+
throw new Error("Invalid CommonJS package object");
|
|
273
|
+
if (typeof metadata?.main === "string" && metadata.main) {
|
|
274
|
+
const main = path.join(rel, metadata.main);
|
|
275
|
+
// Node LOAD_AS_DIRECTORY tries main as file, then main/index;
|
|
276
|
+
// it does not recursively read another directory's package.json.
|
|
277
|
+
const resolved = nodeFile(main) || directoryIndex(main);
|
|
278
|
+
if (resolved) return resolved;
|
|
279
|
+
}
|
|
280
|
+
} catch {
|
|
281
|
+
note(
|
|
282
|
+
"entry-resolution-uncertain",
|
|
283
|
+
manifest,
|
|
284
|
+
"Invalid CommonJS package metadata",
|
|
285
|
+
);
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
return directoryIndex(rel);
|
|
290
|
+
};
|
|
291
|
+
const read = (rel, fileLimit = limits.fileBytes) => {
|
|
110
292
|
if (texts.has(rel)) return texts.get(rel);
|
|
111
293
|
if (expired() || texts.size >= limits.files) {
|
|
112
294
|
note("scan-budget-exceeded", rel, "File/time limit reached");
|
|
113
295
|
return null;
|
|
114
296
|
}
|
|
115
297
|
try {
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
const text =
|
|
122
|
-
bytes +=
|
|
298
|
+
const data = readStaticFile(
|
|
299
|
+
path.join(root, rel),
|
|
300
|
+
Math.min(fileLimit, limits.fileBytes, limits.bytes - bytes),
|
|
301
|
+
checkTime,
|
|
302
|
+
);
|
|
303
|
+
const text = data.toString("utf8");
|
|
304
|
+
bytes += data.length;
|
|
123
305
|
texts.set(rel, text);
|
|
124
306
|
return text;
|
|
125
307
|
} catch (error) {
|
|
126
|
-
note(
|
|
308
|
+
note(
|
|
309
|
+
error.code === "STATIC_INPUT_BUDGET"
|
|
310
|
+
? "scan-budget-exceeded"
|
|
311
|
+
: "source-unreadable",
|
|
312
|
+
rel,
|
|
313
|
+
error.message,
|
|
314
|
+
);
|
|
127
315
|
return null;
|
|
128
316
|
}
|
|
129
317
|
};
|
|
@@ -136,18 +324,50 @@ export function collectPackageSources(pkg, meta, options = {}) {
|
|
|
136
324
|
break;
|
|
137
325
|
}
|
|
138
326
|
const rel = resolveFile(queue.pop(), true);
|
|
139
|
-
if (!rel || seen.has(rel)
|
|
327
|
+
if (!rel || seen.has(rel)) continue;
|
|
328
|
+
if (!sourceFile(rel)) {
|
|
329
|
+
note(
|
|
330
|
+
"source-format-uncertain",
|
|
331
|
+
rel,
|
|
332
|
+
"Resolved module is not supported JavaScript/TypeScript source",
|
|
333
|
+
);
|
|
334
|
+
continue;
|
|
335
|
+
}
|
|
140
336
|
seen.add(rel);
|
|
141
337
|
const text = read(rel);
|
|
142
338
|
if (text === null) continue;
|
|
143
|
-
|
|
339
|
+
let parsed;
|
|
340
|
+
try {
|
|
341
|
+
parsed = localImports(text, rel, checkTime);
|
|
342
|
+
} catch (error) {
|
|
343
|
+
if (error.code !== "STATIC_INPUT_BUDGET") throw error;
|
|
344
|
+
note("scan-budget-exceeded", rel, error.message);
|
|
345
|
+
break;
|
|
346
|
+
}
|
|
347
|
+
for (const diagnostic of parsed.diagnostics)
|
|
348
|
+
note(diagnostic.kind, rel, diagnostic.message);
|
|
144
349
|
if (parsed.error) note("parse-error", rel, parsed.error.message);
|
|
145
|
-
for (const spec of parsed.
|
|
350
|
+
for (const { source: spec, kind, start } of parsed.edges) {
|
|
146
351
|
if (!moduleLinks.has(rel)) moduleLinks.set(rel, new Map());
|
|
352
|
+
const local = path.join(path.dirname(rel), spec);
|
|
353
|
+
let target;
|
|
354
|
+
try {
|
|
355
|
+
if (kind === "require")
|
|
356
|
+
target = resolveRequire(local) || unresolved(local);
|
|
357
|
+
else if (/[%?#\\]/.test(spec)) target = unresolved(local);
|
|
358
|
+
else target = resolveFile(local, true);
|
|
359
|
+
} catch (error) {
|
|
360
|
+
if (error.code !== "STATIC_INPUT_BUDGET") throw error;
|
|
361
|
+
note("scan-budget-exceeded", rel, error.message);
|
|
362
|
+
break;
|
|
363
|
+
}
|
|
364
|
+
// The same spelling may resolve for require but fail for import.
|
|
147
365
|
moduleLinks
|
|
148
366
|
.get(rel)
|
|
149
|
-
.set(spec
|
|
150
|
-
|
|
367
|
+
.set(kind === "require" ? "require:" + spec : spec, target);
|
|
368
|
+
if (kind === "require")
|
|
369
|
+
moduleLinks.get(rel).set("require@" + start, target);
|
|
370
|
+
if (target) queue.push(target);
|
|
151
371
|
}
|
|
152
372
|
}
|
|
153
373
|
return seen;
|
|
@@ -156,6 +376,7 @@ export function collectPackageSources(pkg, meta, options = {}) {
|
|
|
156
376
|
const out = [],
|
|
157
377
|
queue = [root],
|
|
158
378
|
visited = new Set();
|
|
379
|
+
let entriesSeen = 0;
|
|
159
380
|
while (queue.length) {
|
|
160
381
|
if (
|
|
161
382
|
expired() ||
|
|
@@ -184,19 +405,33 @@ export function collectPackageSources(pkg, meta, options = {}) {
|
|
|
184
405
|
visited.add(real);
|
|
185
406
|
let entries;
|
|
186
407
|
try {
|
|
187
|
-
entries =
|
|
408
|
+
entries = readStaticDirectory(
|
|
409
|
+
real,
|
|
410
|
+
limits.entries - entriesSeen,
|
|
411
|
+
checkTime,
|
|
412
|
+
);
|
|
413
|
+
entriesSeen += entries.length;
|
|
188
414
|
} catch (error) {
|
|
189
415
|
note(
|
|
190
|
-
"
|
|
416
|
+
error.code === "STATIC_INPUT_BUDGET"
|
|
417
|
+
? "scan-budget-exceeded"
|
|
418
|
+
: "source-unreadable",
|
|
191
419
|
portable(path.relative(root, real)),
|
|
192
420
|
error.message,
|
|
193
421
|
);
|
|
422
|
+
if (error.code === "STATIC_INPUT_BUDGET") break;
|
|
194
423
|
continue;
|
|
195
424
|
}
|
|
196
|
-
for (const entry of entries
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
425
|
+
for (const entry of entries) {
|
|
426
|
+
if (expired()) {
|
|
427
|
+
note(
|
|
428
|
+
"scan-budget-exceeded",
|
|
429
|
+
null,
|
|
430
|
+
"Directory walk time limit reached",
|
|
431
|
+
);
|
|
432
|
+
return out;
|
|
433
|
+
}
|
|
434
|
+
const file = path.join(real, entry);
|
|
200
435
|
try {
|
|
201
436
|
const resolved = fs.realpathSync(file);
|
|
202
437
|
if (!inside(root, resolved)) {
|
|
@@ -218,20 +453,63 @@ export function collectPackageSources(pkg, meta, options = {}) {
|
|
|
218
453
|
}
|
|
219
454
|
return [...new Set(out)].sort();
|
|
220
455
|
};
|
|
221
|
-
const mounted = [];
|
|
222
456
|
const patch = meta.dsh?.bundle?.patch;
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
457
|
+
let hostSeeds,
|
|
458
|
+
patchComplete = false;
|
|
459
|
+
if (patch !== undefined) {
|
|
460
|
+
const patchFiles = typeof patch === "string" ? [patch] : patch;
|
|
461
|
+
if (
|
|
462
|
+
!Array.isArray(patchFiles) ||
|
|
463
|
+
!patchFiles.every((file) => typeof file === "string")
|
|
464
|
+
) {
|
|
465
|
+
note(
|
|
466
|
+
"patch-unreadable",
|
|
467
|
+
"package.json",
|
|
468
|
+
"Patch declaration must be a path or path list",
|
|
469
|
+
);
|
|
470
|
+
hostSeeds = [];
|
|
471
|
+
} else {
|
|
472
|
+
const result = patchMounts(patchFiles, {
|
|
473
|
+
root,
|
|
474
|
+
limits,
|
|
475
|
+
check: checkTime,
|
|
476
|
+
note,
|
|
477
|
+
read(file) {
|
|
478
|
+
const rel = resolveFile(file, true);
|
|
479
|
+
if (!rel) throw Error("Declared patch is missing or outside package");
|
|
480
|
+
const data = readStaticFile(
|
|
481
|
+
path.join(root, rel),
|
|
482
|
+
Math.min(limits.fileBytes, limits.yamlBytes, limits.bytes - bytes),
|
|
483
|
+
checkTime,
|
|
484
|
+
);
|
|
485
|
+
bytes += data.length;
|
|
486
|
+
return data.toString("utf8");
|
|
487
|
+
},
|
|
488
|
+
});
|
|
489
|
+
patchComplete = result.complete;
|
|
490
|
+
hostSeeds = result.mounts.map((name) => {
|
|
491
|
+
if (name.startsWith(".") || path.isAbsolute(name))
|
|
492
|
+
return resolveFile(name, true);
|
|
493
|
+
if (name === meta.name) return hostEntryFor();
|
|
494
|
+
if (typeof meta.name === "string" && name.startsWith(meta.name + "/")) {
|
|
495
|
+
const subpath = name.slice(meta.name.length + 1);
|
|
496
|
+
if (meta.exports == null) return resolveFile(subpath, true);
|
|
497
|
+
const target = hostEntry(
|
|
498
|
+
{ exports: { ".": meta.exports?.["./" + subpath] } },
|
|
499
|
+
(message) =>
|
|
500
|
+
note("entry-resolution-uncertain", "package.json", message),
|
|
501
|
+
);
|
|
502
|
+
return target ? resolveFile(target, true) : null;
|
|
503
|
+
}
|
|
504
|
+
note(
|
|
505
|
+
"patch-resolution-uncertain",
|
|
506
|
+
name,
|
|
507
|
+
"Mount belongs to another package; cannot use this package root",
|
|
508
|
+
);
|
|
509
|
+
return null;
|
|
510
|
+
});
|
|
511
|
+
}
|
|
512
|
+
} else hostSeeds = [hostEntryFor()];
|
|
235
513
|
const entryFiles = [
|
|
236
514
|
...new Set(
|
|
237
515
|
[...hostSeeds, clientEntry(meta)]
|
|
@@ -241,7 +519,7 @@ export function collectPackageSources(pkg, meta, options = {}) {
|
|
|
241
519
|
),
|
|
242
520
|
];
|
|
243
521
|
const host = graph(hostSeeds);
|
|
244
|
-
const fallback = host.size === 0;
|
|
522
|
+
const fallback = host.size === 0 && (!patchComplete || hostSeeds.length > 0);
|
|
245
523
|
const all = walk();
|
|
246
524
|
const add = (rel, bucket, side) => {
|
|
247
525
|
const text = read(rel);
|
|
@@ -256,7 +534,7 @@ export function collectPackageSources(pkg, meta, options = {}) {
|
|
|
256
534
|
if (fallback) {
|
|
257
535
|
note(
|
|
258
536
|
"entry-fallback",
|
|
259
|
-
|
|
537
|
+
hostTarget ?? null,
|
|
260
538
|
"No resolved host entry; candidate source coverage is uncertain",
|
|
261
539
|
);
|
|
262
540
|
for (const rel of all)
|