@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
package/dist/index.js
CHANGED
|
@@ -34,7 +34,7 @@ async function readDeclaredDependencyNames(cwd) {
|
|
|
34
34
|
return [];
|
|
35
35
|
}
|
|
36
36
|
const names = /* @__PURE__ */ new Set();
|
|
37
|
-
for (const group of ["dependencies", "devDependencies"]) {
|
|
37
|
+
for (const group of ["dependencies", "devDependencies", "optionalDependencies"]) {
|
|
38
38
|
const deps = parsed[group];
|
|
39
39
|
if (typeof deps !== "object" || deps === null) {
|
|
40
40
|
continue;
|
|
@@ -52,14 +52,171 @@ function missingDependencies(declared, packages) {
|
|
|
52
52
|
const present = new Set(packages.map((entry) => entry.name));
|
|
53
53
|
return declared.filter((name) => !present.has(name));
|
|
54
54
|
}
|
|
55
|
+
function disagreements(packages, other) {
|
|
56
|
+
const otherVersions = /* @__PURE__ */ new Map();
|
|
57
|
+
for (const entry of other) {
|
|
58
|
+
if (!otherVersions.has(entry.name)) otherVersions.set(entry.name, entry.version);
|
|
59
|
+
}
|
|
60
|
+
const seen = /* @__PURE__ */ new Set();
|
|
61
|
+
const conflicts = [];
|
|
62
|
+
for (const entry of packages) {
|
|
63
|
+
if (seen.has(entry.name)) continue;
|
|
64
|
+
seen.add(entry.name);
|
|
65
|
+
const otherVersion = otherVersions.get(entry.name);
|
|
66
|
+
if (otherVersion !== void 0 && otherVersion !== entry.version) {
|
|
67
|
+
conflicts.push({ name: entry.name, version: entry.version, otherVersion });
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return conflicts;
|
|
71
|
+
}
|
|
55
72
|
|
|
56
|
-
// src/parsers/
|
|
73
|
+
// src/parsers/bun.ts
|
|
57
74
|
import { readFile as readFile2 } from "fs/promises";
|
|
75
|
+
|
|
76
|
+
// src/parsers/report.ts
|
|
77
|
+
var MAX_SAMPLES = 3;
|
|
78
|
+
function newReport() {
|
|
79
|
+
return { unreadable: 0, samples: [] };
|
|
80
|
+
}
|
|
81
|
+
function recordUnreadable(report, sample) {
|
|
82
|
+
if (!report) return;
|
|
83
|
+
report.unreadable++;
|
|
84
|
+
if (report.samples.length < MAX_SAMPLES) report.samples.push(sample);
|
|
85
|
+
}
|
|
86
|
+
function unreadableWarning(filename, report) {
|
|
87
|
+
if (report.unreadable === 0) return null;
|
|
88
|
+
const shown = report.samples.join(", ");
|
|
89
|
+
const one = report.unreadable === 1;
|
|
90
|
+
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.`;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// src/parsers/bun.ts
|
|
94
|
+
async function parseBunLockfile(lockfilePath, report) {
|
|
95
|
+
let raw;
|
|
96
|
+
try {
|
|
97
|
+
raw = await readFile2(lockfilePath, "utf8");
|
|
98
|
+
} catch (cause) {
|
|
99
|
+
throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, "LOCKFILE_NOT_FOUND", cause);
|
|
100
|
+
}
|
|
101
|
+
let parsed;
|
|
102
|
+
try {
|
|
103
|
+
parsed = JSON.parse(stripTrailingCommas(raw));
|
|
104
|
+
} catch (cause) {
|
|
105
|
+
throw new PatchstackError(`Lockfile at ${lockfilePath} is not valid JSON`, "LOCKFILE_PARSE_ERROR", cause);
|
|
106
|
+
}
|
|
107
|
+
const packages = parsed?.packages;
|
|
108
|
+
if (typeof packages !== "object" || packages === null) {
|
|
109
|
+
throw new PatchstackError(`Lockfile at ${lockfilePath} has no "packages" entries`, "LOCKFILE_PARSE_ERROR");
|
|
110
|
+
}
|
|
111
|
+
const direct = directDependencyNames(parsed);
|
|
112
|
+
const entries = [];
|
|
113
|
+
let skipped = 0;
|
|
114
|
+
for (const [key, value] of Object.entries(packages)) {
|
|
115
|
+
const descriptor = Array.isArray(value) ? value[0] : value;
|
|
116
|
+
if (typeof descriptor !== "string") {
|
|
117
|
+
skipped++;
|
|
118
|
+
recordUnreadable(report, key);
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
const split = splitDescriptor(descriptor);
|
|
122
|
+
if (split === null) {
|
|
123
|
+
skipped++;
|
|
124
|
+
if (!isDeliberatelyExcluded(descriptor)) recordUnreadable(report, key);
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
entries.push({
|
|
128
|
+
name: split.name,
|
|
129
|
+
version: split.version,
|
|
130
|
+
...direct.has(split.name) ? { direct: true } : {},
|
|
131
|
+
...key === split.name ? {} : { path: key }
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
if (entries.length === 0) {
|
|
135
|
+
throw new PatchstackError(
|
|
136
|
+
`Lockfile at ${lockfilePath} lists no registry packages${skipped > 0 ? ` (${skipped} non-registry entr${skipped === 1 ? "y" : "ies"} skipped)` : ""}`,
|
|
137
|
+
"LOCKFILE_PARSE_ERROR"
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
return entries;
|
|
141
|
+
}
|
|
142
|
+
function splitDescriptor(descriptor) {
|
|
143
|
+
const at = descriptor.lastIndexOf("@");
|
|
144
|
+
if (at <= 0) return null;
|
|
145
|
+
const name = descriptor.slice(0, at);
|
|
146
|
+
const version = descriptor.slice(at + 1);
|
|
147
|
+
if (name === "" || version === "") return null;
|
|
148
|
+
if (NON_REGISTRY_VERSION.test(version)) return null;
|
|
149
|
+
return { name, version };
|
|
150
|
+
}
|
|
151
|
+
var NON_REGISTRY_VERSION = /^[a-z][a-z0-9+.-]*:|\//i;
|
|
152
|
+
function isDeliberatelyExcluded(descriptor) {
|
|
153
|
+
const at = descriptor.lastIndexOf("@");
|
|
154
|
+
if (at <= 0) return false;
|
|
155
|
+
return NON_REGISTRY_VERSION.test(descriptor.slice(at + 1));
|
|
156
|
+
}
|
|
157
|
+
function directDependencyNames(parsed) {
|
|
158
|
+
const names = /* @__PURE__ */ new Set();
|
|
159
|
+
const workspaces = parsed?.workspaces;
|
|
160
|
+
if (typeof workspaces !== "object" || workspaces === null) return names;
|
|
161
|
+
for (const workspace of Object.values(workspaces)) {
|
|
162
|
+
if (typeof workspace !== "object" || workspace === null) continue;
|
|
163
|
+
for (const group of ["dependencies", "devDependencies", "optionalDependencies", "peerDependencies"]) {
|
|
164
|
+
const deps = workspace[group];
|
|
165
|
+
if (typeof deps !== "object" || deps === null) continue;
|
|
166
|
+
for (const name of Object.keys(deps)) names.add(name);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
return names;
|
|
170
|
+
}
|
|
171
|
+
function stripTrailingCommas(source) {
|
|
172
|
+
const out = [];
|
|
173
|
+
let inString = false;
|
|
174
|
+
let pendingComma = -1;
|
|
175
|
+
for (let i = 0; i < source.length; i++) {
|
|
176
|
+
const ch = source[i];
|
|
177
|
+
if (inString) {
|
|
178
|
+
out.push(ch);
|
|
179
|
+
if (ch === "\\") {
|
|
180
|
+
const next = source[i + 1];
|
|
181
|
+
if (next !== void 0) {
|
|
182
|
+
out.push(next);
|
|
183
|
+
i++;
|
|
184
|
+
}
|
|
185
|
+
} else if (ch === '"') {
|
|
186
|
+
inString = false;
|
|
187
|
+
}
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
if (ch === '"') {
|
|
191
|
+
inString = true;
|
|
192
|
+
pendingComma = -1;
|
|
193
|
+
out.push(ch);
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (ch === ",") {
|
|
197
|
+
pendingComma = out.length;
|
|
198
|
+
out.push(ch);
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (ch === "}" || ch === "]") {
|
|
202
|
+
if (pendingComma !== -1) out[pendingComma] = " ";
|
|
203
|
+
pendingComma = -1;
|
|
204
|
+
out.push(ch);
|
|
205
|
+
continue;
|
|
206
|
+
}
|
|
207
|
+
if (!/\s/.test(ch)) pendingComma = -1;
|
|
208
|
+
out.push(ch);
|
|
209
|
+
}
|
|
210
|
+
return out.join("");
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// src/parsers/npm.ts
|
|
214
|
+
import { readFile as readFile3 } from "fs/promises";
|
|
58
215
|
import path2 from "path";
|
|
59
216
|
async function parseNpmLockfile(lockfilePath) {
|
|
60
217
|
let raw;
|
|
61
218
|
try {
|
|
62
|
-
raw = await
|
|
219
|
+
raw = await readFile3(lockfilePath, "utf8");
|
|
63
220
|
} catch (cause) {
|
|
64
221
|
throw new PatchstackError(`Could not read lockfile at ${lockfilePath}`, "LOCKFILE_NOT_FOUND", cause);
|
|
65
222
|
}
|
|
@@ -140,8 +297,9 @@ function isDirectV2(pkgPath) {
|
|
|
140
297
|
}
|
|
141
298
|
|
|
142
299
|
// src/parsers/node_modules.ts
|
|
143
|
-
import { lstat, readFile as
|
|
300
|
+
import { lstat, readFile as readFile4, readdir, realpath, stat } from "fs/promises";
|
|
144
301
|
import path3 from "path";
|
|
302
|
+
var PACKAGE_STORE_DIRS = /* @__PURE__ */ new Set([".bun", ".pnpm", ".store"]);
|
|
145
303
|
async function walkNodeModules(cwd) {
|
|
146
304
|
const root = path3.join(cwd, "node_modules");
|
|
147
305
|
try {
|
|
@@ -162,96 +320,125 @@ async function walkNodeModules(cwd) {
|
|
|
162
320
|
cause
|
|
163
321
|
);
|
|
164
322
|
}
|
|
165
|
-
const
|
|
166
|
-
|
|
167
|
-
|
|
323
|
+
const seen = /* @__PURE__ */ new Map();
|
|
324
|
+
try {
|
|
325
|
+
seen.set(await realpath(cwd), -1);
|
|
326
|
+
} catch {
|
|
327
|
+
}
|
|
328
|
+
return dedupeByShallowest(await walk(root, 0, seen, cwd));
|
|
329
|
+
}
|
|
330
|
+
function dedupeByShallowest(found) {
|
|
331
|
+
const best = /* @__PURE__ */ new Map();
|
|
332
|
+
for (const item of found) {
|
|
333
|
+
const current = best.get(item.realpath);
|
|
334
|
+
if (current === void 0 || item.depth < current.depth) best.set(item.realpath, item);
|
|
335
|
+
}
|
|
336
|
+
return [...best.values()].map(({ entry }) => entry);
|
|
168
337
|
}
|
|
169
|
-
|
|
338
|
+
var CONCURRENCY = 32;
|
|
339
|
+
async function walk(dir, depth, seen, root) {
|
|
170
340
|
let names;
|
|
171
341
|
try {
|
|
172
342
|
names = await readdir(dir);
|
|
173
343
|
} catch {
|
|
174
|
-
return;
|
|
344
|
+
return [];
|
|
175
345
|
}
|
|
176
|
-
|
|
177
|
-
if (name.startsWith("."))
|
|
178
|
-
continue;
|
|
179
|
-
}
|
|
346
|
+
const perEntry = await mapLimit(names, async (name) => {
|
|
347
|
+
if (name.startsWith(".") && !PACKAGE_STORE_DIRS.has(name)) return [];
|
|
180
348
|
const fullPath = path3.join(dir, name);
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
349
|
+
const resolved = await claim(fullPath, seen, depth);
|
|
350
|
+
if (resolved === null) return [];
|
|
351
|
+
if (PACKAGE_STORE_DIRS.has(name)) return walk(fullPath, depth, seen, root);
|
|
184
352
|
if (name.startsWith("@")) {
|
|
185
353
|
let subNames;
|
|
186
354
|
try {
|
|
187
355
|
subNames = await readdir(fullPath);
|
|
188
356
|
} catch {
|
|
189
|
-
|
|
357
|
+
return [];
|
|
190
358
|
}
|
|
191
|
-
|
|
192
|
-
if (sub.startsWith("."))
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
359
|
+
const scoped = await mapLimit(subNames, async (sub) => {
|
|
360
|
+
if (sub.startsWith(".")) return [];
|
|
195
361
|
const scopedDir = path3.join(fullPath, sub);
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
}
|
|
202
|
-
continue;
|
|
362
|
+
const scopedReal = await claim(scopedDir, seen, depth);
|
|
363
|
+
if (scopedReal === null) return [];
|
|
364
|
+
return packageAndNested(scopedDir, scopedReal, depth, seen, root);
|
|
365
|
+
});
|
|
366
|
+
return scoped.flat();
|
|
203
367
|
}
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
368
|
+
return packageAndNested(fullPath, resolved, depth, seen, root);
|
|
369
|
+
});
|
|
370
|
+
return perEntry.flat();
|
|
371
|
+
}
|
|
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
|
+
return own === null ? nested : [{ entry: own, realpath: resolved, depth }, ...nested];
|
|
375
|
+
}
|
|
376
|
+
async function mapLimit(items, fn) {
|
|
377
|
+
const results = new Array(items.length);
|
|
378
|
+
let next = 0;
|
|
379
|
+
const workers = Array.from({ length: Math.min(CONCURRENCY, items.length) }, async () => {
|
|
380
|
+
while (next < items.length) {
|
|
381
|
+
const index = next++;
|
|
382
|
+
results[index] = await fn(items[index]);
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
await Promise.all(workers);
|
|
386
|
+
return results;
|
|
387
|
+
}
|
|
388
|
+
function installLocation(root, pkgDir, impl = path3) {
|
|
389
|
+
return impl.relative(root, pkgDir).split(impl.sep).join("/");
|
|
207
390
|
}
|
|
208
|
-
async function readPackage(pkgDir, depth,
|
|
391
|
+
async function readPackage(pkgDir, depth, root) {
|
|
209
392
|
let raw;
|
|
210
393
|
try {
|
|
211
|
-
raw = await
|
|
394
|
+
raw = await readFile4(path3.join(pkgDir, "package.json"), "utf8");
|
|
212
395
|
} catch {
|
|
213
|
-
return;
|
|
396
|
+
return null;
|
|
214
397
|
}
|
|
215
398
|
let parsed;
|
|
216
399
|
try {
|
|
217
400
|
parsed = JSON.parse(raw);
|
|
218
401
|
} catch {
|
|
219
|
-
return;
|
|
402
|
+
return null;
|
|
220
403
|
}
|
|
221
404
|
if (typeof parsed.name !== "string" || parsed.name.length === 0) {
|
|
222
|
-
return;
|
|
405
|
+
return null;
|
|
223
406
|
}
|
|
224
407
|
if (typeof parsed.version !== "string" || parsed.version.length === 0) {
|
|
225
|
-
return;
|
|
408
|
+
return null;
|
|
226
409
|
}
|
|
227
|
-
|
|
228
|
-
name: parsed.name,
|
|
229
|
-
version: parsed.version,
|
|
230
|
-
direct: depth === 0
|
|
231
|
-
});
|
|
410
|
+
return { name: parsed.name, version: parsed.version, path: installLocation(root, pkgDir), direct: depth === 0 };
|
|
232
411
|
}
|
|
233
|
-
async function walkNested(pkgDir,
|
|
412
|
+
async function walkNested(pkgDir, depth, seen, root) {
|
|
234
413
|
const nested = path3.join(pkgDir, "node_modules");
|
|
235
|
-
if (
|
|
236
|
-
return;
|
|
414
|
+
if (await claim(nested, seen, depth + 1) === null) {
|
|
415
|
+
return [];
|
|
237
416
|
}
|
|
238
|
-
|
|
417
|
+
return walk(nested, depth + 1, seen, root);
|
|
239
418
|
}
|
|
240
|
-
async function
|
|
419
|
+
async function claim(dir, seen, depth) {
|
|
420
|
+
let resolved;
|
|
241
421
|
try {
|
|
242
422
|
const info = await lstat(dir);
|
|
243
|
-
|
|
423
|
+
if (!info.isDirectory() && !info.isSymbolicLink()) return null;
|
|
424
|
+
resolved = await realpath(dir);
|
|
425
|
+
const target = await stat(resolved);
|
|
426
|
+
if (!target.isDirectory()) return null;
|
|
244
427
|
} catch {
|
|
245
|
-
return
|
|
428
|
+
return null;
|
|
246
429
|
}
|
|
430
|
+
const claimed = seen.get(resolved);
|
|
431
|
+
if (claimed !== void 0 && claimed <= depth) return null;
|
|
432
|
+
seen.set(resolved, depth);
|
|
433
|
+
return resolved;
|
|
247
434
|
}
|
|
248
435
|
|
|
249
436
|
// src/parsers/pnpm.ts
|
|
250
|
-
import { readFile as
|
|
251
|
-
async function parsePnpmLockfile(lockfilePath) {
|
|
437
|
+
import { readFile as readFile5 } from "fs/promises";
|
|
438
|
+
async function parsePnpmLockfile(lockfilePath, report) {
|
|
252
439
|
let raw;
|
|
253
440
|
try {
|
|
254
|
-
raw = await
|
|
441
|
+
raw = await readFile5(lockfilePath, "utf8");
|
|
255
442
|
} catch (cause) {
|
|
256
443
|
throw new PatchstackError(
|
|
257
444
|
`Could not read lockfile at ${lockfilePath}`,
|
|
@@ -272,6 +459,7 @@ async function parsePnpmLockfile(lockfilePath) {
|
|
|
272
459
|
for (const key of packageKeys) {
|
|
273
460
|
const parsed = parsePackageKey(key);
|
|
274
461
|
if (parsed === null) {
|
|
462
|
+
recordUnreadable(report, key);
|
|
275
463
|
continue;
|
|
276
464
|
}
|
|
277
465
|
entries.push({
|
|
@@ -499,12 +687,12 @@ function extractLeafName(trimmed) {
|
|
|
499
687
|
}
|
|
500
688
|
|
|
501
689
|
// src/parsers/yarn.ts
|
|
502
|
-
import { readFile as
|
|
690
|
+
import { readFile as readFile6 } from "fs/promises";
|
|
503
691
|
import path4 from "path";
|
|
504
|
-
async function parseYarnLockfile(lockfilePath) {
|
|
692
|
+
async function parseYarnLockfile(lockfilePath, report) {
|
|
505
693
|
let raw;
|
|
506
694
|
try {
|
|
507
|
-
raw = await
|
|
695
|
+
raw = await readFile6(lockfilePath, "utf8");
|
|
508
696
|
} catch (cause) {
|
|
509
697
|
throw new PatchstackError(
|
|
510
698
|
`Could not read lockfile at ${lockfilePath}`,
|
|
@@ -512,7 +700,7 @@ async function parseYarnLockfile(lockfilePath) {
|
|
|
512
700
|
cause
|
|
513
701
|
);
|
|
514
702
|
}
|
|
515
|
-
const blocks = parseBlocks(raw);
|
|
703
|
+
const blocks = parseBlocks(raw, report);
|
|
516
704
|
if (blocks.length === 0) {
|
|
517
705
|
throw new PatchstackError(
|
|
518
706
|
`Lockfile at ${lockfilePath} contains no package entries`,
|
|
@@ -541,13 +729,19 @@ async function parseYarnLockfile(lockfilePath) {
|
|
|
541
729
|
}
|
|
542
730
|
return entries;
|
|
543
731
|
}
|
|
544
|
-
|
|
732
|
+
var NON_PACKAGE_KEYS = /* @__PURE__ */ new Set(["__metadata"]);
|
|
733
|
+
function parseBlocks(raw, report) {
|
|
545
734
|
const lines = raw.split(/\r?\n/);
|
|
546
735
|
const blocks = [];
|
|
547
736
|
let current = null;
|
|
548
737
|
const finalize = () => {
|
|
549
|
-
if (current
|
|
738
|
+
if (current === null) {
|
|
739
|
+
return;
|
|
740
|
+
}
|
|
741
|
+
if (current.version.length > 0 && current.names.size > 0) {
|
|
550
742
|
blocks.push(current);
|
|
743
|
+
} else if (current.unreadable) {
|
|
744
|
+
recordUnreadable(report, current.key);
|
|
551
745
|
}
|
|
552
746
|
current = null;
|
|
553
747
|
};
|
|
@@ -564,13 +758,13 @@ function parseBlocks(raw) {
|
|
|
564
758
|
}
|
|
565
759
|
const keyLine = trimmed.slice(0, -1);
|
|
566
760
|
const names = /* @__PURE__ */ new Set();
|
|
761
|
+
let unreadable = false;
|
|
567
762
|
for (const spec of splitDescriptors(keyLine)) {
|
|
568
|
-
const
|
|
569
|
-
if (name !==
|
|
570
|
-
|
|
571
|
-
}
|
|
763
|
+
const classified = classifyDescriptor(spec);
|
|
764
|
+
if (classified.name !== void 0) names.add(classified.name);
|
|
765
|
+
else if (!classified.excluded) unreadable = true;
|
|
572
766
|
}
|
|
573
|
-
current = { names, version: "" };
|
|
767
|
+
current = { names, version: "", key: keyLine, unreadable: unreadable && names.size === 0 };
|
|
574
768
|
continue;
|
|
575
769
|
}
|
|
576
770
|
if (current === null) {
|
|
@@ -625,20 +819,37 @@ function splitDescriptors(keyLine) {
|
|
|
625
819
|
}
|
|
626
820
|
return parts;
|
|
627
821
|
}
|
|
628
|
-
|
|
822
|
+
var NON_REGISTRY_PROTOCOLS = /^(?:workspace|patch|virtual|portal|file|link|exec|git|github|https?|ssh):/i;
|
|
823
|
+
function classifyDescriptor(rawSpec) {
|
|
629
824
|
let s = rawSpec.trim();
|
|
630
825
|
if (s.length === 0) {
|
|
631
|
-
return
|
|
826
|
+
return { excluded: true };
|
|
632
827
|
}
|
|
633
828
|
if (s.startsWith('"') && s.endsWith('"') || s.startsWith("'") && s.endsWith("'")) {
|
|
634
829
|
s = s.slice(1, -1);
|
|
635
830
|
}
|
|
636
|
-
|
|
831
|
+
if (NON_PACKAGE_KEYS.has(s)) {
|
|
832
|
+
return { excluded: true };
|
|
833
|
+
}
|
|
834
|
+
const atIdx = s.indexOf("@", 1);
|
|
637
835
|
if (atIdx <= 0) {
|
|
638
|
-
return
|
|
836
|
+
return {};
|
|
639
837
|
}
|
|
640
838
|
const name = s.slice(0, atIdx);
|
|
641
|
-
|
|
839
|
+
const range = s.slice(atIdx + 1);
|
|
840
|
+
if (name.length === 0) {
|
|
841
|
+
return {};
|
|
842
|
+
}
|
|
843
|
+
if (NON_REGISTRY_PROTOCOLS.test(range)) {
|
|
844
|
+
return { excluded: true };
|
|
845
|
+
}
|
|
846
|
+
const alias = /^npm:(.+)$/i.exec(range);
|
|
847
|
+
if (alias !== null) {
|
|
848
|
+
const target = alias[1] ?? "";
|
|
849
|
+
const targetAt = target.indexOf("@", 1);
|
|
850
|
+
return { name: targetAt > 0 ? target.slice(0, targetAt) : name };
|
|
851
|
+
}
|
|
852
|
+
return { name };
|
|
642
853
|
}
|
|
643
854
|
function parseVersionField(content) {
|
|
644
855
|
if (!content.startsWith("version")) {
|
|
@@ -663,7 +874,7 @@ async function readDirectDepNames(cwd) {
|
|
|
663
874
|
const names = /* @__PURE__ */ new Set();
|
|
664
875
|
let raw;
|
|
665
876
|
try {
|
|
666
|
-
raw = await
|
|
877
|
+
raw = await readFile6(path4.join(cwd, "package.json"), "utf8");
|
|
667
878
|
} catch {
|
|
668
879
|
return names;
|
|
669
880
|
}
|
|
@@ -692,7 +903,10 @@ async function readDirectDepNames(cwd) {
|
|
|
692
903
|
// src/parsers/index.ts
|
|
693
904
|
var LOCKFILE_CANDIDATES = [
|
|
694
905
|
{ filename: "package-lock.json", strategy: "npm-lockfile" },
|
|
695
|
-
|
|
906
|
+
// Read directly, not walked. Bun's isolated layout puts packages under a dot-directory and makes the
|
|
907
|
+
// top level symlinks into it, so a `node_modules` walk finds nothing there — and isolated is the default
|
|
908
|
+
// for new workspaces.
|
|
909
|
+
{ filename: "bun.lock", strategy: "bun-lockfile" },
|
|
696
910
|
{ filename: "bun.lockb", strategy: "node-modules-walk" },
|
|
697
911
|
{ filename: "pnpm-lock.yaml", strategy: "pnpm-lockfile" },
|
|
698
912
|
{ filename: "yarn.lock", strategy: "yarn-lockfile" }
|
|
@@ -727,34 +941,58 @@ async function scanLockfile(cwd) {
|
|
|
727
941
|
const declared = await readDeclaredDependencyNames(cwd);
|
|
728
942
|
const warnings = [];
|
|
729
943
|
let firstParsed = null;
|
|
730
|
-
let
|
|
944
|
+
let walked = null;
|
|
945
|
+
let walkWasCandidate = false;
|
|
946
|
+
const parsedSources = [];
|
|
731
947
|
for (const candidate of candidates) {
|
|
732
948
|
let packages;
|
|
949
|
+
const report = newReport();
|
|
733
950
|
try {
|
|
734
|
-
packages = await runStrategy(candidate, cwd);
|
|
951
|
+
packages = await runStrategy(candidate, cwd, report);
|
|
735
952
|
} catch {
|
|
736
953
|
continue;
|
|
737
954
|
}
|
|
738
|
-
|
|
955
|
+
const unreadable = unreadableWarning(candidate.filename, report);
|
|
956
|
+
if (unreadable !== null) warnings.push(unreadable);
|
|
957
|
+
if (candidate.strategy === "node-modules-walk") {
|
|
958
|
+
walked = packages;
|
|
959
|
+
walkWasCandidate = true;
|
|
960
|
+
}
|
|
739
961
|
firstParsed ??= { packages, filename: candidate.filename };
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
962
|
+
parsedSources.push({ packages, filename: candidate.filename });
|
|
963
|
+
}
|
|
964
|
+
for (const source of parsedSources) {
|
|
965
|
+
const missing = missingDependencies(declared, source.packages);
|
|
966
|
+
if (missing.length > 0) {
|
|
967
|
+
warnings.push(staleWarning(source.filename, missing));
|
|
968
|
+
continue;
|
|
743
969
|
}
|
|
744
|
-
|
|
970
|
+
const conflicts = otherCompleteSources(parsedSources, source, declared).flatMap(
|
|
971
|
+
(other) => disagreements(source.packages, other.packages).map((conflict) => ({ other: other.filename, conflict }))
|
|
972
|
+
);
|
|
973
|
+
if (conflicts.length === 0) {
|
|
974
|
+
return manifestWith(source.packages, warnings);
|
|
975
|
+
}
|
|
976
|
+
warnings.push(conflictWarning(source.filename, conflicts));
|
|
977
|
+
break;
|
|
745
978
|
}
|
|
746
|
-
if (
|
|
979
|
+
if (walked === null) {
|
|
747
980
|
try {
|
|
748
|
-
|
|
749
|
-
const missing = missingDependencies(declared, packages);
|
|
750
|
-
if (missing.length > 0) {
|
|
751
|
-
warnings.push(staleWarning("node_modules/", missing));
|
|
752
|
-
}
|
|
753
|
-
warnings.push("Scanned node_modules/ instead. Delete the stale lockfile to silence this warning.");
|
|
754
|
-
return manifestWith(packages, warnings);
|
|
981
|
+
walked = await walkNodeModules(cwd);
|
|
755
982
|
} catch {
|
|
983
|
+
walked = null;
|
|
756
984
|
}
|
|
757
985
|
}
|
|
986
|
+
if (walked !== null) {
|
|
987
|
+
const missing = missingDependencies(declared, walked);
|
|
988
|
+
if (missing.length > 0) {
|
|
989
|
+
warnings.push(staleWarning("node_modules/", missing));
|
|
990
|
+
}
|
|
991
|
+
warnings.push(
|
|
992
|
+
walkWasCandidate ? "Reporting node_modules/, which is what the build loads." : "Scanned node_modules/ instead. Delete the stale lockfile to silence this warning."
|
|
993
|
+
);
|
|
994
|
+
return manifestWith(walked, warnings);
|
|
995
|
+
}
|
|
758
996
|
if (firstParsed === null) {
|
|
759
997
|
const expected = LOCKFILE_CANDIDATES.map((candidate) => candidate.filename).join(", ");
|
|
760
998
|
throw new PatchstackError(
|
|
@@ -770,6 +1008,15 @@ async function scanLockfile(cwd) {
|
|
|
770
1008
|
function manifestWith(packages, warnings) {
|
|
771
1009
|
return warnings.length > 0 ? { ecosystem: "npm", packages, warnings } : { ecosystem: "npm", packages };
|
|
772
1010
|
}
|
|
1011
|
+
function otherCompleteSources(all, current, declared) {
|
|
1012
|
+
return all.filter(
|
|
1013
|
+
(source) => source.filename !== current.filename && missingDependencies(declared, source.packages).length === 0
|
|
1014
|
+
);
|
|
1015
|
+
}
|
|
1016
|
+
function conflictWarning(filename, conflicts) {
|
|
1017
|
+
const shown = conflicts.slice(0, 3).map(({ other, conflict }) => `${conflict.name} ${conflict.version} vs ${conflict.otherVersion} in ${other}`).join("; ");
|
|
1018
|
+
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.`;
|
|
1019
|
+
}
|
|
773
1020
|
function staleWarning(source, missing) {
|
|
774
1021
|
const sample = missing.slice(0, 3).join(", ");
|
|
775
1022
|
const suffix = missing.length > 3 ? `, +${missing.length - 3} more` : "";
|
|
@@ -784,14 +1031,18 @@ async function presentLockfiles(cwd) {
|
|
|
784
1031
|
);
|
|
785
1032
|
return probed.filter((candidate) => candidate.present).map(({ filename, strategy, filePath }) => ({ ecosystem: "npm", filename, strategy, filePath }));
|
|
786
1033
|
}
|
|
787
|
-
async function runStrategy(detected, cwd) {
|
|
1034
|
+
async function runStrategy(detected, cwd, report) {
|
|
788
1035
|
switch (detected.strategy) {
|
|
789
1036
|
case "npm-lockfile":
|
|
790
1037
|
return parseNpmLockfile(detected.filePath);
|
|
1038
|
+
case "bun-lockfile":
|
|
1039
|
+
return parseBunLockfile(detected.filePath, report);
|
|
1040
|
+
// The hand-written scanners. Each meets entries it was not written for, and each is asked to say how
|
|
1041
|
+
// many it dropped, because a manifest short a package reads exactly like a project without it.
|
|
791
1042
|
case "pnpm-lockfile":
|
|
792
|
-
return parsePnpmLockfile(detected.filePath);
|
|
1043
|
+
return parsePnpmLockfile(detected.filePath, report);
|
|
793
1044
|
case "yarn-lockfile":
|
|
794
|
-
return parseYarnLockfile(detected.filePath);
|
|
1045
|
+
return parseYarnLockfile(detected.filePath, report);
|
|
795
1046
|
case "node-modules-walk":
|
|
796
1047
|
return walkNodeModules(cwd);
|
|
797
1048
|
}
|
|
@@ -806,21 +1057,33 @@ async function exists(filePath) {
|
|
|
806
1057
|
}
|
|
807
1058
|
|
|
808
1059
|
// src/normalize.ts
|
|
809
|
-
function buildWirePayload(manifest) {
|
|
1060
|
+
function buildWirePayload(manifest, options = {}) {
|
|
1061
|
+
const withPaths = options.installPaths === true;
|
|
810
1062
|
const seen = /* @__PURE__ */ new Map();
|
|
811
1063
|
const wirePackages = [];
|
|
1064
|
+
const byIdentity = /* @__PURE__ */ new Map();
|
|
1065
|
+
let entriesWithoutPath = 0;
|
|
812
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
|
+
}
|
|
813
1075
|
const versions = seen.get(entry.name);
|
|
814
1076
|
if (versions) {
|
|
815
|
-
if (versions.has(entry.version)) {
|
|
816
|
-
continue;
|
|
817
|
-
}
|
|
818
1077
|
versions.add(entry.version);
|
|
819
1078
|
} else {
|
|
820
1079
|
seen.set(entry.name, /* @__PURE__ */ new Set([entry.version]));
|
|
821
1080
|
}
|
|
822
|
-
|
|
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);
|
|
823
1085
|
}
|
|
1086
|
+
for (const wire of wirePackages) wire.paths?.sort();
|
|
824
1087
|
wirePackages.sort((a, b) => {
|
|
825
1088
|
if (a.name === b.name) {
|
|
826
1089
|
return compareVersions(a.version, b.version);
|
|
@@ -834,7 +1097,18 @@ function buildWirePayload(manifest) {
|
|
|
834
1097
|
}
|
|
835
1098
|
}
|
|
836
1099
|
return {
|
|
837
|
-
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
|
+
},
|
|
838
1112
|
stats: {
|
|
839
1113
|
uniqueNames: seen.size,
|
|
840
1114
|
duplicateNames,
|
|
@@ -842,6 +1116,10 @@ function buildWirePayload(manifest) {
|
|
|
842
1116
|
}
|
|
843
1117
|
};
|
|
844
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
|
+
}
|
|
845
1123
|
function compareVersions(a, b) {
|
|
846
1124
|
if (a === b) {
|
|
847
1125
|
return 0;
|
|
@@ -1084,12 +1362,14 @@ function isTimeoutError(cause) {
|
|
|
1084
1362
|
}
|
|
1085
1363
|
|
|
1086
1364
|
// src/config.ts
|
|
1087
|
-
import { readFile as
|
|
1365
|
+
import { readFile as readFile7, writeFile, chmod } from "fs/promises";
|
|
1088
1366
|
import path6 from "path";
|
|
1089
1367
|
var CONFIG_FILENAME = ".patchstackrc.json";
|
|
1368
|
+
var SECRET_FILENAME = ".patchstackrc.local.json";
|
|
1090
1369
|
var DEFAULT_ENVIRONMENT = "production";
|
|
1091
1370
|
async function resolveConfig(options) {
|
|
1092
1371
|
const fromFile = await readConfigFile(options.cwd);
|
|
1372
|
+
const fromSecretFile = await readSecretFile(options.cwd);
|
|
1093
1373
|
const fromEnv = readEnv();
|
|
1094
1374
|
const siteUuid = options.cliSiteUuid ?? fromEnv.siteUuid ?? fromFile.siteUuid ?? null;
|
|
1095
1375
|
const endpoint = options.cliEndpoint ?? fromEnv.endpoint ?? fromFile.endpoint ?? DEFAULT_ENDPOINT;
|
|
@@ -1114,8 +1394,8 @@ async function resolveConfig(options) {
|
|
|
1114
1394
|
"CONFIG_MISSING"
|
|
1115
1395
|
);
|
|
1116
1396
|
}
|
|
1117
|
-
const apiKeyRaw = fromEnv.apiKey ?? fromFile.apiKey ?? null;
|
|
1118
|
-
const pulseAuthRaw = fromEnv.pulseAuth ?? fromFile.pulseAuth ?? apiKeyRaw;
|
|
1397
|
+
const apiKeyRaw = fromEnv.apiKey ?? fromSecretFile.apiKey ?? fromFile.apiKey ?? null;
|
|
1398
|
+
const pulseAuthRaw = fromEnv.pulseAuth ?? fromSecretFile.pulseAuth ?? fromFile.pulseAuth ?? apiKeyRaw;
|
|
1119
1399
|
return {
|
|
1120
1400
|
siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,
|
|
1121
1401
|
apiKey: apiKeyRaw === null || apiKeyRaw.length === 0 ? null : apiKeyRaw,
|
|
@@ -1132,23 +1412,88 @@ async function writeConfigFile(cwd, config) {
|
|
|
1132
1412
|
await writeFile(target, content, "utf8");
|
|
1133
1413
|
return target;
|
|
1134
1414
|
}
|
|
1415
|
+
async function writeSecretFile(cwd, secrets) {
|
|
1416
|
+
const target = path6.join(cwd, SECRET_FILENAME);
|
|
1417
|
+
await writeFile(target, JSON.stringify(secrets, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
1418
|
+
try {
|
|
1419
|
+
await chmod(target, 384);
|
|
1420
|
+
} catch {
|
|
1421
|
+
}
|
|
1422
|
+
const ignore = await ensureIgnored(cwd, SECRET_FILENAME);
|
|
1423
|
+
return { path: target, ...ignore };
|
|
1424
|
+
}
|
|
1425
|
+
async function ensureIgnored(cwd, entry) {
|
|
1426
|
+
const target = path6.join(cwd, ".gitignore");
|
|
1427
|
+
let existing = "";
|
|
1428
|
+
try {
|
|
1429
|
+
existing = await readFile7(target, "utf8");
|
|
1430
|
+
} catch (err) {
|
|
1431
|
+
const code = err.code;
|
|
1432
|
+
if (code !== "ENOENT") {
|
|
1433
|
+
return { ignored: false, reason: `.gitignore could not be read (${code ?? "unknown error"})` };
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
if (ignoresEntry(existing, entry)) return { ignored: true };
|
|
1437
|
+
const separator = existing === "" || existing.endsWith("\n") ? "" : "\n";
|
|
1438
|
+
const block = `${separator}
|
|
1439
|
+
# Patchstack credential \u2014 never commit this
|
|
1440
|
+
${entry}
|
|
1441
|
+
`;
|
|
1442
|
+
try {
|
|
1443
|
+
await writeFile(target, existing + block, "utf8");
|
|
1444
|
+
} catch (err) {
|
|
1445
|
+
const code = err.code;
|
|
1446
|
+
return { ignored: false, reason: `.gitignore could not be written (${code ?? "unknown error"})` };
|
|
1447
|
+
}
|
|
1448
|
+
try {
|
|
1449
|
+
const written = await readFile7(target, "utf8");
|
|
1450
|
+
if (ignoresEntry(written, entry)) return { ignored: true };
|
|
1451
|
+
return { ignored: false, reason: ".gitignore does not cover the file after writing it" };
|
|
1452
|
+
} catch (err) {
|
|
1453
|
+
const code = err.code;
|
|
1454
|
+
return { ignored: false, reason: `.gitignore could not be read back (${code ?? "unknown error"})` };
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
function ignoresEntry(contents, entry) {
|
|
1458
|
+
let ignored = false;
|
|
1459
|
+
for (const raw of contents.split("\n")) {
|
|
1460
|
+
const line = raw.trim();
|
|
1461
|
+
if (line === "" || line.startsWith("#")) continue;
|
|
1462
|
+
const negated = line.startsWith("!");
|
|
1463
|
+
const pattern = negated ? line.slice(1).trim() : line;
|
|
1464
|
+
if (pattern === entry || pattern === `/${entry}`) ignored = !negated;
|
|
1465
|
+
}
|
|
1466
|
+
return ignored;
|
|
1467
|
+
}
|
|
1135
1468
|
async function persistSiteUuid(cwd, siteUuid) {
|
|
1136
1469
|
const existing = await readConfigFile(cwd);
|
|
1137
1470
|
return writeConfigFile(cwd, { ...existing, siteUuid });
|
|
1138
1471
|
}
|
|
1139
1472
|
async function persistApiKey(cwd, apiKey) {
|
|
1140
|
-
const { pulseAuth:
|
|
1141
|
-
|
|
1473
|
+
const { apiKey: _movedKey, pulseAuth: _movedPulse, ...publicConfig } = await readConfigFile(cwd);
|
|
1474
|
+
const existingSecrets = await readSecretFile(cwd);
|
|
1475
|
+
const target = await writeSecretFile(cwd, { ...existingSecrets, apiKey, pulseAuth: void 0 });
|
|
1476
|
+
if (_movedKey !== void 0 || _movedPulse !== void 0) {
|
|
1477
|
+
await writeConfigFile(cwd, publicConfig);
|
|
1478
|
+
}
|
|
1479
|
+
return target;
|
|
1142
1480
|
}
|
|
1143
1481
|
async function persistPulseAuth(cwd, pulseAuth) {
|
|
1144
|
-
const existing = await
|
|
1145
|
-
return
|
|
1482
|
+
const existing = await readSecretFile(cwd);
|
|
1483
|
+
return writeSecretFile(cwd, { ...existing, pulseAuth });
|
|
1484
|
+
}
|
|
1485
|
+
async function readSecretFile(cwd) {
|
|
1486
|
+
const file = await readJsonFile(cwd, SECRET_FILENAME);
|
|
1487
|
+
return { apiKey: file.apiKey, pulseAuth: file.pulseAuth };
|
|
1146
1488
|
}
|
|
1147
1489
|
async function readConfigFile(cwd) {
|
|
1148
|
-
|
|
1490
|
+
return readJsonFile(cwd, CONFIG_FILENAME);
|
|
1491
|
+
}
|
|
1492
|
+
async function readJsonFile(cwd, filename) {
|
|
1493
|
+
const target = path6.join(cwd, filename);
|
|
1149
1494
|
let raw;
|
|
1150
1495
|
try {
|
|
1151
|
-
raw = await
|
|
1496
|
+
raw = await readFile7(target, "utf8");
|
|
1152
1497
|
} catch (err) {
|
|
1153
1498
|
if (err.code === "ENOENT") {
|
|
1154
1499
|
return {};
|
|
@@ -1350,7 +1695,7 @@ async function scanAndReport(options = {}) {
|
|
|
1350
1695
|
const cwd = options.cwd ?? process.cwd();
|
|
1351
1696
|
const config = options.config ?? await resolveConfig({ cwd });
|
|
1352
1697
|
const manifest = await scanLockfile(cwd);
|
|
1353
|
-
const { payload, stats } = buildWirePayload(manifest);
|
|
1698
|
+
const { payload, stats } = buildWirePayload(manifest, { installPaths: options.installPaths === true });
|
|
1354
1699
|
const response = await postManifest(config, payload);
|
|
1355
1700
|
if (config.siteUuid === null && response.uuid !== void 0 && response.uuid.length > 0) {
|
|
1356
1701
|
await persistSiteUuid(cwd, response.uuid);
|