@patchstack/connect 0.3.30 → 0.3.32
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 +180 -20
- package/README.md +17 -3
- package/dist/{chunk-LLKP5EJS.js → chunk-3G2I6QL6.js} +1 -1
- package/dist/chunk-3G2I6QL6.js.map +1 -0
- package/dist/cli.js +309 -130
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +48 -18
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +58 -3
- package/dist/index.d.ts +58 -3
- package/dist/index.js +48 -18
- package/dist/index.js.map +1 -1
- package/dist/protect.cjs +2749 -1373
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.cts +316 -0
- package/dist/protect.d.ts +134 -18
- package/dist/protect.edge.js +2776 -1412
- package/dist/protect.edge.js.map +3 -4
- package/dist/protect.js +2697 -1355
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-ZSP76JWQ.js → refresh-manifest-SWCPQ52Z.js} +49 -19
- package/dist/refresh-manifest-SWCPQ52Z.js.map +1 -0
- package/package.json +44 -11
- package/dist/chunk-LLKP5EJS.js.map +0 -1
- package/dist/refresh-manifest-ZSP76JWQ.js.map +0 -1
package/dist/index.d.ts
CHANGED
|
@@ -87,10 +87,45 @@ declare function scanLockfile(cwd: string): Promise<Manifest>;
|
|
|
87
87
|
interface WirePackage {
|
|
88
88
|
name: string;
|
|
89
89
|
version: string;
|
|
90
|
+
/**
|
|
91
|
+
* Where this exact name+version is installed, repo-relative and sorted.
|
|
92
|
+
*
|
|
93
|
+
* The same package can be installed more than once at different versions — a workspace pinning an old
|
|
94
|
+
* copy under `apps/api/node_modules`, a transitive dependency getting its own nested install. Without
|
|
95
|
+
* the locations, `lodash@4.17.11` and `lodash@4.17.21` arrive as two bare pairs and nothing can say
|
|
96
|
+
* WHICH one the app's own import resolves to. A consumer then has to treat every installed version as
|
|
97
|
+
* if the code used it: it warns on a copy nothing reaches, or pins a rule to a route running the safe
|
|
98
|
+
* one — a rule that never fires while reporting as protection.
|
|
99
|
+
*
|
|
100
|
+
* Node resolves an import by walking up from the importing file, so the map's import sites plus these
|
|
101
|
+
* paths together answer the question. Neither half answers it alone.
|
|
102
|
+
*
|
|
103
|
+
* OPT-IN. Absent unless the caller asks for locations (`scan --install-paths`), and absent even then
|
|
104
|
+
* when the scan source does not record them — see `installPathsComplete`, which is what separates
|
|
105
|
+
* "not installed there" from "we were not told".
|
|
106
|
+
*
|
|
107
|
+
* Off by default because it changes what leaves the machine. The package's standing promise is that
|
|
108
|
+
* `scan` sends names and versions and no paths of any kind; locations are a real widening of that, and
|
|
109
|
+
* an upload that widens it should be an explicit choice rather than a consequence of upgrading.
|
|
110
|
+
*/
|
|
111
|
+
paths?: string[];
|
|
90
112
|
}
|
|
91
113
|
interface WirePayload {
|
|
92
114
|
ecosystem: Manifest['ecosystem'];
|
|
93
115
|
packages: WirePackage[];
|
|
116
|
+
/**
|
|
117
|
+
* Whether every entry's `paths` is the complete set of locations for it.
|
|
118
|
+
*
|
|
119
|
+
* False when locations were not requested at all, when the scan source cannot supply them (a yarn.lock
|
|
120
|
+
* is flat — hoisting is decided at install time and the file does not record it; a v1 npm lockfile
|
|
121
|
+
* describes the dependency graph, not the tree), or when only some entries had one. A consumer MUST NOT
|
|
122
|
+
* read a missing or short `paths` as "installed nowhere else" while this is false; absence is then "not
|
|
123
|
+
* recorded", which is not an answer.
|
|
124
|
+
*
|
|
125
|
+
* The default is therefore `false`, and that is the safe direction: the field withholds a negative
|
|
126
|
+
* rather than granting one.
|
|
127
|
+
*/
|
|
128
|
+
installPathsComplete: boolean;
|
|
94
129
|
}
|
|
95
130
|
interface NormalizeStats {
|
|
96
131
|
uniqueNames: number;
|
|
@@ -101,7 +136,14 @@ interface NormalizeResult {
|
|
|
101
136
|
payload: WirePayload;
|
|
102
137
|
stats: NormalizeStats;
|
|
103
138
|
}
|
|
104
|
-
|
|
139
|
+
interface WireOptions {
|
|
140
|
+
/**
|
|
141
|
+
* Include each package's install location. Off by default — see `WirePackage.paths`: it widens what
|
|
142
|
+
* `scan` transmits, so it is the caller's explicit choice, not a side effect of upgrading.
|
|
143
|
+
*/
|
|
144
|
+
installPaths?: boolean;
|
|
145
|
+
}
|
|
146
|
+
declare function buildWirePayload(manifest: Manifest, options?: WireOptions): NormalizeResult;
|
|
105
147
|
declare function compareVersions(a: string, b: string): number;
|
|
106
148
|
|
|
107
149
|
declare const DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest";
|
|
@@ -214,18 +256,26 @@ interface StackDescriptor {
|
|
|
214
256
|
/** Hosting-related build-environment variable NAMES (never their values). */
|
|
215
257
|
hostingEnvKeys: string[];
|
|
216
258
|
}
|
|
259
|
+
/**
|
|
260
|
+
* An environment, described structurally so it needs no ambient types.
|
|
261
|
+
*
|
|
262
|
+
* `@types/node` is not a dependency of this package, so nothing in its public surface may name
|
|
263
|
+
* `NodeJS.ProcessEnv`: a consumer without those types cannot compile against a signature that does.
|
|
264
|
+
* `process.env` satisfies this, and so does a plain object.
|
|
265
|
+
*/
|
|
266
|
+
type EnvLike = Record<string, string | undefined>;
|
|
217
267
|
/**
|
|
218
268
|
* Return the sorted NAMES of hosting-related environment variables present in
|
|
219
269
|
* `env`. Only names are surfaced — never values — so a build fingerprint can
|
|
220
270
|
* distinguish "deployed on Cloudflare" from "deployed on Vercel" without ever
|
|
221
271
|
* disclosing a secret.
|
|
222
272
|
*/
|
|
223
|
-
declare function collectHostingEnvKeys(env?:
|
|
273
|
+
declare function collectHostingEnvKeys(env?: EnvLike): string[];
|
|
224
274
|
/**
|
|
225
275
|
* Derive a {@link StackDescriptor} from the wire packages and the build
|
|
226
276
|
* environment. Never throws: an unrecognised stack just yields nulls.
|
|
227
277
|
*/
|
|
228
|
-
declare function detectStack(packages: readonly WirePackage[], env?:
|
|
278
|
+
declare function detectStack(packages: readonly WirePackage[], env?: EnvLike): StackDescriptor;
|
|
229
279
|
/** True when the descriptor carries no useful signal (nothing worth injecting). */
|
|
230
280
|
declare function isEmptyStack(stack: StackDescriptor): boolean;
|
|
231
281
|
|
|
@@ -270,6 +320,11 @@ declare function ensureSourceWidget(cwd: string, siteUuid: string): SourceWidget
|
|
|
270
320
|
interface ScanAndReportOptions {
|
|
271
321
|
cwd?: string;
|
|
272
322
|
config?: Config;
|
|
323
|
+
/**
|
|
324
|
+
* Include each package's install location in the uploaded payload. Off by default: it widens what
|
|
325
|
+
* leaves the machine, so it is an explicit choice rather than something an upgrade turns on.
|
|
326
|
+
*/
|
|
327
|
+
installPaths?: boolean;
|
|
273
328
|
}
|
|
274
329
|
interface ScanAndReportResult {
|
|
275
330
|
manifest: Manifest;
|
package/dist/index.js
CHANGED
|
@@ -325,7 +325,7 @@ async function walkNodeModules(cwd) {
|
|
|
325
325
|
seen.set(await realpath(cwd), -1);
|
|
326
326
|
} catch {
|
|
327
327
|
}
|
|
328
|
-
return dedupeByShallowest(await walk(root, 0, seen));
|
|
328
|
+
return dedupeByShallowest(await walk(root, 0, seen, cwd));
|
|
329
329
|
}
|
|
330
330
|
function dedupeByShallowest(found) {
|
|
331
331
|
const best = /* @__PURE__ */ new Map();
|
|
@@ -336,7 +336,7 @@ function dedupeByShallowest(found) {
|
|
|
336
336
|
return [...best.values()].map(({ entry }) => entry);
|
|
337
337
|
}
|
|
338
338
|
var CONCURRENCY = 32;
|
|
339
|
-
async function walk(dir, depth, seen) {
|
|
339
|
+
async function walk(dir, depth, seen, root) {
|
|
340
340
|
let names;
|
|
341
341
|
try {
|
|
342
342
|
names = await readdir(dir);
|
|
@@ -348,7 +348,7 @@ async function walk(dir, depth, seen) {
|
|
|
348
348
|
const fullPath = path3.join(dir, name);
|
|
349
349
|
const resolved = await claim(fullPath, seen, depth);
|
|
350
350
|
if (resolved === null) return [];
|
|
351
|
-
if (PACKAGE_STORE_DIRS.has(name)) return walk(fullPath, depth, seen);
|
|
351
|
+
if (PACKAGE_STORE_DIRS.has(name)) return walk(fullPath, depth, seen, root);
|
|
352
352
|
if (name.startsWith("@")) {
|
|
353
353
|
let subNames;
|
|
354
354
|
try {
|
|
@@ -361,16 +361,16 @@ async function walk(dir, depth, seen) {
|
|
|
361
361
|
const scopedDir = path3.join(fullPath, sub);
|
|
362
362
|
const scopedReal = await claim(scopedDir, seen, depth);
|
|
363
363
|
if (scopedReal === null) return [];
|
|
364
|
-
return packageAndNested(scopedDir, scopedReal, depth, seen);
|
|
364
|
+
return packageAndNested(scopedDir, scopedReal, depth, seen, root);
|
|
365
365
|
});
|
|
366
366
|
return scoped.flat();
|
|
367
367
|
}
|
|
368
|
-
return packageAndNested(fullPath, resolved, depth, seen);
|
|
368
|
+
return packageAndNested(fullPath, resolved, depth, seen, root);
|
|
369
369
|
});
|
|
370
370
|
return perEntry.flat();
|
|
371
371
|
}
|
|
372
|
-
async function packageAndNested(pkgDir, resolved, depth, seen) {
|
|
373
|
-
const [own, nested] = await Promise.all([readPackage(pkgDir, depth), walkNested(pkgDir, depth, seen)]);
|
|
372
|
+
async function packageAndNested(pkgDir, resolved, depth, seen, root) {
|
|
373
|
+
const [own, nested] = await Promise.all([readPackage(pkgDir, depth, root), walkNested(pkgDir, depth, seen, root)]);
|
|
374
374
|
return own === null ? nested : [{ entry: own, realpath: resolved, depth }, ...nested];
|
|
375
375
|
}
|
|
376
376
|
async function mapLimit(items, fn) {
|
|
@@ -385,7 +385,10 @@ async function mapLimit(items, fn) {
|
|
|
385
385
|
await Promise.all(workers);
|
|
386
386
|
return results;
|
|
387
387
|
}
|
|
388
|
-
|
|
388
|
+
function installLocation(root, pkgDir, impl = path3) {
|
|
389
|
+
return impl.relative(root, pkgDir).split(impl.sep).join("/");
|
|
390
|
+
}
|
|
391
|
+
async function readPackage(pkgDir, depth, root) {
|
|
389
392
|
let raw;
|
|
390
393
|
try {
|
|
391
394
|
raw = await readFile4(path3.join(pkgDir, "package.json"), "utf8");
|
|
@@ -404,14 +407,14 @@ async function readPackage(pkgDir, depth) {
|
|
|
404
407
|
if (typeof parsed.version !== "string" || parsed.version.length === 0) {
|
|
405
408
|
return null;
|
|
406
409
|
}
|
|
407
|
-
return { name: parsed.name, version: parsed.version, direct: depth === 0 };
|
|
410
|
+
return { name: parsed.name, version: parsed.version, path: installLocation(root, pkgDir), direct: depth === 0 };
|
|
408
411
|
}
|
|
409
|
-
async function walkNested(pkgDir, depth, seen) {
|
|
412
|
+
async function walkNested(pkgDir, depth, seen, root) {
|
|
410
413
|
const nested = path3.join(pkgDir, "node_modules");
|
|
411
414
|
if (await claim(nested, seen, depth + 1) === null) {
|
|
412
415
|
return [];
|
|
413
416
|
}
|
|
414
|
-
return walk(nested, depth + 1, seen);
|
|
417
|
+
return walk(nested, depth + 1, seen, root);
|
|
415
418
|
}
|
|
416
419
|
async function claim(dir, seen, depth) {
|
|
417
420
|
let resolved;
|
|
@@ -1054,21 +1057,33 @@ async function exists(filePath) {
|
|
|
1054
1057
|
}
|
|
1055
1058
|
|
|
1056
1059
|
// src/normalize.ts
|
|
1057
|
-
function buildWirePayload(manifest) {
|
|
1060
|
+
function buildWirePayload(manifest, options = {}) {
|
|
1061
|
+
const withPaths = options.installPaths === true;
|
|
1058
1062
|
const seen = /* @__PURE__ */ new Map();
|
|
1059
1063
|
const wirePackages = [];
|
|
1064
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
1065
|
+
let entriesWithoutPath = 0;
|
|
1060
1066
|
for (const entry of manifest.packages) {
|
|
1067
|
+
const identity = `${entry.name}@${entry.version}`;
|
|
1068
|
+
const location = withPaths && entry.path !== void 0 && entry.path !== "" ? entry.path : void 0;
|
|
1069
|
+
if (location === void 0) entriesWithoutPath++;
|
|
1070
|
+
const existing = byIdentity.get(identity);
|
|
1071
|
+
if (existing) {
|
|
1072
|
+
if (location !== void 0) addPath(existing, location);
|
|
1073
|
+
continue;
|
|
1074
|
+
}
|
|
1061
1075
|
const versions = seen.get(entry.name);
|
|
1062
1076
|
if (versions) {
|
|
1063
|
-
if (versions.has(entry.version)) {
|
|
1064
|
-
continue;
|
|
1065
|
-
}
|
|
1066
1077
|
versions.add(entry.version);
|
|
1067
1078
|
} else {
|
|
1068
1079
|
seen.set(entry.name, /* @__PURE__ */ new Set([entry.version]));
|
|
1069
1080
|
}
|
|
1070
|
-
|
|
1081
|
+
const wire = { name: entry.name, version: entry.version };
|
|
1082
|
+
if (location !== void 0) addPath(wire, location);
|
|
1083
|
+
byIdentity.set(identity, wire);
|
|
1084
|
+
wirePackages.push(wire);
|
|
1071
1085
|
}
|
|
1086
|
+
for (const wire of wirePackages) wire.paths?.sort();
|
|
1072
1087
|
wirePackages.sort((a, b) => {
|
|
1073
1088
|
if (a.name === b.name) {
|
|
1074
1089
|
return compareVersions(a.version, b.version);
|
|
@@ -1082,7 +1097,18 @@ function buildWirePayload(manifest) {
|
|
|
1082
1097
|
}
|
|
1083
1098
|
}
|
|
1084
1099
|
return {
|
|
1085
|
-
payload: {
|
|
1100
|
+
payload: {
|
|
1101
|
+
ecosystem: manifest.ecosystem,
|
|
1102
|
+
packages: wirePackages,
|
|
1103
|
+
// Every entry had a location, so a missing path anywhere means the package is not installed there.
|
|
1104
|
+
// One entry without one forfeits that for the whole payload: a consumer reading a short `paths` has
|
|
1105
|
+
// no way to tell which entry was the incomplete one.
|
|
1106
|
+
//
|
|
1107
|
+
// No separate check for "locations were not requested": with them off every entry counts as without
|
|
1108
|
+
// one, so this is already false — which is the default, and the safe direction. An explicit
|
|
1109
|
+
// `withPaths &&` here was a guard no test could distinguish from its absence.
|
|
1110
|
+
installPathsComplete: entriesWithoutPath === 0 && manifest.packages.length > 0
|
|
1111
|
+
},
|
|
1086
1112
|
stats: {
|
|
1087
1113
|
uniqueNames: seen.size,
|
|
1088
1114
|
duplicateNames,
|
|
@@ -1090,6 +1116,10 @@ function buildWirePayload(manifest) {
|
|
|
1090
1116
|
}
|
|
1091
1117
|
};
|
|
1092
1118
|
}
|
|
1119
|
+
function addPath(wire, installPath) {
|
|
1120
|
+
if (wire.paths === void 0) wire.paths = [installPath];
|
|
1121
|
+
else if (!wire.paths.includes(installPath)) wire.paths.push(installPath);
|
|
1122
|
+
}
|
|
1093
1123
|
function compareVersions(a, b) {
|
|
1094
1124
|
if (a === b) {
|
|
1095
1125
|
return 0;
|
|
@@ -1665,7 +1695,7 @@ async function scanAndReport(options = {}) {
|
|
|
1665
1695
|
const cwd = options.cwd ?? process.cwd();
|
|
1666
1696
|
const config = options.config ?? await resolveConfig({ cwd });
|
|
1667
1697
|
const manifest = await scanLockfile(cwd);
|
|
1668
|
-
const { payload, stats } = buildWirePayload(manifest);
|
|
1698
|
+
const { payload, stats } = buildWirePayload(manifest, { installPaths: options.installPaths === true });
|
|
1669
1699
|
const response = await postManifest(config, payload);
|
|
1670
1700
|
if (config.siteUuid === null && response.uuid !== void 0 && response.uuid.length > 0) {
|
|
1671
1701
|
await persistSiteUuid(cwd, response.uuid);
|