@patchstack/connect 0.3.30 → 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 +5 -2
- package/README.md +16 -1
- package/dist/cli.js +214 -109
- 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 +48 -1
- package/dist/index.d.ts +48 -1
- package/dist/index.js +48 -18
- package/dist/index.js.map +1 -1
- package/dist/protect.cjs +176 -28
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.edge.js +129 -11
- package/dist/protect.edge.js.map +2 -2
- package/dist/protect.js +130 -12
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-ZSP76JWQ.js → refresh-manifest-KNZQBC4V.js} +48 -18
- package/dist/refresh-manifest-KNZQBC4V.js.map +1 -0
- package/package.json +1 -1
- package/dist/refresh-manifest-ZSP76JWQ.js.map +0 -1
package/dist/index.d.cts
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";
|
|
@@ -270,6 +312,11 @@ declare function ensureSourceWidget(cwd: string, siteUuid: string): SourceWidget
|
|
|
270
312
|
interface ScanAndReportOptions {
|
|
271
313
|
cwd?: string;
|
|
272
314
|
config?: Config;
|
|
315
|
+
/**
|
|
316
|
+
* Include each package's install location in the uploaded payload. Off by default: it widens what
|
|
317
|
+
* leaves the machine, so it is an explicit choice rather than something an upgrade turns on.
|
|
318
|
+
*/
|
|
319
|
+
installPaths?: boolean;
|
|
273
320
|
}
|
|
274
321
|
interface ScanAndReportResult {
|
|
275
322
|
manifest: Manifest;
|
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";
|
|
@@ -270,6 +312,11 @@ declare function ensureSourceWidget(cwd: string, siteUuid: string): SourceWidget
|
|
|
270
312
|
interface ScanAndReportOptions {
|
|
271
313
|
cwd?: string;
|
|
272
314
|
config?: Config;
|
|
315
|
+
/**
|
|
316
|
+
* Include each package's install location in the uploaded payload. Off by default: it widens what
|
|
317
|
+
* leaves the machine, so it is an explicit choice rather than something an upgrade turns on.
|
|
318
|
+
*/
|
|
319
|
+
installPaths?: boolean;
|
|
273
320
|
}
|
|
274
321
|
interface ScanAndReportResult {
|
|
275
322
|
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);
|