@patchstack/connect 0.3.29 → 0.3.31
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/AGENT-INSTALL.md +42 -23
- package/README.md +40 -11
- package/dist/{chunk-MJOTFUDE.js → chunk-LLKP5EJS.js} +1 -1
- package/dist/chunk-LLKP5EJS.js.map +1 -0
- package/dist/cli.js +1265 -287
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +456 -111
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -10
- package/dist/index.d.ts +76 -10
- package/dist/index.js +449 -104
- package/dist/index.js.map +1 -1
- package/dist/protect/templates/astro-middleware.ts +33 -13
- package/dist/protect/templates/demo-rules.json +2 -3
- package/dist/protect/templates/express-guard.cjs +26 -15
- package/dist/protect/templates/express-guard.js +26 -15
- package/dist/protect/templates/express-guard.ts +33 -16
- package/dist/protect/templates/fastify-plugin.cjs +33 -11
- package/dist/protect/templates/fastify-plugin.js +33 -11
- package/dist/protect/templates/fastify-plugin.ts +40 -12
- package/dist/protect/templates/generic-guard.cjs +28 -12
- package/dist/protect/templates/generic-guard.js +28 -13
- package/dist/protect/templates/generic-guard.ts +41 -20
- package/dist/protect/templates/guard.ts +47 -27
- package/dist/protect/templates/next-middleware.ts +29 -12
- package/dist/protect/templates/nuxt-middleware.ts +29 -12
- package/dist/protect/templates/rules.json +2 -2
- package/dist/protect/templates/sveltekit-hooks.ts +33 -13
- package/dist/protect.cjs +1204 -252
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.ts +39 -11
- package/dist/protect.edge.js +792 -143
- package/dist/protect.edge.js.map +4 -4
- package/dist/protect.js +794 -145
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-VRBE6RH6.js → refresh-manifest-KNZQBC4V.js} +387 -100
- package/dist/refresh-manifest-KNZQBC4V.js.map +1 -0
- package/package.json +5 -2
- package/dist/chunk-MJOTFUDE.js.map +0 -1
- package/dist/refresh-manifest-VRBE6RH6.js.map +0 -1
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
pulseFetch
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LLKP5EJS.js";
|
|
4
4
|
|
|
5
5
|
// src/config.ts
|
|
6
|
-
import { readFile, writeFile } from "fs/promises";
|
|
6
|
+
import { readFile, writeFile, chmod } from "fs/promises";
|
|
7
7
|
import path from "path";
|
|
8
8
|
|
|
9
9
|
// src/types.ts
|
|
@@ -110,9 +110,11 @@ function isTimeoutError(cause) {
|
|
|
110
110
|
|
|
111
111
|
// src/config.ts
|
|
112
112
|
var CONFIG_FILENAME = ".patchstackrc.json";
|
|
113
|
+
var SECRET_FILENAME = ".patchstackrc.local.json";
|
|
113
114
|
var DEFAULT_ENVIRONMENT = "production";
|
|
114
115
|
async function resolveConfig(options) {
|
|
115
116
|
const fromFile = await readConfigFile(options.cwd);
|
|
117
|
+
const fromSecretFile = await readSecretFile(options.cwd);
|
|
116
118
|
const fromEnv = readEnv();
|
|
117
119
|
const siteUuid = options.cliSiteUuid ?? fromEnv.siteUuid ?? fromFile.siteUuid ?? null;
|
|
118
120
|
const endpoint = options.cliEndpoint ?? fromEnv.endpoint ?? fromFile.endpoint ?? DEFAULT_ENDPOINT;
|
|
@@ -137,8 +139,8 @@ async function resolveConfig(options) {
|
|
|
137
139
|
"CONFIG_MISSING"
|
|
138
140
|
);
|
|
139
141
|
}
|
|
140
|
-
const apiKeyRaw = fromEnv.apiKey ?? fromFile.apiKey ?? null;
|
|
141
|
-
const pulseAuthRaw = fromEnv.pulseAuth ?? fromFile.pulseAuth ?? apiKeyRaw;
|
|
142
|
+
const apiKeyRaw = fromEnv.apiKey ?? fromSecretFile.apiKey ?? fromFile.apiKey ?? null;
|
|
143
|
+
const pulseAuthRaw = fromEnv.pulseAuth ?? fromSecretFile.pulseAuth ?? fromFile.pulseAuth ?? apiKeyRaw;
|
|
142
144
|
return {
|
|
143
145
|
siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,
|
|
144
146
|
apiKey: apiKeyRaw === null || apiKeyRaw.length === 0 ? null : apiKeyRaw,
|
|
@@ -149,8 +151,15 @@ async function resolveConfig(options) {
|
|
|
149
151
|
widget: fromFile.widget !== false
|
|
150
152
|
};
|
|
151
153
|
}
|
|
154
|
+
async function readSecretFile(cwd) {
|
|
155
|
+
const file = await readJsonFile(cwd, SECRET_FILENAME);
|
|
156
|
+
return { apiKey: file.apiKey, pulseAuth: file.pulseAuth };
|
|
157
|
+
}
|
|
152
158
|
async function readConfigFile(cwd) {
|
|
153
|
-
|
|
159
|
+
return readJsonFile(cwd, CONFIG_FILENAME);
|
|
160
|
+
}
|
|
161
|
+
async function readJsonFile(cwd, filename) {
|
|
162
|
+
const target = path.join(cwd, filename);
|
|
154
163
|
let raw;
|
|
155
164
|
try {
|
|
156
165
|
raw = await readFile(target, "utf8");
|
|
@@ -228,7 +237,7 @@ async function readDeclaredDependencyNames(cwd) {
|
|
|
228
237
|
return [];
|
|
229
238
|
}
|
|
230
239
|
const names = /* @__PURE__ */ new Set();
|
|
231
|
-
for (const group of ["dependencies", "devDependencies"]) {
|
|
240
|
+
for (const group of ["dependencies", "devDependencies", "optionalDependencies"]) {
|
|
232
241
|
const deps = parsed[group];
|
|
233
242
|
if (typeof deps !== "object" || deps === null) {
|
|
234
243
|
continue;
|
|
@@ -246,14 +255,171 @@ function missingDependencies(declared, packages) {
|
|
|
246
255
|
const present = new Set(packages.map((entry) => entry.name));
|
|
247
256
|
return declared.filter((name) => !present.has(name));
|
|
248
257
|
}
|
|
258
|
+
function disagreements(packages, other) {
|
|
259
|
+
const otherVersions = /* @__PURE__ */ new Map();
|
|
260
|
+
for (const entry of other) {
|
|
261
|
+
if (!otherVersions.has(entry.name)) otherVersions.set(entry.name, entry.version);
|
|
262
|
+
}
|
|
263
|
+
const seen = /* @__PURE__ */ new Set();
|
|
264
|
+
const conflicts = [];
|
|
265
|
+
for (const entry of packages) {
|
|
266
|
+
if (seen.has(entry.name)) continue;
|
|
267
|
+
seen.add(entry.name);
|
|
268
|
+
const otherVersion = otherVersions.get(entry.name);
|
|
269
|
+
if (otherVersion !== void 0 && otherVersion !== entry.version) {
|
|
270
|
+
conflicts.push({ name: entry.name, version: entry.version, otherVersion });
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
return conflicts;
|
|
274
|
+
}
|
|
249
275
|
|
|
250
|
-
// src/parsers/
|
|
276
|
+
// src/parsers/bun.ts
|
|
251
277
|
import { readFile as readFile3 } from "fs/promises";
|
|
278
|
+
|
|
279
|
+
// src/parsers/report.ts
|
|
280
|
+
var MAX_SAMPLES = 3;
|
|
281
|
+
function newReport() {
|
|
282
|
+
return { unreadable: 0, samples: [] };
|
|
283
|
+
}
|
|
284
|
+
function recordUnreadable(report, sample) {
|
|
285
|
+
if (!report) return;
|
|
286
|
+
report.unreadable++;
|
|
287
|
+
if (report.samples.length < MAX_SAMPLES) report.samples.push(sample);
|
|
288
|
+
}
|
|
289
|
+
function unreadableWarning(filename, report) {
|
|
290
|
+
if (report.unreadable === 0) return null;
|
|
291
|
+
const shown = report.samples.join(", ");
|
|
292
|
+
const one = report.unreadable === 1;
|
|
293
|
+
return `${filename}: ${report.unreadable} entr${one ? "y was" : "ies were"} not in a form this scanner reads (${shown}${report.unreadable > report.samples.length ? ", \u2026" : ""}), so ${one ? "it is" : "they are"} absent from this manifest. Anything installed only through ${one ? "that entry" : "those entries"} is not being checked.`;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// src/parsers/bun.ts
|
|
297
|
+
async function parseBunLockfile(lockfilePath, report) {
|
|
298
|
+
let raw;
|
|
299
|
+
try {
|
|
300
|
+
raw = await readFile3(lockfilePath, "utf8");
|
|
301
|
+
} catch (cause) {
|
|
302
|
+
throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, "LOCKFILE_NOT_FOUND", cause);
|
|
303
|
+
}
|
|
304
|
+
let parsed;
|
|
305
|
+
try {
|
|
306
|
+
parsed = JSON.parse(stripTrailingCommas(raw));
|
|
307
|
+
} catch (cause) {
|
|
308
|
+
throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, "LOCKFILE_PARSE_ERROR", cause);
|
|
309
|
+
}
|
|
310
|
+
const packages = parsed?.packages;
|
|
311
|
+
if (typeof packages !== "object" || packages === null) {
|
|
312
|
+
throw new PatchstackError(`Lockfile at ${lockfilePath} has no "packages" entries`, "LOCKFILE_PARSE_ERROR");
|
|
313
|
+
}
|
|
314
|
+
const direct = directDependencyNames(parsed);
|
|
315
|
+
const entries = [];
|
|
316
|
+
let skipped = 0;
|
|
317
|
+
for (const [key, value] of Object.entries(packages)) {
|
|
318
|
+
const descriptor = Array.isArray(value) ? value[0] : value;
|
|
319
|
+
if (typeof descriptor !== "string") {
|
|
320
|
+
skipped++;
|
|
321
|
+
recordUnreadable(report, key);
|
|
322
|
+
continue;
|
|
323
|
+
}
|
|
324
|
+
const split = splitDescriptor(descriptor);
|
|
325
|
+
if (split === null) {
|
|
326
|
+
skipped++;
|
|
327
|
+
if (!isDeliberatelyExcluded(descriptor)) recordUnreadable(report, key);
|
|
328
|
+
continue;
|
|
329
|
+
}
|
|
330
|
+
entries.push({
|
|
331
|
+
name: split.name,
|
|
332
|
+
version: split.version,
|
|
333
|
+
...direct.has(split.name) ? { direct: true } : {},
|
|
334
|
+
...key === split.name ? {} : { path: key }
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
if (entries.length === 0) {
|
|
338
|
+
throw new PatchstackError(
|
|
339
|
+
`Lockfile at ${lockfilePath} lists no registry packages${skipped > 0 ? ` (${skipped} non-registry entr${skipped === 1 ? "y" : "ies"} skipped)` : ""}`,
|
|
340
|
+
"LOCKFILE_PARSE_ERROR"
|
|
341
|
+
);
|
|
342
|
+
}
|
|
343
|
+
return entries;
|
|
344
|
+
}
|
|
345
|
+
function splitDescriptor(descriptor) {
|
|
346
|
+
const at = descriptor.lastIndexOf("@");
|
|
347
|
+
if (at <= 0) return null;
|
|
348
|
+
const name = descriptor.slice(0, at);
|
|
349
|
+
const version = descriptor.slice(at + 1);
|
|
350
|
+
if (name === "" || version === "") return null;
|
|
351
|
+
if (NON_REGISTRY_VERSION.test(version)) return null;
|
|
352
|
+
return { name, version };
|
|
353
|
+
}
|
|
354
|
+
var NON_REGISTRY_VERSION = /^[a-z][a-z0-9+.-]*:|\//i;
|
|
355
|
+
function isDeliberatelyExcluded(descriptor) {
|
|
356
|
+
const at = descriptor.lastIndexOf("@");
|
|
357
|
+
if (at <= 0) return false;
|
|
358
|
+
return NON_REGISTRY_VERSION.test(descriptor.slice(at + 1));
|
|
359
|
+
}
|
|
360
|
+
function directDependencyNames(parsed) {
|
|
361
|
+
const names = /* @__PURE__ */ new Set();
|
|
362
|
+
const workspaces = parsed?.workspaces;
|
|
363
|
+
if (typeof workspaces !== "object" || workspaces === null) return names;
|
|
364
|
+
for (const workspace of Object.values(workspaces)) {
|
|
365
|
+
if (typeof workspace !== "object" || workspace === null) continue;
|
|
366
|
+
for (const group of ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]) {
|
|
367
|
+
const deps = workspace[group];
|
|
368
|
+
if (typeof deps !== "object" || deps === null) continue;
|
|
369
|
+
for (const name of Object.keys(deps)) names.add(name);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
return names;
|
|
373
|
+
}
|
|
374
|
+
function stripTrailingCommas(source) {
|
|
375
|
+
const out = [];
|
|
376
|
+
let inString = false;
|
|
377
|
+
let pendingComma = -1;
|
|
378
|
+
for (let i = 0; i < source.length; i++) {
|
|
379
|
+
const ch = source[i];
|
|
380
|
+
if (inString) {
|
|
381
|
+
out.push(ch);
|
|
382
|
+
if (ch === "\\") {
|
|
383
|
+
const next = source[i + 1];
|
|
384
|
+
if (next !== void 0) {
|
|
385
|
+
out.push(next);
|
|
386
|
+
i++;
|
|
387
|
+
}
|
|
388
|
+
} else if (ch === '"') {
|
|
389
|
+
inString = false;
|
|
390
|
+
}
|
|
391
|
+
continue;
|
|
392
|
+
}
|
|
393
|
+
if (ch === '"') {
|
|
394
|
+
inString = true;
|
|
395
|
+
pendingComma = -1;
|
|
396
|
+
out.push(ch);
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
if (ch === ",") {
|
|
400
|
+
pendingComma = out.length;
|
|
401
|
+
out.push(ch);
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
if (ch === "}" || ch === "]") {
|
|
405
|
+
if (pendingComma !== -1) out[pendingComma] = " ";
|
|
406
|
+
pendingComma = -1;
|
|
407
|
+
out.push(ch);
|
|
408
|
+
continue;
|
|
409
|
+
}
|
|
410
|
+
if (!/\s/.test(ch)) pendingComma = -1;
|
|
411
|
+
out.push(ch);
|
|
412
|
+
}
|
|
413
|
+
return out.join("");
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// src/parsers/npm.ts
|
|
417
|
+
import { readFile as readFile4 } from "fs/promises";
|
|
252
418
|
import path3 from "path";
|
|
253
419
|
async function parseNpmLockfile(lockfilePath) {
|
|
254
420
|
let raw;
|
|
255
421
|
try {
|
|
256
|
-
raw = await
|
|
422
|
+
raw = await readFile4(lockfilePath, "utf8");
|
|
257
423
|
} catch (cause) {
|
|
258
424
|
throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, "LOCKFILE_NOT_FOUND", cause);
|
|
259
425
|
}
|
|
@@ -334,8 +500,9 @@ function isDirectV2(pkgPath) {
|
|
|
334
500
|
}
|
|
335
501
|
|
|
336
502
|
// src/parsers/node_modules.ts
|
|
337
|
-
import { lstat, readFile as
|
|
503
|
+
import { lstat, readFile as readFile5, readdir, realpath, stat } from "fs/promises";
|
|
338
504
|
import path4 from "path";
|
|
505
|
+
var PACKAGE_STORE_DIRS = /* @__PURE__ */ new Set([".bun", ".pnpm", ".store"]);
|
|
339
506
|
async function walkNodeModules(cwd) {
|
|
340
507
|
const root = path4.join(cwd, "node_modules");
|
|
341
508
|
try {
|
|
@@ -356,96 +523,125 @@ async function walkNodeModules(cwd) {
|
|
|
356
523
|
cause
|
|
357
524
|
);
|
|
358
525
|
}
|
|
359
|
-
const
|
|
360
|
-
|
|
361
|
-
|
|
526
|
+
const seen = /* @__PURE__ */ new Map();
|
|
527
|
+
try {
|
|
528
|
+
seen.set(await realpath(cwd), -1);
|
|
529
|
+
} catch {
|
|
530
|
+
}
|
|
531
|
+
return dedupeByShallowest(await walk(root, 0, seen, cwd));
|
|
532
|
+
}
|
|
533
|
+
function dedupeByShallowest(found) {
|
|
534
|
+
const best = /* @__PURE__ */ new Map();
|
|
535
|
+
for (const item of found) {
|
|
536
|
+
const current = best.get(item.realpath);
|
|
537
|
+
if (current === void 0 || item.depth < current.depth) best.set(item.realpath, item);
|
|
538
|
+
}
|
|
539
|
+
return [...best.values()].map(({ entry }) => entry);
|
|
362
540
|
}
|
|
363
|
-
|
|
541
|
+
var CONCURRENCY = 32;
|
|
542
|
+
async function walk(dir, depth, seen, root) {
|
|
364
543
|
let names;
|
|
365
544
|
try {
|
|
366
545
|
names = await readdir(dir);
|
|
367
546
|
} catch {
|
|
368
|
-
return;
|
|
547
|
+
return [];
|
|
369
548
|
}
|
|
370
|
-
|
|
371
|
-
if (name.startsWith("."))
|
|
372
|
-
continue;
|
|
373
|
-
}
|
|
549
|
+
const perEntry = await mapLimit(names, async (name) => {
|
|
550
|
+
if (name.startsWith(".") && !PACKAGE_STORE_DIRS.has(name)) return [];
|
|
374
551
|
const fullPath = path4.join(dir, name);
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
552
|
+
const resolved = await claim(fullPath, seen, depth);
|
|
553
|
+
if (resolved === null) return [];
|
|
554
|
+
if (PACKAGE_STORE_DIRS.has(name)) return walk(fullPath, depth, seen, root);
|
|
378
555
|
if (name.startsWith("@")) {
|
|
379
556
|
let subNames;
|
|
380
557
|
try {
|
|
381
558
|
subNames = await readdir(fullPath);
|
|
382
559
|
} catch {
|
|
383
|
-
|
|
560
|
+
return [];
|
|
384
561
|
}
|
|
385
|
-
|
|
386
|
-
if (sub.startsWith("."))
|
|
387
|
-
continue;
|
|
388
|
-
}
|
|
562
|
+
const scoped = await mapLimit(subNames, async (sub) => {
|
|
563
|
+
if (sub.startsWith(".")) return [];
|
|
389
564
|
const scopedDir = path4.join(fullPath, sub);
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
}
|
|
396
|
-
continue;
|
|
565
|
+
const scopedReal = await claim(scopedDir, seen, depth);
|
|
566
|
+
if (scopedReal === null) return [];
|
|
567
|
+
return packageAndNested(scopedDir, scopedReal, depth, seen, root);
|
|
568
|
+
});
|
|
569
|
+
return scoped.flat();
|
|
397
570
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
571
|
+
return packageAndNested(fullPath, resolved, depth, seen, root);
|
|
572
|
+
});
|
|
573
|
+
return perEntry.flat();
|
|
401
574
|
}
|
|
402
|
-
async function
|
|
575
|
+
async function packageAndNested(pkgDir, resolved, depth, seen, root) {
|
|
576
|
+
const [own, nested] = await Promise.all([readPackage(pkgDir, depth, root), walkNested(pkgDir, depth, seen, root)]);
|
|
577
|
+
return own === null ? nested : [{ entry: own, realpath: resolved, depth }, ...nested];
|
|
578
|
+
}
|
|
579
|
+
async function mapLimit(items, fn) {
|
|
580
|
+
const results = new Array(items.length);
|
|
581
|
+
let next = 0;
|
|
582
|
+
const workers = Array.from({ length: Math.min(CONCURRENCY, items.length) }, async () => {
|
|
583
|
+
while (next < items.length) {
|
|
584
|
+
const index = next++;
|
|
585
|
+
results[index] = await fn(items[index]);
|
|
586
|
+
}
|
|
587
|
+
});
|
|
588
|
+
await Promise.all(workers);
|
|
589
|
+
return results;
|
|
590
|
+
}
|
|
591
|
+
function installLocation(root, pkgDir, impl = path4) {
|
|
592
|
+
return impl.relative(root, pkgDir).split(impl.sep).join("/");
|
|
593
|
+
}
|
|
594
|
+
async function readPackage(pkgDir, depth, root) {
|
|
403
595
|
let raw;
|
|
404
596
|
try {
|
|
405
|
-
raw = await
|
|
597
|
+
raw = await readFile5(path4.join(pkgDir, "package.json"), "utf8");
|
|
406
598
|
} catch {
|
|
407
|
-
return;
|
|
599
|
+
return null;
|
|
408
600
|
}
|
|
409
601
|
let parsed;
|
|
410
602
|
try {
|
|
411
603
|
parsed = JSON.parse(raw);
|
|
412
604
|
} catch {
|
|
413
|
-
return;
|
|
605
|
+
return null;
|
|
414
606
|
}
|
|
415
607
|
if (typeof parsed.name !== "string" || parsed.name.length === 0) {
|
|
416
|
-
return;
|
|
608
|
+
return null;
|
|
417
609
|
}
|
|
418
610
|
if (typeof parsed.version !== "string" || parsed.version.length === 0) {
|
|
419
|
-
return;
|
|
611
|
+
return null;
|
|
420
612
|
}
|
|
421
|
-
|
|
422
|
-
name: parsed.name,
|
|
423
|
-
version: parsed.version,
|
|
424
|
-
direct: depth === 0
|
|
425
|
-
});
|
|
613
|
+
return { name: parsed.name, version: parsed.version, path: installLocation(root, pkgDir), direct: depth === 0 };
|
|
426
614
|
}
|
|
427
|
-
async function walkNested(pkgDir,
|
|
615
|
+
async function walkNested(pkgDir, depth, seen, root) {
|
|
428
616
|
const nested = path4.join(pkgDir, "node_modules");
|
|
429
|
-
if (
|
|
430
|
-
return;
|
|
617
|
+
if (await claim(nested, seen, depth + 1) === null) {
|
|
618
|
+
return [];
|
|
431
619
|
}
|
|
432
|
-
|
|
620
|
+
return walk(nested, depth + 1, seen, root);
|
|
433
621
|
}
|
|
434
|
-
async function
|
|
622
|
+
async function claim(dir, seen, depth) {
|
|
623
|
+
let resolved;
|
|
435
624
|
try {
|
|
436
625
|
const info = await lstat(dir);
|
|
437
|
-
|
|
626
|
+
if (!info.isDirectory() && !info.isSymbolicLink()) return null;
|
|
627
|
+
resolved = await realpath(dir);
|
|
628
|
+
const target = await stat(resolved);
|
|
629
|
+
if (!target.isDirectory()) return null;
|
|
438
630
|
} catch {
|
|
439
|
-
return
|
|
631
|
+
return null;
|
|
440
632
|
}
|
|
633
|
+
const claimed = seen.get(resolved);
|
|
634
|
+
if (claimed !== void 0 && claimed <= depth) return null;
|
|
635
|
+
seen.set(resolved, depth);
|
|
636
|
+
return resolved;
|
|
441
637
|
}
|
|
442
638
|
|
|
443
639
|
// src/parsers/pnpm.ts
|
|
444
|
-
import { readFile as
|
|
445
|
-
async function parsePnpmLockfile(lockfilePath) {
|
|
640
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
641
|
+
async function parsePnpmLockfile(lockfilePath, report) {
|
|
446
642
|
let raw;
|
|
447
643
|
try {
|
|
448
|
-
raw = await
|
|
644
|
+
raw = await readFile6(lockfilePath, "utf8");
|
|
449
645
|
} catch (cause) {
|
|
450
646
|
throw new PatchstackError(
|
|
451
647
|
`Could not read lockfile at ${lockfilePath}`,
|
|
@@ -466,6 +662,7 @@ async function parsePnpmLockfile(lockfilePath) {
|
|
|
466
662
|
for (const key of packageKeys) {
|
|
467
663
|
const parsed = parsePackageKey(key);
|
|
468
664
|
if (parsed === null) {
|
|
665
|
+
recordUnreadable(report, key);
|
|
469
666
|
continue;
|
|
470
667
|
}
|
|
471
668
|
entries.push({
|
|
@@ -693,12 +890,12 @@ function extractLeafName(trimmed) {
|
|
|
693
890
|
}
|
|
694
891
|
|
|
695
892
|
// src/parsers/yarn.ts
|
|
696
|
-
import { readFile as
|
|
893
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
697
894
|
import path5 from "path";
|
|
698
|
-
async function parseYarnLockfile(lockfilePath) {
|
|
895
|
+
async function parseYarnLockfile(lockfilePath, report) {
|
|
699
896
|
let raw;
|
|
700
897
|
try {
|
|
701
|
-
raw = await
|
|
898
|
+
raw = await readFile7(lockfilePath, "utf8");
|
|
702
899
|
} catch (cause) {
|
|
703
900
|
throw new PatchstackError(
|
|
704
901
|
`Could not read lockfile at ${lockfilePath}`,
|
|
@@ -706,7 +903,7 @@ async function parseYarnLockfile(lockfilePath) {
|
|
|
706
903
|
cause
|
|
707
904
|
);
|
|
708
905
|
}
|
|
709
|
-
const blocks = parseBlocks(raw);
|
|
906
|
+
const blocks = parseBlocks(raw, report);
|
|
710
907
|
if (blocks.length === 0) {
|
|
711
908
|
throw new PatchstackError(
|
|
712
909
|
`Lockfile at ${lockfilePath} contains no package entries`,
|
|
@@ -735,13 +932,19 @@ async function parseYarnLockfile(lockfilePath) {
|
|
|
735
932
|
}
|
|
736
933
|
return entries;
|
|
737
934
|
}
|
|
738
|
-
|
|
935
|
+
var NON_PACKAGE_KEYS = /* @__PURE__ */ new Set(["__metadata"]);
|
|
936
|
+
function parseBlocks(raw, report) {
|
|
739
937
|
const lines = raw.split(/\r?\n/);
|
|
740
938
|
const blocks = [];
|
|
741
939
|
let current = null;
|
|
742
940
|
const finalize = () => {
|
|
743
|
-
if (current
|
|
941
|
+
if (current === null) {
|
|
942
|
+
return;
|
|
943
|
+
}
|
|
944
|
+
if (current.version.length > 0 && current.names.size > 0) {
|
|
744
945
|
blocks.push(current);
|
|
946
|
+
} else if (current.unreadable) {
|
|
947
|
+
recordUnreadable(report, current.key);
|
|
745
948
|
}
|
|
746
949
|
current = null;
|
|
747
950
|
};
|
|
@@ -758,13 +961,13 @@ function parseBlocks(raw) {
|
|
|
758
961
|
}
|
|
759
962
|
const keyLine = trimmed.slice(0, -1);
|
|
760
963
|
const names = /* @__PURE__ */ new Set();
|
|
964
|
+
let unreadable = false;
|
|
761
965
|
for (const spec of splitDescriptors(keyLine)) {
|
|
762
|
-
const
|
|
763
|
-
if (name !==
|
|
764
|
-
|
|
765
|
-
}
|
|
966
|
+
const classified = classifyDescriptor(spec);
|
|
967
|
+
if (classified.name !== void 0) names.add(classified.name);
|
|
968
|
+
else if (!classified.excluded) unreadable = true;
|
|
766
969
|
}
|
|
767
|
-
current = { names, version: "" };
|
|
970
|
+
current = { names, version: "", key: keyLine, unreadable: unreadable && names.size === 0 };
|
|
768
971
|
continue;
|
|
769
972
|
}
|
|
770
973
|
if (current === null) {
|
|
@@ -819,20 +1022,37 @@ function splitDescriptors(keyLine) {
|
|
|
819
1022
|
}
|
|
820
1023
|
return parts;
|
|
821
1024
|
}
|
|
822
|
-
|
|
1025
|
+
var NON_REGISTRY_PROTOCOLS = /^(?:workspace|patch|virtual|portal|file|link|exec|git|github|https?|ssh):/i;
|
|
1026
|
+
function classifyDescriptor(rawSpec) {
|
|
823
1027
|
let s = rawSpec.trim();
|
|
824
1028
|
if (s.length === 0) {
|
|
825
|
-
return
|
|
1029
|
+
return { excluded: true };
|
|
826
1030
|
}
|
|
827
1031
|
if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
|
|
828
1032
|
s = s.slice(1, -1);
|
|
829
1033
|
}
|
|
830
|
-
|
|
1034
|
+
if (NON_PACKAGE_KEYS.has(s)) {
|
|
1035
|
+
return { excluded: true };
|
|
1036
|
+
}
|
|
1037
|
+
const atIdx = s.indexOf("@", 1);
|
|
831
1038
|
if (atIdx <= 0) {
|
|
832
|
-
return
|
|
1039
|
+
return {};
|
|
833
1040
|
}
|
|
834
1041
|
const name = s.slice(0, atIdx);
|
|
835
|
-
|
|
1042
|
+
const range = s.slice(atIdx + 1);
|
|
1043
|
+
if (name.length === 0) {
|
|
1044
|
+
return {};
|
|
1045
|
+
}
|
|
1046
|
+
if (NON_REGISTRY_PROTOCOLS.test(range)) {
|
|
1047
|
+
return { excluded: true };
|
|
1048
|
+
}
|
|
1049
|
+
const alias = /^npm:(.+)$/i.exec(range);
|
|
1050
|
+
if (alias !== null) {
|
|
1051
|
+
const target = alias[1] ?? "";
|
|
1052
|
+
const targetAt = target.indexOf("@", 1);
|
|
1053
|
+
return { name: targetAt > 0 ? target.slice(0, targetAt) : name };
|
|
1054
|
+
}
|
|
1055
|
+
return { name };
|
|
836
1056
|
}
|
|
837
1057
|
function parseVersionField(content) {
|
|
838
1058
|
if (!content.startsWith("version")) {
|
|
@@ -857,7 +1077,7 @@ async function readDirectDepNames(cwd) {
|
|
|
857
1077
|
const names = /* @__PURE__ */ new Set();
|
|
858
1078
|
let raw;
|
|
859
1079
|
try {
|
|
860
|
-
raw = await
|
|
1080
|
+
raw = await readFile7(path5.join(cwd, "package.json"), "utf8");
|
|
861
1081
|
} catch {
|
|
862
1082
|
return names;
|
|
863
1083
|
}
|
|
@@ -886,7 +1106,10 @@ async function readDirectDepNames(cwd) {
|
|
|
886
1106
|
// src/parsers/index.ts
|
|
887
1107
|
var LOCKFILE_CANDIDATES = [
|
|
888
1108
|
{ filename: "package-lock.json", strategy: "npm-lockfile" },
|
|
889
|
-
|
|
1109
|
+
// Read directly, not walked. Bun's isolated layout puts packages under a dot-directory and makes the
|
|
1110
|
+
// top level symlinks into it, so a `node_modules` walk finds nothing there — and isolated is the default
|
|
1111
|
+
// for new workspaces.
|
|
1112
|
+
{ filename: "bun.lock", strategy: "bun-lockfile" },
|
|
890
1113
|
{ filename: "bun.lockb", strategy: "node-modules-walk" },
|
|
891
1114
|
{ filename: "pnpm-lock.yaml", strategy: "pnpm-lockfile" },
|
|
892
1115
|
{ filename: "yarn.lock", strategy: "yarn-lockfile" }
|
|
@@ -921,33 +1144,57 @@ async function scanLockfile(cwd) {
|
|
|
921
1144
|
const declared = await readDeclaredDependencyNames(cwd);
|
|
922
1145
|
const warnings = [];
|
|
923
1146
|
let firstParsed = null;
|
|
924
|
-
let
|
|
1147
|
+
let walked = null;
|
|
1148
|
+
let walkWasCandidate = false;
|
|
1149
|
+
const parsedSources = [];
|
|
925
1150
|
for (const candidate of candidates) {
|
|
926
1151
|
let packages;
|
|
1152
|
+
const report = newReport();
|
|
927
1153
|
try {
|
|
928
|
-
packages = await runStrategy(candidate, cwd);
|
|
1154
|
+
packages = await runStrategy(candidate, cwd, report);
|
|
929
1155
|
} catch {
|
|
930
1156
|
continue;
|
|
931
1157
|
}
|
|
932
|
-
|
|
1158
|
+
const unreadable = unreadableWarning(candidate.filename, report);
|
|
1159
|
+
if (unreadable !== null) warnings.push(unreadable);
|
|
1160
|
+
if (candidate.strategy === "node-modules-walk") {
|
|
1161
|
+
walked = packages;
|
|
1162
|
+
walkWasCandidate = true;
|
|
1163
|
+
}
|
|
933
1164
|
firstParsed ??= { packages, filename: candidate.filename };
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1165
|
+
parsedSources.push({ packages, filename: candidate.filename });
|
|
1166
|
+
}
|
|
1167
|
+
for (const source of parsedSources) {
|
|
1168
|
+
const missing = missingDependencies(declared, source.packages);
|
|
1169
|
+
if (missing.length > 0) {
|
|
1170
|
+
warnings.push(staleWarning(source.filename, missing));
|
|
1171
|
+
continue;
|
|
1172
|
+
}
|
|
1173
|
+
const conflicts = otherCompleteSources(parsedSources, source, declared).flatMap(
|
|
1174
|
+
(other) => disagreements(source.packages, other.packages).map((conflict) => ({ other: other.filename, conflict }))
|
|
1175
|
+
);
|
|
1176
|
+
if (conflicts.length === 0) {
|
|
1177
|
+
return manifestWith(source.packages, warnings);
|
|
937
1178
|
}
|
|
938
|
-
warnings.push(
|
|
1179
|
+
warnings.push(conflictWarning(source.filename, conflicts));
|
|
1180
|
+
break;
|
|
939
1181
|
}
|
|
940
|
-
if (
|
|
1182
|
+
if (walked === null) {
|
|
941
1183
|
try {
|
|
942
|
-
|
|
943
|
-
const missing = missingDependencies(declared, packages);
|
|
944
|
-
if (missing.length > 0) {
|
|
945
|
-
warnings.push(staleWarning("node_modules/", missing));
|
|
946
|
-
}
|
|
947
|
-
warnings.push("Scanned node_modules/ instead. Delete the stale lockfile to silence this warning.");
|
|
948
|
-
return manifestWith(packages, warnings);
|
|
1184
|
+
walked = await walkNodeModules(cwd);
|
|
949
1185
|
} catch {
|
|
1186
|
+
walked = null;
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
if (walked !== null) {
|
|
1190
|
+
const missing = missingDependencies(declared, walked);
|
|
1191
|
+
if (missing.length > 0) {
|
|
1192
|
+
warnings.push(staleWarning("node_modules/", missing));
|
|
950
1193
|
}
|
|
1194
|
+
warnings.push(
|
|
1195
|
+
walkWasCandidate ? "Reporting node_modules/, which is what the build loads." : "Scanned node_modules/ instead. Delete the stale lockfile to silence this warning."
|
|
1196
|
+
);
|
|
1197
|
+
return manifestWith(walked, warnings);
|
|
951
1198
|
}
|
|
952
1199
|
if (firstParsed === null) {
|
|
953
1200
|
const expected = LOCKFILE_CANDIDATES.map((candidate) => candidate.filename).join(", ");
|
|
@@ -964,6 +1211,15 @@ async function scanLockfile(cwd) {
|
|
|
964
1211
|
function manifestWith(packages, warnings) {
|
|
965
1212
|
return warnings.length > 0 ? { ecosystem: "npm", packages, warnings } : { ecosystem: "npm", packages };
|
|
966
1213
|
}
|
|
1214
|
+
function otherCompleteSources(all, current, declared) {
|
|
1215
|
+
return all.filter(
|
|
1216
|
+
(source) => source.filename !== current.filename && missingDependencies(declared, source.packages).length === 0
|
|
1217
|
+
);
|
|
1218
|
+
}
|
|
1219
|
+
function conflictWarning(filename, conflicts) {
|
|
1220
|
+
const shown = conflicts.slice(0, 3).map(({ other, conflict }) => `${conflict.name} ${conflict.version} vs ${conflict.otherVersion} in ${other}`).join("; ");
|
|
1221
|
+
return `${filename} and another lockfile disagree about installed versions (${shown}${conflicts.length > 3 ? `, +${conflicts.length - 3} more` : ""}). Scanned node_modules/ instead. Remove whichever lockfile your package manager does not maintain.`;
|
|
1222
|
+
}
|
|
967
1223
|
function staleWarning(source, missing) {
|
|
968
1224
|
const sample = missing.slice(0, 3).join(", ");
|
|
969
1225
|
const suffix = missing.length > 3 ? `, +${missing.length - 3} more` : "";
|
|
@@ -978,14 +1234,18 @@ async function presentLockfiles(cwd) {
|
|
|
978
1234
|
);
|
|
979
1235
|
return probed.filter((candidate) => candidate.present).map(({ filename, strategy, filePath }) => ({ ecosystem: "npm", filename, strategy, filePath }));
|
|
980
1236
|
}
|
|
981
|
-
async function runStrategy(detected, cwd) {
|
|
1237
|
+
async function runStrategy(detected, cwd, report) {
|
|
982
1238
|
switch (detected.strategy) {
|
|
983
1239
|
case "npm-lockfile":
|
|
984
1240
|
return parseNpmLockfile(detected.filePath);
|
|
1241
|
+
case "bun-lockfile":
|
|
1242
|
+
return parseBunLockfile(detected.filePath, report);
|
|
1243
|
+
// The hand-written scanners. Each meets entries it was not written for, and each is asked to say how
|
|
1244
|
+
// many it dropped, because a manifest short a package reads exactly like a project without it.
|
|
985
1245
|
case "pnpm-lockfile":
|
|
986
|
-
return parsePnpmLockfile(detected.filePath);
|
|
1246
|
+
return parsePnpmLockfile(detected.filePath, report);
|
|
987
1247
|
case "yarn-lockfile":
|
|
988
|
-
return parseYarnLockfile(detected.filePath);
|
|
1248
|
+
return parseYarnLockfile(detected.filePath, report);
|
|
989
1249
|
case "node-modules-walk":
|
|
990
1250
|
return walkNodeModules(cwd);
|
|
991
1251
|
}
|
|
@@ -1000,21 +1260,33 @@ async function exists(filePath) {
|
|
|
1000
1260
|
}
|
|
1001
1261
|
|
|
1002
1262
|
// src/normalize.ts
|
|
1003
|
-
function buildWirePayload(manifest) {
|
|
1263
|
+
function buildWirePayload(manifest, options = {}) {
|
|
1264
|
+
const withPaths = options.installPaths === true;
|
|
1004
1265
|
const seen = /* @__PURE__ */ new Map();
|
|
1005
1266
|
const wirePackages = [];
|
|
1267
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
1268
|
+
let entriesWithoutPath = 0;
|
|
1006
1269
|
for (const entry of manifest.packages) {
|
|
1270
|
+
const identity = `${entry.name}@${entry.version}`;
|
|
1271
|
+
const location = withPaths && entry.path !== void 0 && entry.path !== "" ? entry.path : void 0;
|
|
1272
|
+
if (location === void 0) entriesWithoutPath++;
|
|
1273
|
+
const existing = byIdentity.get(identity);
|
|
1274
|
+
if (existing) {
|
|
1275
|
+
if (location !== void 0) addPath(existing, location);
|
|
1276
|
+
continue;
|
|
1277
|
+
}
|
|
1007
1278
|
const versions = seen.get(entry.name);
|
|
1008
1279
|
if (versions) {
|
|
1009
|
-
if (versions.has(entry.version)) {
|
|
1010
|
-
continue;
|
|
1011
|
-
}
|
|
1012
1280
|
versions.add(entry.version);
|
|
1013
1281
|
} else {
|
|
1014
1282
|
seen.set(entry.name, /* @__PURE__ */ new Set([entry.version]));
|
|
1015
1283
|
}
|
|
1016
|
-
|
|
1284
|
+
const wire = { name: entry.name, version: entry.version };
|
|
1285
|
+
if (location !== void 0) addPath(wire, location);
|
|
1286
|
+
byIdentity.set(identity, wire);
|
|
1287
|
+
wirePackages.push(wire);
|
|
1017
1288
|
}
|
|
1289
|
+
for (const wire of wirePackages) wire.paths?.sort();
|
|
1018
1290
|
wirePackages.sort((a, b) => {
|
|
1019
1291
|
if (a.name === b.name) {
|
|
1020
1292
|
return compareVersions(a.version, b.version);
|
|
@@ -1028,7 +1300,18 @@ function buildWirePayload(manifest) {
|
|
|
1028
1300
|
}
|
|
1029
1301
|
}
|
|
1030
1302
|
return {
|
|
1031
|
-
payload: {
|
|
1303
|
+
payload: {
|
|
1304
|
+
ecosystem: manifest.ecosystem,
|
|
1305
|
+
packages: wirePackages,
|
|
1306
|
+
// Every entry had a location, so a missing path anywhere means the package is not installed there.
|
|
1307
|
+
// One entry without one forfeits that for the whole payload: a consumer reading a short `paths` has
|
|
1308
|
+
// no way to tell which entry was the incomplete one.
|
|
1309
|
+
//
|
|
1310
|
+
// No separate check for "locations were not requested": with them off every entry counts as without
|
|
1311
|
+
// one, so this is already false — which is the default, and the safe direction. An explicit
|
|
1312
|
+
// `withPaths &&` here was a guard no test could distinguish from its absence.
|
|
1313
|
+
installPathsComplete: entriesWithoutPath === 0 && manifest.packages.length > 0
|
|
1314
|
+
},
|
|
1032
1315
|
stats: {
|
|
1033
1316
|
uniqueNames: seen.size,
|
|
1034
1317
|
duplicateNames,
|
|
@@ -1036,6 +1319,10 @@ function buildWirePayload(manifest) {
|
|
|
1036
1319
|
}
|
|
1037
1320
|
};
|
|
1038
1321
|
}
|
|
1322
|
+
function addPath(wire, installPath) {
|
|
1323
|
+
if (wire.paths === void 0) wire.paths = [installPath];
|
|
1324
|
+
else if (!wire.paths.includes(installPath)) wire.paths.push(installPath);
|
|
1325
|
+
}
|
|
1039
1326
|
function compareVersions(a, b) {
|
|
1040
1327
|
if (a === b) {
|
|
1041
1328
|
return 0;
|
|
@@ -1114,4 +1401,4 @@ async function reportManifest(cwd) {
|
|
|
1114
1401
|
export {
|
|
1115
1402
|
reportManifest
|
|
1116
1403
|
};
|
|
1117
|
-
//# sourceMappingURL=refresh-manifest-
|
|
1404
|
+
//# sourceMappingURL=refresh-manifest-KNZQBC4V.js.map
|