@patchstack/connect 0.3.29 → 0.3.30
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 +37 -21
- package/README.md +24 -10
- package/dist/{chunk-MJOTFUDE.js → chunk-LLKP5EJS.js} +1 -1
- package/dist/chunk-LLKP5EJS.js.map +1 -0
- package/dist/cli.js +1062 -189
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +419 -104
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +28 -9
- package/dist/index.d.ts +28 -9
- package/dist/index.js +412 -97
- 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 +1039 -235
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.ts +39 -11
- package/dist/protect.edge.js +663 -132
- package/dist/protect.edge.js.map +4 -4
- package/dist/protect.js +665 -134
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-VRBE6RH6.js → refresh-manifest-ZSP76JWQ.js} +351 -94
- package/dist/refresh-manifest-ZSP76JWQ.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,122 @@ 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));
|
|
362
532
|
}
|
|
363
|
-
|
|
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);
|
|
540
|
+
}
|
|
541
|
+
var CONCURRENCY = 32;
|
|
542
|
+
async function walk(dir, depth, seen) {
|
|
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);
|
|
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);
|
|
568
|
+
});
|
|
569
|
+
return scoped.flat();
|
|
397
570
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
571
|
+
return packageAndNested(fullPath, resolved, depth, seen);
|
|
572
|
+
});
|
|
573
|
+
return perEntry.flat();
|
|
574
|
+
}
|
|
575
|
+
async function packageAndNested(pkgDir, resolved, depth, seen) {
|
|
576
|
+
const [own, nested] = await Promise.all([readPackage(pkgDir, depth), walkNested(pkgDir, depth, seen)]);
|
|
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;
|
|
401
590
|
}
|
|
402
|
-
async function readPackage(pkgDir, depth
|
|
591
|
+
async function readPackage(pkgDir, depth) {
|
|
403
592
|
let raw;
|
|
404
593
|
try {
|
|
405
|
-
raw = await
|
|
594
|
+
raw = await readFile5(path4.join(pkgDir, "package.json"), "utf8");
|
|
406
595
|
} catch {
|
|
407
|
-
return;
|
|
596
|
+
return null;
|
|
408
597
|
}
|
|
409
598
|
let parsed;
|
|
410
599
|
try {
|
|
411
600
|
parsed = JSON.parse(raw);
|
|
412
601
|
} catch {
|
|
413
|
-
return;
|
|
602
|
+
return null;
|
|
414
603
|
}
|
|
415
604
|
if (typeof parsed.name !== "string" || parsed.name.length === 0) {
|
|
416
|
-
return;
|
|
605
|
+
return null;
|
|
417
606
|
}
|
|
418
607
|
if (typeof parsed.version !== "string" || parsed.version.length === 0) {
|
|
419
|
-
return;
|
|
608
|
+
return null;
|
|
420
609
|
}
|
|
421
|
-
|
|
422
|
-
name: parsed.name,
|
|
423
|
-
version: parsed.version,
|
|
424
|
-
direct: depth === 0
|
|
425
|
-
});
|
|
610
|
+
return { name: parsed.name, version: parsed.version, direct: depth === 0 };
|
|
426
611
|
}
|
|
427
|
-
async function walkNested(pkgDir,
|
|
612
|
+
async function walkNested(pkgDir, depth, seen) {
|
|
428
613
|
const nested = path4.join(pkgDir, "node_modules");
|
|
429
|
-
if (
|
|
430
|
-
return;
|
|
614
|
+
if (await claim(nested, seen, depth + 1) === null) {
|
|
615
|
+
return [];
|
|
431
616
|
}
|
|
432
|
-
|
|
617
|
+
return walk(nested, depth + 1, seen);
|
|
433
618
|
}
|
|
434
|
-
async function
|
|
619
|
+
async function claim(dir, seen, depth) {
|
|
620
|
+
let resolved;
|
|
435
621
|
try {
|
|
436
622
|
const info = await lstat(dir);
|
|
437
|
-
|
|
623
|
+
if (!info.isDirectory() && !info.isSymbolicLink()) return null;
|
|
624
|
+
resolved = await realpath(dir);
|
|
625
|
+
const target = await stat(resolved);
|
|
626
|
+
if (!target.isDirectory()) return null;
|
|
438
627
|
} catch {
|
|
439
|
-
return
|
|
628
|
+
return null;
|
|
440
629
|
}
|
|
630
|
+
const claimed = seen.get(resolved);
|
|
631
|
+
if (claimed !== void 0 && claimed <= depth) return null;
|
|
632
|
+
seen.set(resolved, depth);
|
|
633
|
+
return resolved;
|
|
441
634
|
}
|
|
442
635
|
|
|
443
636
|
// src/parsers/pnpm.ts
|
|
444
|
-
import { readFile as
|
|
445
|
-
async function parsePnpmLockfile(lockfilePath) {
|
|
637
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
638
|
+
async function parsePnpmLockfile(lockfilePath, report) {
|
|
446
639
|
let raw;
|
|
447
640
|
try {
|
|
448
|
-
raw = await
|
|
641
|
+
raw = await readFile6(lockfilePath, "utf8");
|
|
449
642
|
} catch (cause) {
|
|
450
643
|
throw new PatchstackError(
|
|
451
644
|
`Could not read lockfile at ${lockfilePath}`,
|
|
@@ -466,6 +659,7 @@ async function parsePnpmLockfile(lockfilePath) {
|
|
|
466
659
|
for (const key of packageKeys) {
|
|
467
660
|
const parsed = parsePackageKey(key);
|
|
468
661
|
if (parsed === null) {
|
|
662
|
+
recordUnreadable(report, key);
|
|
469
663
|
continue;
|
|
470
664
|
}
|
|
471
665
|
entries.push({
|
|
@@ -693,12 +887,12 @@ function extractLeafName(trimmed) {
|
|
|
693
887
|
}
|
|
694
888
|
|
|
695
889
|
// src/parsers/yarn.ts
|
|
696
|
-
import { readFile as
|
|
890
|
+
import { readFile as readFile7 } from "fs/promises";
|
|
697
891
|
import path5 from "path";
|
|
698
|
-
async function parseYarnLockfile(lockfilePath) {
|
|
892
|
+
async function parseYarnLockfile(lockfilePath, report) {
|
|
699
893
|
let raw;
|
|
700
894
|
try {
|
|
701
|
-
raw = await
|
|
895
|
+
raw = await readFile7(lockfilePath, "utf8");
|
|
702
896
|
} catch (cause) {
|
|
703
897
|
throw new PatchstackError(
|
|
704
898
|
`Could not read lockfile at ${lockfilePath}`,
|
|
@@ -706,7 +900,7 @@ async function parseYarnLockfile(lockfilePath) {
|
|
|
706
900
|
cause
|
|
707
901
|
);
|
|
708
902
|
}
|
|
709
|
-
const blocks = parseBlocks(raw);
|
|
903
|
+
const blocks = parseBlocks(raw, report);
|
|
710
904
|
if (blocks.length === 0) {
|
|
711
905
|
throw new PatchstackError(
|
|
712
906
|
`Lockfile at ${lockfilePath} contains no package entries`,
|
|
@@ -735,13 +929,19 @@ async function parseYarnLockfile(lockfilePath) {
|
|
|
735
929
|
}
|
|
736
930
|
return entries;
|
|
737
931
|
}
|
|
738
|
-
|
|
932
|
+
var NON_PACKAGE_KEYS = /* @__PURE__ */ new Set(["__metadata"]);
|
|
933
|
+
function parseBlocks(raw, report) {
|
|
739
934
|
const lines = raw.split(/\r?\n/);
|
|
740
935
|
const blocks = [];
|
|
741
936
|
let current = null;
|
|
742
937
|
const finalize = () => {
|
|
743
|
-
if (current
|
|
938
|
+
if (current === null) {
|
|
939
|
+
return;
|
|
940
|
+
}
|
|
941
|
+
if (current.version.length > 0 && current.names.size > 0) {
|
|
744
942
|
blocks.push(current);
|
|
943
|
+
} else if (current.unreadable) {
|
|
944
|
+
recordUnreadable(report, current.key);
|
|
745
945
|
}
|
|
746
946
|
current = null;
|
|
747
947
|
};
|
|
@@ -758,13 +958,13 @@ function parseBlocks(raw) {
|
|
|
758
958
|
}
|
|
759
959
|
const keyLine = trimmed.slice(0, -1);
|
|
760
960
|
const names = /* @__PURE__ */ new Set();
|
|
961
|
+
let unreadable = false;
|
|
761
962
|
for (const spec of splitDescriptors(keyLine)) {
|
|
762
|
-
const
|
|
763
|
-
if (name !==
|
|
764
|
-
|
|
765
|
-
}
|
|
963
|
+
const classified = classifyDescriptor(spec);
|
|
964
|
+
if (classified.name !== void 0) names.add(classified.name);
|
|
965
|
+
else if (!classified.excluded) unreadable = true;
|
|
766
966
|
}
|
|
767
|
-
current = { names, version: "" };
|
|
967
|
+
current = { names, version: "", key: keyLine, unreadable: unreadable && names.size === 0 };
|
|
768
968
|
continue;
|
|
769
969
|
}
|
|
770
970
|
if (current === null) {
|
|
@@ -819,20 +1019,37 @@ function splitDescriptors(keyLine) {
|
|
|
819
1019
|
}
|
|
820
1020
|
return parts;
|
|
821
1021
|
}
|
|
822
|
-
|
|
1022
|
+
var NON_REGISTRY_PROTOCOLS = /^(?:workspace|patch|virtual|portal|file|link|exec|git|github|https?|ssh):/i;
|
|
1023
|
+
function classifyDescriptor(rawSpec) {
|
|
823
1024
|
let s = rawSpec.trim();
|
|
824
1025
|
if (s.length === 0) {
|
|
825
|
-
return
|
|
1026
|
+
return { excluded: true };
|
|
826
1027
|
}
|
|
827
1028
|
if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
|
|
828
1029
|
s = s.slice(1, -1);
|
|
829
1030
|
}
|
|
830
|
-
|
|
1031
|
+
if (NON_PACKAGE_KEYS.has(s)) {
|
|
1032
|
+
return { excluded: true };
|
|
1033
|
+
}
|
|
1034
|
+
const atIdx = s.indexOf("@", 1);
|
|
831
1035
|
if (atIdx <= 0) {
|
|
832
|
-
return
|
|
1036
|
+
return {};
|
|
833
1037
|
}
|
|
834
1038
|
const name = s.slice(0, atIdx);
|
|
835
|
-
|
|
1039
|
+
const range = s.slice(atIdx + 1);
|
|
1040
|
+
if (name.length === 0) {
|
|
1041
|
+
return {};
|
|
1042
|
+
}
|
|
1043
|
+
if (NON_REGISTRY_PROTOCOLS.test(range)) {
|
|
1044
|
+
return { excluded: true };
|
|
1045
|
+
}
|
|
1046
|
+
const alias = /^npm:(.+)$/i.exec(range);
|
|
1047
|
+
if (alias !== null) {
|
|
1048
|
+
const target = alias[1] ?? "";
|
|
1049
|
+
const targetAt = target.indexOf("@", 1);
|
|
1050
|
+
return { name: targetAt > 0 ? target.slice(0, targetAt) : name };
|
|
1051
|
+
}
|
|
1052
|
+
return { name };
|
|
836
1053
|
}
|
|
837
1054
|
function parseVersionField(content) {
|
|
838
1055
|
if (!content.startsWith("version")) {
|
|
@@ -857,7 +1074,7 @@ async function readDirectDepNames(cwd) {
|
|
|
857
1074
|
const names = /* @__PURE__ */ new Set();
|
|
858
1075
|
let raw;
|
|
859
1076
|
try {
|
|
860
|
-
raw = await
|
|
1077
|
+
raw = await readFile7(path5.join(cwd, "package.json"), "utf8");
|
|
861
1078
|
} catch {
|
|
862
1079
|
return names;
|
|
863
1080
|
}
|
|
@@ -886,7 +1103,10 @@ async function readDirectDepNames(cwd) {
|
|
|
886
1103
|
// src/parsers/index.ts
|
|
887
1104
|
var LOCKFILE_CANDIDATES = [
|
|
888
1105
|
{ filename: "package-lock.json", strategy: "npm-lockfile" },
|
|
889
|
-
|
|
1106
|
+
// Read directly, not walked. Bun's isolated layout puts packages under a dot-directory and makes the
|
|
1107
|
+
// top level symlinks into it, so a `node_modules` walk finds nothing there — and isolated is the default
|
|
1108
|
+
// for new workspaces.
|
|
1109
|
+
{ filename: "bun.lock", strategy: "bun-lockfile" },
|
|
890
1110
|
{ filename: "bun.lockb", strategy: "node-modules-walk" },
|
|
891
1111
|
{ filename: "pnpm-lock.yaml", strategy: "pnpm-lockfile" },
|
|
892
1112
|
{ filename: "yarn.lock", strategy: "yarn-lockfile" }
|
|
@@ -921,33 +1141,57 @@ async function scanLockfile(cwd) {
|
|
|
921
1141
|
const declared = await readDeclaredDependencyNames(cwd);
|
|
922
1142
|
const warnings = [];
|
|
923
1143
|
let firstParsed = null;
|
|
924
|
-
let
|
|
1144
|
+
let walked = null;
|
|
1145
|
+
let walkWasCandidate = false;
|
|
1146
|
+
const parsedSources = [];
|
|
925
1147
|
for (const candidate of candidates) {
|
|
926
1148
|
let packages;
|
|
1149
|
+
const report = newReport();
|
|
927
1150
|
try {
|
|
928
|
-
packages = await runStrategy(candidate, cwd);
|
|
1151
|
+
packages = await runStrategy(candidate, cwd, report);
|
|
929
1152
|
} catch {
|
|
930
1153
|
continue;
|
|
931
1154
|
}
|
|
932
|
-
|
|
1155
|
+
const unreadable = unreadableWarning(candidate.filename, report);
|
|
1156
|
+
if (unreadable !== null) warnings.push(unreadable);
|
|
1157
|
+
if (candidate.strategy === "node-modules-walk") {
|
|
1158
|
+
walked = packages;
|
|
1159
|
+
walkWasCandidate = true;
|
|
1160
|
+
}
|
|
933
1161
|
firstParsed ??= { packages, filename: candidate.filename };
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
1162
|
+
parsedSources.push({ packages, filename: candidate.filename });
|
|
1163
|
+
}
|
|
1164
|
+
for (const source of parsedSources) {
|
|
1165
|
+
const missing = missingDependencies(declared, source.packages);
|
|
1166
|
+
if (missing.length > 0) {
|
|
1167
|
+
warnings.push(staleWarning(source.filename, missing));
|
|
1168
|
+
continue;
|
|
1169
|
+
}
|
|
1170
|
+
const conflicts = otherCompleteSources(parsedSources, source, declared).flatMap(
|
|
1171
|
+
(other) => disagreements(source.packages, other.packages).map((conflict) => ({ other: other.filename, conflict }))
|
|
1172
|
+
);
|
|
1173
|
+
if (conflicts.length === 0) {
|
|
1174
|
+
return manifestWith(source.packages, warnings);
|
|
937
1175
|
}
|
|
938
|
-
warnings.push(
|
|
1176
|
+
warnings.push(conflictWarning(source.filename, conflicts));
|
|
1177
|
+
break;
|
|
939
1178
|
}
|
|
940
|
-
if (
|
|
1179
|
+
if (walked === null) {
|
|
941
1180
|
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);
|
|
1181
|
+
walked = await walkNodeModules(cwd);
|
|
949
1182
|
} catch {
|
|
1183
|
+
walked = null;
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
if (walked !== null) {
|
|
1187
|
+
const missing = missingDependencies(declared, walked);
|
|
1188
|
+
if (missing.length > 0) {
|
|
1189
|
+
warnings.push(staleWarning("node_modules/", missing));
|
|
950
1190
|
}
|
|
1191
|
+
warnings.push(
|
|
1192
|
+
walkWasCandidate ? "Reporting node_modules/, which is what the build loads." : "Scanned node_modules/ instead. Delete the stale lockfile to silence this warning."
|
|
1193
|
+
);
|
|
1194
|
+
return manifestWith(walked, warnings);
|
|
951
1195
|
}
|
|
952
1196
|
if (firstParsed === null) {
|
|
953
1197
|
const expected = LOCKFILE_CANDIDATES.map((candidate) => candidate.filename).join(", ");
|
|
@@ -964,6 +1208,15 @@ async function scanLockfile(cwd) {
|
|
|
964
1208
|
function manifestWith(packages, warnings) {
|
|
965
1209
|
return warnings.length > 0 ? { ecosystem: "npm", packages, warnings } : { ecosystem: "npm", packages };
|
|
966
1210
|
}
|
|
1211
|
+
function otherCompleteSources(all, current, declared) {
|
|
1212
|
+
return all.filter(
|
|
1213
|
+
(source) => source.filename !== current.filename && missingDependencies(declared, source.packages).length === 0
|
|
1214
|
+
);
|
|
1215
|
+
}
|
|
1216
|
+
function conflictWarning(filename, conflicts) {
|
|
1217
|
+
const shown = conflicts.slice(0, 3).map(({ other, conflict }) => `${conflict.name} ${conflict.version} vs ${conflict.otherVersion} in ${other}`).join("; ");
|
|
1218
|
+
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.`;
|
|
1219
|
+
}
|
|
967
1220
|
function staleWarning(source, missing) {
|
|
968
1221
|
const sample = missing.slice(0, 3).join(", ");
|
|
969
1222
|
const suffix = missing.length > 3 ? `, +${missing.length - 3} more` : "";
|
|
@@ -978,14 +1231,18 @@ async function presentLockfiles(cwd) {
|
|
|
978
1231
|
);
|
|
979
1232
|
return probed.filter((candidate) => candidate.present).map(({ filename, strategy, filePath }) => ({ ecosystem: "npm", filename, strategy, filePath }));
|
|
980
1233
|
}
|
|
981
|
-
async function runStrategy(detected, cwd) {
|
|
1234
|
+
async function runStrategy(detected, cwd, report) {
|
|
982
1235
|
switch (detected.strategy) {
|
|
983
1236
|
case "npm-lockfile":
|
|
984
1237
|
return parseNpmLockfile(detected.filePath);
|
|
1238
|
+
case "bun-lockfile":
|
|
1239
|
+
return parseBunLockfile(detected.filePath, report);
|
|
1240
|
+
// The hand-written scanners. Each meets entries it was not written for, and each is asked to say how
|
|
1241
|
+
// many it dropped, because a manifest short a package reads exactly like a project without it.
|
|
985
1242
|
case "pnpm-lockfile":
|
|
986
|
-
return parsePnpmLockfile(detected.filePath);
|
|
1243
|
+
return parsePnpmLockfile(detected.filePath, report);
|
|
987
1244
|
case "yarn-lockfile":
|
|
988
|
-
return parseYarnLockfile(detected.filePath);
|
|
1245
|
+
return parseYarnLockfile(detected.filePath, report);
|
|
989
1246
|
case "node-modules-walk":
|
|
990
1247
|
return walkNodeModules(cwd);
|
|
991
1248
|
}
|
|
@@ -1114,4 +1371,4 @@ async function reportManifest(cwd) {
|
|
|
1114
1371
|
export {
|
|
1115
1372
|
reportManifest
|
|
1116
1373
|
};
|
|
1117
|
-
//# sourceMappingURL=refresh-manifest-
|
|
1374
|
+
//# sourceMappingURL=refresh-manifest-ZSP76JWQ.js.map
|