@patchstack/connect 0.5.6 → 0.5.8
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 +2 -2
- package/README.md +4 -4
- package/dist/chunk-4OHAYSV2.js +295 -0
- package/dist/chunk-4OHAYSV2.js.map +1 -0
- package/dist/cli.js +1089 -470
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +450 -41
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +10 -3
- package/dist/index.d.ts +10 -3
- package/dist/index.js +461 -39
- package/dist/index.js.map +1 -1
- package/dist/protect/templates/demo-rules.json +1 -1
- package/dist/protect.cjs +1226 -244
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.cts +7 -2
- package/dist/protect.d.ts +7 -2
- package/dist/protect.edge.js +992 -179
- package/dist/protect.edge.js.map +3 -3
- package/dist/protect.js +846 -192
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-2ALIJGZ3.js → refresh-manifest-IE6SPHG6.js} +168 -41
- package/dist/refresh-manifest-IE6SPHG6.js.map +1 -0
- package/package.json +1 -1
- package/dist/chunk-IOODP4ZB.js +0 -104
- package/dist/chunk-IOODP4ZB.js.map +0 -1
- package/dist/refresh-manifest-2ALIJGZ3.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,131 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { readFileSync as
|
|
5
|
-
import
|
|
4
|
+
import { readFileSync as readFileSync19 } from "fs";
|
|
5
|
+
import path17 from "path";
|
|
6
6
|
import { createRequire as createRequire2 } from "module";
|
|
7
7
|
|
|
8
|
+
// src/safe-file.ts
|
|
9
|
+
import {
|
|
10
|
+
chmodSync,
|
|
11
|
+
closeSync,
|
|
12
|
+
fsyncSync,
|
|
13
|
+
lstatSync,
|
|
14
|
+
mkdirSync,
|
|
15
|
+
openSync,
|
|
16
|
+
readFileSync,
|
|
17
|
+
realpathSync,
|
|
18
|
+
renameSync,
|
|
19
|
+
statSync,
|
|
20
|
+
unlinkSync,
|
|
21
|
+
writeFileSync
|
|
22
|
+
} from "fs";
|
|
23
|
+
import { randomUUID } from "crypto";
|
|
24
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
25
|
+
function pathError(message) {
|
|
26
|
+
return Object.assign(new Error(message), { code: "EINVAL" });
|
|
27
|
+
}
|
|
28
|
+
function existingEntry(path18) {
|
|
29
|
+
try {
|
|
30
|
+
return lstatSync(path18);
|
|
31
|
+
} catch (error) {
|
|
32
|
+
if (error.code === "ENOENT") return null;
|
|
33
|
+
throw error;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function assertWritableTarget(target) {
|
|
37
|
+
const entry = existingEntry(target);
|
|
38
|
+
if (entry?.isSymbolicLink()) throw pathError(`refusing to replace symbolic link: ${target}`);
|
|
39
|
+
if (entry && !entry.isFile()) throw pathError(`refusing to replace non-file path: ${target}`);
|
|
40
|
+
return entry;
|
|
41
|
+
}
|
|
42
|
+
function atomicWriteFileSync(target, content, options = {}) {
|
|
43
|
+
const current = assertWritableTarget(target);
|
|
44
|
+
const preserveMode = options.preserveMode !== false;
|
|
45
|
+
const exactMode = options.mode ?? (preserveMode && current ? current.mode & 4095 : options.createMode);
|
|
46
|
+
const temporary = join(dirname(target), `.${randomUUID()}.patchstack-tmp`);
|
|
47
|
+
let descriptor = null;
|
|
48
|
+
try {
|
|
49
|
+
descriptor = openSync(temporary, "wx", exactMode ?? 438);
|
|
50
|
+
writeFileSync(descriptor, content, options.encoding);
|
|
51
|
+
fsyncSync(descriptor);
|
|
52
|
+
closeSync(descriptor);
|
|
53
|
+
descriptor = null;
|
|
54
|
+
if (exactMode !== void 0) chmodSync(temporary, exactMode);
|
|
55
|
+
try {
|
|
56
|
+
renameSync(temporary, target);
|
|
57
|
+
} catch (error) {
|
|
58
|
+
const code = error.code;
|
|
59
|
+
if (process.platform !== "win32" || code !== "EEXIST" && code !== "EPERM") throw error;
|
|
60
|
+
try {
|
|
61
|
+
unlinkSync(target);
|
|
62
|
+
} catch (unlinkError) {
|
|
63
|
+
if (unlinkError.code !== "ENOENT") throw unlinkError;
|
|
64
|
+
}
|
|
65
|
+
renameSync(temporary, target);
|
|
66
|
+
}
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (descriptor !== null) {
|
|
69
|
+
try {
|
|
70
|
+
closeSync(descriptor);
|
|
71
|
+
} catch {
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
try {
|
|
75
|
+
unlinkSync(temporary);
|
|
76
|
+
} catch {
|
|
77
|
+
}
|
|
78
|
+
throw error;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
function projectTarget(cwd, target, allowRoot = false) {
|
|
82
|
+
const logicalRoot = resolve(cwd);
|
|
83
|
+
const logicalTarget = isAbsolute(target) ? resolve(target) : resolve(logicalRoot, target);
|
|
84
|
+
const rel = relative(logicalRoot, logicalTarget);
|
|
85
|
+
if (!allowRoot && rel === "" || rel === ".." || rel.startsWith(`..${sep}`) || isAbsolute(rel)) {
|
|
86
|
+
throw pathError(`path is outside the project: ${target}`);
|
|
87
|
+
}
|
|
88
|
+
const root = realpathSync(logicalRoot);
|
|
89
|
+
return { root, path: rel === "" ? root : join(root, rel), parts: rel === "" ? [] : rel.split(sep) };
|
|
90
|
+
}
|
|
91
|
+
function assertProjectParents(root, parts) {
|
|
92
|
+
let current = root;
|
|
93
|
+
for (const part of parts.slice(0, -1)) {
|
|
94
|
+
current = join(current, part);
|
|
95
|
+
const entry = existingEntry(current);
|
|
96
|
+
if (entry === null) throw Object.assign(new Error(`project directory does not exist: ${current}`), { code: "ENOENT" });
|
|
97
|
+
if (entry.isSymbolicLink() || !entry.isDirectory()) {
|
|
98
|
+
throw pathError(`project path contains a non-directory or symbolic link: ${current}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function ensureProjectDirectorySync(cwd, target) {
|
|
103
|
+
const resolved = projectTarget(cwd, target, true);
|
|
104
|
+
let current = resolved.root;
|
|
105
|
+
for (const part of resolved.parts) {
|
|
106
|
+
current = join(current, part);
|
|
107
|
+
let entry = existingEntry(current);
|
|
108
|
+
if (entry === null) {
|
|
109
|
+
mkdirSync(current);
|
|
110
|
+
entry = lstatSync(current);
|
|
111
|
+
}
|
|
112
|
+
if (entry.isSymbolicLink() || !entry.isDirectory()) {
|
|
113
|
+
throw pathError(`project path contains a non-directory or symbolic link: ${current}`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
return resolved.path;
|
|
117
|
+
}
|
|
118
|
+
function writeProjectFileSync(cwd, target, content, options = {}) {
|
|
119
|
+
const resolved = projectTarget(cwd, target);
|
|
120
|
+
assertProjectParents(resolved.root, resolved.parts);
|
|
121
|
+
atomicWriteFileSync(resolved.path, content, options);
|
|
122
|
+
}
|
|
123
|
+
function copyProjectFileSync(cwd, source, target) {
|
|
124
|
+
const content = readFileSync(source);
|
|
125
|
+
const sourceMode = statSync(source).mode & 4095;
|
|
126
|
+
writeProjectFileSync(cwd, target, content, { createMode: sourceMode });
|
|
127
|
+
}
|
|
128
|
+
|
|
8
129
|
// src/parsers/index.ts
|
|
9
130
|
import { access } from "fs/promises";
|
|
10
131
|
import path5 from "path";
|
|
@@ -1217,8 +1338,8 @@ function computeManifestChecksum(packages) {
|
|
|
1217
1338
|
var RULES = [
|
|
1218
1339
|
{ platform: "netlify", any: ["NETLIFY", "NETLIFY_BUILD_BASE", "DEPLOY_PRIME_URL"] },
|
|
1219
1340
|
{ platform: "vercel", any: ["VERCEL", "VERCEL_ENV", "VERCEL_URL"] },
|
|
1220
|
-
{ platform: "cloudflare", any: ["CF_PAGES", "CF_PAGES_URL", "CLOUDFLARE_ACCOUNT_ID"] },
|
|
1221
|
-
{ platform: "aws", any: ["AWS_APP_ID", "AWS_LAMBDA_FUNCTION_NAME", "AWS_EXECUTION_ENV"] },
|
|
1341
|
+
{ platform: "cloudflare", any: ["CF_PAGES", "CF_PAGES_URL", "WORKERS_CI", "WORKERS_CI_BRANCH", "CLOUDFLARE_ACCOUNT_ID"] },
|
|
1342
|
+
{ platform: "aws", any: ["AWS_APP_ID", "AWS_BRANCH", "AWS_LAMBDA_FUNCTION_NAME", "AWS_EXECUTION_ENV"] },
|
|
1222
1343
|
{ platform: "render", any: ["RENDER", "RENDER_SERVICE_ID", "RENDER_EXTERNAL_URL"] },
|
|
1223
1344
|
{ platform: "railway", any: ["RAILWAY_ENVIRONMENT", "RAILWAY_ENVIRONMENT_NAME", "RAILWAY_PROJECT_ID"] },
|
|
1224
1345
|
{ platform: "fly", any: ["FLY_APP_NAME", "FLY_REGION", "FLY_ALLOC_ID"] },
|
|
@@ -1241,12 +1362,134 @@ function detectHostingPlatform(env = process.env) {
|
|
|
1241
1362
|
return { platform: null, evidence: [] };
|
|
1242
1363
|
}
|
|
1243
1364
|
|
|
1365
|
+
// src/protect/safe-origin.js
|
|
1366
|
+
function isSafeOrigin(value) {
|
|
1367
|
+
try {
|
|
1368
|
+
const u = new URL(value);
|
|
1369
|
+
if (u.protocol === "https:") return true;
|
|
1370
|
+
return u.protocol === "http:" && (u.hostname === "localhost" || u.hostname === "127.0.0.1" || u.hostname === "[::1]" || u.hostname === "::1");
|
|
1371
|
+
} catch {
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
|
|
1376
|
+
// src/endpoint-policy.ts
|
|
1377
|
+
function assertConnectableEndpoint(config, target = config.endpoint) {
|
|
1378
|
+
if (config.endpointTrusted === false) {
|
|
1379
|
+
throw new PatchstackError(
|
|
1380
|
+
"The custom endpoint in .patchstackrc.json must be confirmed with --endpoint or PATCHSTACK_ENDPOINT before it can be used.",
|
|
1381
|
+
"CONFIG_INVALID"
|
|
1382
|
+
);
|
|
1383
|
+
}
|
|
1384
|
+
if (safeRemoteUrl(config.endpoint) === null) {
|
|
1385
|
+
throw new PatchstackError(
|
|
1386
|
+
"Patchstack endpoints must use HTTPS, except for localhost development endpoints.",
|
|
1387
|
+
"CONFIG_INVALID"
|
|
1388
|
+
);
|
|
1389
|
+
}
|
|
1390
|
+
let endpointOrigin;
|
|
1391
|
+
let targetOrigin;
|
|
1392
|
+
try {
|
|
1393
|
+
endpointOrigin = new URL(config.endpoint).origin;
|
|
1394
|
+
targetOrigin = new URL(target).origin;
|
|
1395
|
+
} catch (cause) {
|
|
1396
|
+
throw new PatchstackError("The configured Patchstack endpoint is not a valid URL.", "CONFIG_INVALID", cause);
|
|
1397
|
+
}
|
|
1398
|
+
if (safeRemoteUrl(target) === null || targetOrigin !== endpointOrigin) {
|
|
1399
|
+
throw new PatchstackError(
|
|
1400
|
+
"A Patchstack request resolved outside the configured endpoint origin.",
|
|
1401
|
+
"CONFIG_INVALID"
|
|
1402
|
+
);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
function safeRemoteUrl(value) {
|
|
1406
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 2048) return null;
|
|
1407
|
+
try {
|
|
1408
|
+
const parsed = new URL(value);
|
|
1409
|
+
if (!isSafeOrigin(parsed.toString()) || parsed.username !== "" || parsed.password !== "") return null;
|
|
1410
|
+
return parsed.toString();
|
|
1411
|
+
} catch {
|
|
1412
|
+
return null;
|
|
1413
|
+
}
|
|
1414
|
+
}
|
|
1415
|
+
function safeDisplayString(value, maxLength = 512) {
|
|
1416
|
+
if (typeof value !== "string") return null;
|
|
1417
|
+
const clean = value.replace(/\u001b(?:\[[0-?]*[ -/]*[@-~]|\][^\u0007]*(?:\u0007|\u001b\\))/g, "").replace(/[\u0000-\u001f\u007f-\u009f]/g, " ").replace(/\s+/g, " ").trim();
|
|
1418
|
+
if (clean.length === 0) return null;
|
|
1419
|
+
return clean.slice(0, maxLength);
|
|
1420
|
+
}
|
|
1421
|
+
function isCanonicalUuid(value) {
|
|
1422
|
+
return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
1423
|
+
}
|
|
1424
|
+
function isCredential(value) {
|
|
1425
|
+
if (typeof value !== "string" || value.length === 0 || value.length > 4096) return false;
|
|
1426
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
1427
|
+
const code = value.charCodeAt(i);
|
|
1428
|
+
if (code < 33 || code > 126) return false;
|
|
1429
|
+
}
|
|
1430
|
+
const separator = value.lastIndexOf("-");
|
|
1431
|
+
return separator > 0 && separator < value.length - 1 && /^\d+$/.test(value.slice(separator + 1));
|
|
1432
|
+
}
|
|
1433
|
+
function isHeaderValue(value, maxLength = 8192) {
|
|
1434
|
+
if (typeof value !== "string" || value.length === 0 || value.length > maxLength) return false;
|
|
1435
|
+
for (let i = 0; i < value.length; i += 1) {
|
|
1436
|
+
const code = value.charCodeAt(i);
|
|
1437
|
+
if (code < 33 || code > 126) return false;
|
|
1438
|
+
}
|
|
1439
|
+
return true;
|
|
1440
|
+
}
|
|
1441
|
+
|
|
1442
|
+
// src/bounded-response.ts
|
|
1443
|
+
var MAX_CONTROL_RESPONSE_BYTES = 256 * 1024;
|
|
1444
|
+
async function readBoundedText(response, maxBytes = MAX_CONTROL_RESPONSE_BYTES) {
|
|
1445
|
+
const stated2 = Number(response.headers?.get?.("content-length"));
|
|
1446
|
+
if (Number.isFinite(stated2) && stated2 > maxBytes) {
|
|
1447
|
+
void response.body?.cancel().catch(() => {
|
|
1448
|
+
});
|
|
1449
|
+
throw new Error(`response exceeds ${maxBytes} bytes`);
|
|
1450
|
+
}
|
|
1451
|
+
const stream = response.body;
|
|
1452
|
+
if (stream === null || typeof stream.getReader !== "function") {
|
|
1453
|
+
if (stated2 === 0) return "";
|
|
1454
|
+
throw new Error("response body cannot be read with a size bound");
|
|
1455
|
+
}
|
|
1456
|
+
const reader = stream.getReader();
|
|
1457
|
+
const chunks = [];
|
|
1458
|
+
let total = 0;
|
|
1459
|
+
try {
|
|
1460
|
+
for (; ; ) {
|
|
1461
|
+
const { done, value } = await reader.read();
|
|
1462
|
+
if (done) break;
|
|
1463
|
+
total += value.byteLength;
|
|
1464
|
+
if (total > maxBytes) {
|
|
1465
|
+
void reader.cancel().catch(() => {
|
|
1466
|
+
});
|
|
1467
|
+
throw new Error(`response exceeds ${maxBytes} bytes`);
|
|
1468
|
+
}
|
|
1469
|
+
chunks.push(value);
|
|
1470
|
+
}
|
|
1471
|
+
} finally {
|
|
1472
|
+
reader.releaseLock();
|
|
1473
|
+
}
|
|
1474
|
+
const bytes = new Uint8Array(total);
|
|
1475
|
+
let offset = 0;
|
|
1476
|
+
for (const chunk of chunks) {
|
|
1477
|
+
bytes.set(chunk, offset);
|
|
1478
|
+
offset += chunk.byteLength;
|
|
1479
|
+
}
|
|
1480
|
+
return new TextDecoder().decode(bytes);
|
|
1481
|
+
}
|
|
1482
|
+
async function readBoundedJson(response, maxBytes = MAX_CONTROL_RESPONSE_BYTES) {
|
|
1483
|
+
const text = await readBoundedText(response, maxBytes);
|
|
1484
|
+
return JSON.parse(text);
|
|
1485
|
+
}
|
|
1486
|
+
|
|
1244
1487
|
// src/pulse-token.ts
|
|
1245
1488
|
var TOKEN_SKEW_MS = 6e4;
|
|
1246
1489
|
function buildTokenUrl(manifestEndpoint) {
|
|
1247
1490
|
const url = new URL(manifestEndpoint);
|
|
1248
|
-
const
|
|
1249
|
-
url.pathname =
|
|
1491
|
+
const path18 = url.pathname.replace(/\/$/, "");
|
|
1492
|
+
url.pathname = path18.endsWith("/manifest") ? `${path18.slice(0, -"/manifest".length)}/token` : "/monitor/pulse/token";
|
|
1250
1493
|
url.search = "";
|
|
1251
1494
|
url.hash = "";
|
|
1252
1495
|
return url.toString();
|
|
@@ -1258,22 +1501,48 @@ function parsePulseAuth(credential) {
|
|
|
1258
1501
|
if (!/^\d+$/.test(clientId)) return null;
|
|
1259
1502
|
return { clientId, clientSecret: credential.slice(0, index) };
|
|
1260
1503
|
}
|
|
1261
|
-
var cached =
|
|
1262
|
-
var inflight =
|
|
1263
|
-
function
|
|
1264
|
-
|
|
1504
|
+
var cached = /* @__PURE__ */ new Map();
|
|
1505
|
+
var inflight = /* @__PURE__ */ new Map();
|
|
1506
|
+
function cacheKey(tokenUrl, credential) {
|
|
1507
|
+
return `${tokenUrl}
|
|
1508
|
+
${credential}`;
|
|
1509
|
+
}
|
|
1510
|
+
function clearPulseToken(config) {
|
|
1511
|
+
if (!config || typeof config.pulseAuth !== "string") {
|
|
1512
|
+
cached.clear();
|
|
1513
|
+
inflight.clear();
|
|
1514
|
+
return;
|
|
1515
|
+
}
|
|
1516
|
+
try {
|
|
1517
|
+
const key = cacheKey(buildTokenUrl(config.endpoint), config.pulseAuth);
|
|
1518
|
+
cached.delete(key);
|
|
1519
|
+
inflight.delete(key);
|
|
1520
|
+
} catch {
|
|
1521
|
+
}
|
|
1265
1522
|
}
|
|
1266
1523
|
async function getPulseToken(config, fetchImpl = fetch) {
|
|
1267
1524
|
if (typeof config.pulseAuth !== "string" || config.pulseAuth.length === 0) return null;
|
|
1268
|
-
if (
|
|
1269
|
-
|
|
1525
|
+
if (config.endpointTrusted === false) return null;
|
|
1526
|
+
let tokenUrl;
|
|
1527
|
+
try {
|
|
1528
|
+
tokenUrl = buildTokenUrl(config.endpoint);
|
|
1529
|
+
} catch {
|
|
1530
|
+
return null;
|
|
1531
|
+
}
|
|
1532
|
+
if (!isSafeOrigin(tokenUrl)) return null;
|
|
1533
|
+
const key = cacheKey(tokenUrl, config.pulseAuth);
|
|
1534
|
+
const existing = cached.get(key);
|
|
1535
|
+
if (existing !== void 0 && Date.now() < existing.expiresAt - TOKEN_SKEW_MS) {
|
|
1536
|
+
return existing.token;
|
|
1270
1537
|
}
|
|
1271
|
-
|
|
1538
|
+
const pending = inflight.get(key);
|
|
1539
|
+
if (pending !== void 0) return pending;
|
|
1272
1540
|
const credentials = parsePulseAuth(config.pulseAuth);
|
|
1273
1541
|
if (credentials === null) return null;
|
|
1274
|
-
|
|
1542
|
+
let exchange;
|
|
1543
|
+
exchange = (async () => {
|
|
1275
1544
|
try {
|
|
1276
|
-
const response = await fetchImpl(
|
|
1545
|
+
const response = await fetchImpl(tokenUrl, {
|
|
1277
1546
|
method: "POST",
|
|
1278
1547
|
headers: {
|
|
1279
1548
|
"Content-Type": "application/json",
|
|
@@ -1288,25 +1557,27 @@ async function getPulseToken(config, fetchImpl = fetch) {
|
|
|
1288
1557
|
signal: AbortSignal.timeout(config.timeoutMs)
|
|
1289
1558
|
});
|
|
1290
1559
|
if (!response.ok) return null;
|
|
1291
|
-
const body = await response
|
|
1292
|
-
if (
|
|
1560
|
+
const body = await readBoundedJson(response);
|
|
1561
|
+
if (!isHeaderValue(body.access_token)) return null;
|
|
1293
1562
|
const expiresIn = Number(body.expires_in);
|
|
1294
1563
|
const ttlMs = Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn * 1e3 : 36e5;
|
|
1295
|
-
cached
|
|
1564
|
+
cached.set(key, { token: body.access_token, expiresAt: Date.now() + ttlMs });
|
|
1296
1565
|
return body.access_token;
|
|
1297
1566
|
} catch {
|
|
1298
1567
|
return null;
|
|
1299
1568
|
} finally {
|
|
1300
|
-
inflight
|
|
1569
|
+
if (inflight.get(key) === exchange) inflight.delete(key);
|
|
1301
1570
|
}
|
|
1302
1571
|
})();
|
|
1303
|
-
|
|
1572
|
+
inflight.set(key, exchange);
|
|
1573
|
+
return exchange;
|
|
1304
1574
|
}
|
|
1305
1575
|
async function pulseAuthHeader(config, fetchImpl = fetch) {
|
|
1306
1576
|
const token = await getPulseToken(config, fetchImpl);
|
|
1307
1577
|
return token === null ? {} : { Authorization: `Bearer ${token}` };
|
|
1308
1578
|
}
|
|
1309
1579
|
async function pulseFetch(config, url, init, fetchImpl = fetch) {
|
|
1580
|
+
assertConnectableEndpoint(config, url);
|
|
1310
1581
|
const send = async () => {
|
|
1311
1582
|
const auth = await pulseAuthHeader(config, fetchImpl);
|
|
1312
1583
|
const response = await fetchImpl(url, {
|
|
@@ -1317,7 +1588,7 @@ async function pulseFetch(config, url, init, fetchImpl = fetch) {
|
|
|
1317
1588
|
};
|
|
1318
1589
|
const first = await send();
|
|
1319
1590
|
if (first.response.status === 401 && first.authenticated) {
|
|
1320
|
-
clearPulseToken();
|
|
1591
|
+
clearPulseToken(config);
|
|
1321
1592
|
return (await send()).response;
|
|
1322
1593
|
}
|
|
1323
1594
|
return first.response;
|
|
@@ -1349,12 +1620,13 @@ var DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest";
|
|
|
1349
1620
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
1350
1621
|
var CLAIM_TOKEN_HEADER = "X-Patchstack-Claim-Token";
|
|
1351
1622
|
function claimTokenHeader(config) {
|
|
1352
|
-
return
|
|
1623
|
+
return config.endpointTrusted !== false && isHeaderValue(config.claimToken) ? { [CLAIM_TOKEN_HEADER]: config.claimToken } : {};
|
|
1353
1624
|
}
|
|
1354
1625
|
function claimOutcomeLines(claim3, config) {
|
|
1355
1626
|
if (typeof config.claimToken !== "string" || config.claimToken === "") return [];
|
|
1356
1627
|
if (claim3?.state === "claimed" || claim3?.state === "owned-by-you") {
|
|
1357
|
-
const
|
|
1628
|
+
const dashboardUrl = safeRemoteUrl(claim3.dashboard_url);
|
|
1629
|
+
const dashboard = dashboardUrl === null ? [] : [`Dashboard: ${dashboardUrl}`];
|
|
1358
1630
|
return [
|
|
1359
1631
|
claim3.state === "claimed" ? "Connected to your Patchstack account." : "This site is already connected to your Patchstack account.",
|
|
1360
1632
|
...dashboard
|
|
@@ -1385,8 +1657,8 @@ function authFailureMessage(status, config) {
|
|
|
1385
1657
|
}
|
|
1386
1658
|
function buildRulesUrl(manifestEndpoint, siteUuid) {
|
|
1387
1659
|
const url = new URL(manifestEndpoint);
|
|
1388
|
-
const
|
|
1389
|
-
url.pathname =
|
|
1660
|
+
const path18 = url.pathname.replace(/\/$/, "");
|
|
1661
|
+
url.pathname = path18.endsWith("/manifest") ? `${path18.slice(0, -"/manifest".length)}/rules/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/rules/${encodeURIComponent(siteUuid)}`;
|
|
1390
1662
|
url.search = "";
|
|
1391
1663
|
url.hash = "";
|
|
1392
1664
|
return url.toString();
|
|
@@ -1401,16 +1673,16 @@ function buildSettingsUrl(endpoint, siteUuid) {
|
|
|
1401
1673
|
}
|
|
1402
1674
|
function buildPackageRemovedUrl(manifestEndpoint, siteUuid) {
|
|
1403
1675
|
const url = new URL(manifestEndpoint);
|
|
1404
|
-
const
|
|
1405
|
-
url.pathname =
|
|
1676
|
+
const path18 = url.pathname.replace(/\/$/, "");
|
|
1677
|
+
url.pathname = path18.endsWith("/manifest") ? `${path18.slice(0, -"/manifest".length)}/package-removed/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/package-removed/${encodeURIComponent(siteUuid)}`;
|
|
1406
1678
|
url.search = "";
|
|
1407
1679
|
url.hash = "";
|
|
1408
1680
|
return url.toString();
|
|
1409
1681
|
}
|
|
1410
1682
|
function buildInputMapUrl(manifestEndpoint, siteUuid) {
|
|
1411
1683
|
const url = new URL(manifestEndpoint);
|
|
1412
|
-
const
|
|
1413
|
-
url.pathname =
|
|
1684
|
+
const path18 = url.pathname.replace(/\/$/, "");
|
|
1685
|
+
url.pathname = path18.endsWith("/manifest") ? `${path18.slice(0, -"/manifest".length)}/input-map/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/input-map/${encodeURIComponent(siteUuid)}`;
|
|
1414
1686
|
url.search = "";
|
|
1415
1687
|
url.hash = "";
|
|
1416
1688
|
return url.toString();
|
|
@@ -1450,7 +1722,7 @@ async function postInputMap(config, map, buildId) {
|
|
|
1450
1722
|
if (!response.ok) {
|
|
1451
1723
|
return { result: "failed", message: `Patchstack returned ${response.status}.` };
|
|
1452
1724
|
}
|
|
1453
|
-
const body = await response
|
|
1725
|
+
const body = await readBoundedJson(response);
|
|
1454
1726
|
const revision = Number(body.revision);
|
|
1455
1727
|
if ((body.result === "stored" || body.result === "unchanged") && Number.isInteger(revision) && revision > 0) {
|
|
1456
1728
|
return { result: body.result, revision };
|
|
@@ -1487,9 +1759,9 @@ async function postPackageRemoved(config) {
|
|
|
1487
1759
|
if (!response.ok) {
|
|
1488
1760
|
return { result: "failed", message: `Patchstack returned ${response.status}.` };
|
|
1489
1761
|
}
|
|
1490
|
-
const body = await response
|
|
1762
|
+
const body = await readBoundedJson(response);
|
|
1491
1763
|
if (body.status === "deleted" || body.status === "flagged") {
|
|
1492
|
-
return { result: body.status, message: body.message
|
|
1764
|
+
return { result: body.status, message: safeDisplayString(body.message) };
|
|
1493
1765
|
}
|
|
1494
1766
|
return { result: "failed", message: "Patchstack returned an unexpected response." };
|
|
1495
1767
|
} catch {
|
|
@@ -1501,6 +1773,7 @@ async function fetchSiteStatus(config) {
|
|
|
1501
1773
|
const url = new URL(buildSettingsUrl(config.endpoint, config.siteUuid));
|
|
1502
1774
|
url.searchParams.set("t", Date.now().toString());
|
|
1503
1775
|
try {
|
|
1776
|
+
assertConnectableEndpoint(config, url.toString());
|
|
1504
1777
|
const response = await fetch(url.toString(), {
|
|
1505
1778
|
method: "GET",
|
|
1506
1779
|
headers: {
|
|
@@ -1533,6 +1806,42 @@ function validationFields(body) {
|
|
|
1533
1806
|
if (typeof errors !== "object" || errors === null || Array.isArray(errors)) return [];
|
|
1534
1807
|
return Object.keys(errors);
|
|
1535
1808
|
}
|
|
1809
|
+
function manifestResponse(body, provisioning) {
|
|
1810
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) return null;
|
|
1811
|
+
const value = body;
|
|
1812
|
+
if (typeof value.stored !== "boolean") return null;
|
|
1813
|
+
const uuid = value.uuid === void 0 ? void 0 : isCanonicalUuid(value.uuid) ? value.uuid : null;
|
|
1814
|
+
if (uuid === null || provisioning && uuid === void 0) return null;
|
|
1815
|
+
if (value.api_key !== void 0 && !isCredential(value.api_key)) return null;
|
|
1816
|
+
if (value.manifest_id !== void 0 && (!Number.isInteger(value.manifest_id) || Number(value.manifest_id) <= 0)) return null;
|
|
1817
|
+
const claimValue = value.claim;
|
|
1818
|
+
let claim3;
|
|
1819
|
+
if (claimValue !== void 0) {
|
|
1820
|
+
if (typeof claimValue !== "object" || claimValue === null || Array.isArray(claimValue)) return null;
|
|
1821
|
+
const raw = claimValue;
|
|
1822
|
+
if (!["claimed", "owned-by-you", "owned-by-other", "rejected"].includes(String(raw.state))) return null;
|
|
1823
|
+
const dashboardUrl = raw.dashboard_url === void 0 || raw.dashboard_url === null ? null : safeRemoteUrl(raw.dashboard_url);
|
|
1824
|
+
if (raw.dashboard_url !== void 0 && raw.dashboard_url !== null && dashboardUrl === null) return null;
|
|
1825
|
+
claim3 = {
|
|
1826
|
+
state: raw.state,
|
|
1827
|
+
...Number.isInteger(raw.site_id) ? { site_id: Number(raw.site_id) } : {},
|
|
1828
|
+
...dashboardUrl !== null ? { dashboard_url: dashboardUrl } : {},
|
|
1829
|
+
...raw.reason === "expired" || raw.reason === "invalid" ? { reason: raw.reason } : {}
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
const result = {
|
|
1833
|
+
stored: value.stored,
|
|
1834
|
+
...uuid !== void 0 ? { uuid } : {},
|
|
1835
|
+
...typeof value.api_key === "string" ? { api_key: value.api_key } : {},
|
|
1836
|
+
...Number.isInteger(value.manifest_id) ? { manifest_id: Number(value.manifest_id) } : {},
|
|
1837
|
+
...safeDisplayString(value.checksum, 256) !== null ? { checksum: safeDisplayString(value.checksum, 256) } : {},
|
|
1838
|
+
...safeDisplayString(value.reason) !== null ? { reason: safeDisplayString(value.reason) } : {},
|
|
1839
|
+
...safeDisplayString(value.message) !== null ? { message: safeDisplayString(value.message) } : {},
|
|
1840
|
+
...safeDisplayString(value.error) !== null ? { error: safeDisplayString(value.error) } : {},
|
|
1841
|
+
...claim3 !== void 0 ? { claim: claim3 } : {}
|
|
1842
|
+
};
|
|
1843
|
+
return result;
|
|
1844
|
+
}
|
|
1536
1845
|
function environmentRejected(err) {
|
|
1537
1846
|
return err instanceof PatchstackError && err.code === "VALIDATION_ERROR" && err.fields.includes("environment");
|
|
1538
1847
|
}
|
|
@@ -1551,6 +1860,7 @@ async function postManifestWithEnvironmentFallback(config, payload) {
|
|
|
1551
1860
|
async function postManifest(config, payload) {
|
|
1552
1861
|
const url = buildEndpointUrl(config.endpoint, config.siteUuid);
|
|
1553
1862
|
const timeoutMs = config.timeoutMs;
|
|
1863
|
+
assertConnectableEndpoint(config, url);
|
|
1554
1864
|
let response;
|
|
1555
1865
|
try {
|
|
1556
1866
|
response = await pulseFetch(config, url, {
|
|
@@ -1578,25 +1888,27 @@ async function postManifest(config, payload) {
|
|
|
1578
1888
|
cause
|
|
1579
1889
|
);
|
|
1580
1890
|
}
|
|
1581
|
-
|
|
1582
|
-
let
|
|
1891
|
+
let text = "";
|
|
1892
|
+
let parsed = null;
|
|
1583
1893
|
try {
|
|
1584
|
-
|
|
1894
|
+
text = await readBoundedText(response);
|
|
1895
|
+
parsed = text.length > 0 ? JSON.parse(text) : null;
|
|
1585
1896
|
} catch {
|
|
1586
|
-
|
|
1897
|
+
parsed = null;
|
|
1587
1898
|
}
|
|
1899
|
+
const body = manifestResponse(parsed, config.siteUuid === null);
|
|
1588
1900
|
if (response.status === 404) {
|
|
1589
1901
|
throw new PatchstackError(
|
|
1590
|
-
|
|
1902
|
+
safeDisplayString(parsed?.error) ?? "Site not found. Check that your site UUID is correct and that the app is registered as a Pulse app in your Patchstack dashboard.",
|
|
1591
1903
|
"SITE_NOT_FOUND"
|
|
1592
1904
|
);
|
|
1593
1905
|
}
|
|
1594
1906
|
if (response.status === 422) {
|
|
1595
1907
|
const refused2 = new PatchstackError(
|
|
1596
|
-
|
|
1908
|
+
safeDisplayString(parsed?.message) ?? "Patchstack rejected the manifest payload (validation failed).",
|
|
1597
1909
|
"VALIDATION_ERROR"
|
|
1598
1910
|
);
|
|
1599
|
-
refused2.fields = validationFields(
|
|
1911
|
+
refused2.fields = validationFields(parsed);
|
|
1600
1912
|
throw refused2;
|
|
1601
1913
|
}
|
|
1602
1914
|
const refused = authFailureMessage(response.status, config);
|
|
@@ -1605,12 +1917,12 @@ async function postManifest(config, payload) {
|
|
|
1605
1917
|
}
|
|
1606
1918
|
if (response.status < 200 || response.status >= 300) {
|
|
1607
1919
|
throw new PatchstackError(
|
|
1608
|
-
`Patchstack returned ${response.status}: ${text
|
|
1920
|
+
`Patchstack returned ${response.status}: ${safeDisplayString(text, 200) ?? "no response details"}`,
|
|
1609
1921
|
"SERVER_ERROR"
|
|
1610
1922
|
);
|
|
1611
1923
|
}
|
|
1612
1924
|
if (body === null) {
|
|
1613
|
-
throw new PatchstackError("Patchstack returned an
|
|
1925
|
+
throw new PatchstackError("Patchstack returned an unexpected response.", "SERVER_ERROR");
|
|
1614
1926
|
}
|
|
1615
1927
|
return body;
|
|
1616
1928
|
}
|
|
@@ -1623,7 +1935,7 @@ function isTimeoutError(cause) {
|
|
|
1623
1935
|
|
|
1624
1936
|
// src/demo.ts
|
|
1625
1937
|
import { readFile as readFile7 } from "fs/promises";
|
|
1626
|
-
import { join } from "path";
|
|
1938
|
+
import { join as join2 } from "path";
|
|
1627
1939
|
var NODE_SERIALIZE_DEMO = {
|
|
1628
1940
|
name: "node-serialize",
|
|
1629
1941
|
packageName: "node-serialize",
|
|
@@ -1692,7 +2004,7 @@ Add this deliberately vulnerable package first: npm install --save-exact ${scena
|
|
|
1692
2004
|
}
|
|
1693
2005
|
async function readPersistedSiteUuid(cwd) {
|
|
1694
2006
|
try {
|
|
1695
|
-
const value = JSON.parse(await readFile7(
|
|
2007
|
+
const value = JSON.parse(await readFile7(join2(cwd, ".patchstackrc.json"), "utf8")).siteUuid;
|
|
1696
2008
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1697
2009
|
} catch {
|
|
1698
2010
|
return null;
|
|
@@ -1777,7 +2089,7 @@ async function waitForDemoRule(manifestEndpoint, siteUuid, scenario, options = {
|
|
|
1777
2089
|
const pollIntervalMs = options.pollIntervalMs ?? 1e3;
|
|
1778
2090
|
const requestTimeoutMs = options.requestTimeoutMs ?? 1e4;
|
|
1779
2091
|
const fetchFn = options.fetchFn ?? fetch;
|
|
1780
|
-
const sleep = options.sleep ?? ((ms) => new Promise((
|
|
2092
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
|
|
1781
2093
|
const now = options.now ?? Date.now;
|
|
1782
2094
|
const started = now();
|
|
1783
2095
|
const rulesUrl = buildRulesUrl(manifestEndpoint, siteUuid);
|
|
@@ -1826,13 +2138,25 @@ function renderDemoTestCommands(url, scenario) {
|
|
|
1826
2138
|
}
|
|
1827
2139
|
|
|
1828
2140
|
// src/config.ts
|
|
1829
|
-
import { readFile as readFile9,
|
|
2141
|
+
import { readFile as readFile9, chmod, lstat as lstat2, open, rename, unlink } from "fs/promises";
|
|
1830
2142
|
import path8 from "path";
|
|
2143
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
1831
2144
|
|
|
1832
2145
|
// src/environment.ts
|
|
1833
|
-
import { readFileSync } from "fs";
|
|
2146
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1834
2147
|
import path6 from "path";
|
|
1835
2148
|
var set = (value) => value !== void 0 && value !== "";
|
|
2149
|
+
var PRODUCTION_BRANCHES = /* @__PURE__ */ new Set([
|
|
2150
|
+
"main",
|
|
2151
|
+
"master",
|
|
2152
|
+
"production",
|
|
2153
|
+
"prod",
|
|
2154
|
+
"release",
|
|
2155
|
+
"live"
|
|
2156
|
+
]);
|
|
2157
|
+
function environmentFromBranch(variable, branch) {
|
|
2158
|
+
return PRODUCTION_BRANCHES.has(branch.toLowerCase()) ? { environment: "production", evidence: `${variable}=${branch} (a production branch by name)` } : { environment: "sandbox", evidence: `${variable}=${branch} (not a production branch by name)` };
|
|
2159
|
+
}
|
|
1836
2160
|
var DISCRIMINATORS = [
|
|
1837
2161
|
{
|
|
1838
2162
|
platform: "vercel",
|
|
@@ -1861,6 +2185,79 @@ var DISCRIMINATORS = [
|
|
|
1861
2185
|
if (!set(env.RAILWAY_ENVIRONMENT_NAME)) return null;
|
|
1862
2186
|
return env.RAILWAY_ENVIRONMENT_NAME === "production" ? { environment: "production", evidence: "RAILWAY_ENVIRONMENT_NAME=production" } : { environment: "sandbox", evidence: `RAILWAY_ENVIRONMENT_NAME=${env.RAILWAY_ENVIRONMENT_NAME}` };
|
|
1863
2187
|
}
|
|
2188
|
+
},
|
|
2189
|
+
{
|
|
2190
|
+
// Pages and Workers Builds both name the branch and neither names the tier.
|
|
2191
|
+
platform: "cloudflare",
|
|
2192
|
+
read: (env) => {
|
|
2193
|
+
if (set(env.CF_PAGES) && set(env.CF_PAGES_BRANCH)) {
|
|
2194
|
+
return environmentFromBranch("CF_PAGES_BRANCH", env.CF_PAGES_BRANCH);
|
|
2195
|
+
}
|
|
2196
|
+
if (set(env.WORKERS_CI) && set(env.WORKERS_CI_BRANCH)) {
|
|
2197
|
+
return environmentFromBranch("WORKERS_CI_BRANCH", env.WORKERS_CI_BRANCH);
|
|
2198
|
+
}
|
|
2199
|
+
return null;
|
|
2200
|
+
}
|
|
2201
|
+
},
|
|
2202
|
+
{
|
|
2203
|
+
// Amplify names the branch, and separately whether the build is a pull-request preview.
|
|
2204
|
+
platform: "aws",
|
|
2205
|
+
read: (env) => {
|
|
2206
|
+
if (!set(env.AWS_APP_ID) || !set(env.AWS_BRANCH)) return null;
|
|
2207
|
+
return set(env.AWS_PULL_REQUEST_ID) ? { environment: "sandbox", evidence: "AWS_PULL_REQUEST_ID set (a pull-request preview)" } : environmentFromBranch("AWS_BRANCH", env.AWS_BRANCH);
|
|
2208
|
+
}
|
|
2209
|
+
},
|
|
2210
|
+
{
|
|
2211
|
+
// A Deployment build carries `REPLIT_DEPLOYMENT`; the workspace carries `REPL_ID` alone. Read here,
|
|
2212
|
+
// before the hosted-builder rule, so a workspace build in a Replit project is not its publish step.
|
|
2213
|
+
platform: "replit",
|
|
2214
|
+
read: (env) => {
|
|
2215
|
+
if (set(env.REPLIT_DEPLOYMENT)) {
|
|
2216
|
+
return { environment: "production", evidence: "REPLIT_DEPLOYMENT set (a Replit Deployment build)" };
|
|
2217
|
+
}
|
|
2218
|
+
if (set(env.REPL_ID)) {
|
|
2219
|
+
return { environment: "sandbox", evidence: "REPL_ID set without REPLIT_DEPLOYMENT (the workspace)" };
|
|
2220
|
+
}
|
|
2221
|
+
return null;
|
|
2222
|
+
}
|
|
2223
|
+
},
|
|
2224
|
+
{
|
|
2225
|
+
// GitHub Pages deploys build here. A pull request checks out a merge ref whose name says nothing, so
|
|
2226
|
+
// the event is read first; a tag is a release; otherwise the branch name decides.
|
|
2227
|
+
platform: "github-actions",
|
|
2228
|
+
read: (env) => {
|
|
2229
|
+
if (env.GITHUB_ACTIONS !== "true") return null;
|
|
2230
|
+
if (set(env.GITHUB_EVENT_NAME) && env.GITHUB_EVENT_NAME.startsWith("pull_request")) {
|
|
2231
|
+
return { environment: "sandbox", evidence: `GITHUB_EVENT_NAME=${env.GITHUB_EVENT_NAME} (a pull-request build)` };
|
|
2232
|
+
}
|
|
2233
|
+
if (env.GITHUB_REF_TYPE === "tag") {
|
|
2234
|
+
return { environment: "production", evidence: "GITHUB_REF_TYPE=tag (a tag build)" };
|
|
2235
|
+
}
|
|
2236
|
+
if (!set(env.GITHUB_REF_NAME)) return null;
|
|
2237
|
+
return environmentFromBranch("GITHUB_REF_NAME", env.GITHUB_REF_NAME);
|
|
2238
|
+
}
|
|
2239
|
+
},
|
|
2240
|
+
{
|
|
2241
|
+
// The tier, when a job declares one, is GitLab's own word for it. Without one: a merge request is a
|
|
2242
|
+
// check, a tag is a release, the default branch goes live, and any other branch name decides.
|
|
2243
|
+
platform: "gitlab-ci",
|
|
2244
|
+
read: (env) => {
|
|
2245
|
+
if (env.GITLAB_CI !== "true") return null;
|
|
2246
|
+
if (set(env.CI_ENVIRONMENT_TIER)) {
|
|
2247
|
+
return env.CI_ENVIRONMENT_TIER === "production" ? { environment: "production", evidence: "CI_ENVIRONMENT_TIER=production" } : { environment: "sandbox", evidence: `CI_ENVIRONMENT_TIER=${env.CI_ENVIRONMENT_TIER}` };
|
|
2248
|
+
}
|
|
2249
|
+
if (set(env.CI_MERGE_REQUEST_IID)) {
|
|
2250
|
+
return { environment: "sandbox", evidence: "CI_MERGE_REQUEST_IID set (a merge-request build)" };
|
|
2251
|
+
}
|
|
2252
|
+
if (set(env.CI_COMMIT_TAG)) {
|
|
2253
|
+
return { environment: "production", evidence: "CI_COMMIT_TAG set (a tag build)" };
|
|
2254
|
+
}
|
|
2255
|
+
if (!set(env.CI_COMMIT_BRANCH)) return null;
|
|
2256
|
+
if (set(env.CI_DEFAULT_BRANCH) && env.CI_COMMIT_BRANCH === env.CI_DEFAULT_BRANCH) {
|
|
2257
|
+
return { environment: "production", evidence: `CI_COMMIT_BRANCH=${env.CI_COMMIT_BRANCH} (the default branch)` };
|
|
2258
|
+
}
|
|
2259
|
+
return environmentFromBranch("CI_COMMIT_BRANCH", env.CI_COMMIT_BRANCH);
|
|
2260
|
+
}
|
|
1864
2261
|
}
|
|
1865
2262
|
];
|
|
1866
2263
|
var HOSTED_BUILDER_PACKAGES = [
|
|
@@ -1872,7 +2269,7 @@ var HOSTED_BUILDER_PACKAGES = [
|
|
|
1872
2269
|
function detectHostedBuilder(cwd) {
|
|
1873
2270
|
let declared;
|
|
1874
2271
|
try {
|
|
1875
|
-
const pkg = JSON.parse(
|
|
2272
|
+
const pkg = JSON.parse(readFileSync2(path6.join(cwd, "package.json"), "utf8"));
|
|
1876
2273
|
declared = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
1877
2274
|
} catch {
|
|
1878
2275
|
return null;
|
|
@@ -2098,7 +2495,15 @@ async function resolveConfig(options) {
|
|
|
2098
2495
|
const fromSecretFile = await readSecretFile(options.cwd);
|
|
2099
2496
|
const fromEnv = readEnv();
|
|
2100
2497
|
const siteUuid = options.cliSiteUuid ?? fromEnv.siteUuid ?? fromFile.siteUuid ?? null;
|
|
2101
|
-
const
|
|
2498
|
+
const endpointRaw = options.cliEndpoint ?? fromEnv.endpoint ?? fromFile.endpoint ?? DEFAULT_ENDPOINT;
|
|
2499
|
+
const endpointTrusted = endpointRaw === DEFAULT_ENDPOINT || options.cliEndpoint !== void 0 || fromEnv.endpoint !== void 0 || fromFile.endpoint === void 0;
|
|
2500
|
+
const endpoint = safeRemoteUrl(endpointRaw);
|
|
2501
|
+
if (endpoint === null) {
|
|
2502
|
+
throw new PatchstackError(
|
|
2503
|
+
"Patchstack endpoints must be valid HTTPS URLs, except for localhost development endpoints.",
|
|
2504
|
+
"CONFIG_INVALID"
|
|
2505
|
+
);
|
|
2506
|
+
}
|
|
2102
2507
|
const timeoutMs = fromEnv.timeoutMs ?? fromFile.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2103
2508
|
const environmentRaw = fromEnv.environment ?? fromFile.environment;
|
|
2104
2509
|
if (environmentRaw !== void 0 && !isEnvironment(environmentRaw)) {
|
|
@@ -2110,7 +2515,7 @@ async function resolveConfig(options) {
|
|
|
2110
2515
|
const inferred = environmentRaw === void 0 ? inferEnvironment(process.env, detectHostedBuilder(options.cwd)) : null;
|
|
2111
2516
|
const environment = environmentRaw ?? inferred.environment;
|
|
2112
2517
|
const environmentEvidence = inferred?.evidence ?? [];
|
|
2113
|
-
if (siteUuid !== null && siteUuid.length > 0 && !
|
|
2518
|
+
if (siteUuid !== null && siteUuid.length > 0 && !isCanonicalUuid(siteUuid)) {
|
|
2114
2519
|
throw new PatchstackError(
|
|
2115
2520
|
`Site UUID "${siteUuid}" does not look like a valid UUID.`,
|
|
2116
2521
|
"CONFIG_INVALID"
|
|
@@ -2133,6 +2538,7 @@ async function resolveConfig(options) {
|
|
|
2133
2538
|
siteUrl: identity.url,
|
|
2134
2539
|
siteName: identity.name,
|
|
2135
2540
|
endpoint,
|
|
2541
|
+
endpointTrusted,
|
|
2136
2542
|
timeoutMs,
|
|
2137
2543
|
environment,
|
|
2138
2544
|
environmentEvidence,
|
|
@@ -2143,13 +2549,13 @@ async function resolveConfig(options) {
|
|
|
2143
2549
|
async function writeConfigFile(cwd, config) {
|
|
2144
2550
|
const target = path8.join(cwd, CONFIG_FILENAME);
|
|
2145
2551
|
const content = JSON.stringify(config, null, 2) + "\n";
|
|
2146
|
-
await
|
|
2552
|
+
await writeAtomicFile(target, content, 420);
|
|
2147
2553
|
return target;
|
|
2148
2554
|
}
|
|
2149
2555
|
var SECRET_CONFIG_FILENAME = SECRET_FILENAME;
|
|
2150
2556
|
async function writeSecretFile(cwd, secrets) {
|
|
2151
2557
|
const target = path8.join(cwd, SECRET_FILENAME);
|
|
2152
|
-
await
|
|
2558
|
+
await writeAtomicFile(target, JSON.stringify(secrets, null, 2) + "\n", 384);
|
|
2153
2559
|
try {
|
|
2154
2560
|
await chmod(target, 384);
|
|
2155
2561
|
} catch {
|
|
@@ -2157,6 +2563,39 @@ async function writeSecretFile(cwd, secrets) {
|
|
|
2157
2563
|
const ignore = await ensureIgnored(cwd, SECRET_FILENAME);
|
|
2158
2564
|
return { path: target, ...ignore };
|
|
2159
2565
|
}
|
|
2566
|
+
async function writeAtomicFile(target, content, mode) {
|
|
2567
|
+
const temporary = `${target}.${process.pid}.${randomUUID2()}.tmp`;
|
|
2568
|
+
let handle;
|
|
2569
|
+
try {
|
|
2570
|
+
handle = await open(temporary, "wx", mode);
|
|
2571
|
+
await handle.writeFile(content, "utf8");
|
|
2572
|
+
await handle.sync();
|
|
2573
|
+
await handle.close();
|
|
2574
|
+
handle = void 0;
|
|
2575
|
+
try {
|
|
2576
|
+
await rename(temporary, target);
|
|
2577
|
+
} catch (error) {
|
|
2578
|
+
const code = error.code;
|
|
2579
|
+
if (process.platform !== "win32" || code !== "EEXIST" && code !== "EPERM") throw error;
|
|
2580
|
+
try {
|
|
2581
|
+
await unlink(target);
|
|
2582
|
+
} catch (unlinkError) {
|
|
2583
|
+
if (unlinkError.code !== "ENOENT") throw unlinkError;
|
|
2584
|
+
}
|
|
2585
|
+
await rename(temporary, target);
|
|
2586
|
+
}
|
|
2587
|
+
} catch (error) {
|
|
2588
|
+
try {
|
|
2589
|
+
await handle?.close();
|
|
2590
|
+
} catch {
|
|
2591
|
+
}
|
|
2592
|
+
try {
|
|
2593
|
+
await unlink(temporary);
|
|
2594
|
+
} catch {
|
|
2595
|
+
}
|
|
2596
|
+
throw error;
|
|
2597
|
+
}
|
|
2598
|
+
}
|
|
2160
2599
|
async function ensureIgnored(cwd, entry) {
|
|
2161
2600
|
const target = path8.join(cwd, ".gitignore");
|
|
2162
2601
|
let existing = "";
|
|
@@ -2175,7 +2614,7 @@ async function ensureIgnored(cwd, entry) {
|
|
|
2175
2614
|
${entry}
|
|
2176
2615
|
`;
|
|
2177
2616
|
try {
|
|
2178
|
-
await
|
|
2617
|
+
await writeAtomicFile(target, existing + block, 420);
|
|
2179
2618
|
} catch (err) {
|
|
2180
2619
|
const code = err.code;
|
|
2181
2620
|
return { ignored: false, reason: `.gitignore could not be written (${code ?? "unknown error"})` };
|
|
@@ -2245,8 +2684,15 @@ async function readJsonFile(cwd, filename) {
|
|
|
2245
2684
|
const target = path8.join(cwd, filename);
|
|
2246
2685
|
let raw;
|
|
2247
2686
|
try {
|
|
2687
|
+
if ((await lstat2(target)).isSymbolicLink()) {
|
|
2688
|
+
throw new PatchstackError(
|
|
2689
|
+
`Could not read ${target}: configuration files must not be symbolic links.`,
|
|
2690
|
+
"CONFIG_INVALID"
|
|
2691
|
+
);
|
|
2692
|
+
}
|
|
2248
2693
|
raw = await readFile9(target, "utf8");
|
|
2249
2694
|
} catch (err) {
|
|
2695
|
+
if (err instanceof PatchstackError) throw err;
|
|
2250
2696
|
if (err.code === "ENOENT") {
|
|
2251
2697
|
return {};
|
|
2252
2698
|
}
|
|
@@ -2291,15 +2737,12 @@ function readEnv() {
|
|
|
2291
2737
|
environment: environmentRaw !== void 0 && environmentRaw.length > 0 ? environmentRaw : void 0
|
|
2292
2738
|
};
|
|
2293
2739
|
}
|
|
2294
|
-
function isUuid(value) {
|
|
2295
|
-
return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value);
|
|
2296
|
-
}
|
|
2297
2740
|
function isEnvironment(value) {
|
|
2298
2741
|
return value === "production" || value === "sandbox" || value === "local";
|
|
2299
2742
|
}
|
|
2300
2743
|
|
|
2301
2744
|
// src/mark-build.ts
|
|
2302
|
-
import { existsSync, readFileSync as
|
|
2745
|
+
import { existsSync, readFileSync as readFileSync3, readdirSync, realpathSync as realpathSync2, statSync as statSync2 } from "fs";
|
|
2303
2746
|
import path9 from "path";
|
|
2304
2747
|
|
|
2305
2748
|
// src/stack.ts
|
|
@@ -2401,7 +2844,7 @@ function staleBuildAge(files, startedAt) {
|
|
|
2401
2844
|
let newest = null;
|
|
2402
2845
|
for (const file of files) {
|
|
2403
2846
|
try {
|
|
2404
|
-
const at =
|
|
2847
|
+
const at = statSync2(file).mtimeMs;
|
|
2405
2848
|
if (newest === null || at > newest) newest = at;
|
|
2406
2849
|
} catch {
|
|
2407
2850
|
continue;
|
|
@@ -2432,20 +2875,20 @@ function buildDirCandidates(options = {}) {
|
|
|
2432
2875
|
function resolveBuildDir(cwd, override, options = {}) {
|
|
2433
2876
|
if (override !== void 0 && override !== "") {
|
|
2434
2877
|
const abs = path9.resolve(cwd, override);
|
|
2435
|
-
return existsSync(abs) &&
|
|
2878
|
+
return existsSync(abs) && statSync2(abs).isDirectory() ? abs : null;
|
|
2436
2879
|
}
|
|
2437
2880
|
let boundary;
|
|
2438
2881
|
try {
|
|
2439
|
-
boundary =
|
|
2882
|
+
boundary = realpathSync2(cwd);
|
|
2440
2883
|
} catch {
|
|
2441
2884
|
return null;
|
|
2442
2885
|
}
|
|
2443
2886
|
for (const candidate of buildDirCandidates(options)) {
|
|
2444
2887
|
const abs = path9.resolve(cwd, candidate);
|
|
2445
|
-
if (!existsSync(abs) || !
|
|
2888
|
+
if (!existsSync(abs) || !statSync2(abs).isDirectory()) continue;
|
|
2446
2889
|
let real;
|
|
2447
2890
|
try {
|
|
2448
|
-
real =
|
|
2891
|
+
real = realpathSync2(abs);
|
|
2449
2892
|
} catch {
|
|
2450
2893
|
continue;
|
|
2451
2894
|
}
|
|
@@ -2613,25 +3056,25 @@ function ensureSourceMarker(cwd, shell, framework, checksum = null) {
|
|
|
2613
3056
|
return { shell: null, source: "", action: "no-anchor" };
|
|
2614
3057
|
}
|
|
2615
3058
|
const file = path9.resolve(cwd, shell);
|
|
2616
|
-
const before =
|
|
3059
|
+
const before = readFileSync3(file, "utf8");
|
|
2617
3060
|
const result = ensureMarkerInJsxShell(before, framework, checksum);
|
|
2618
3061
|
if (result.source !== before) {
|
|
2619
|
-
|
|
3062
|
+
writeProjectFileSync(cwd, file, result.source, { encoding: "utf8" });
|
|
2620
3063
|
}
|
|
2621
3064
|
return { ...result, shell };
|
|
2622
3065
|
}
|
|
2623
3066
|
|
|
2624
3067
|
// src/guide.ts
|
|
2625
|
-
import { existsSync as existsSync13, readFileSync as
|
|
3068
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
|
|
2626
3069
|
import path12 from "path";
|
|
2627
3070
|
|
|
2628
3071
|
// src/architecture.ts
|
|
2629
|
-
import { existsSync as existsSync2, readFileSync as
|
|
3072
|
+
import { existsSync as existsSync2, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
2630
3073
|
import path10 from "path";
|
|
2631
3074
|
|
|
2632
3075
|
// src/map/sources.ts
|
|
2633
|
-
import { readFileSync as
|
|
2634
|
-
import { join as
|
|
3076
|
+
import { readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync as realpathSync3, statSync as statSync3 } from "fs";
|
|
3077
|
+
import { join as join3, relative as relative2, isAbsolute as isAbsolute2 } from "path";
|
|
2635
3078
|
|
|
2636
3079
|
// src/map/ast.ts
|
|
2637
3080
|
function rootIdentifier(node, ts) {
|
|
@@ -2840,7 +3283,7 @@ function hasEntrySignal(text) {
|
|
|
2840
3283
|
}
|
|
2841
3284
|
function detectFramework(cwd) {
|
|
2842
3285
|
try {
|
|
2843
|
-
const pkg = JSON.parse(
|
|
3286
|
+
const pkg = JSON.parse(readFileSync4(join3(cwd, "package.json"), "utf8"));
|
|
2844
3287
|
const d = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2845
3288
|
if (d["@tanstack/react-start"] || d["@tanstack/start"] || d["@tanstack/solid-start"]) return "tanstack-start";
|
|
2846
3289
|
if (d["next"]) return "next";
|
|
@@ -2852,11 +3295,11 @@ function detectFramework(cwd) {
|
|
|
2852
3295
|
} catch {
|
|
2853
3296
|
}
|
|
2854
3297
|
try {
|
|
2855
|
-
if (
|
|
3298
|
+
if (statSync3(join3(cwd, "supabase", "functions")).isDirectory()) return "supabase-functions";
|
|
2856
3299
|
} catch {
|
|
2857
3300
|
}
|
|
2858
3301
|
try {
|
|
2859
|
-
if (
|
|
3302
|
+
if (statSync3(join3(cwd, "functions")).isDirectory()) return "deno-functions";
|
|
2860
3303
|
} catch {
|
|
2861
3304
|
}
|
|
2862
3305
|
return "unknown";
|
|
@@ -2898,7 +3341,7 @@ function probeForSource(dir, budget) {
|
|
|
2898
3341
|
for (const e of entries) {
|
|
2899
3342
|
if (budget.left-- <= 0) return "unknown";
|
|
2900
3343
|
if (e.isDirectory()) {
|
|
2901
|
-
if (!SKIP_IS_DEFINITIONAL.has(e.name)) dirs.push(
|
|
3344
|
+
if (!SKIP_IS_DEFINITIONAL.has(e.name)) dirs.push(join3(dir, e.name));
|
|
2902
3345
|
} else if (isSourceFile(e.name)) return true;
|
|
2903
3346
|
else if (e.isSymbolicLink()) return "unknown";
|
|
2904
3347
|
}
|
|
@@ -2911,7 +3354,7 @@ function probeForSource(dir, budget) {
|
|
|
2911
3354
|
function collectSources(dir, boundary, opts, out = [], seen = /* @__PURE__ */ new Set(), stats = { discovered: 0, unwalked: 0, skippedWithSource: [], skippedUnknown: 0 }, probeBudget = { left: PROBE_BUDGET }) {
|
|
2912
3355
|
let key;
|
|
2913
3356
|
try {
|
|
2914
|
-
key =
|
|
3357
|
+
key = realpathSync3(dir);
|
|
2915
3358
|
} catch {
|
|
2916
3359
|
stats.unwalked++;
|
|
2917
3360
|
return out;
|
|
@@ -2935,19 +3378,19 @@ function collectSources(dir, boundary, opts, out = [], seen = /* @__PURE__ */ ne
|
|
|
2935
3378
|
const probeable = !SKIP_IS_DEFINITIONAL.has(e.name);
|
|
2936
3379
|
if (probeable && !e.isDirectory() && e.isSymbolicLink()) stats.skippedUnknown++;
|
|
2937
3380
|
else if (probeable && e.isDirectory()) {
|
|
2938
|
-
const held = probeForSource(
|
|
2939
|
-
if (held === true) stats.skippedWithSource.push(
|
|
3381
|
+
const held = probeForSource(join3(dir, e.name), probeBudget);
|
|
3382
|
+
if (held === true) stats.skippedWithSource.push(join3(dir, e.name));
|
|
2940
3383
|
else if (held === "unknown") stats.skippedUnknown++;
|
|
2941
3384
|
}
|
|
2942
3385
|
continue;
|
|
2943
3386
|
}
|
|
2944
|
-
const full =
|
|
3387
|
+
const full = join3(dir, e.name);
|
|
2945
3388
|
if (e.isDirectory()) collectSources(full, boundary, opts, out, seen, stats, probeBudget);
|
|
2946
3389
|
else if (e.isSymbolicLink()) {
|
|
2947
3390
|
let st, real;
|
|
2948
3391
|
try {
|
|
2949
|
-
st =
|
|
2950
|
-
real =
|
|
3392
|
+
st = statSync3(full);
|
|
3393
|
+
real = realpathSync3(full);
|
|
2951
3394
|
} catch {
|
|
2952
3395
|
stats.unwalked++;
|
|
2953
3396
|
continue;
|
|
@@ -2970,8 +3413,8 @@ function collectSources(dir, boundary, opts, out = [], seen = /* @__PURE__ */ ne
|
|
|
2970
3413
|
}
|
|
2971
3414
|
function isInside(candidate, boundary) {
|
|
2972
3415
|
if (candidate === boundary) return true;
|
|
2973
|
-
const rel =
|
|
2974
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
3416
|
+
const rel = relative2(boundary, candidate);
|
|
3417
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
2975
3418
|
}
|
|
2976
3419
|
var DEPLOYMENT_SHAPES = [
|
|
2977
3420
|
// Config first: these are declarations by the project itself, and they survive a build output being
|
|
@@ -2997,13 +3440,13 @@ var DEPLOYMENT_SHAPES = [
|
|
|
2997
3440
|
function detectDeploymentShapes(cwd, opts = {}) {
|
|
2998
3441
|
let boundary = opts.boundary ?? cwd;
|
|
2999
3442
|
try {
|
|
3000
|
-
boundary =
|
|
3443
|
+
boundary = realpathSync3(boundary);
|
|
3001
3444
|
} catch {
|
|
3002
3445
|
}
|
|
3003
|
-
const inProject = (
|
|
3446
|
+
const inProject = (path18) => {
|
|
3004
3447
|
if (opts.followOutside) return true;
|
|
3005
3448
|
try {
|
|
3006
|
-
return isInside(
|
|
3449
|
+
return isInside(realpathSync3(path18), boundary);
|
|
3007
3450
|
} catch {
|
|
3008
3451
|
return false;
|
|
3009
3452
|
}
|
|
@@ -3011,9 +3454,9 @@ function detectDeploymentShapes(cwd, opts = {}) {
|
|
|
3011
3454
|
const found = [];
|
|
3012
3455
|
for (const candidate of DEPLOYMENT_SHAPES) {
|
|
3013
3456
|
for (const file of candidate.files ?? []) {
|
|
3014
|
-
const full =
|
|
3457
|
+
const full = join3(cwd, file);
|
|
3015
3458
|
try {
|
|
3016
|
-
if (
|
|
3459
|
+
if (statSync3(full).isFile() && inProject(full)) {
|
|
3017
3460
|
found.push({ shape: candidate.shape, source: file, evidence: candidate.evidence });
|
|
3018
3461
|
break;
|
|
3019
3462
|
}
|
|
@@ -3021,9 +3464,9 @@ function detectDeploymentShapes(cwd, opts = {}) {
|
|
|
3021
3464
|
}
|
|
3022
3465
|
}
|
|
3023
3466
|
for (const dir of candidate.dirs ?? []) {
|
|
3024
|
-
const full =
|
|
3467
|
+
const full = join3(cwd, dir);
|
|
3025
3468
|
try {
|
|
3026
|
-
if (!
|
|
3469
|
+
if (!statSync3(full).isDirectory() || !inProject(full)) continue;
|
|
3027
3470
|
} catch {
|
|
3028
3471
|
continue;
|
|
3029
3472
|
}
|
|
@@ -3040,7 +3483,7 @@ function holdsFunctionEntry(dir) {
|
|
|
3040
3483
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
3041
3484
|
if (!entry.isDirectory() || entry.name.startsWith("_")) continue;
|
|
3042
3485
|
try {
|
|
3043
|
-
for (const nested of readdirSync2(
|
|
3486
|
+
for (const nested of readdirSync2(join3(dir, entry.name), { withFileTypes: true })) {
|
|
3044
3487
|
if (nested.isFile() && /^index\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(nested.name)) return true;
|
|
3045
3488
|
}
|
|
3046
3489
|
} catch {
|
|
@@ -3056,7 +3499,7 @@ function holdsSourceFile(dir) {
|
|
|
3056
3499
|
if (entry.isFile() && isSourceFile(entry.name)) return true;
|
|
3057
3500
|
if (entry.isDirectory()) {
|
|
3058
3501
|
try {
|
|
3059
|
-
for (const nested of readdirSync2(
|
|
3502
|
+
for (const nested of readdirSync2(join3(dir, entry.name), { withFileTypes: true })) {
|
|
3060
3503
|
if (nested.isFile() && isSourceFile(nested.name)) return true;
|
|
3061
3504
|
}
|
|
3062
3505
|
} catch {
|
|
@@ -3069,8 +3512,8 @@ function holdsSourceFile(dir) {
|
|
|
3069
3512
|
}
|
|
3070
3513
|
|
|
3071
3514
|
// src/map/surface.ts
|
|
3072
|
-
import { readFileSync as
|
|
3073
|
-
import { join as
|
|
3515
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
3516
|
+
import { join as join4 } from "path";
|
|
3074
3517
|
var STATIC_GENERATORS = [
|
|
3075
3518
|
{ dep: "vite", label: "vite" },
|
|
3076
3519
|
{ dep: "react-scripts", label: "create-react-app" },
|
|
@@ -3111,7 +3554,7 @@ var SHAPE_SIGNAL = {
|
|
|
3111
3554
|
};
|
|
3112
3555
|
function readManifest(cwd) {
|
|
3113
3556
|
try {
|
|
3114
|
-
const parsed = JSON.parse(
|
|
3557
|
+
const parsed = JSON.parse(readFileSync5(join4(cwd, "package.json"), "utf8"));
|
|
3115
3558
|
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
3116
3559
|
} catch {
|
|
3117
3560
|
return null;
|
|
@@ -3124,7 +3567,7 @@ function nextExportsStatically(cwd, manifest, ts) {
|
|
|
3124
3567
|
for (const file of ["next.config.js", "next.config.mjs", "next.config.ts", "next.config.cjs"]) {
|
|
3125
3568
|
let text;
|
|
3126
3569
|
try {
|
|
3127
|
-
text =
|
|
3570
|
+
text = readFileSync5(join4(cwd, file), "utf8");
|
|
3128
3571
|
} catch {
|
|
3129
3572
|
continue;
|
|
3130
3573
|
}
|
|
@@ -3283,7 +3726,7 @@ var SERVING_CALL = /\b(?:createServer|createSecureServer|Bun\.serve|Deno\.serve|
|
|
|
3283
3726
|
var MAX_ENTRY_BYTES = 256 * 1024;
|
|
3284
3727
|
function readManifest2(cwd) {
|
|
3285
3728
|
try {
|
|
3286
|
-
const parsed = JSON.parse(
|
|
3729
|
+
const parsed = JSON.parse(readFileSync6(path10.join(cwd, "package.json"), "utf8"));
|
|
3287
3730
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
3288
3731
|
} catch {
|
|
3289
3732
|
return {};
|
|
@@ -3317,12 +3760,12 @@ function serverEntries(cwd, manifest) {
|
|
|
3317
3760
|
if (!main2.startsWith("..")) candidates.add(main2);
|
|
3318
3761
|
}
|
|
3319
3762
|
const found = [];
|
|
3320
|
-
for (const
|
|
3321
|
-
const file = path10.join(cwd,
|
|
3763
|
+
for (const relative8 of candidates) {
|
|
3764
|
+
const file = path10.join(cwd, relative8);
|
|
3322
3765
|
try {
|
|
3323
|
-
if (!existsSync2(file) || !
|
|
3324
|
-
const text =
|
|
3325
|
-
if (SERVING_CALL.test(text)) found.push(
|
|
3766
|
+
if (!existsSync2(file) || !statSync4(file).isFile()) continue;
|
|
3767
|
+
const text = readFileSync6(file, "utf8").slice(0, MAX_ENTRY_BYTES);
|
|
3768
|
+
if (SERVING_CALL.test(text)) found.push(relative8);
|
|
3326
3769
|
} catch {
|
|
3327
3770
|
}
|
|
3328
3771
|
}
|
|
@@ -3375,14 +3818,14 @@ function classifyArchitecture(cwd) {
|
|
|
3375
3818
|
}
|
|
3376
3819
|
|
|
3377
3820
|
// src/protect/install/util.ts
|
|
3378
|
-
import { readFileSync as
|
|
3379
|
-
import { join as
|
|
3821
|
+
import { readFileSync as readFileSync7, existsSync as existsSync3 } from "fs";
|
|
3822
|
+
import { join as join5, dirname as dirname2 } from "path";
|
|
3380
3823
|
import { fileURLToPath } from "url";
|
|
3381
|
-
var read = (p) =>
|
|
3824
|
+
var read = (p) => readFileSync7(p, "utf8");
|
|
3382
3825
|
var log = (msg) => console.log(`patchstack protect: ${msg}`);
|
|
3383
3826
|
function hasDependency(cwd, name) {
|
|
3384
3827
|
try {
|
|
3385
|
-
const pkg = JSON.parse(read(
|
|
3828
|
+
const pkg = JSON.parse(read(join5(cwd, "package.json")));
|
|
3386
3829
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }[name]);
|
|
3387
3830
|
} catch {
|
|
3388
3831
|
return false;
|
|
@@ -3394,7 +3837,7 @@ function hasResolvableCredential(cwd, env = process.env) {
|
|
|
3394
3837
|
}
|
|
3395
3838
|
for (const file of [".patchstackrc.local.json", ".patchstackrc.json"]) {
|
|
3396
3839
|
try {
|
|
3397
|
-
const parsed = JSON.parse(read(
|
|
3840
|
+
const parsed = JSON.parse(read(join5(cwd, file)));
|
|
3398
3841
|
if (typeof parsed.apiKey === "string" && parsed.apiKey !== "") return true;
|
|
3399
3842
|
if (typeof parsed.pulseAuth === "string" && parsed.pulseAuth !== "") return true;
|
|
3400
3843
|
} catch {
|
|
@@ -3408,7 +3851,7 @@ function isSiteUuid(value) {
|
|
|
3408
3851
|
return typeof value === "string" && UUID_RE.test(value);
|
|
3409
3852
|
}
|
|
3410
3853
|
function bakeSiteUuid(cwd, guardRelPath) {
|
|
3411
|
-
const rc =
|
|
3854
|
+
const rc = join5(cwd, ".patchstackrc.json");
|
|
3412
3855
|
if (!existsSync3(rc)) {
|
|
3413
3856
|
log("no .patchstackrc.json \u2014 guard uses PATCHSTACK_SITE_UUID env or the bundled fallback");
|
|
3414
3857
|
return false;
|
|
@@ -3424,32 +3867,32 @@ function bakeSiteUuid(cwd, guardRelPath) {
|
|
|
3424
3867
|
log(".patchstackrc.json siteUuid missing or malformed \u2014 guard uses PATCHSTACK_SITE_UUID env or the bundled fallback");
|
|
3425
3868
|
return false;
|
|
3426
3869
|
}
|
|
3427
|
-
const guardPath =
|
|
3870
|
+
const guardPath = join5(cwd, guardRelPath);
|
|
3428
3871
|
if (!existsSync3(guardPath)) return false;
|
|
3429
3872
|
const source = read(guardPath);
|
|
3430
3873
|
if (!source.includes(SITE_UUID_PLACEHOLDER)) {
|
|
3431
3874
|
log(`${guardRelPath} site UUID already baked`);
|
|
3432
3875
|
return false;
|
|
3433
3876
|
}
|
|
3434
|
-
|
|
3877
|
+
writeProjectFileSync(cwd, guardPath, source.replace(SITE_UUID_PLACEHOLDER, uuid), { encoding: "utf8" });
|
|
3435
3878
|
log(`baked site UUID into ${guardRelPath} \u2014 live rules from the Patchstack API`);
|
|
3436
3879
|
return true;
|
|
3437
3880
|
}
|
|
3438
|
-
var HERE =
|
|
3881
|
+
var HERE = dirname2(fileURLToPath(import.meta.url));
|
|
3439
3882
|
function templatesDir() {
|
|
3440
|
-
const builtLayout =
|
|
3883
|
+
const builtLayout = join5(HERE, "protect", "templates");
|
|
3441
3884
|
const candidates = [
|
|
3442
3885
|
builtLayout,
|
|
3443
|
-
|
|
3886
|
+
join5(HERE, "..", "templates"),
|
|
3444
3887
|
// source: src/protect/install/ → src/protect/templates
|
|
3445
|
-
|
|
3888
|
+
join5(HERE, "templates")
|
|
3446
3889
|
];
|
|
3447
3890
|
return candidates.find((p) => existsSync3(p)) ?? builtLayout;
|
|
3448
3891
|
}
|
|
3449
3892
|
|
|
3450
3893
|
// src/protect/install/adapters/tanstack-supabase.ts
|
|
3451
|
-
import {
|
|
3452
|
-
import { join as
|
|
3894
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3895
|
+
import { join as join6 } from "path";
|
|
3453
3896
|
var CLIENT_TUNNEL = [
|
|
3454
3897
|
"",
|
|
3455
3898
|
" // PATCHSTACK: in the browser, tunnel Supabase traffic through the app's own server guard",
|
|
@@ -3519,7 +3962,7 @@ function reconcileBlock(s, region, block, legacyConst, insertBefore) {
|
|
|
3519
3962
|
return s.replace(insertBefore, block + "\n\n" + insertBefore);
|
|
3520
3963
|
}
|
|
3521
3964
|
function detect(cwd) {
|
|
3522
|
-
const pkgPath =
|
|
3965
|
+
const pkgPath = join6(cwd, "package.json");
|
|
3523
3966
|
if (!existsSync4(pkgPath)) return false;
|
|
3524
3967
|
let pkg;
|
|
3525
3968
|
try {
|
|
@@ -3528,22 +3971,22 @@ function detect(cwd) {
|
|
|
3528
3971
|
return false;
|
|
3529
3972
|
}
|
|
3530
3973
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3531
|
-
return Boolean(deps["@tanstack/react-start"]) && existsSync4(
|
|
3974
|
+
return Boolean(deps["@tanstack/react-start"]) && existsSync4(join6(cwd, "src/start.ts")) && existsSync4(join6(cwd, "src/integrations/supabase/client.ts"));
|
|
3532
3975
|
}
|
|
3533
3976
|
var GUARD_FILE = "src/integrations/patchstack/guard.ts";
|
|
3534
3977
|
function scaffold(cwd, opts) {
|
|
3535
3978
|
const templates = templatesDir();
|
|
3536
|
-
const dst =
|
|
3537
|
-
|
|
3538
|
-
|
|
3979
|
+
const dst = join6(cwd, "src/integrations/patchstack");
|
|
3980
|
+
ensureProjectDirectorySync(cwd, dst);
|
|
3981
|
+
copyProjectFileSync(cwd, join6(templates, "guard.ts"), join6(dst, "guard.ts"));
|
|
3539
3982
|
const changed = [GUARD_FILE];
|
|
3540
|
-
const rulesDst =
|
|
3983
|
+
const rulesDst = join6(dst, "rules.json");
|
|
3541
3984
|
if (opts.demo) {
|
|
3542
|
-
|
|
3985
|
+
copyProjectFileSync(cwd, join6(templates, "demo-rules.json"), rulesDst);
|
|
3543
3986
|
changed.push("src/integrations/patchstack/rules.json");
|
|
3544
3987
|
log("scaffolded guard.ts + rules.json (demo sample rule set)");
|
|
3545
3988
|
} else if (!existsSync4(rulesDst)) {
|
|
3546
|
-
|
|
3989
|
+
copyProjectFileSync(cwd, join6(templates, "rules.json"), rulesDst);
|
|
3547
3990
|
changed.push("src/integrations/patchstack/rules.json");
|
|
3548
3991
|
log("scaffolded guard.ts + rules.json (starter rules)");
|
|
3549
3992
|
} else {
|
|
@@ -3552,7 +3995,7 @@ function scaffold(cwd, opts) {
|
|
|
3552
3995
|
return changed;
|
|
3553
3996
|
}
|
|
3554
3997
|
function patchClient(cwd) {
|
|
3555
|
-
const p =
|
|
3998
|
+
const p = join6(cwd, "src/integrations/supabase/client.ts");
|
|
3556
3999
|
const s = read(p);
|
|
3557
4000
|
if (s.includes("x-ps-target")) {
|
|
3558
4001
|
log("client.ts already wired");
|
|
@@ -3563,12 +4006,12 @@ function patchClient(cwd) {
|
|
|
3563
4006
|
log("client.ts anchor not found \u2014 skipping (template changed?)");
|
|
3564
4007
|
return false;
|
|
3565
4008
|
}
|
|
3566
|
-
|
|
4009
|
+
writeProjectFileSync(cwd, p, s.replace(anchor, anchor + "\n" + CLIENT_TUNNEL), { encoding: "utf8" });
|
|
3567
4010
|
log("patched client.ts (tunnel Supabase through the guard)");
|
|
3568
4011
|
return true;
|
|
3569
4012
|
}
|
|
3570
4013
|
function patchStart(cwd) {
|
|
3571
|
-
const p =
|
|
4014
|
+
const p = join6(cwd, "src/start.ts");
|
|
3572
4015
|
let s = read(p);
|
|
3573
4016
|
const importAnchor = 'import { createStart, createMiddleware } from "@tanstack/react-start";';
|
|
3574
4017
|
const exportAnchor = "export const startInstance";
|
|
@@ -3600,7 +4043,7 @@ function patchStart(cwd) {
|
|
|
3600
4043
|
log("start.ts already wired");
|
|
3601
4044
|
return false;
|
|
3602
4045
|
}
|
|
3603
|
-
|
|
4046
|
+
writeProjectFileSync(cwd, p, s, { encoding: "utf8" });
|
|
3604
4047
|
log("patched start.ts (guard registered as request + function middleware)");
|
|
3605
4048
|
return true;
|
|
3606
4049
|
}
|
|
@@ -3615,9 +4058,9 @@ function wire(cwd, opts) {
|
|
|
3615
4058
|
return { ok: true, changed: [...new Set(changed)] };
|
|
3616
4059
|
}
|
|
3617
4060
|
function verify(cwd) {
|
|
3618
|
-
const guardPath =
|
|
3619
|
-
const clientPath =
|
|
3620
|
-
const startPath =
|
|
4061
|
+
const guardPath = join6(cwd, GUARD_FILE);
|
|
4062
|
+
const clientPath = join6(cwd, "src/integrations/supabase/client.ts");
|
|
4063
|
+
const startPath = join6(cwd, "src/start.ts");
|
|
3621
4064
|
const guard = existsSync4(guardPath) ? read(guardPath) : "";
|
|
3622
4065
|
const client = existsSync4(clientPath) ? read(clientPath) : "";
|
|
3623
4066
|
const start = existsSync4(startPath) ? read(startPath) : "";
|
|
@@ -3638,11 +4081,11 @@ var tanstackSupabaseAdapter = {
|
|
|
3638
4081
|
};
|
|
3639
4082
|
|
|
3640
4083
|
// src/protect/install/adapters/next.ts
|
|
3641
|
-
import { existsSync as existsSync5
|
|
3642
|
-
import { join as
|
|
4084
|
+
import { existsSync as existsSync5 } from "fs";
|
|
4085
|
+
import { join as join7 } from "path";
|
|
3643
4086
|
function hasNextDep(cwd) {
|
|
3644
4087
|
try {
|
|
3645
|
-
const pkg = JSON.parse(read(
|
|
4088
|
+
const pkg = JSON.parse(read(join7(cwd, "package.json")));
|
|
3646
4089
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }.next);
|
|
3647
4090
|
} catch {
|
|
3648
4091
|
return false;
|
|
@@ -3651,9 +4094,9 @@ function hasNextDep(cwd) {
|
|
|
3651
4094
|
function middlewareInfo(cwd) {
|
|
3652
4095
|
const candidates = ["middleware.ts", "middleware.js", "src/middleware.ts", "src/middleware.js"];
|
|
3653
4096
|
for (const rel of candidates) {
|
|
3654
|
-
if (existsSync5(
|
|
4097
|
+
if (existsSync5(join7(cwd, rel))) return { relDir: rel.includes("/") ? "src" : ".", relFile: rel, exists: true };
|
|
3655
4098
|
}
|
|
3656
|
-
const useSrc = existsSync5(
|
|
4099
|
+
const useSrc = existsSync5(join7(cwd, "src"));
|
|
3657
4100
|
return { relDir: useSrc ? "src" : ".", relFile: useSrc ? "src/middleware.ts" : "middleware.ts", exists: false };
|
|
3658
4101
|
}
|
|
3659
4102
|
function detect2(cwd) {
|
|
@@ -3665,15 +4108,15 @@ function rulesFile(relDir) {
|
|
|
3665
4108
|
function wire2(cwd, opts) {
|
|
3666
4109
|
const templates = templatesDir();
|
|
3667
4110
|
const mw = middlewareInfo(cwd);
|
|
3668
|
-
const dir =
|
|
3669
|
-
|
|
3670
|
-
const rulesDst =
|
|
4111
|
+
const dir = join7(cwd, mw.relDir === "." ? "" : mw.relDir);
|
|
4112
|
+
ensureProjectDirectorySync(cwd, dir);
|
|
4113
|
+
const rulesDst = join7(dir, "patchstack.rules.json");
|
|
3671
4114
|
const changed = [];
|
|
3672
4115
|
if (opts.demo || !existsSync5(rulesDst)) {
|
|
3673
|
-
|
|
4116
|
+
copyProjectFileSync(cwd, join7(templates, opts.demo ? "demo-rules.json" : "rules.json"), rulesDst);
|
|
3674
4117
|
changed.push(rulesFile(mw.relDir));
|
|
3675
4118
|
}
|
|
3676
|
-
const mwPath =
|
|
4119
|
+
const mwPath = join7(cwd, mw.relFile);
|
|
3677
4120
|
const existing = mw.exists ? read(mwPath) : "";
|
|
3678
4121
|
if (mw.exists && existing.includes("patchstack-next")) {
|
|
3679
4122
|
log(`${mw.relFile} already has the Patchstack middleware \u2014 left as-is`);
|
|
@@ -3685,7 +4128,7 @@ function wire2(cwd, opts) {
|
|
|
3685
4128
|
);
|
|
3686
4129
|
return { ok: true, changed };
|
|
3687
4130
|
}
|
|
3688
|
-
|
|
4131
|
+
copyProjectFileSync(cwd, join7(templates, "next-middleware.ts"), mwPath);
|
|
3689
4132
|
if (!opts.demo) bakeSiteUuid(cwd, mw.relFile);
|
|
3690
4133
|
changed.push(mw.relFile);
|
|
3691
4134
|
log(`scaffolded ${mw.relFile} (Patchstack middleware)`);
|
|
@@ -3693,8 +4136,8 @@ function wire2(cwd, opts) {
|
|
|
3693
4136
|
}
|
|
3694
4137
|
function verify2(cwd) {
|
|
3695
4138
|
const mw = middlewareInfo(cwd);
|
|
3696
|
-
const present2 = mw.exists && read(
|
|
3697
|
-
const rulesPresent = existsSync5(
|
|
4139
|
+
const present2 = mw.exists && read(join7(cwd, mw.relFile)).includes("patchstack-next");
|
|
4140
|
+
const rulesPresent = existsSync5(join7(cwd, rulesFile(mw.relDir)));
|
|
3698
4141
|
return {
|
|
3699
4142
|
wired: present2 && rulesPresent,
|
|
3700
4143
|
checks: [
|
|
@@ -3712,27 +4155,27 @@ var nextAdapter = {
|
|
|
3712
4155
|
};
|
|
3713
4156
|
|
|
3714
4157
|
// src/protect/install/adapters/sveltekit.ts
|
|
3715
|
-
import { join as
|
|
4158
|
+
import { join as join9 } from "path";
|
|
3716
4159
|
|
|
3717
4160
|
// src/protect/install/seam.ts
|
|
3718
|
-
import { existsSync as existsSync6
|
|
3719
|
-
import { join as
|
|
4161
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4162
|
+
import { join as join8, dirname as dirname3 } from "path";
|
|
3720
4163
|
function rulesRel(seamRel) {
|
|
3721
|
-
const d =
|
|
4164
|
+
const d = dirname3(seamRel);
|
|
3722
4165
|
return (d === "." ? "patchstack.rules.json" : `${d}/patchstack.rules.json`).replace(/\\/g, "/");
|
|
3723
4166
|
}
|
|
3724
4167
|
function wireSeam(cwd, opts, spec) {
|
|
3725
4168
|
const templates = templatesDir();
|
|
3726
|
-
const existing = spec.candidates.find((c) => existsSync6(
|
|
4169
|
+
const existing = spec.candidates.find((c) => existsSync6(join8(cwd, c)));
|
|
3727
4170
|
const seamRel = existing ?? spec.target;
|
|
3728
|
-
|
|
3729
|
-
const rulesDst =
|
|
4171
|
+
ensureProjectDirectorySync(cwd, dirname3(join8(cwd, seamRel)));
|
|
4172
|
+
const rulesDst = join8(cwd, rulesRel(seamRel));
|
|
3730
4173
|
const changed = [];
|
|
3731
4174
|
if (opts.demo || !existsSync6(rulesDst)) {
|
|
3732
|
-
|
|
4175
|
+
copyProjectFileSync(cwd, join8(templates, opts.demo ? "demo-rules.json" : "rules.json"), rulesDst);
|
|
3733
4176
|
changed.push(rulesRel(seamRel));
|
|
3734
4177
|
}
|
|
3735
|
-
const current = existing ? read(
|
|
4178
|
+
const current = existing ? read(join8(cwd, existing)) : "";
|
|
3736
4179
|
if (existing && current.includes(spec.marker)) {
|
|
3737
4180
|
log(`${existing} already has the Patchstack guard \u2014 left as-is`);
|
|
3738
4181
|
return { ok: true, changed };
|
|
@@ -3741,17 +4184,17 @@ function wireSeam(cwd, opts, spec) {
|
|
|
3741
4184
|
log(`existing ${existing} left untouched \u2014 scaffolded ${rulesRel(seamRel)}; ${spec.planHint}`);
|
|
3742
4185
|
return { ok: true, changed };
|
|
3743
4186
|
}
|
|
3744
|
-
|
|
4187
|
+
copyProjectFileSync(cwd, join8(templates, spec.templateName), join8(cwd, seamRel));
|
|
3745
4188
|
if (!opts.demo) bakeSiteUuid(cwd, seamRel);
|
|
3746
4189
|
changed.push(seamRel);
|
|
3747
4190
|
log(`scaffolded ${seamRel}`);
|
|
3748
4191
|
return { ok: true, changed: [...new Set(changed)] };
|
|
3749
4192
|
}
|
|
3750
4193
|
function verifySeam(cwd, spec) {
|
|
3751
|
-
const existing = spec.candidates.find((c) => existsSync6(
|
|
4194
|
+
const existing = spec.candidates.find((c) => existsSync6(join8(cwd, c)));
|
|
3752
4195
|
const seamRel = existing ?? spec.target;
|
|
3753
|
-
const present2 = existing ? read(
|
|
3754
|
-
const rulesPresent = existsSync6(
|
|
4196
|
+
const present2 = existing ? read(join8(cwd, existing)).includes(spec.marker) : false;
|
|
4197
|
+
const rulesPresent = existsSync6(join8(cwd, rulesRel(seamRel)));
|
|
3755
4198
|
return {
|
|
3756
4199
|
wired: present2 && rulesPresent,
|
|
3757
4200
|
checks: [
|
|
@@ -3772,7 +4215,7 @@ var SPEC = {
|
|
|
3772
4215
|
};
|
|
3773
4216
|
function detect3(cwd) {
|
|
3774
4217
|
try {
|
|
3775
|
-
const pkg = JSON.parse(read(
|
|
4218
|
+
const pkg = JSON.parse(read(join9(cwd, "package.json")));
|
|
3776
4219
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }["@sveltejs/kit"]);
|
|
3777
4220
|
} catch {
|
|
3778
4221
|
return false;
|
|
@@ -3793,7 +4236,7 @@ var sveltekitAdapter = {
|
|
|
3793
4236
|
};
|
|
3794
4237
|
|
|
3795
4238
|
// src/protect/install/adapters/astro.ts
|
|
3796
|
-
import { join as
|
|
4239
|
+
import { join as join10 } from "path";
|
|
3797
4240
|
var SPEC2 = {
|
|
3798
4241
|
templateName: "astro-middleware.ts",
|
|
3799
4242
|
candidates: ["src/middleware.ts", "src/middleware.js", "src/middleware/index.ts", "src/middleware/index.js"],
|
|
@@ -3804,7 +4247,7 @@ var SPEC2 = {
|
|
|
3804
4247
|
};
|
|
3805
4248
|
function detect4(cwd) {
|
|
3806
4249
|
try {
|
|
3807
|
-
const pkg = JSON.parse(read(
|
|
4250
|
+
const pkg = JSON.parse(read(join10(cwd, "package.json")));
|
|
3808
4251
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }.astro);
|
|
3809
4252
|
} catch {
|
|
3810
4253
|
return false;
|
|
@@ -3851,8 +4294,8 @@ var nuxtAdapter = {
|
|
|
3851
4294
|
};
|
|
3852
4295
|
|
|
3853
4296
|
// src/protect/install/find-app.ts
|
|
3854
|
-
import { readFileSync as
|
|
3855
|
-
import { join as
|
|
4297
|
+
import { readFileSync as readFileSync8, existsSync as existsSync7, readdirSync as readdirSync3, lstatSync as lstatSync2 } from "fs";
|
|
4298
|
+
import { join as join11, relative as relative3, dirname as dirname4 } from "path";
|
|
3856
4299
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
3857
4300
|
"node_modules",
|
|
3858
4301
|
"patchstack",
|
|
@@ -3873,7 +4316,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
|
3873
4316
|
]);
|
|
3874
4317
|
var MAX_DEPTH = 8;
|
|
3875
4318
|
function findAppInstance(cwd, re) {
|
|
3876
|
-
const root = existsSync7(
|
|
4319
|
+
const root = existsSync7(join11(cwd, "src")) ? join11(cwd, "src") : cwd;
|
|
3877
4320
|
const found = [];
|
|
3878
4321
|
const walk2 = (dir, depth) => {
|
|
3879
4322
|
if (depth > MAX_DEPTH) return;
|
|
@@ -3885,10 +4328,10 @@ function findAppInstance(cwd, re) {
|
|
|
3885
4328
|
}
|
|
3886
4329
|
for (const name of entries) {
|
|
3887
4330
|
if (SKIP_DIRS2.has(name) || name.startsWith(".")) continue;
|
|
3888
|
-
const p =
|
|
4331
|
+
const p = join11(dir, name);
|
|
3889
4332
|
let st;
|
|
3890
4333
|
try {
|
|
3891
|
-
st =
|
|
4334
|
+
st = lstatSync2(p);
|
|
3892
4335
|
} catch {
|
|
3893
4336
|
continue;
|
|
3894
4337
|
}
|
|
@@ -3897,7 +4340,7 @@ function findAppInstance(cwd, re) {
|
|
|
3897
4340
|
else if (/\.(?:ts|js|mjs|cjs)$/.test(name)) {
|
|
3898
4341
|
let src;
|
|
3899
4342
|
try {
|
|
3900
|
-
src =
|
|
4343
|
+
src = readFileSync8(p, "utf8");
|
|
3901
4344
|
} catch {
|
|
3902
4345
|
continue;
|
|
3903
4346
|
}
|
|
@@ -3905,7 +4348,7 @@ function findAppInstance(cwd, re) {
|
|
|
3905
4348
|
if (!m) continue;
|
|
3906
4349
|
const appVar = m[1];
|
|
3907
4350
|
found.push({
|
|
3908
|
-
relPath:
|
|
4351
|
+
relPath: relative3(cwd, p).replace(/\\/g, "/"),
|
|
3909
4352
|
appVar,
|
|
3910
4353
|
listens: listensOn(src, appVar)
|
|
3911
4354
|
});
|
|
@@ -3944,7 +4387,7 @@ function declaredEntries(cwd) {
|
|
|
3944
4387
|
const paths = /* @__PURE__ */ new Set();
|
|
3945
4388
|
let pkg;
|
|
3946
4389
|
try {
|
|
3947
|
-
pkg = JSON.parse(
|
|
4390
|
+
pkg = JSON.parse(readFileSync8(join11(cwd, "package.json"), "utf8"));
|
|
3948
4391
|
} catch {
|
|
3949
4392
|
return paths;
|
|
3950
4393
|
}
|
|
@@ -3963,24 +4406,24 @@ function declaredEntries(cwd) {
|
|
|
3963
4406
|
return paths;
|
|
3964
4407
|
}
|
|
3965
4408
|
function importSpecifier(fromRel, toRel, preserveExtension = false) {
|
|
3966
|
-
let spec =
|
|
4409
|
+
let spec = relative3(dirname4(fromRel), toRel).replace(/\\/g, "/");
|
|
3967
4410
|
if (!preserveExtension) spec = spec.replace(/\.(?:ts|js)$/, "");
|
|
3968
4411
|
if (!spec.startsWith(".")) spec = `./${spec}`;
|
|
3969
4412
|
return spec;
|
|
3970
4413
|
}
|
|
3971
4414
|
|
|
3972
4415
|
// src/protect/install/register.ts
|
|
3973
|
-
import {
|
|
3974
|
-
import { join as
|
|
4416
|
+
import { existsSync as existsSync10 } from "fs";
|
|
4417
|
+
import { join as join14, resolve as resolve3 } from "path";
|
|
3975
4418
|
|
|
3976
4419
|
// src/protect/install/generic.ts
|
|
3977
|
-
import { readFileSync as
|
|
3978
|
-
import { join as
|
|
4420
|
+
import { readFileSync as readFileSync9, existsSync as existsSync9, readdirSync as readdirSync4, lstatSync as lstatSync3 } from "fs";
|
|
4421
|
+
import { join as join13, dirname as dirname6 } from "path";
|
|
3979
4422
|
|
|
3980
4423
|
// src/protect/install/source-scope.ts
|
|
3981
4424
|
import { execFileSync } from "child_process";
|
|
3982
4425
|
import { existsSync as existsSync8 } from "fs";
|
|
3983
|
-
import { dirname as
|
|
4426
|
+
import { dirname as dirname5, join as join12, resolve as resolve2 } from "path";
|
|
3984
4427
|
|
|
3985
4428
|
// src/protect/install/node-flags.ts
|
|
3986
4429
|
var flagName = (token) => token.split("=")[0];
|
|
@@ -4269,9 +4712,9 @@ function moduleKindOf(filePath, packageIsModule) {
|
|
|
4269
4712
|
return packageIsModule ? "esm" : "cjs";
|
|
4270
4713
|
}
|
|
4271
4714
|
function resolvesToGuardModule(specifier, query) {
|
|
4272
|
-
const resolved =
|
|
4273
|
-
if (withoutModuleExtension(resolved) !==
|
|
4274
|
-
const existing = GUARD_FILENAMES.filter((name) => existsSync8(
|
|
4715
|
+
const resolved = resolve2(dirname5(query.fromFile), specifier);
|
|
4716
|
+
if (withoutModuleExtension(resolved) !== join12(query.guardDir, "guard")) return false;
|
|
4717
|
+
const existing = GUARD_FILENAMES.filter((name) => existsSync8(join12(query.guardDir, name)));
|
|
4275
4718
|
if (existing.length === 0) return false;
|
|
4276
4719
|
const extension = /(\.[A-Za-z0-9]+)$/.exec(specifier)?.[1] ?? "";
|
|
4277
4720
|
if (!query.strictExtensions) return true;
|
|
@@ -4290,16 +4733,16 @@ function withoutModuleExtension(filePath) {
|
|
|
4290
4733
|
// src/protect/install/generic.ts
|
|
4291
4734
|
function packageIsEsm(cwd) {
|
|
4292
4735
|
try {
|
|
4293
|
-
return JSON.parse(read(
|
|
4736
|
+
return JSON.parse(read(join13(cwd, "package.json"))).type === "module";
|
|
4294
4737
|
} catch {
|
|
4295
4738
|
return false;
|
|
4296
4739
|
}
|
|
4297
4740
|
}
|
|
4298
4741
|
function genericDir(cwd) {
|
|
4299
|
-
return existsSync9(
|
|
4742
|
+
return existsSync9(join13(cwd, "src")) ? "src/patchstack" : "patchstack";
|
|
4300
4743
|
}
|
|
4301
4744
|
function hasTypeScript(cwd) {
|
|
4302
|
-
return existsSync9(
|
|
4745
|
+
return existsSync9(join13(cwd, "tsconfig.json")) || candidateEntries(cwd).some((entry) => /\.tsx?$/.test(entry));
|
|
4303
4746
|
}
|
|
4304
4747
|
function genericGuardTarget(cwd) {
|
|
4305
4748
|
if (hasTypeScript(cwd)) {
|
|
@@ -4313,7 +4756,7 @@ function genericGuardTarget(cwd) {
|
|
|
4313
4756
|
function genericScaffoldFiles(cwd) {
|
|
4314
4757
|
const dir = genericDir(cwd);
|
|
4315
4758
|
const candidates = ["guard.ts", "guard.js", "guard.cjs", "rules.json"];
|
|
4316
|
-
return candidates.map((file) => `${dir}/${file}`).filter((
|
|
4759
|
+
return candidates.map((file) => `${dir}/${file}`).filter((relative8) => existsSync9(join13(cwd, relative8)));
|
|
4317
4760
|
}
|
|
4318
4761
|
function scaffoldGeneric(cwd, opts, guardTemplate, guardFile) {
|
|
4319
4762
|
const target = genericGuardTarget(cwd);
|
|
@@ -4321,15 +4764,15 @@ function scaffoldGeneric(cwd, opts, guardTemplate, guardFile) {
|
|
|
4321
4764
|
guardFile ??= target.file;
|
|
4322
4765
|
const templates = templatesDir();
|
|
4323
4766
|
const dir = genericDir(cwd);
|
|
4324
|
-
const dst =
|
|
4325
|
-
|
|
4326
|
-
|
|
4767
|
+
const dst = join13(cwd, dir);
|
|
4768
|
+
ensureProjectDirectorySync(cwd, dst);
|
|
4769
|
+
copyProjectFileSync(cwd, join13(templates, guardTemplate), join13(dst, guardFile));
|
|
4327
4770
|
const guardRel = `${dir}/${guardFile}`;
|
|
4328
4771
|
const changed = [guardRel];
|
|
4329
4772
|
if (!opts.demo) bakeSiteUuid(cwd, guardRel);
|
|
4330
|
-
const rulesDst =
|
|
4773
|
+
const rulesDst = join13(dst, "rules.json");
|
|
4331
4774
|
if (opts.demo || !existsSync9(rulesDst)) {
|
|
4332
|
-
|
|
4775
|
+
copyProjectFileSync(cwd, join13(templates, opts.demo ? "demo-rules.json" : "rules.json"), rulesDst);
|
|
4333
4776
|
changed.push(`${dir}/rules.json`);
|
|
4334
4777
|
}
|
|
4335
4778
|
return { changed, dir };
|
|
@@ -4350,17 +4793,17 @@ function candidateEntries(cwd) {
|
|
|
4350
4793
|
"app.ts",
|
|
4351
4794
|
"app.js"
|
|
4352
4795
|
];
|
|
4353
|
-
const hits = names.filter((n) => existsSync9(
|
|
4796
|
+
const hits = names.filter((n) => existsSync9(join13(cwd, n)));
|
|
4354
4797
|
try {
|
|
4355
|
-
const pkg = JSON.parse(read(
|
|
4356
|
-
if (typeof pkg.main === "string" && existsSync9(
|
|
4798
|
+
const pkg = JSON.parse(read(join13(cwd, "package.json")));
|
|
4799
|
+
if (typeof pkg.main === "string" && existsSync9(join13(cwd, pkg.main))) hits.push(pkg.main);
|
|
4357
4800
|
} catch {
|
|
4358
4801
|
}
|
|
4359
4802
|
return [...new Set(hits)];
|
|
4360
4803
|
}
|
|
4361
4804
|
function usesExpress(cwd) {
|
|
4362
4805
|
try {
|
|
4363
|
-
const pkg = JSON.parse(read(
|
|
4806
|
+
const pkg = JSON.parse(read(join13(cwd, "package.json")));
|
|
4364
4807
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }.express);
|
|
4365
4808
|
} catch {
|
|
4366
4809
|
return false;
|
|
@@ -4464,9 +4907,9 @@ function escapeForRegExp(value) {
|
|
|
4464
4907
|
function genericVerify(cwd) {
|
|
4465
4908
|
const dir = genericDir(cwd);
|
|
4466
4909
|
const target = genericGuardTarget(cwd);
|
|
4467
|
-
const present2 = GUARD_FILENAMES.find((name) => existsSync9(
|
|
4910
|
+
const present2 = GUARD_FILENAMES.find((name) => existsSync9(join13(cwd, dir, name)));
|
|
4468
4911
|
const scaffolded = present2 !== void 0;
|
|
4469
|
-
const imported = scaffolded && guardIsImported(cwd,
|
|
4912
|
+
const imported = scaffolded && guardIsImported(cwd, join13(cwd, dir, present2));
|
|
4470
4913
|
return {
|
|
4471
4914
|
wired: scaffolded && imported,
|
|
4472
4915
|
checks: [
|
|
@@ -4500,9 +4943,9 @@ var NON_RUNTIME_DIRS = /* @__PURE__ */ new Set([
|
|
|
4500
4943
|
"__mocks__"
|
|
4501
4944
|
]);
|
|
4502
4945
|
function guardIsImported(cwd, guardPath) {
|
|
4503
|
-
const guardDir =
|
|
4946
|
+
const guardDir = dirname6(guardPath);
|
|
4504
4947
|
const packageIsModule = packageIsEsm(cwd);
|
|
4505
|
-
const root = existsSync9(
|
|
4948
|
+
const root = existsSync9(join13(cwd, "src")) ? join13(cwd, "src") : cwd;
|
|
4506
4949
|
let found = false;
|
|
4507
4950
|
const walk2 = (d, depth) => {
|
|
4508
4951
|
if (found || depth > 8) return;
|
|
@@ -4515,10 +4958,10 @@ function guardIsImported(cwd, guardPath) {
|
|
|
4515
4958
|
for (const name of entries) {
|
|
4516
4959
|
if (found) return;
|
|
4517
4960
|
if (NON_RUNTIME_DIRS.has(name) || name.startsWith(".")) continue;
|
|
4518
|
-
const p =
|
|
4961
|
+
const p = join13(d, name);
|
|
4519
4962
|
let st;
|
|
4520
4963
|
try {
|
|
4521
|
-
st =
|
|
4964
|
+
st = lstatSync3(p);
|
|
4522
4965
|
} catch {
|
|
4523
4966
|
continue;
|
|
4524
4967
|
}
|
|
@@ -4526,7 +4969,7 @@ function guardIsImported(cwd, guardPath) {
|
|
|
4526
4969
|
if (st.isDirectory()) walk2(p, depth + 1);
|
|
4527
4970
|
else if (/\.(?:ts|tsx|js|jsx|mjs|cjs)$/.test(name) && p !== guardPath) {
|
|
4528
4971
|
try {
|
|
4529
|
-
if (importsAndUsesGuard(
|
|
4972
|
+
if (importsAndUsesGuard(readFileSync9(p, "utf8"), p, guardDir, packageIsModule)) found = true;
|
|
4530
4973
|
} catch {
|
|
4531
4974
|
}
|
|
4532
4975
|
}
|
|
@@ -4574,7 +5017,7 @@ function guardTarget(cwd, entryRel, spec) {
|
|
|
4574
5017
|
}
|
|
4575
5018
|
function packageType(cwd) {
|
|
4576
5019
|
try {
|
|
4577
|
-
return JSON.parse(read(
|
|
5020
|
+
return JSON.parse(read(join14(cwd, "package.json"))).type;
|
|
4578
5021
|
} catch {
|
|
4579
5022
|
return void 0;
|
|
4580
5023
|
}
|
|
@@ -4610,8 +5053,8 @@ function bindsGuard(line, maskedLine, importName, call, fileRel, guard) {
|
|
|
4610
5053
|
}
|
|
4611
5054
|
function namesGuardModule(specifier, fileRel, guard) {
|
|
4612
5055
|
return resolvesToGuardModule(specifier, {
|
|
4613
|
-
fromFile:
|
|
4614
|
-
guardDir:
|
|
5056
|
+
fromFile: join14(guard.cwd, fileRel),
|
|
5057
|
+
guardDir: resolve3(join14(guard.cwd, guard.guardDir)),
|
|
4615
5058
|
kind: moduleKindOf(fileRel, packageType(guard.cwd) === "module"),
|
|
4616
5059
|
strictExtensions: true
|
|
4617
5060
|
});
|
|
@@ -4630,7 +5073,7 @@ function wireRegister(cwd, opts, spec) {
|
|
|
4630
5073
|
}
|
|
4631
5074
|
const target = guardTarget(cwd, entry.relPath, spec);
|
|
4632
5075
|
const { changed, dir } = scaffoldGeneric(cwd, opts, target.template, target.file);
|
|
4633
|
-
const p =
|
|
5076
|
+
const p = join14(cwd, entry.relPath);
|
|
4634
5077
|
const s = read(p);
|
|
4635
5078
|
const state = wiringState(s, spec, entry.appVar, entry.relPath, { cwd, guardDir: dir });
|
|
4636
5079
|
if (state.importAtTopLevel && state.callInAppScope) {
|
|
@@ -4669,7 +5112,7 @@ function wireRegister(cwd, opts, spec) {
|
|
|
4669
5112
|
lines.splice(callIdx + 1, 0, REGION, spec.call(entry.appVar), "// #endregion patchstack");
|
|
4670
5113
|
}
|
|
4671
5114
|
const patched = lines.join("\n");
|
|
4672
|
-
|
|
5115
|
+
writeProjectFileSync(cwd, p, patched, { encoding: "utf8" });
|
|
4673
5116
|
if (spec.callAfter) {
|
|
4674
5117
|
const guardIndex = lines.findIndex((line) => line.includes(spec.call(entry.appVar)));
|
|
4675
5118
|
const early = routesBefore(patched, entry.appVar, guardIndex);
|
|
@@ -4681,7 +5124,7 @@ function wireRegister(cwd, opts, spec) {
|
|
|
4681
5124
|
}
|
|
4682
5125
|
const parsed = parses(p);
|
|
4683
5126
|
if (parsed === false) {
|
|
4684
|
-
|
|
5127
|
+
writeProjectFileSync(cwd, p, s, { encoding: "utf8" });
|
|
4685
5128
|
log(`${entry.relPath} would not parse after patching \u2014 reverted; ${spec.manualHint}`);
|
|
4686
5129
|
return { ok: true, changed };
|
|
4687
5130
|
}
|
|
@@ -4700,17 +5143,17 @@ function firstStatementLine(lines) {
|
|
|
4700
5143
|
return i;
|
|
4701
5144
|
}
|
|
4702
5145
|
function serverIsGuarded(cwd, spec, server, guardDir) {
|
|
4703
|
-
const source = read(
|
|
5146
|
+
const source = read(join14(cwd, server.relPath));
|
|
4704
5147
|
const state = wiringState(source, spec, server.appVar, server.relPath, { cwd, guardDir });
|
|
4705
5148
|
if (!state.importAtTopLevel || !state.callInAppScope || !state.ordered) return false;
|
|
4706
5149
|
return !spec.callAfter || routesBefore(source, server.appVar, state.callIndex).length === 0;
|
|
4707
5150
|
}
|
|
4708
5151
|
function verifyRegister(cwd, spec) {
|
|
4709
|
-
const dir = existsSync10(
|
|
5152
|
+
const dir = existsSync10(join14(cwd, "src")) ? "src/patchstack" : "patchstack";
|
|
4710
5153
|
const entry = findAppInstance(cwd, spec.appRe);
|
|
4711
5154
|
const target = entry ? guardTarget(cwd, entry.relPath, spec) : null;
|
|
4712
|
-
const scaffolded = target ? existsSync10(
|
|
4713
|
-
const entrySource = entry ? read(
|
|
5155
|
+
const scaffolded = target ? existsSync10(join14(cwd, dir, target.file)) : false;
|
|
5156
|
+
const entrySource = entry ? read(join14(cwd, entry.relPath)) : "";
|
|
4714
5157
|
const state = entry ? wiringState(entrySource, spec, entry.appVar, entry.relPath, { cwd, guardDir: dir }) : { importAtTopLevel: false, callInAppScope: false, ordered: false, callIndex: -1, appIndex: -1 };
|
|
4715
5158
|
const wired = state.importAtTopLevel && state.callInAppScope && state.ordered;
|
|
4716
5159
|
const early = spec.callAfter && entry ? routesBefore(entrySource, entry.appVar, state.callIndex) : [];
|
|
@@ -4801,7 +5244,7 @@ var expressAdapter = {
|
|
|
4801
5244
|
|
|
4802
5245
|
// src/protect/install/reporting.ts
|
|
4803
5246
|
import { existsSync as existsSync11 } from "fs";
|
|
4804
|
-
import { join as
|
|
5247
|
+
import { join as join15 } from "path";
|
|
4805
5248
|
|
|
4806
5249
|
// src/protect/reporting-state.js
|
|
4807
5250
|
var REPORTING_STATES = Object.freeze([
|
|
@@ -4859,7 +5302,7 @@ function explainReportingState(state) {
|
|
|
4859
5302
|
|
|
4860
5303
|
// src/protect/install/reporting.ts
|
|
4861
5304
|
function siteIdentity(cwd, env) {
|
|
4862
|
-
const rc =
|
|
5305
|
+
const rc = join15(cwd, ".patchstackrc.json");
|
|
4863
5306
|
let recorded;
|
|
4864
5307
|
if (existsSync11(rc)) {
|
|
4865
5308
|
try {
|
|
@@ -5010,7 +5453,7 @@ function runVerify(cwd) {
|
|
|
5010
5453
|
}
|
|
5011
5454
|
|
|
5012
5455
|
// src/widget.ts
|
|
5013
|
-
import { existsSync as existsSync12, readFileSync as
|
|
5456
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
5014
5457
|
import path11 from "path";
|
|
5015
5458
|
var WIDGET_SCRIPT_URL = "https://cdn.patchstack.com/patchstack-widget.js";
|
|
5016
5459
|
var WIDGET_MARKER_ATTR = "data-patchstack-connect-widget";
|
|
@@ -5064,10 +5507,10 @@ function ensureSourceWidget(cwd, siteUuid, jsxShell = null) {
|
|
|
5064
5507
|
if (!existsSync12(file)) {
|
|
5065
5508
|
return { shell: null, action: "no-shell" };
|
|
5066
5509
|
}
|
|
5067
|
-
const before =
|
|
5510
|
+
const before = readFileSync10(file, "utf8");
|
|
5068
5511
|
const { html, action } = ensureWidgetInHtml(before, siteUuid);
|
|
5069
5512
|
if (html !== before) {
|
|
5070
|
-
|
|
5513
|
+
writeProjectFileSync(cwd, file, html, { encoding: "utf8" });
|
|
5071
5514
|
}
|
|
5072
5515
|
return { shell, action };
|
|
5073
5516
|
}
|
|
@@ -5157,7 +5600,7 @@ function installCommand(pm) {
|
|
|
5157
5600
|
}
|
|
5158
5601
|
function readPackageJson(cwd) {
|
|
5159
5602
|
try {
|
|
5160
|
-
return JSON.parse(
|
|
5603
|
+
return JSON.parse(readFileSync11(path12.join(cwd, "package.json"), "utf8"));
|
|
5161
5604
|
} catch {
|
|
5162
5605
|
return null;
|
|
5163
5606
|
}
|
|
@@ -5165,7 +5608,7 @@ function readPackageJson(cwd) {
|
|
|
5165
5608
|
function installedVersion(cwd, declaredRange) {
|
|
5166
5609
|
try {
|
|
5167
5610
|
const pkg = JSON.parse(
|
|
5168
|
-
|
|
5611
|
+
readFileSync11(path12.join(cwd, "node_modules", "@patchstack", "connect", "package.json"), "utf8")
|
|
5169
5612
|
);
|
|
5170
5613
|
if (typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
5171
5614
|
return pkg.version;
|
|
@@ -5206,10 +5649,10 @@ function findWidgetMarker(cwd, siteUuid) {
|
|
|
5206
5649
|
}
|
|
5207
5650
|
budget -= 1;
|
|
5208
5651
|
try {
|
|
5209
|
-
if (
|
|
5652
|
+
if (statSync5(full).size > WIDGET_SCAN_MAX_BYTES) {
|
|
5210
5653
|
continue;
|
|
5211
5654
|
}
|
|
5212
|
-
const content =
|
|
5655
|
+
const content = readFileSync11(full, "utf8");
|
|
5213
5656
|
if (!content.includes(WIDGET_NEEDLE2)) {
|
|
5214
5657
|
continue;
|
|
5215
5658
|
}
|
|
@@ -5232,7 +5675,7 @@ function findProductionMarker(cwd, widgetFileHint) {
|
|
|
5232
5675
|
return false;
|
|
5233
5676
|
}
|
|
5234
5677
|
try {
|
|
5235
|
-
return
|
|
5678
|
+
return readFileSync11(path12.join(cwd, widgetFileHint), "utf8").includes(PROD_MARKER_NEEDLE);
|
|
5236
5679
|
} catch {
|
|
5237
5680
|
return false;
|
|
5238
5681
|
}
|
|
@@ -5271,7 +5714,7 @@ async function collectGuideState(cwd) {
|
|
|
5271
5714
|
try {
|
|
5272
5715
|
const config = await resolveConfig({ cwd });
|
|
5273
5716
|
siteUuid = config.siteUuid;
|
|
5274
|
-
if (siteUuid !== null) {
|
|
5717
|
+
if (siteUuid !== null && config.endpointTrusted !== false) {
|
|
5275
5718
|
claimUrl = buildClaimUrl(config.endpoint, siteUuid);
|
|
5276
5719
|
}
|
|
5277
5720
|
if (config.endpoint !== DEFAULT_ENDPOINT) {
|
|
@@ -5535,10 +5978,19 @@ import { tmpdir as tmpdir2 } from "os";
|
|
|
5535
5978
|
import path14 from "path";
|
|
5536
5979
|
|
|
5537
5980
|
// src/device-flow.ts
|
|
5538
|
-
import {
|
|
5981
|
+
import {
|
|
5982
|
+
closeSync as closeSync2,
|
|
5983
|
+
constants,
|
|
5984
|
+
fstatSync,
|
|
5985
|
+
openSync as openSync2,
|
|
5986
|
+
readFileSync as readFileSync12,
|
|
5987
|
+
renameSync as renameSync2,
|
|
5988
|
+
rmSync,
|
|
5989
|
+
writeFileSync as writeFileSync2
|
|
5990
|
+
} from "fs";
|
|
5539
5991
|
import { tmpdir } from "os";
|
|
5540
5992
|
import path13 from "path";
|
|
5541
|
-
import { createHash as createHash2 } from "crypto";
|
|
5993
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
5542
5994
|
function baseFrom(manifestEndpoint) {
|
|
5543
5995
|
const url = new URL(manifestEndpoint);
|
|
5544
5996
|
const p = url.pathname.replace(/\/$/, "");
|
|
@@ -5552,16 +6004,61 @@ function pendingPath(siteUuid, intent) {
|
|
|
5552
6004
|
return path13.join(tmpdir(), `patchstack-${intent}-${key}.json`);
|
|
5553
6005
|
}
|
|
5554
6006
|
function savePending(siteUuid, intent, pending) {
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
6007
|
+
const target = pendingPath(siteUuid, intent);
|
|
6008
|
+
const temporary = `${target}.${process.pid}.${randomUUID3()}.tmp`;
|
|
6009
|
+
let descriptor = null;
|
|
6010
|
+
try {
|
|
6011
|
+
descriptor = openSync2(temporary, "wx", 384);
|
|
6012
|
+
writeFileSync2(descriptor, JSON.stringify(pending), "utf8");
|
|
6013
|
+
closeSync2(descriptor);
|
|
6014
|
+
descriptor = null;
|
|
6015
|
+
try {
|
|
6016
|
+
renameSync2(temporary, target);
|
|
6017
|
+
} catch (error) {
|
|
6018
|
+
const code = error.code;
|
|
6019
|
+
if (process.platform !== "win32" || code !== "EEXIST" && code !== "EPERM") throw error;
|
|
6020
|
+
try {
|
|
6021
|
+
rmSync(target);
|
|
6022
|
+
} catch (removeError) {
|
|
6023
|
+
if (removeError.code !== "ENOENT") throw removeError;
|
|
6024
|
+
}
|
|
6025
|
+
renameSync2(temporary, target);
|
|
6026
|
+
}
|
|
6027
|
+
} catch (error) {
|
|
6028
|
+
if (descriptor !== null) {
|
|
6029
|
+
try {
|
|
6030
|
+
closeSync2(descriptor);
|
|
6031
|
+
} catch {
|
|
6032
|
+
}
|
|
6033
|
+
}
|
|
6034
|
+
try {
|
|
6035
|
+
rmSync(temporary);
|
|
6036
|
+
} catch {
|
|
6037
|
+
}
|
|
6038
|
+
throw error;
|
|
6039
|
+
}
|
|
5559
6040
|
}
|
|
5560
6041
|
function readPending(siteUuid, intent) {
|
|
6042
|
+
let descriptor = null;
|
|
5561
6043
|
try {
|
|
5562
|
-
|
|
6044
|
+
const noFollow = typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
|
|
6045
|
+
descriptor = openSync2(pendingPath(siteUuid, intent), constants.O_RDONLY | noFollow);
|
|
6046
|
+
const stat2 = fstatSync(descriptor);
|
|
6047
|
+
if (!stat2.isFile()) return null;
|
|
6048
|
+
if (typeof process.getuid === "function" && typeof stat2.uid === "number" && stat2.uid !== process.getuid()) {
|
|
6049
|
+
return null;
|
|
6050
|
+
}
|
|
6051
|
+
if (process.platform !== "win32" && (stat2.mode & 63) !== 0) return null;
|
|
6052
|
+
return JSON.parse(readFileSync12(descriptor, "utf8"));
|
|
5563
6053
|
} catch {
|
|
5564
6054
|
return null;
|
|
6055
|
+
} finally {
|
|
6056
|
+
if (descriptor !== null) {
|
|
6057
|
+
try {
|
|
6058
|
+
closeSync2(descriptor);
|
|
6059
|
+
} catch {
|
|
6060
|
+
}
|
|
6061
|
+
}
|
|
5565
6062
|
}
|
|
5566
6063
|
}
|
|
5567
6064
|
function clearPending(siteUuid, intent) {
|
|
@@ -5579,17 +6076,25 @@ async function requestDeviceCode(config, intent, conflictMessage, deps = {}) {
|
|
|
5579
6076
|
message: "No site UUID configured \u2014 run `patchstack-connect scan` first."
|
|
5580
6077
|
};
|
|
5581
6078
|
}
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
6079
|
+
let base;
|
|
6080
|
+
let started;
|
|
6081
|
+
try {
|
|
6082
|
+
base = baseFrom(config.endpoint);
|
|
6083
|
+
assertConnectableEndpoint(config, base);
|
|
6084
|
+
started = await pulseFetch(
|
|
6085
|
+
config,
|
|
6086
|
+
`${base}/device/code`,
|
|
6087
|
+
{
|
|
6088
|
+
method: "POST",
|
|
6089
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
6090
|
+
body: JSON.stringify({ site_uuid: config.siteUuid, intent }),
|
|
6091
|
+
signal: AbortSignal.timeout(config.timeoutMs)
|
|
6092
|
+
},
|
|
6093
|
+
fetchImpl
|
|
6094
|
+
);
|
|
6095
|
+
} catch {
|
|
6096
|
+
return { status: "failed", message: "Could not start the request." };
|
|
6097
|
+
}
|
|
5593
6098
|
if (started.status === 409) return { status: "conflict", message: conflictMessage };
|
|
5594
6099
|
if (started.status === 404) {
|
|
5595
6100
|
return { status: "not-found", message: "Patchstack does not recognise this site UUID." };
|
|
@@ -5597,34 +6102,76 @@ async function requestDeviceCode(config, intent, conflictMessage, deps = {}) {
|
|
|
5597
6102
|
if (!started.ok) {
|
|
5598
6103
|
return { status: "failed", message: `Could not start the request (HTTP ${started.status}).` };
|
|
5599
6104
|
}
|
|
5600
|
-
|
|
6105
|
+
let body;
|
|
6106
|
+
try {
|
|
6107
|
+
const raw = await readBoundedJson(started);
|
|
6108
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new Error("unexpected response");
|
|
6109
|
+
body = raw;
|
|
6110
|
+
} catch {
|
|
6111
|
+
return { status: "failed", message: "Patchstack returned an unexpected response." };
|
|
6112
|
+
}
|
|
6113
|
+
const deviceCode = safeDisplayString(body.device_code, 2048);
|
|
6114
|
+
const userCode = safeDisplayString(body.user_code, 128);
|
|
6115
|
+
const expiresIn = Number(body.expires_in);
|
|
6116
|
+
const interval = Number(body.interval);
|
|
6117
|
+
if (deviceCode === null || userCode === null || !Number.isInteger(expiresIn) || expiresIn < 1 || expiresIn > 86400 || !Number.isFinite(interval) || interval < 1 || interval > 300) {
|
|
6118
|
+
return { status: "failed", message: "Patchstack returned an unexpected response." };
|
|
6119
|
+
}
|
|
5601
6120
|
const pending = {
|
|
5602
|
-
deviceCode
|
|
5603
|
-
userCode
|
|
6121
|
+
deviceCode,
|
|
6122
|
+
userCode,
|
|
5604
6123
|
// Points at the API, which redirects to the dashboard SPA — the CLI only knows the API origin,
|
|
5605
6124
|
// and the approval page lives on the app.
|
|
5606
|
-
verificationUri: `${base}/device?code=${encodeURIComponent(
|
|
5607
|
-
expiresAt: now() +
|
|
5608
|
-
intervalMs:
|
|
6125
|
+
verificationUri: `${base}/device?code=${encodeURIComponent(userCode)}`,
|
|
6126
|
+
expiresAt: now() + expiresIn * 1e3,
|
|
6127
|
+
intervalMs: interval * 1e3
|
|
5609
6128
|
};
|
|
5610
6129
|
savePending(config.siteUuid, intent, pending);
|
|
5611
6130
|
return { status: "started", pending };
|
|
5612
6131
|
}
|
|
5613
6132
|
async function pollDeviceToken(config, intent, pending, deps = {}) {
|
|
5614
6133
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
6134
|
+
let polled;
|
|
6135
|
+
try {
|
|
6136
|
+
const url = `${baseFrom(config.endpoint)}/device/token`;
|
|
6137
|
+
assertConnectableEndpoint(config, url);
|
|
6138
|
+
polled = await fetchImpl(url, {
|
|
6139
|
+
method: "POST",
|
|
6140
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
6141
|
+
body: JSON.stringify({ device_code: pending.deviceCode }),
|
|
6142
|
+
signal: AbortSignal.timeout(config.timeoutMs)
|
|
6143
|
+
});
|
|
6144
|
+
} catch {
|
|
6145
|
+
return { state: "expired" };
|
|
6146
|
+
}
|
|
5620
6147
|
if (polled.status === 428) return { state: "pending" };
|
|
5621
6148
|
if (!polled.ok) {
|
|
5622
6149
|
if (config.siteUuid !== null) clearPending(config.siteUuid, intent);
|
|
5623
6150
|
return { state: "expired" };
|
|
5624
6151
|
}
|
|
5625
|
-
|
|
6152
|
+
let raw;
|
|
6153
|
+
try {
|
|
6154
|
+
raw = await readBoundedJson(polled);
|
|
6155
|
+
} catch {
|
|
6156
|
+
raw = null;
|
|
6157
|
+
}
|
|
5626
6158
|
if (config.siteUuid !== null) clearPending(config.siteUuid, intent);
|
|
5627
|
-
return { state: "
|
|
6159
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return { state: "expired" };
|
|
6160
|
+
const value = raw;
|
|
6161
|
+
const apiKey = value.api_key === void 0 ? void 0 : isCredential(value.api_key) ? value.api_key : null;
|
|
6162
|
+
if (apiKey === null) return { state: "expired" };
|
|
6163
|
+
const dashboardUrl = value.dashboard_url === void 0 ? void 0 : safeRemoteUrl(value.dashboard_url);
|
|
6164
|
+
if (value.dashboard_url !== void 0 && dashboardUrl === null) return { state: "expired" };
|
|
6165
|
+
const account = value.account === void 0 ? void 0 : safeDisplayString(value.account, 256);
|
|
6166
|
+
if (value.account !== void 0 && account === null) return { state: "expired" };
|
|
6167
|
+
return {
|
|
6168
|
+
state: "approved",
|
|
6169
|
+
body: {
|
|
6170
|
+
...apiKey !== void 0 ? { api_key: apiKey } : {},
|
|
6171
|
+
...dashboardUrl !== void 0 ? { dashboard_url: dashboardUrl } : {},
|
|
6172
|
+
...account !== void 0 ? { account } : {}
|
|
6173
|
+
}
|
|
6174
|
+
};
|
|
5628
6175
|
}
|
|
5629
6176
|
async function pollUntilApproved(config, intent, pending, deps = {}) {
|
|
5630
6177
|
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
@@ -5726,11 +6273,11 @@ async function claim2(config, onPrompt, deps = {}) {
|
|
|
5726
6273
|
}
|
|
5727
6274
|
|
|
5728
6275
|
// src/protect/install/runtime/check.ts
|
|
5729
|
-
import { relative as
|
|
6276
|
+
import { relative as relative4 } from "path";
|
|
5730
6277
|
|
|
5731
6278
|
// src/protect/install/runtime/entry.ts
|
|
5732
|
-
import { existsSync as existsSync14, readFileSync as
|
|
5733
|
-
import { join as
|
|
6279
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13, realpathSync as realpathSync4, statSync as statSync6 } from "fs";
|
|
6280
|
+
import { join as join16, resolve as resolve4, sep as sep2 } from "path";
|
|
5734
6281
|
var CONVENTIONAL = [
|
|
5735
6282
|
"server.js",
|
|
5736
6283
|
"server.mjs",
|
|
@@ -5756,31 +6303,31 @@ var CONVENTIONAL = [
|
|
|
5756
6303
|
var LOADABLE = /\.(?:js|mjs|cjs)$/;
|
|
5757
6304
|
function readPackage2(cwd) {
|
|
5758
6305
|
try {
|
|
5759
|
-
const parsed = JSON.parse(
|
|
6306
|
+
const parsed = JSON.parse(readFileSync13(join16(cwd, "package.json"), "utf8"));
|
|
5760
6307
|
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
5761
6308
|
} catch {
|
|
5762
6309
|
return null;
|
|
5763
6310
|
}
|
|
5764
6311
|
}
|
|
5765
|
-
function isFile(
|
|
6312
|
+
function isFile(path18) {
|
|
5766
6313
|
try {
|
|
5767
|
-
return
|
|
6314
|
+
return statSync6(path18).isFile();
|
|
5768
6315
|
} catch {
|
|
5769
6316
|
return false;
|
|
5770
6317
|
}
|
|
5771
6318
|
}
|
|
5772
6319
|
function loadable(cwd, candidate) {
|
|
5773
|
-
const written =
|
|
6320
|
+
const written = resolve4(cwd, candidate);
|
|
5774
6321
|
if (!LOADABLE.test(written) || !isFile(written)) return null;
|
|
5775
6322
|
let root;
|
|
5776
|
-
let
|
|
6323
|
+
let path18;
|
|
5777
6324
|
try {
|
|
5778
|
-
root =
|
|
5779
|
-
|
|
6325
|
+
root = realpathSync4(cwd);
|
|
6326
|
+
path18 = realpathSync4(written);
|
|
5780
6327
|
} catch {
|
|
5781
6328
|
return null;
|
|
5782
6329
|
}
|
|
5783
|
-
if (
|
|
6330
|
+
if (path18 !== root && !path18.startsWith(root + sep2)) return null;
|
|
5784
6331
|
return written;
|
|
5785
6332
|
}
|
|
5786
6333
|
function passable(token) {
|
|
@@ -5826,7 +6373,7 @@ function resolveEntry(cwd) {
|
|
|
5826
6373
|
}
|
|
5827
6374
|
return {
|
|
5828
6375
|
kind: "unavailable",
|
|
5829
|
-
reason: existsSync14(
|
|
6376
|
+
reason: existsSync14(join16(cwd, "package.json")) ? 'no directly loadable entry was found \u2014 declare one as a "start" script of the form `node <file>`, or run the check against a built output' : "there is no package.json here"
|
|
5830
6377
|
};
|
|
5831
6378
|
}
|
|
5832
6379
|
|
|
@@ -5860,8 +6407,8 @@ function preloadPath() {
|
|
|
5860
6407
|
// beside this file, in the repository
|
|
5861
6408
|
];
|
|
5862
6409
|
for (const candidate of candidates) {
|
|
5863
|
-
const
|
|
5864
|
-
if (existsSync15(
|
|
6410
|
+
const path18 = fileURLToPath2(candidate);
|
|
6411
|
+
if (existsSync15(path18)) return path18;
|
|
5865
6412
|
}
|
|
5866
6413
|
return null;
|
|
5867
6414
|
}
|
|
@@ -5895,7 +6442,7 @@ async function askListener(listener, challenge, answer, budgetMs) {
|
|
|
5895
6442
|
timeout: budgetMs
|
|
5896
6443
|
};
|
|
5897
6444
|
if (listener.scheme === "https") options.agent = new HttpsAgent({ rejectUnauthorized: false });
|
|
5898
|
-
return new Promise((
|
|
6445
|
+
return new Promise((resolve5) => {
|
|
5899
6446
|
const req = send(options, (res) => {
|
|
5900
6447
|
const body = [];
|
|
5901
6448
|
let read2 = 0;
|
|
@@ -5909,7 +6456,7 @@ async function askListener(listener, challenge, answer, budgetMs) {
|
|
|
5909
6456
|
});
|
|
5910
6457
|
const finish = () => {
|
|
5911
6458
|
const text = Buffer.concat(body).toString("utf8").trim();
|
|
5912
|
-
|
|
6459
|
+
resolve5({
|
|
5913
6460
|
...listener,
|
|
5914
6461
|
outcome: text === answer ? "traversed" : "answered-without-sentinel",
|
|
5915
6462
|
detail: text === answer ? void 0 : `answered with ${res.statusCode ?? "?"} and no sentinel`
|
|
@@ -5919,7 +6466,7 @@ async function askListener(listener, challenge, answer, budgetMs) {
|
|
|
5919
6466
|
res.on("close", finish);
|
|
5920
6467
|
});
|
|
5921
6468
|
req.on("timeout", () => req.destroy(new Error("timed out")));
|
|
5922
|
-
req.on("error", (err) =>
|
|
6469
|
+
req.on("error", (err) => resolve5({ ...listener, outcome: "unreachable", detail: err.message }));
|
|
5923
6470
|
req.end();
|
|
5924
6471
|
});
|
|
5925
6472
|
}
|
|
@@ -6013,12 +6560,12 @@ async function probeRuntimeTraversal(options) {
|
|
|
6013
6560
|
let onFrozen = null;
|
|
6014
6561
|
const freeze = async () => {
|
|
6015
6562
|
if (!child.connected) return false;
|
|
6016
|
-
return new Promise((
|
|
6563
|
+
return new Promise((resolve5) => {
|
|
6017
6564
|
const settle2 = (confirmed2) => {
|
|
6018
6565
|
clearTimeout(timer);
|
|
6019
6566
|
onFrozen = null;
|
|
6020
6567
|
child.removeListener("exit", onGone);
|
|
6021
|
-
|
|
6568
|
+
resolve5(confirmed2);
|
|
6022
6569
|
};
|
|
6023
6570
|
const onGone = () => settle2(false);
|
|
6024
6571
|
const timer = setTimeout(() => settle2(false), Math.min(FREEZE_MS, remaining()));
|
|
@@ -6031,13 +6578,13 @@ async function probeRuntimeTraversal(options) {
|
|
|
6031
6578
|
}
|
|
6032
6579
|
});
|
|
6033
6580
|
};
|
|
6034
|
-
await new Promise((
|
|
6581
|
+
await new Promise((resolve5) => {
|
|
6035
6582
|
let settleTimer = null;
|
|
6036
6583
|
const done = () => {
|
|
6037
6584
|
discovering = false;
|
|
6038
6585
|
clearTimeout(runTimer);
|
|
6039
6586
|
if (settleTimer) clearTimeout(settleTimer);
|
|
6040
|
-
|
|
6587
|
+
resolve5();
|
|
6041
6588
|
};
|
|
6042
6589
|
const runTimer = setTimeout(done, remaining());
|
|
6043
6590
|
const settle2 = () => {
|
|
@@ -6177,7 +6724,7 @@ async function runRuntimeCheck(cwd, options = {}) {
|
|
|
6177
6724
|
settleMs: options.settleMs,
|
|
6178
6725
|
platform: options.platform
|
|
6179
6726
|
});
|
|
6180
|
-
return { ...result, entry: { file:
|
|
6727
|
+
return { ...result, entry: { file: relative4(cwd, entry.file).replace(/\\/g, "/"), from: entry.from } };
|
|
6181
6728
|
}
|
|
6182
6729
|
function runtimeExitCode(report) {
|
|
6183
6730
|
if (report.outcome === "proven") return 0;
|
|
@@ -6208,15 +6755,15 @@ function formatRuntimeCheck(report) {
|
|
|
6208
6755
|
}
|
|
6209
6756
|
|
|
6210
6757
|
// src/map-command.ts
|
|
6211
|
-
import
|
|
6758
|
+
import path15 from "path";
|
|
6212
6759
|
|
|
6213
6760
|
// src/map/ts-loader.ts
|
|
6214
6761
|
import { createRequire } from "module";
|
|
6215
6762
|
import { pathToFileURL } from "url";
|
|
6216
|
-
import { join as
|
|
6763
|
+
import { join as join17 } from "path";
|
|
6217
6764
|
async function loadTypeScript(cwd) {
|
|
6218
6765
|
try {
|
|
6219
|
-
const req = createRequire(pathToFileURL(
|
|
6766
|
+
const req = createRequire(pathToFileURL(join17(cwd, "package.json")));
|
|
6220
6767
|
const resolved = req.resolve("typescript");
|
|
6221
6768
|
const mod = await import(pathToFileURL(resolved).href);
|
|
6222
6769
|
return mod.default ?? mod;
|
|
@@ -6231,9 +6778,9 @@ async function loadTypeScript(cwd) {
|
|
|
6231
6778
|
}
|
|
6232
6779
|
|
|
6233
6780
|
// src/map/extract.ts
|
|
6234
|
-
import { readFileSync as
|
|
6781
|
+
import { readFileSync as readFileSync16, realpathSync as realpathSync6 } from "fs";
|
|
6235
6782
|
import { createHash as createHash5 } from "crypto";
|
|
6236
|
-
import { relative as
|
|
6783
|
+
import { relative as relative6 } from "path";
|
|
6237
6784
|
|
|
6238
6785
|
// src/map/bindings.ts
|
|
6239
6786
|
import { builtinModules } from "module";
|
|
@@ -6461,14 +7008,14 @@ function directSinks(node, ts, bindings, ctx) {
|
|
|
6461
7008
|
const root = base ? rootIdentifier(base, ts) : void 0;
|
|
6462
7009
|
if (!root) return {};
|
|
6463
7010
|
const spec = bindings.resolve(root);
|
|
6464
|
-
const
|
|
7011
|
+
const relative8 = spec !== void 0 && (spec.startsWith(".") || spec.startsWith("/"));
|
|
6465
7012
|
const pkg = npmPackageOf(spec);
|
|
6466
7013
|
if (pkg) return { pkg, root, spec };
|
|
6467
|
-
if (
|
|
7014
|
+
if (relative8 && ctx && spec) {
|
|
6468
7015
|
const viaModule = ctx.graph.importedBinding(ctx.file, spec, bindings.exportNameOf(root) ?? root);
|
|
6469
7016
|
if (viaModule) return { pkg: viaModule.package, root, spec };
|
|
6470
7017
|
}
|
|
6471
|
-
return { local: bindings.locals.has(root), root, spec, relative:
|
|
7018
|
+
return { local: bindings.locals.has(root), root, spec, relative: relative8 };
|
|
6472
7019
|
};
|
|
6473
7020
|
const attributionOf = (b, pkg) => b.pkg ? "import" : pkg ? "inferred" : void 0;
|
|
6474
7021
|
const dbApi = (pkg, attribution) => {
|
|
@@ -6645,8 +7192,8 @@ function dedupeSinks(sinks, owner) {
|
|
|
6645
7192
|
}
|
|
6646
7193
|
|
|
6647
7194
|
// src/map/module-graph.ts
|
|
6648
|
-
import { readFileSync as
|
|
6649
|
-
import { dirname as
|
|
7195
|
+
import { readFileSync as readFileSync14, realpathSync as realpathSync5, statSync as statSync7 } from "fs";
|
|
7196
|
+
import { dirname as dirname8, join as join18, relative as relative5, resolve as resolvePath } from "path";
|
|
6650
7197
|
var RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
6651
7198
|
function createModuleGraph(ts, opts) {
|
|
6652
7199
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -6654,7 +7201,7 @@ function createModuleGraph(ts, opts) {
|
|
|
6654
7201
|
if (cache.has(file)) return cache.get(file) ?? null;
|
|
6655
7202
|
let entry = null;
|
|
6656
7203
|
try {
|
|
6657
|
-
const text =
|
|
7204
|
+
const text = readFileSync14(file, "utf8");
|
|
6658
7205
|
const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, file));
|
|
6659
7206
|
const bindings = buildModuleBindings(sf, ts);
|
|
6660
7207
|
entry = { fnSinks: collectLocalSinks(sf, ts, bindings), calleesOf: collectCallees(sf, ts), bindings };
|
|
@@ -6670,7 +7217,7 @@ function createModuleGraph(ts, opts) {
|
|
|
6670
7217
|
if (!opts.followOutside) {
|
|
6671
7218
|
let real = target;
|
|
6672
7219
|
try {
|
|
6673
|
-
real =
|
|
7220
|
+
real = realpathSync5(target);
|
|
6674
7221
|
} catch {
|
|
6675
7222
|
}
|
|
6676
7223
|
if (!isInside(real, opts.boundary)) return void 0;
|
|
@@ -6688,7 +7235,7 @@ function createModuleGraph(ts, opts) {
|
|
|
6688
7235
|
for (const callee of mod.calleesOf.get(exportName) ?? []) {
|
|
6689
7236
|
for (const s of mod.fnSinks.get(callee) ?? []) collected.push(s);
|
|
6690
7237
|
}
|
|
6691
|
-
const rel =
|
|
7238
|
+
const rel = relative5(opts.cwd, target);
|
|
6692
7239
|
return collected.map((s) => ({ ...s, file: rel }));
|
|
6693
7240
|
},
|
|
6694
7241
|
// A different question about the same module: not "what sinks are in there" but "what does this
|
|
@@ -6714,7 +7261,7 @@ function createModuleGraph(ts, opts) {
|
|
|
6714
7261
|
}
|
|
6715
7262
|
function resolveRelativeModule(fromFile, spec) {
|
|
6716
7263
|
if (!spec.startsWith(".")) return void 0;
|
|
6717
|
-
const dir =
|
|
7264
|
+
const dir = dirname8(fromFile);
|
|
6718
7265
|
const base = resolvePath(dir, spec);
|
|
6719
7266
|
const candidates = [];
|
|
6720
7267
|
const jsLike = /\.(js|jsx|mjs|cjs)$/.exec(base);
|
|
@@ -6724,10 +7271,10 @@ function resolveRelativeModule(fromFile, spec) {
|
|
|
6724
7271
|
}
|
|
6725
7272
|
candidates.push(base);
|
|
6726
7273
|
for (const e of RESOLVE_EXTS) candidates.push(base + e);
|
|
6727
|
-
for (const e of RESOLVE_EXTS) candidates.push(
|
|
7274
|
+
for (const e of RESOLVE_EXTS) candidates.push(join18(base, "index" + e));
|
|
6728
7275
|
for (const c of candidates) {
|
|
6729
7276
|
try {
|
|
6730
|
-
if (
|
|
7277
|
+
if (statSync7(c).isFile()) return c;
|
|
6731
7278
|
} catch {
|
|
6732
7279
|
}
|
|
6733
7280
|
}
|
|
@@ -6753,8 +7300,8 @@ function collectCallees(sf, ts) {
|
|
|
6753
7300
|
}
|
|
6754
7301
|
|
|
6755
7302
|
// src/map/coordinates.ts
|
|
6756
|
-
function runtimeCoordinate(source,
|
|
6757
|
-
if (/\[\d*\]/.test(
|
|
7303
|
+
function runtimeCoordinate(source, path18) {
|
|
7304
|
+
if (/\[\d*\]/.test(path18)) {
|
|
6758
7305
|
return { runtimeParameter: null, runtimeParameterReason: "array traversal: needs an array_key_value rule, not a dotted parameter" };
|
|
6759
7306
|
}
|
|
6760
7307
|
switch (source) {
|
|
@@ -6763,15 +7310,15 @@ function runtimeCoordinate(source, path17) {
|
|
|
6763
7310
|
case "multipart":
|
|
6764
7311
|
case "body":
|
|
6765
7312
|
case "server-fn-data":
|
|
6766
|
-
return { runtimeParameter: `post.${
|
|
7313
|
+
return { runtimeParameter: `post.${path18}` };
|
|
6767
7314
|
case "query":
|
|
6768
|
-
return { runtimeParameter: `get.${
|
|
7315
|
+
return { runtimeParameter: `get.${path18}` };
|
|
6769
7316
|
case "cookie":
|
|
6770
|
-
return { runtimeParameter: `cookie.${
|
|
7317
|
+
return { runtimeParameter: `cookie.${path18}` };
|
|
6771
7318
|
case "file":
|
|
6772
|
-
return { runtimeParameter: `files.${
|
|
7319
|
+
return { runtimeParameter: `files.${path18}` };
|
|
6773
7320
|
case "header":
|
|
6774
|
-
return { runtimeParameter: `server.HTTP_${
|
|
7321
|
+
return { runtimeParameter: `server.HTTP_${path18.toUpperCase().replace(/-/g, "_")}` };
|
|
6775
7322
|
case "route-param":
|
|
6776
7323
|
return { runtimeParameter: null, runtimeParameterReason: "route parameters are not exposed by the runtime resolver" };
|
|
6777
7324
|
default:
|
|
@@ -6803,8 +7350,8 @@ function addressSpaceOf(source) {
|
|
|
6803
7350
|
function isProvenFlow(confidence) {
|
|
6804
7351
|
return confidence === "exact-local" || confidence === "transformed-local";
|
|
6805
7352
|
}
|
|
6806
|
-
function inputIdOf(source,
|
|
6807
|
-
return `${addressSpaceOf(source)}:${
|
|
7353
|
+
function inputIdOf(source, path18) {
|
|
7354
|
+
return `${addressSpaceOf(source)}:${path18}`;
|
|
6808
7355
|
}
|
|
6809
7356
|
function withCoordinates(fields, source) {
|
|
6810
7357
|
return fields.map((f) => {
|
|
@@ -6996,6 +7543,7 @@ function requestMemberAccesses(params, body, ts, opts = {}) {
|
|
|
6996
7543
|
}
|
|
6997
7544
|
|
|
6998
7545
|
// src/map/flows.ts
|
|
7546
|
+
var MAX_DEPENDENCY_INPUT_FLOWS_PER_ENDPOINT = 100;
|
|
6999
7547
|
function spaceOfKey(key) {
|
|
7000
7548
|
if (key === "query") return "get";
|
|
7001
7549
|
if (key === "params") return "route-param";
|
|
@@ -7006,16 +7554,23 @@ function spaceOfKey(key) {
|
|
|
7006
7554
|
if (key === "files") return "files";
|
|
7007
7555
|
return void 0;
|
|
7008
7556
|
}
|
|
7009
|
-
function linkedFlows(body, params, inputs, sinks, ts) {
|
|
7010
|
-
const { flows, limitations } = linkFlows(body, params, inputs, sinks, ts);
|
|
7011
|
-
return
|
|
7557
|
+
function linkedFlows(body, params, inputs, sinks, ts, invocations = []) {
|
|
7558
|
+
const { flows, limitations, dependencyInputFlows, dependencyInputFlowsTruncated } = linkFlows(body, params, inputs, sinks, ts, invocations);
|
|
7559
|
+
return {
|
|
7560
|
+
flows,
|
|
7561
|
+
...limitations.length > 0 ? { limitations } : {},
|
|
7562
|
+
...dependencyInputFlows.length > 0 ? { dependencyInputFlows } : {},
|
|
7563
|
+
...dependencyInputFlowsTruncated ? { dependencyInputFlowsTruncated: true } : {}
|
|
7564
|
+
};
|
|
7012
7565
|
}
|
|
7013
|
-
function linkFlows(bodyNode, params, inputs, sinks, ts) {
|
|
7014
|
-
if (!bodyNode ||
|
|
7566
|
+
function linkFlows(bodyNode, params, inputs, sinks, ts, invocations) {
|
|
7567
|
+
if (!bodyNode || inputs.length === 0 || sinks.length === 0 && invocations.length === 0) {
|
|
7568
|
+
return { flows: [], limitations: [], dependencyInputFlows: [], dependencyInputFlowsTruncated: false };
|
|
7569
|
+
}
|
|
7015
7570
|
const CONTAINER_KEYS = /* @__PURE__ */ new Set([...REQ_SOURCES, "data", "input", "payload"]);
|
|
7016
7571
|
const rootPath = /* @__PURE__ */ new Map();
|
|
7017
|
-
const addRoot = (name,
|
|
7018
|
-
if (!rootPath.has(name)) rootPath.set(name, { path:
|
|
7572
|
+
const addRoot = (name, path18, space) => {
|
|
7573
|
+
if (!rootPath.has(name)) rootPath.set(name, { path: path18, space });
|
|
7019
7574
|
};
|
|
7020
7575
|
for (const p of params ?? []) {
|
|
7021
7576
|
if (!p?.name) continue;
|
|
@@ -7146,7 +7701,57 @@ function linkFlows(bodyNode, params, inputs, sinks, ts) {
|
|
|
7146
7701
|
}
|
|
7147
7702
|
for (const l of sinkLimits) allLimits.push(l);
|
|
7148
7703
|
}
|
|
7149
|
-
|
|
7704
|
+
const dependencyInputFlows = [];
|
|
7705
|
+
let dependencyInputFlowsTruncated = false;
|
|
7706
|
+
const seenDependencyInputs = /* @__PURE__ */ new Set();
|
|
7707
|
+
const modeledCalls = new Set(sinks.filter((s) => s.file === void 0 && s.start !== void 0 && s.end !== void 0).map((s) => `${s.start}:${s.end}`));
|
|
7708
|
+
for (const invocation of invocations) {
|
|
7709
|
+
if (invocation.package.startsWith("node:")) continue;
|
|
7710
|
+
for (const site of invocation.sites) {
|
|
7711
|
+
if (site.start === void 0 || site.end === void 0) continue;
|
|
7712
|
+
const span = `${site.start}:${site.end}`;
|
|
7713
|
+
if (modeledCalls.has(span)) continue;
|
|
7714
|
+
const call = callBySpan.get(span);
|
|
7715
|
+
if (!call) continue;
|
|
7716
|
+
const args = call.arguments ?? [];
|
|
7717
|
+
for (let argumentIndex = 0; argumentIndex < args.length; argumentIndex++) {
|
|
7718
|
+
const argument = args[argumentIndex];
|
|
7719
|
+
if (sinkArgumentLimitations(argument, ts, rootPath).length > 0) continue;
|
|
7720
|
+
const whole = pathFromTainted(argument, ts, rootPath);
|
|
7721
|
+
for (const read2 of taintedReadPaths(argument, ts, rootPath, false)) {
|
|
7722
|
+
if (read2.space === void 0) continue;
|
|
7723
|
+
for (const input of inputs) {
|
|
7724
|
+
if (addressSpaceOf(input.source) !== read2.space) continue;
|
|
7725
|
+
const inputPath = normalizePath(input.name);
|
|
7726
|
+
if (read2.path !== inputPath && !read2.path.startsWith(inputPath + ".")) continue;
|
|
7727
|
+
const key = `${span}:${argumentIndex}:${input.id}:${invocation.package}:${invocation.symbol}`;
|
|
7728
|
+
if (seenDependencyInputs.has(key)) continue;
|
|
7729
|
+
seenDependencyInputs.add(key);
|
|
7730
|
+
if (dependencyInputFlows.length >= MAX_DEPENDENCY_INPUT_FLOWS_PER_ENDPOINT) {
|
|
7731
|
+
dependencyInputFlowsTruncated = true;
|
|
7732
|
+
continue;
|
|
7733
|
+
}
|
|
7734
|
+
dependencyInputFlows.push({
|
|
7735
|
+
input: input.name,
|
|
7736
|
+
inputId: input.id,
|
|
7737
|
+
package: invocation.package,
|
|
7738
|
+
specifier: invocation.specifiers[0],
|
|
7739
|
+
api: invocation.api,
|
|
7740
|
+
symbol: invocation.symbol,
|
|
7741
|
+
kind: invocation.kind,
|
|
7742
|
+
resolution: invocation.resolution,
|
|
7743
|
+
argumentIndex,
|
|
7744
|
+
argumentUse: whole?.path === read2.path && whole.space === read2.space ? "direct" : "within-expression",
|
|
7745
|
+
line: site.line,
|
|
7746
|
+
start: site.start,
|
|
7747
|
+
end: site.end
|
|
7748
|
+
});
|
|
7749
|
+
}
|
|
7750
|
+
}
|
|
7751
|
+
}
|
|
7752
|
+
}
|
|
7753
|
+
}
|
|
7754
|
+
return { flows, limitations: dedupeLimitations(allLimits), dependencyInputFlows, dependencyInputFlowsTruncated };
|
|
7150
7755
|
}
|
|
7151
7756
|
function dedupeLimitations(list) {
|
|
7152
7757
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7160,8 +7765,8 @@ function dedupeLimitations(list) {
|
|
|
7160
7765
|
function join22(base, seg) {
|
|
7161
7766
|
return base ? `${base}.${seg}` : seg;
|
|
7162
7767
|
}
|
|
7163
|
-
function normalizePath(
|
|
7164
|
-
return
|
|
7768
|
+
function normalizePath(path18) {
|
|
7769
|
+
return path18.replace(/\[\d*\]/g, "").split(".").filter(Boolean).join(".");
|
|
7165
7770
|
}
|
|
7166
7771
|
function fluentChainCalls(call, ts) {
|
|
7167
7772
|
let root = call;
|
|
@@ -7192,7 +7797,7 @@ function fluentChainCalls(call, ts) {
|
|
|
7192
7797
|
collect(root);
|
|
7193
7798
|
return out;
|
|
7194
7799
|
}
|
|
7195
|
-
function taintedReadPaths(node, ts, rootPath) {
|
|
7800
|
+
function taintedReadPaths(node, ts, rootPath, includeDeferredBodies = true) {
|
|
7196
7801
|
const out = [];
|
|
7197
7802
|
const seen = /* @__PURE__ */ new Set();
|
|
7198
7803
|
const add = (r) => {
|
|
@@ -7204,6 +7809,7 @@ function taintedReadPaths(node, ts, rootPath) {
|
|
|
7204
7809
|
};
|
|
7205
7810
|
const visit = (n) => {
|
|
7206
7811
|
if (!n) return;
|
|
7812
|
+
if (!includeDeferredBodies && (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n))) return;
|
|
7207
7813
|
if (ts.isPropertyAccessExpression(n) || ts.isElementAccessExpression(n)) {
|
|
7208
7814
|
const read2 = pathFromTainted(n, ts, rootPath);
|
|
7209
7815
|
if (read2 !== void 0) {
|
|
@@ -7286,6 +7892,120 @@ function pathFromTainted(node, ts, rootPath) {
|
|
|
7286
7892
|
return { path: normalizePath([base.path, ...segs].filter(Boolean).join(".")), space };
|
|
7287
7893
|
}
|
|
7288
7894
|
|
|
7895
|
+
// src/map/invocations.ts
|
|
7896
|
+
var MAX_SITES = 3;
|
|
7897
|
+
function collectInvocations(sf, ts, bindings, ctx, root = sf) {
|
|
7898
|
+
const found = [];
|
|
7899
|
+
const counts = { total: 0, dependency: 0, local: 0, ambiguous: 0 };
|
|
7900
|
+
const isLocalName = (name, node) => bindings.locals.has(name) || isShadowedByEnclosingBinding(node, name, ts);
|
|
7901
|
+
const lineOf2 = (node) => {
|
|
7902
|
+
try {
|
|
7903
|
+
return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
7904
|
+
} catch {
|
|
7905
|
+
return void 0;
|
|
7906
|
+
}
|
|
7907
|
+
};
|
|
7908
|
+
const traceRoot = (root2) => {
|
|
7909
|
+
const specifier = bindings.resolve(root2);
|
|
7910
|
+
if (specifier === void 0) return null;
|
|
7911
|
+
const direct = npmPackageOf(specifier);
|
|
7912
|
+
if (direct !== void 0) {
|
|
7913
|
+
const resolution = bindings.originOf(root2) ?? "direct";
|
|
7914
|
+
return { pkg: direct, specifier, resolution, named: resolution === "direct" };
|
|
7915
|
+
}
|
|
7916
|
+
const exportName = bindings.exportNameOf(root2) ?? root2;
|
|
7917
|
+
const viaModule = ctx.graph.importedBinding(ctx.file, specifier, exportName);
|
|
7918
|
+
if (viaModule !== void 0) {
|
|
7919
|
+
return {
|
|
7920
|
+
pkg: viaModule.package,
|
|
7921
|
+
specifier,
|
|
7922
|
+
resolution: "reexport",
|
|
7923
|
+
named: viaModule.origin === "direct",
|
|
7924
|
+
apiName: viaModule.name
|
|
7925
|
+
};
|
|
7926
|
+
}
|
|
7927
|
+
return null;
|
|
7928
|
+
};
|
|
7929
|
+
const push = (traced, api, receiver, kind, node) => {
|
|
7930
|
+
const span = spanOf(node);
|
|
7931
|
+
found.push({
|
|
7932
|
+
package: traced.pkg,
|
|
7933
|
+
specifiers: [traced.specifier],
|
|
7934
|
+
api,
|
|
7935
|
+
...receiver !== void 0 ? { receiver } : {},
|
|
7936
|
+
symbol: receiver !== void 0 ? `${receiver}.${api}` : api,
|
|
7937
|
+
kind,
|
|
7938
|
+
// Only ever `import`: a global has no package to correlate against, and an inferred package is not
|
|
7939
|
+
// evidence about the value being called.
|
|
7940
|
+
attribution: "import",
|
|
7941
|
+
resolution: traced.resolution,
|
|
7942
|
+
callCount: 1,
|
|
7943
|
+
sites: [{ file: ctx.owner, line: lineOf2(node), start: span.start, end: span.end }]
|
|
7944
|
+
});
|
|
7945
|
+
};
|
|
7946
|
+
const visit = (node) => {
|
|
7947
|
+
if (root !== sf && node !== root && isUninvokedFunctionDeclaration(node, ts)) return;
|
|
7948
|
+
if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
|
|
7949
|
+
const callee = node.expression;
|
|
7950
|
+
counts.total++;
|
|
7951
|
+
if (ts.isIdentifier(callee)) {
|
|
7952
|
+
const traced = traceRoot(callee.text);
|
|
7953
|
+
if (traced?.named) {
|
|
7954
|
+
const api = traced.apiName ?? bindings.exportNameOf(callee.text) ?? callee.text;
|
|
7955
|
+
push(traced, api, void 0, ts.isNewExpression(node) ? "construct" : "call", node);
|
|
7956
|
+
counts.dependency++;
|
|
7957
|
+
} else if (traced) {
|
|
7958
|
+
counts.ambiguous++;
|
|
7959
|
+
} else if (isLocalName(callee.text, node)) {
|
|
7960
|
+
counts.local++;
|
|
7961
|
+
} else {
|
|
7962
|
+
counts.ambiguous++;
|
|
7963
|
+
}
|
|
7964
|
+
} else if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name)) {
|
|
7965
|
+
const root2 = rootIdentifier(callee.expression, ts);
|
|
7966
|
+
const traced = root2 !== void 0 ? traceRoot(root2) : null;
|
|
7967
|
+
if (traced && root2 !== void 0) {
|
|
7968
|
+
counts.dependency++;
|
|
7969
|
+
const receiver = traced.resolution === "direct" && bindings.isPackageMember(root2) ? bindings.exportNameOf(root2) ?? root2 : void 0;
|
|
7970
|
+
push(traced, callee.name.text, receiver, "member", node);
|
|
7971
|
+
} else if (root2 !== void 0 && ts.isIdentifier(callee.expression) && isLocalName(root2, node)) {
|
|
7972
|
+
counts.local++;
|
|
7973
|
+
} else {
|
|
7974
|
+
counts.ambiguous++;
|
|
7975
|
+
}
|
|
7976
|
+
} else {
|
|
7977
|
+
counts.ambiguous++;
|
|
7978
|
+
}
|
|
7979
|
+
}
|
|
7980
|
+
ts.forEachChild(node, visit);
|
|
7981
|
+
};
|
|
7982
|
+
visit(root);
|
|
7983
|
+
return { invocations: found, counts };
|
|
7984
|
+
}
|
|
7985
|
+
function createInvocationInventory() {
|
|
7986
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
7987
|
+
return {
|
|
7988
|
+
add(invocations) {
|
|
7989
|
+
for (const invocation of invocations) {
|
|
7990
|
+
const key = `${invocation.package}|${invocation.symbol}|${invocation.kind}|${invocation.resolution}`;
|
|
7991
|
+
const existing = byKey.get(key);
|
|
7992
|
+
if (existing === void 0) {
|
|
7993
|
+
byKey.set(key, { ...invocation });
|
|
7994
|
+
continue;
|
|
7995
|
+
}
|
|
7996
|
+
existing.callCount += invocation.callCount;
|
|
7997
|
+
for (const specifier of invocation.specifiers) {
|
|
7998
|
+
if (!existing.specifiers.includes(specifier)) existing.specifiers.push(specifier);
|
|
7999
|
+
}
|
|
8000
|
+
if (existing.sites.length < MAX_SITES) existing.sites.push(...invocation.sites.slice(0, MAX_SITES - existing.sites.length));
|
|
8001
|
+
}
|
|
8002
|
+
},
|
|
8003
|
+
list() {
|
|
8004
|
+
return [...byKey.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([, invocation]) => ({ ...invocation, specifiers: [...invocation.specifiers].sort() }));
|
|
8005
|
+
}
|
|
8006
|
+
};
|
|
8007
|
+
}
|
|
8008
|
+
|
|
7289
8009
|
// src/map/entries.ts
|
|
7290
8010
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
7291
8011
|
function extractFromFile(sf, ts, localSinks, bindings, ctx) {
|
|
@@ -7309,7 +8029,7 @@ function extractFromFile(sf, ts, localSinks, bindings, ctx) {
|
|
|
7309
8029
|
...spanOf(decl),
|
|
7310
8030
|
inputs,
|
|
7311
8031
|
sinks,
|
|
7312
|
-
...linkedFlows(handlerBody, handlerFn?.parameters, inputs, sinks, ts)
|
|
8032
|
+
...linkedFlows(handlerBody, handlerFn?.parameters, inputs, sinks, ts, handlerInvocations(handlerBody, ts, bindings, ctx))
|
|
7313
8033
|
};
|
|
7314
8034
|
if (validatorCall && inputs.length === 0) ep.inputsResolved = false;
|
|
7315
8035
|
out.push(ep);
|
|
@@ -7384,6 +8104,10 @@ function hasUseServerDirective(fn, ts) {
|
|
|
7384
8104
|
const first = fn.body?.statements?.[0];
|
|
7385
8105
|
return Boolean(first && ts.isExpressionStatement(first) && ts.isStringLiteralLike(first.expression) && first.expression.text === "use server");
|
|
7386
8106
|
}
|
|
8107
|
+
function handlerInvocations(body, ts, bindings, ctx) {
|
|
8108
|
+
const sf = body?.getSourceFile?.();
|
|
8109
|
+
return sf ? collectInvocations(sf, ts, bindings, ctx, body).invocations : [];
|
|
8110
|
+
}
|
|
7387
8111
|
function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, ctx, extra = {}) {
|
|
7388
8112
|
const entryKind = kindLabel === "route-registration" || kindLabel === "server-action" || kindLabel === "edge-function" ? kindLabel : "route-handler";
|
|
7389
8113
|
const payloadStyle = kindLabel === "server-action";
|
|
@@ -7402,14 +8126,14 @@ function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, c
|
|
|
7402
8126
|
end: extra.end,
|
|
7403
8127
|
inputs,
|
|
7404
8128
|
sinks,
|
|
7405
|
-
...linkedFlows(body, params, inputs, sinks, ts)
|
|
8129
|
+
...linkedFlows(body, params, inputs, sinks, ts, handlerInvocations(body, ts, bindings, ctx))
|
|
7406
8130
|
};
|
|
7407
8131
|
}
|
|
7408
8132
|
|
|
7409
8133
|
// src/map/imports.ts
|
|
7410
|
-
import { readFileSync as
|
|
7411
|
-
import { join as
|
|
7412
|
-
var
|
|
8134
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
8135
|
+
import { join as join19 } from "path";
|
|
8136
|
+
var MAX_SITES2 = 5;
|
|
7413
8137
|
var NPM_NAME = /^(?:@[^/@\s~][^/@\s]*\/)?[^/@\s.~][^/@\s]*$/;
|
|
7414
8138
|
function isNpmPackageName(pkg) {
|
|
7415
8139
|
return pkg.startsWith("node:") || NPM_NAME.test(pkg);
|
|
@@ -7419,7 +8143,7 @@ function readPathAliases(cwd) {
|
|
|
7419
8143
|
for (const name of ["tsconfig.json", "jsconfig.json"]) {
|
|
7420
8144
|
let paths;
|
|
7421
8145
|
try {
|
|
7422
|
-
const raw =
|
|
8146
|
+
const raw = readFileSync15(join19(cwd, name), "utf8");
|
|
7423
8147
|
const parsed = JSON.parse(stripJsonComments(raw));
|
|
7424
8148
|
paths = parsed.compilerOptions?.paths;
|
|
7425
8149
|
} catch {
|
|
@@ -7623,7 +8347,7 @@ function createImportInventory(aliases = []) {
|
|
|
7623
8347
|
for (const n of edge.names) acc.names.add(n);
|
|
7624
8348
|
if (!parsed) acc.namesComplete = false;
|
|
7625
8349
|
acc.siteCount++;
|
|
7626
|
-
if (acc.sites.length <
|
|
8350
|
+
if (acc.sites.length < MAX_SITES2) acc.sites.push({ file: relFile, line: edge.line });
|
|
7627
8351
|
}
|
|
7628
8352
|
},
|
|
7629
8353
|
list() {
|
|
@@ -7659,127 +8383,15 @@ function lineAt(starts, pos) {
|
|
|
7659
8383
|
return lo + 1;
|
|
7660
8384
|
}
|
|
7661
8385
|
|
|
7662
|
-
// src/map/invocations.ts
|
|
7663
|
-
var MAX_SITES2 = 3;
|
|
7664
|
-
function collectInvocations(sf, ts, bindings, ctx) {
|
|
7665
|
-
const found = [];
|
|
7666
|
-
const counts = { total: 0, dependency: 0, local: 0, ambiguous: 0 };
|
|
7667
|
-
const isLocalName = (name, node) => bindings.locals.has(name) || isShadowedByEnclosingBinding(node, name, ts);
|
|
7668
|
-
const lineOf2 = (node) => {
|
|
7669
|
-
try {
|
|
7670
|
-
return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
7671
|
-
} catch {
|
|
7672
|
-
return void 0;
|
|
7673
|
-
}
|
|
7674
|
-
};
|
|
7675
|
-
const traceRoot = (root) => {
|
|
7676
|
-
const specifier = bindings.resolve(root);
|
|
7677
|
-
if (specifier === void 0) return null;
|
|
7678
|
-
const direct = npmPackageOf(specifier);
|
|
7679
|
-
if (direct !== void 0) {
|
|
7680
|
-
const resolution = bindings.originOf(root) ?? "direct";
|
|
7681
|
-
return { pkg: direct, specifier, resolution, named: resolution === "direct" };
|
|
7682
|
-
}
|
|
7683
|
-
const exportName = bindings.exportNameOf(root) ?? root;
|
|
7684
|
-
const viaModule = ctx.graph.importedBinding(ctx.file, specifier, exportName);
|
|
7685
|
-
if (viaModule !== void 0) {
|
|
7686
|
-
return {
|
|
7687
|
-
pkg: viaModule.package,
|
|
7688
|
-
specifier,
|
|
7689
|
-
resolution: "reexport",
|
|
7690
|
-
named: viaModule.origin === "direct",
|
|
7691
|
-
apiName: viaModule.name
|
|
7692
|
-
};
|
|
7693
|
-
}
|
|
7694
|
-
return null;
|
|
7695
|
-
};
|
|
7696
|
-
const push = (traced, api, receiver, kind, node) => {
|
|
7697
|
-
const span = spanOf(node);
|
|
7698
|
-
found.push({
|
|
7699
|
-
package: traced.pkg,
|
|
7700
|
-
specifiers: [traced.specifier],
|
|
7701
|
-
api,
|
|
7702
|
-
...receiver !== void 0 ? { receiver } : {},
|
|
7703
|
-
symbol: receiver !== void 0 ? `${receiver}.${api}` : api,
|
|
7704
|
-
kind,
|
|
7705
|
-
// Only ever `import`: a global has no package to correlate against, and an inferred package is not
|
|
7706
|
-
// evidence about the value being called.
|
|
7707
|
-
attribution: "import",
|
|
7708
|
-
resolution: traced.resolution,
|
|
7709
|
-
callCount: 1,
|
|
7710
|
-
sites: [{ file: ctx.owner, line: lineOf2(node), start: span.start, end: span.end }]
|
|
7711
|
-
});
|
|
7712
|
-
};
|
|
7713
|
-
const visit = (node) => {
|
|
7714
|
-
if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
|
|
7715
|
-
const callee = node.expression;
|
|
7716
|
-
counts.total++;
|
|
7717
|
-
if (ts.isIdentifier(callee)) {
|
|
7718
|
-
const traced = traceRoot(callee.text);
|
|
7719
|
-
if (traced?.named) {
|
|
7720
|
-
const api = traced.apiName ?? bindings.exportNameOf(callee.text) ?? callee.text;
|
|
7721
|
-
push(traced, api, void 0, ts.isNewExpression(node) ? "construct" : "call", node);
|
|
7722
|
-
counts.dependency++;
|
|
7723
|
-
} else if (traced) {
|
|
7724
|
-
counts.ambiguous++;
|
|
7725
|
-
} else if (isLocalName(callee.text, node)) {
|
|
7726
|
-
counts.local++;
|
|
7727
|
-
} else {
|
|
7728
|
-
counts.ambiguous++;
|
|
7729
|
-
}
|
|
7730
|
-
} else if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name)) {
|
|
7731
|
-
const root = rootIdentifier(callee.expression, ts);
|
|
7732
|
-
const traced = root !== void 0 ? traceRoot(root) : null;
|
|
7733
|
-
if (traced && root !== void 0) {
|
|
7734
|
-
counts.dependency++;
|
|
7735
|
-
const receiver = traced.resolution === "direct" && bindings.isPackageMember(root) ? bindings.exportNameOf(root) ?? root : void 0;
|
|
7736
|
-
push(traced, callee.name.text, receiver, "member", node);
|
|
7737
|
-
} else if (root !== void 0 && ts.isIdentifier(callee.expression) && isLocalName(root, node)) {
|
|
7738
|
-
counts.local++;
|
|
7739
|
-
} else {
|
|
7740
|
-
counts.ambiguous++;
|
|
7741
|
-
}
|
|
7742
|
-
} else {
|
|
7743
|
-
counts.ambiguous++;
|
|
7744
|
-
}
|
|
7745
|
-
}
|
|
7746
|
-
ts.forEachChild(node, visit);
|
|
7747
|
-
};
|
|
7748
|
-
visit(sf);
|
|
7749
|
-
return { invocations: found, counts };
|
|
7750
|
-
}
|
|
7751
|
-
function createInvocationInventory() {
|
|
7752
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
7753
|
-
return {
|
|
7754
|
-
add(invocations) {
|
|
7755
|
-
for (const invocation of invocations) {
|
|
7756
|
-
const key = `${invocation.package}|${invocation.symbol}|${invocation.kind}|${invocation.resolution}`;
|
|
7757
|
-
const existing = byKey.get(key);
|
|
7758
|
-
if (existing === void 0) {
|
|
7759
|
-
byKey.set(key, { ...invocation });
|
|
7760
|
-
continue;
|
|
7761
|
-
}
|
|
7762
|
-
existing.callCount += invocation.callCount;
|
|
7763
|
-
for (const specifier of invocation.specifiers) {
|
|
7764
|
-
if (!existing.specifiers.includes(specifier)) existing.specifiers.push(specifier);
|
|
7765
|
-
}
|
|
7766
|
-
if (existing.sites.length < MAX_SITES2) existing.sites.push(...invocation.sites.slice(0, MAX_SITES2 - existing.sites.length));
|
|
7767
|
-
}
|
|
7768
|
-
},
|
|
7769
|
-
list() {
|
|
7770
|
-
return [...byKey.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([, invocation]) => ({ ...invocation, specifiers: [...invocation.specifiers].sort() }));
|
|
7771
|
-
}
|
|
7772
|
-
};
|
|
7773
|
-
}
|
|
7774
|
-
|
|
7775
8386
|
// src/map/extract.ts
|
|
8387
|
+
var MAX_DEPENDENCY_INPUT_FLOWS_PER_MAP = 500;
|
|
7776
8388
|
async function extractInputMap(cwd, ts, options = {}) {
|
|
7777
8389
|
const notes = [];
|
|
7778
8390
|
const endpoints = [];
|
|
7779
8391
|
const failed = [];
|
|
7780
8392
|
let boundary = cwd;
|
|
7781
8393
|
try {
|
|
7782
|
-
boundary =
|
|
8394
|
+
boundary = realpathSync6(cwd);
|
|
7783
8395
|
} catch {
|
|
7784
8396
|
}
|
|
7785
8397
|
const graph = createModuleGraph(ts, { cwd, boundary, followOutside: options.followSymlinks });
|
|
@@ -7787,6 +8399,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7787
8399
|
const files = collectSources(cwd, boundary, { followOutside: options.followSymlinks }, [], /* @__PURE__ */ new Set(), stats);
|
|
7788
8400
|
const imports = createImportInventory(readPathAliases(cwd));
|
|
7789
8401
|
const invocations = createInvocationInventory();
|
|
8402
|
+
let dependencyInputFlowCount = 0;
|
|
7790
8403
|
let parsed = 0;
|
|
7791
8404
|
let preFiltered = 0;
|
|
7792
8405
|
let importScanFailures = 0;
|
|
@@ -7798,8 +8411,8 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7798
8411
|
const hopTargets = /* @__PURE__ */ new Set();
|
|
7799
8412
|
for (const file of files) {
|
|
7800
8413
|
try {
|
|
7801
|
-
const text =
|
|
7802
|
-
const relFile =
|
|
8414
|
+
const text = readFileSync16(file, "utf8");
|
|
8415
|
+
const relFile = relative6(cwd, file);
|
|
7803
8416
|
sourceBytes += text.length;
|
|
7804
8417
|
if (!hasEntrySignal(text)) {
|
|
7805
8418
|
preFiltered++;
|
|
@@ -7830,6 +8443,13 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7830
8443
|
calls.ambiguous += called.counts.ambiguous;
|
|
7831
8444
|
const localSinks = collectLocalSinks(sf, ts, bindings, ctx);
|
|
7832
8445
|
for (const ep of extractFromFile(sf, ts, localSinks, bindings, ctx)) {
|
|
8446
|
+
const links = ep.dependencyInputFlows ?? [];
|
|
8447
|
+
const remaining = Math.max(0, MAX_DEPENDENCY_INPUT_FLOWS_PER_MAP - dependencyInputFlowCount);
|
|
8448
|
+
if (links.length > remaining) {
|
|
8449
|
+
ep.dependencyInputFlows = links.slice(0, remaining);
|
|
8450
|
+
ep.dependencyInputFlowsTruncated = true;
|
|
8451
|
+
}
|
|
8452
|
+
dependencyInputFlowCount += ep.dependencyInputFlows?.length ?? 0;
|
|
7833
8453
|
if (ep.route === void 0 && ep.entryKind === "edge-function") {
|
|
7834
8454
|
const fn = functionNameFromPath(relFile);
|
|
7835
8455
|
if (fn) ep.route = "/" + fn;
|
|
@@ -7845,7 +8465,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7845
8465
|
}
|
|
7846
8466
|
} catch (e) {
|
|
7847
8467
|
if (typeof process !== "undefined" && process.env?.PS_MAP_DEBUG) console.error("[patchstack] map error", file, e);
|
|
7848
|
-
failed.push(
|
|
8468
|
+
failed.push(relative6(cwd, file));
|
|
7849
8469
|
}
|
|
7850
8470
|
}
|
|
7851
8471
|
let hopParsed = 0;
|
|
@@ -7853,10 +8473,10 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7853
8473
|
for (const target of hopTargets) {
|
|
7854
8474
|
if (entryFiles.has(target)) continue;
|
|
7855
8475
|
try {
|
|
7856
|
-
const text =
|
|
8476
|
+
const text = readFileSync16(target, "utf8");
|
|
7857
8477
|
const sf = ts.createSourceFile(target, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, target));
|
|
7858
8478
|
const bindings = buildModuleBindings(sf, ts);
|
|
7859
|
-
const called = collectInvocations(sf, ts, bindings, { file: target, owner:
|
|
8479
|
+
const called = collectInvocations(sf, ts, bindings, { file: target, owner: relative6(cwd, target), graph });
|
|
7860
8480
|
invocations.add(called.invocations);
|
|
7861
8481
|
calls.total += called.counts.total;
|
|
7862
8482
|
calls.dependency += called.counts.dependency;
|
|
@@ -7870,6 +8490,9 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7870
8490
|
}
|
|
7871
8491
|
notes.push("Static analysis is best-effort \u2014 this is the DETECTED surface, not a proof of completeness.");
|
|
7872
8492
|
notes.push('`inputs` and `sinks` are INVENTORIES (both present in the handler). Only `flows` asserts that an input reaches a sink: require confidence "exact-local" or "transformed-local" before pinning a rule, and identify the input by `inputId` \u2014 a field NAME can occur in more than one request namespace.');
|
|
8493
|
+
if (dependencyInputFlowCount > 0) {
|
|
8494
|
+
notes.push("`dependencyInputFlows` records request inputs observed in arguments of dependency API calls. This is weaker than a modeled sink: it does not establish what the dependency does internally or authorize a blocking rule. Absence is not evidence that no such call exists.");
|
|
8495
|
+
}
|
|
7873
8496
|
notes.push("Sinks are followed into same-file helpers and ONE hop into an imported relative module (a dependency\u2019s internals are not followed); deeper or dynamic indirection is not traced. Sinks inside declared-but-uncalled local functions are excluded.");
|
|
7874
8497
|
notes.push('A sink `package` is resolved from the file\u2019s imports (`attribution: "import"`) or inferred from another import in the same file (`"inferred"`); an inferred package is a hint for a reviewer, not evidence about the receiver, and never licenses a rule.');
|
|
7875
8498
|
if (!options.followSymlinks) notes.push("Symlinks leaving the project directory were not followed (use --follow-symlinks to include them).");
|
|
@@ -7878,6 +8501,10 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7878
8501
|
notes.push(`${failed.length} file(s) could not be analyzed and were skipped (fail-open): ${sample}${failed.length > 5 ? ", \u2026" : ""}.`);
|
|
7879
8502
|
}
|
|
7880
8503
|
const unresolved = endpoints.filter((e) => e.inputsResolved === false).length;
|
|
8504
|
+
const truncatedDependencyFlows = endpoints.filter((e) => e.dependencyInputFlowsTruncated).length;
|
|
8505
|
+
if (truncatedDependencyFlows > 0) {
|
|
8506
|
+
notes.push(`${truncatedDependencyFlows} endpoint(s) had more dependency-input links than the bounded map carries; those endpoint records are marked dependencyInputFlowsTruncated.`);
|
|
8507
|
+
}
|
|
7881
8508
|
if (unresolved > 0) {
|
|
7882
8509
|
notes.push(`${unresolved} endpoint(s) declare an input validator that could not be statically parsed \u2014 their inputs are UNKNOWN, not empty (marked inputsResolved: false).`);
|
|
7883
8510
|
}
|
|
@@ -7888,7 +8515,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7888
8515
|
if (endpoints.length === 0) notes.push("No recognized server-side entry points found under the analyzed roots.");
|
|
7889
8516
|
const importList = imports.list();
|
|
7890
8517
|
const unmodelled = importList.filter((d) => d.recognizedSinkKinds.length === 0).length;
|
|
7891
|
-
const skippedDirsWithSource = stats.skippedWithSource.map((d) =>
|
|
8518
|
+
const skippedDirsWithSource = stats.skippedWithSource.map((d) => relative6(cwd, d));
|
|
7892
8519
|
const importCoverageGaps = {
|
|
7893
8520
|
unreadableFiles: failed.length,
|
|
7894
8521
|
unscannableFiles: importScanFailures,
|
|
@@ -8002,8 +8629,8 @@ function getStringFlag(flags, name) {
|
|
|
8002
8629
|
}
|
|
8003
8630
|
|
|
8004
8631
|
// src/build-stamp.ts
|
|
8005
|
-
import {
|
|
8006
|
-
import { join as
|
|
8632
|
+
import { lstatSync as lstatSync4, readFileSync as readFileSync17, readdirSync as readdirSync6, realpathSync as realpathSync7 } from "fs";
|
|
8633
|
+
import { join as join20, relative as relative7, sep as sep3 } from "path";
|
|
8007
8634
|
var RULES_FILE_NAMES = /* @__PURE__ */ new Set(["rules.json", "patchstack.rules.json"]);
|
|
8008
8635
|
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "coverage", "tmp", "vendor"]);
|
|
8009
8636
|
var MAX_DEPTH2 = 6;
|
|
@@ -8021,11 +8648,11 @@ function guardImportsRulesFile(dir, rulesName) {
|
|
|
8021
8648
|
}
|
|
8022
8649
|
for (const name of siblings) {
|
|
8023
8650
|
if (!GUARD_SOURCE.test(name)) continue;
|
|
8024
|
-
const
|
|
8651
|
+
const path18 = join20(dir, name);
|
|
8025
8652
|
try {
|
|
8026
|
-
const entry =
|
|
8653
|
+
const entry = lstatSync4(path18);
|
|
8027
8654
|
if (!entry.isFile() || entry.isSymbolicLink() || entry.size > MAX_GUARD_SOURCE_BYTES) continue;
|
|
8028
|
-
const source = stripComments(
|
|
8655
|
+
const source = stripComments(readFileSync17(path18, "utf8"));
|
|
8029
8656
|
if (source.includes("@patchstack/connect/protect") && importsRules.test(source)) return true;
|
|
8030
8657
|
} catch {
|
|
8031
8658
|
}
|
|
@@ -8042,14 +8669,14 @@ function findRulesFile(cwd) {
|
|
|
8042
8669
|
const unreadable = [];
|
|
8043
8670
|
let root;
|
|
8044
8671
|
try {
|
|
8045
|
-
root =
|
|
8672
|
+
root = realpathSync7(cwd);
|
|
8046
8673
|
} catch {
|
|
8047
8674
|
return { kind: "none" };
|
|
8048
8675
|
}
|
|
8049
|
-
const contained = (
|
|
8676
|
+
const contained = (path18) => {
|
|
8050
8677
|
try {
|
|
8051
|
-
const real =
|
|
8052
|
-
return real === root || real.startsWith(root +
|
|
8678
|
+
const real = realpathSync7(path18);
|
|
8679
|
+
return real === root || real.startsWith(root + sep3);
|
|
8053
8680
|
} catch {
|
|
8054
8681
|
return false;
|
|
8055
8682
|
}
|
|
@@ -8064,32 +8691,32 @@ function findRulesFile(cwd) {
|
|
|
8064
8691
|
}
|
|
8065
8692
|
for (const name of entries) {
|
|
8066
8693
|
if (name.startsWith(".")) continue;
|
|
8067
|
-
const
|
|
8694
|
+
const path18 = join20(dir, name);
|
|
8068
8695
|
let entry;
|
|
8069
8696
|
try {
|
|
8070
|
-
entry =
|
|
8697
|
+
entry = lstatSync4(path18);
|
|
8071
8698
|
} catch {
|
|
8072
8699
|
continue;
|
|
8073
8700
|
}
|
|
8074
8701
|
if (entry.isSymbolicLink()) continue;
|
|
8075
8702
|
if (entry.isDirectory()) {
|
|
8076
|
-
if (!SKIP_DIRECTORIES.has(name)) walk2(
|
|
8703
|
+
if (!SKIP_DIRECTORIES.has(name)) walk2(path18, depth + 1);
|
|
8077
8704
|
continue;
|
|
8078
8705
|
}
|
|
8079
8706
|
if (!entry.isFile() || !RULES_FILE_NAMES.has(name)) continue;
|
|
8080
|
-
if (!contained(
|
|
8707
|
+
if (!contained(path18)) continue;
|
|
8081
8708
|
if (!guardImportsRulesFile(dir, name)) continue;
|
|
8082
8709
|
try {
|
|
8083
|
-
if (isRuleBundle(JSON.parse(
|
|
8710
|
+
if (isRuleBundle(JSON.parse(readFileSync17(path18, "utf8")))) found.push(path18);
|
|
8084
8711
|
} catch {
|
|
8085
|
-
unreadable.push(
|
|
8712
|
+
unreadable.push(path18);
|
|
8086
8713
|
}
|
|
8087
8714
|
}
|
|
8088
8715
|
};
|
|
8089
8716
|
walk2(cwd, 0);
|
|
8090
|
-
if (found.length > 1) return { kind: "ambiguous", paths: found.map((p) =>
|
|
8717
|
+
if (found.length > 1) return { kind: "ambiguous", paths: found.map((p) => relative7(cwd, p)) };
|
|
8091
8718
|
if (found.length === 1) return { kind: "one", path: found[0] };
|
|
8092
|
-
if (unreadable.length > 0) return { kind: "unreadable", paths: unreadable.map((p) =>
|
|
8719
|
+
if (unreadable.length > 0) return { kind: "unreadable", paths: unreadable.map((p) => relative7(cwd, p)) };
|
|
8093
8720
|
return { kind: "none" };
|
|
8094
8721
|
}
|
|
8095
8722
|
function detectIndent(text) {
|
|
@@ -8110,10 +8737,10 @@ function applyBuildStamp(cwd, id) {
|
|
|
8110
8737
|
reason: `more than one candidate guard rules file (${location.paths.join(", ")}) \u2014 none was stamped`
|
|
8111
8738
|
};
|
|
8112
8739
|
}
|
|
8113
|
-
const file =
|
|
8740
|
+
const file = relative7(cwd, location.path);
|
|
8114
8741
|
let text;
|
|
8115
8742
|
try {
|
|
8116
|
-
text =
|
|
8743
|
+
text = readFileSync17(location.path, "utf8");
|
|
8117
8744
|
} catch (err) {
|
|
8118
8745
|
return { kind: "skipped", reason: `${file} could not be read (${err.message})` };
|
|
8119
8746
|
}
|
|
@@ -8133,17 +8760,9 @@ function applyBuildStamp(cwd, id) {
|
|
|
8133
8760
|
if (Object.keys(namespace).length === 0) delete bundle[BUILD_STAMP_KEY];
|
|
8134
8761
|
else bundle[BUILD_STAMP_KEY] = namespace;
|
|
8135
8762
|
const serialised = JSON.stringify(bundle, null, detectIndent(text)) + (text.endsWith("\n") ? "\n" : "");
|
|
8136
|
-
const temporary = `${location.path}.patchstack-${process.pid}.tmp`;
|
|
8137
8763
|
try {
|
|
8138
|
-
|
|
8139
|
-
writeFileSync8(temporary, serialised, { encoding: "utf8", mode });
|
|
8140
|
-
chmodSync(temporary, mode);
|
|
8141
|
-
renameSync(temporary, location.path);
|
|
8764
|
+
writeProjectFileSync(cwd, location.path, serialised, { encoding: "utf8" });
|
|
8142
8765
|
} catch (err) {
|
|
8143
|
-
try {
|
|
8144
|
-
unlinkSync(temporary);
|
|
8145
|
-
} catch {
|
|
8146
|
-
}
|
|
8147
8766
|
return { kind: "skipped", reason: `${file} could not be written (${err.message})` };
|
|
8148
8767
|
}
|
|
8149
8768
|
return id === null ? { kind: "cleared", file } : { kind: "stamped", file, id };
|
|
@@ -8282,7 +8901,7 @@ async function runMap(flags) {
|
|
|
8282
8901
|
const json = JSON.stringify(map, null, 2);
|
|
8283
8902
|
const out = getStringFlag(flags, "out");
|
|
8284
8903
|
if (out) {
|
|
8285
|
-
|
|
8904
|
+
atomicWriteFileSync(path15.resolve(out), json, { encoding: "utf8" });
|
|
8286
8905
|
console.error(`patchstack: wrote ${out}`);
|
|
8287
8906
|
} else if (flags.get("upload") !== true) {
|
|
8288
8907
|
console.log(json);
|
|
@@ -8331,8 +8950,8 @@ async function runMap(flags) {
|
|
|
8331
8950
|
}
|
|
8332
8951
|
|
|
8333
8952
|
// src/setup.ts
|
|
8334
|
-
import { readFileSync as
|
|
8335
|
-
import
|
|
8953
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
8954
|
+
import path16 from "path";
|
|
8336
8955
|
var SCAN_COMMAND = "patchstack-connect scan";
|
|
8337
8956
|
var MARK_BUILD_COMMAND = "patchstack-connect mark-build";
|
|
8338
8957
|
function setupProtection(cwd) {
|
|
@@ -8357,8 +8976,8 @@ function prependHook(existing, command) {
|
|
|
8357
8976
|
return [command, ...remaining].join(" && ");
|
|
8358
8977
|
}
|
|
8359
8978
|
function wireBuildScripts(cwd, packageManager) {
|
|
8360
|
-
const target =
|
|
8361
|
-
const raw =
|
|
8979
|
+
const target = path16.join(cwd, "package.json");
|
|
8980
|
+
const raw = readFileSync18(target, "utf8");
|
|
8362
8981
|
const pkg = JSON.parse(raw);
|
|
8363
8982
|
const scripts = pkg.scripts ?? {};
|
|
8364
8983
|
const build = scripts.build;
|
|
@@ -8404,7 +9023,7 @@ function wireBuildScripts(cwd, packageManager) {
|
|
|
8404
9023
|
const indentMatch = raw.match(/^[\t ]+(?=")/m)?.[0];
|
|
8405
9024
|
const indent = indentMatch?.includes(" ") ? " " : indentMatch?.length ?? 2;
|
|
8406
9025
|
const trailingNewline = raw.endsWith("\n") ? "\n" : "";
|
|
8407
|
-
|
|
9026
|
+
writeProjectFileSync(cwd, target, `${JSON.stringify(pkg, null, indent)}${trailingNewline}`, { encoding: "utf8" });
|
|
8408
9027
|
if (build === void 0 || build.trim().length === 0) {
|
|
8409
9028
|
return {
|
|
8410
9029
|
changed: true,
|
|
@@ -8578,7 +9197,7 @@ Environment:
|
|
|
8578
9197
|
PATCHSTACK_API_BASE API origin for /oauth/token and /api/logs/log (default: https://api.patchstack.com)
|
|
8579
9198
|
PATCHSTACK_ENDPOINT API endpoint (default: https://api.patchstack.com/monitor/pulse/manifest)
|
|
8580
9199
|
PATCHSTACK_TIMEOUT_MS Request timeout in ms (default: 30000; a scan that needs longer saves what worked to .patchstackrc.json)
|
|
8581
|
-
PATCHSTACK_ENVIRONMENT Manifest label: production, sandbox or local. Unset,
|
|
9200
|
+
PATCHSTACK_ENVIRONMENT Manifest label: production, sandbox or local. Unset, the build platform's own tier or branch name decides; a developer machine reports local.
|
|
8582
9201
|
PATCHSTACK_MODE (protect) Runtime guard mode: block (default) | dry-run
|
|
8583
9202
|
PATCHSTACK_ROUTE_WAF (protect) Set to 1 to also screen every request at the route level (opt-in)
|
|
8584
9203
|
|
|
@@ -8887,7 +9506,7 @@ async function runScan(args, options = {}) {
|
|
|
8887
9506
|
}
|
|
8888
9507
|
if (config.environment === "local") {
|
|
8889
9508
|
console.log(
|
|
8890
|
-
"Reporting from this machine as the local environment: the dashboard will show the app as configured, not deployed.
|
|
9509
|
+
"Reporting from this machine as the local environment: the dashboard will show the app as configured, not deployed. A build on a platform this recognises reports production or sandbox on its own; on a platform it does not know, set PATCHSTACK_ENVIRONMENT=production for the build that goes live."
|
|
8891
9510
|
);
|
|
8892
9511
|
} else {
|
|
8893
9512
|
const because = (config.environmentEvidence ?? []).length > 0 ? ` (${config.environmentEvidence.join("; ")})` : "";
|
|
@@ -9241,7 +9860,7 @@ async function runGuide(args) {
|
|
|
9241
9860
|
console.log("\u2014\u2014\u2014\u2014 Full reference guide (ships as AGENT-INSTALL.md) \u2014\u2014\u2014\u2014");
|
|
9242
9861
|
console.log("");
|
|
9243
9862
|
const guidePath = new URL("../AGENT-INSTALL.md", import.meta.url);
|
|
9244
|
-
console.log(
|
|
9863
|
+
console.log(readFileSync19(guidePath, "utf8"));
|
|
9245
9864
|
return 0;
|
|
9246
9865
|
}
|
|
9247
9866
|
async function runSetup(args) {
|
|
@@ -9416,7 +10035,7 @@ function describeStack(stack) {
|
|
|
9416
10035
|
}
|
|
9417
10036
|
function readBuildScript(cwd) {
|
|
9418
10037
|
try {
|
|
9419
|
-
const pkg = JSON.parse(
|
|
10038
|
+
const pkg = JSON.parse(readFileSync19(path17.join(cwd, "package.json"), "utf8"));
|
|
9420
10039
|
return pkg.scripts?.build;
|
|
9421
10040
|
} catch {
|
|
9422
10041
|
return void 0;
|
|
@@ -9496,7 +10115,7 @@ async function runMarkBuild(args) {
|
|
|
9496
10115
|
let marked = 0;
|
|
9497
10116
|
let widgetTouched = 0;
|
|
9498
10117
|
for (const file of files) {
|
|
9499
|
-
const before =
|
|
10118
|
+
const before = readFileSync19(file, "utf8");
|
|
9500
10119
|
let after = injectMarker(before, snippet);
|
|
9501
10120
|
if (widgetUuid !== null) {
|
|
9502
10121
|
const ensured = ensureWidgetInHtml(after, widgetUuid);
|
|
@@ -9506,7 +10125,7 @@ async function runMarkBuild(args) {
|
|
|
9506
10125
|
after = ensured.html;
|
|
9507
10126
|
}
|
|
9508
10127
|
if (after !== before) {
|
|
9509
|
-
|
|
10128
|
+
writeProjectFileSync(cwd, file, after, { encoding: "utf8" });
|
|
9510
10129
|
marked += 1;
|
|
9511
10130
|
}
|
|
9512
10131
|
}
|