@patchstack/connect 0.5.6 → 0.5.7
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/dist/chunk-2K5SHFUM.js +295 -0
- package/dist/chunk-2K5SHFUM.js.map +1 -0
- package/dist/cli.js +1001 -466
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +364 -39
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +6 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.js +375 -37
- package/dist/index.js.map +1 -1
- package/dist/protect/templates/demo-rules.json +1 -1
- package/dist/protect.cjs +1135 -241
- 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 +988 -179
- package/dist/protect.edge.js.map +3 -3
- package/dist/protect.js +842 -192
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-2ALIJGZ3.js → refresh-manifest-3YWIYENU.js} +82 -39
- package/dist/refresh-manifest-3YWIYENU.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";
|
|
@@ -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;
|
|
1270
1531
|
}
|
|
1271
|
-
if (
|
|
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;
|
|
1537
|
+
}
|
|
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,11 +2138,12 @@ 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 !== "";
|
|
1836
2149
|
var DISCRIMINATORS = [
|
|
@@ -1872,7 +2185,7 @@ var HOSTED_BUILDER_PACKAGES = [
|
|
|
1872
2185
|
function detectHostedBuilder(cwd) {
|
|
1873
2186
|
let declared;
|
|
1874
2187
|
try {
|
|
1875
|
-
const pkg = JSON.parse(
|
|
2188
|
+
const pkg = JSON.parse(readFileSync2(path6.join(cwd, "package.json"), "utf8"));
|
|
1876
2189
|
declared = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
|
|
1877
2190
|
} catch {
|
|
1878
2191
|
return null;
|
|
@@ -2098,7 +2411,15 @@ async function resolveConfig(options) {
|
|
|
2098
2411
|
const fromSecretFile = await readSecretFile(options.cwd);
|
|
2099
2412
|
const fromEnv = readEnv();
|
|
2100
2413
|
const siteUuid = options.cliSiteUuid ?? fromEnv.siteUuid ?? fromFile.siteUuid ?? null;
|
|
2101
|
-
const
|
|
2414
|
+
const endpointRaw = options.cliEndpoint ?? fromEnv.endpoint ?? fromFile.endpoint ?? DEFAULT_ENDPOINT;
|
|
2415
|
+
const endpointTrusted = endpointRaw === DEFAULT_ENDPOINT || options.cliEndpoint !== void 0 || fromEnv.endpoint !== void 0 || fromFile.endpoint === void 0;
|
|
2416
|
+
const endpoint = safeRemoteUrl(endpointRaw);
|
|
2417
|
+
if (endpoint === null) {
|
|
2418
|
+
throw new PatchstackError(
|
|
2419
|
+
"Patchstack endpoints must be valid HTTPS URLs, except for localhost development endpoints.",
|
|
2420
|
+
"CONFIG_INVALID"
|
|
2421
|
+
);
|
|
2422
|
+
}
|
|
2102
2423
|
const timeoutMs = fromEnv.timeoutMs ?? fromFile.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
2103
2424
|
const environmentRaw = fromEnv.environment ?? fromFile.environment;
|
|
2104
2425
|
if (environmentRaw !== void 0 && !isEnvironment(environmentRaw)) {
|
|
@@ -2110,7 +2431,7 @@ async function resolveConfig(options) {
|
|
|
2110
2431
|
const inferred = environmentRaw === void 0 ? inferEnvironment(process.env, detectHostedBuilder(options.cwd)) : null;
|
|
2111
2432
|
const environment = environmentRaw ?? inferred.environment;
|
|
2112
2433
|
const environmentEvidence = inferred?.evidence ?? [];
|
|
2113
|
-
if (siteUuid !== null && siteUuid.length > 0 && !
|
|
2434
|
+
if (siteUuid !== null && siteUuid.length > 0 && !isCanonicalUuid(siteUuid)) {
|
|
2114
2435
|
throw new PatchstackError(
|
|
2115
2436
|
`Site UUID "${siteUuid}" does not look like a valid UUID.`,
|
|
2116
2437
|
"CONFIG_INVALID"
|
|
@@ -2133,6 +2454,7 @@ async function resolveConfig(options) {
|
|
|
2133
2454
|
siteUrl: identity.url,
|
|
2134
2455
|
siteName: identity.name,
|
|
2135
2456
|
endpoint,
|
|
2457
|
+
endpointTrusted,
|
|
2136
2458
|
timeoutMs,
|
|
2137
2459
|
environment,
|
|
2138
2460
|
environmentEvidence,
|
|
@@ -2143,13 +2465,13 @@ async function resolveConfig(options) {
|
|
|
2143
2465
|
async function writeConfigFile(cwd, config) {
|
|
2144
2466
|
const target = path8.join(cwd, CONFIG_FILENAME);
|
|
2145
2467
|
const content = JSON.stringify(config, null, 2) + "\n";
|
|
2146
|
-
await
|
|
2468
|
+
await writeAtomicFile(target, content, 420);
|
|
2147
2469
|
return target;
|
|
2148
2470
|
}
|
|
2149
2471
|
var SECRET_CONFIG_FILENAME = SECRET_FILENAME;
|
|
2150
2472
|
async function writeSecretFile(cwd, secrets) {
|
|
2151
2473
|
const target = path8.join(cwd, SECRET_FILENAME);
|
|
2152
|
-
await
|
|
2474
|
+
await writeAtomicFile(target, JSON.stringify(secrets, null, 2) + "\n", 384);
|
|
2153
2475
|
try {
|
|
2154
2476
|
await chmod(target, 384);
|
|
2155
2477
|
} catch {
|
|
@@ -2157,6 +2479,39 @@ async function writeSecretFile(cwd, secrets) {
|
|
|
2157
2479
|
const ignore = await ensureIgnored(cwd, SECRET_FILENAME);
|
|
2158
2480
|
return { path: target, ...ignore };
|
|
2159
2481
|
}
|
|
2482
|
+
async function writeAtomicFile(target, content, mode) {
|
|
2483
|
+
const temporary = `${target}.${process.pid}.${randomUUID2()}.tmp`;
|
|
2484
|
+
let handle;
|
|
2485
|
+
try {
|
|
2486
|
+
handle = await open(temporary, "wx", mode);
|
|
2487
|
+
await handle.writeFile(content, "utf8");
|
|
2488
|
+
await handle.sync();
|
|
2489
|
+
await handle.close();
|
|
2490
|
+
handle = void 0;
|
|
2491
|
+
try {
|
|
2492
|
+
await rename(temporary, target);
|
|
2493
|
+
} catch (error) {
|
|
2494
|
+
const code = error.code;
|
|
2495
|
+
if (process.platform !== "win32" || code !== "EEXIST" && code !== "EPERM") throw error;
|
|
2496
|
+
try {
|
|
2497
|
+
await unlink(target);
|
|
2498
|
+
} catch (unlinkError) {
|
|
2499
|
+
if (unlinkError.code !== "ENOENT") throw unlinkError;
|
|
2500
|
+
}
|
|
2501
|
+
await rename(temporary, target);
|
|
2502
|
+
}
|
|
2503
|
+
} catch (error) {
|
|
2504
|
+
try {
|
|
2505
|
+
await handle?.close();
|
|
2506
|
+
} catch {
|
|
2507
|
+
}
|
|
2508
|
+
try {
|
|
2509
|
+
await unlink(temporary);
|
|
2510
|
+
} catch {
|
|
2511
|
+
}
|
|
2512
|
+
throw error;
|
|
2513
|
+
}
|
|
2514
|
+
}
|
|
2160
2515
|
async function ensureIgnored(cwd, entry) {
|
|
2161
2516
|
const target = path8.join(cwd, ".gitignore");
|
|
2162
2517
|
let existing = "";
|
|
@@ -2175,7 +2530,7 @@ async function ensureIgnored(cwd, entry) {
|
|
|
2175
2530
|
${entry}
|
|
2176
2531
|
`;
|
|
2177
2532
|
try {
|
|
2178
|
-
await
|
|
2533
|
+
await writeAtomicFile(target, existing + block, 420);
|
|
2179
2534
|
} catch (err) {
|
|
2180
2535
|
const code = err.code;
|
|
2181
2536
|
return { ignored: false, reason: `.gitignore could not be written (${code ?? "unknown error"})` };
|
|
@@ -2245,8 +2600,15 @@ async function readJsonFile(cwd, filename) {
|
|
|
2245
2600
|
const target = path8.join(cwd, filename);
|
|
2246
2601
|
let raw;
|
|
2247
2602
|
try {
|
|
2603
|
+
if ((await lstat2(target)).isSymbolicLink()) {
|
|
2604
|
+
throw new PatchstackError(
|
|
2605
|
+
`Could not read ${target}: configuration files must not be symbolic links.`,
|
|
2606
|
+
"CONFIG_INVALID"
|
|
2607
|
+
);
|
|
2608
|
+
}
|
|
2248
2609
|
raw = await readFile9(target, "utf8");
|
|
2249
2610
|
} catch (err) {
|
|
2611
|
+
if (err instanceof PatchstackError) throw err;
|
|
2250
2612
|
if (err.code === "ENOENT") {
|
|
2251
2613
|
return {};
|
|
2252
2614
|
}
|
|
@@ -2291,15 +2653,12 @@ function readEnv() {
|
|
|
2291
2653
|
environment: environmentRaw !== void 0 && environmentRaw.length > 0 ? environmentRaw : void 0
|
|
2292
2654
|
};
|
|
2293
2655
|
}
|
|
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
2656
|
function isEnvironment(value) {
|
|
2298
2657
|
return value === "production" || value === "sandbox" || value === "local";
|
|
2299
2658
|
}
|
|
2300
2659
|
|
|
2301
2660
|
// src/mark-build.ts
|
|
2302
|
-
import { existsSync, readFileSync as
|
|
2661
|
+
import { existsSync, readFileSync as readFileSync3, readdirSync, realpathSync as realpathSync2, statSync as statSync2 } from "fs";
|
|
2303
2662
|
import path9 from "path";
|
|
2304
2663
|
|
|
2305
2664
|
// src/stack.ts
|
|
@@ -2401,7 +2760,7 @@ function staleBuildAge(files, startedAt) {
|
|
|
2401
2760
|
let newest = null;
|
|
2402
2761
|
for (const file of files) {
|
|
2403
2762
|
try {
|
|
2404
|
-
const at =
|
|
2763
|
+
const at = statSync2(file).mtimeMs;
|
|
2405
2764
|
if (newest === null || at > newest) newest = at;
|
|
2406
2765
|
} catch {
|
|
2407
2766
|
continue;
|
|
@@ -2432,20 +2791,20 @@ function buildDirCandidates(options = {}) {
|
|
|
2432
2791
|
function resolveBuildDir(cwd, override, options = {}) {
|
|
2433
2792
|
if (override !== void 0 && override !== "") {
|
|
2434
2793
|
const abs = path9.resolve(cwd, override);
|
|
2435
|
-
return existsSync(abs) &&
|
|
2794
|
+
return existsSync(abs) && statSync2(abs).isDirectory() ? abs : null;
|
|
2436
2795
|
}
|
|
2437
2796
|
let boundary;
|
|
2438
2797
|
try {
|
|
2439
|
-
boundary =
|
|
2798
|
+
boundary = realpathSync2(cwd);
|
|
2440
2799
|
} catch {
|
|
2441
2800
|
return null;
|
|
2442
2801
|
}
|
|
2443
2802
|
for (const candidate of buildDirCandidates(options)) {
|
|
2444
2803
|
const abs = path9.resolve(cwd, candidate);
|
|
2445
|
-
if (!existsSync(abs) || !
|
|
2804
|
+
if (!existsSync(abs) || !statSync2(abs).isDirectory()) continue;
|
|
2446
2805
|
let real;
|
|
2447
2806
|
try {
|
|
2448
|
-
real =
|
|
2807
|
+
real = realpathSync2(abs);
|
|
2449
2808
|
} catch {
|
|
2450
2809
|
continue;
|
|
2451
2810
|
}
|
|
@@ -2613,25 +2972,25 @@ function ensureSourceMarker(cwd, shell, framework, checksum = null) {
|
|
|
2613
2972
|
return { shell: null, source: "", action: "no-anchor" };
|
|
2614
2973
|
}
|
|
2615
2974
|
const file = path9.resolve(cwd, shell);
|
|
2616
|
-
const before =
|
|
2975
|
+
const before = readFileSync3(file, "utf8");
|
|
2617
2976
|
const result = ensureMarkerInJsxShell(before, framework, checksum);
|
|
2618
2977
|
if (result.source !== before) {
|
|
2619
|
-
|
|
2978
|
+
writeProjectFileSync(cwd, file, result.source, { encoding: "utf8" });
|
|
2620
2979
|
}
|
|
2621
2980
|
return { ...result, shell };
|
|
2622
2981
|
}
|
|
2623
2982
|
|
|
2624
2983
|
// src/guide.ts
|
|
2625
|
-
import { existsSync as existsSync13, readFileSync as
|
|
2984
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
|
|
2626
2985
|
import path12 from "path";
|
|
2627
2986
|
|
|
2628
2987
|
// src/architecture.ts
|
|
2629
|
-
import { existsSync as existsSync2, readFileSync as
|
|
2988
|
+
import { existsSync as existsSync2, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
2630
2989
|
import path10 from "path";
|
|
2631
2990
|
|
|
2632
2991
|
// src/map/sources.ts
|
|
2633
|
-
import { readFileSync as
|
|
2634
|
-
import { join as
|
|
2992
|
+
import { readFileSync as readFileSync4, readdirSync as readdirSync2, realpathSync as realpathSync3, statSync as statSync3 } from "fs";
|
|
2993
|
+
import { join as join3, relative as relative2, isAbsolute as isAbsolute2 } from "path";
|
|
2635
2994
|
|
|
2636
2995
|
// src/map/ast.ts
|
|
2637
2996
|
function rootIdentifier(node, ts) {
|
|
@@ -2840,7 +3199,7 @@ function hasEntrySignal(text) {
|
|
|
2840
3199
|
}
|
|
2841
3200
|
function detectFramework(cwd) {
|
|
2842
3201
|
try {
|
|
2843
|
-
const pkg = JSON.parse(
|
|
3202
|
+
const pkg = JSON.parse(readFileSync4(join3(cwd, "package.json"), "utf8"));
|
|
2844
3203
|
const d = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2845
3204
|
if (d["@tanstack/react-start"] || d["@tanstack/start"] || d["@tanstack/solid-start"]) return "tanstack-start";
|
|
2846
3205
|
if (d["next"]) return "next";
|
|
@@ -2852,11 +3211,11 @@ function detectFramework(cwd) {
|
|
|
2852
3211
|
} catch {
|
|
2853
3212
|
}
|
|
2854
3213
|
try {
|
|
2855
|
-
if (
|
|
3214
|
+
if (statSync3(join3(cwd, "supabase", "functions")).isDirectory()) return "supabase-functions";
|
|
2856
3215
|
} catch {
|
|
2857
3216
|
}
|
|
2858
3217
|
try {
|
|
2859
|
-
if (
|
|
3218
|
+
if (statSync3(join3(cwd, "functions")).isDirectory()) return "deno-functions";
|
|
2860
3219
|
} catch {
|
|
2861
3220
|
}
|
|
2862
3221
|
return "unknown";
|
|
@@ -2898,7 +3257,7 @@ function probeForSource(dir, budget) {
|
|
|
2898
3257
|
for (const e of entries) {
|
|
2899
3258
|
if (budget.left-- <= 0) return "unknown";
|
|
2900
3259
|
if (e.isDirectory()) {
|
|
2901
|
-
if (!SKIP_IS_DEFINITIONAL.has(e.name)) dirs.push(
|
|
3260
|
+
if (!SKIP_IS_DEFINITIONAL.has(e.name)) dirs.push(join3(dir, e.name));
|
|
2902
3261
|
} else if (isSourceFile(e.name)) return true;
|
|
2903
3262
|
else if (e.isSymbolicLink()) return "unknown";
|
|
2904
3263
|
}
|
|
@@ -2911,7 +3270,7 @@ function probeForSource(dir, budget) {
|
|
|
2911
3270
|
function collectSources(dir, boundary, opts, out = [], seen = /* @__PURE__ */ new Set(), stats = { discovered: 0, unwalked: 0, skippedWithSource: [], skippedUnknown: 0 }, probeBudget = { left: PROBE_BUDGET }) {
|
|
2912
3271
|
let key;
|
|
2913
3272
|
try {
|
|
2914
|
-
key =
|
|
3273
|
+
key = realpathSync3(dir);
|
|
2915
3274
|
} catch {
|
|
2916
3275
|
stats.unwalked++;
|
|
2917
3276
|
return out;
|
|
@@ -2935,19 +3294,19 @@ function collectSources(dir, boundary, opts, out = [], seen = /* @__PURE__ */ ne
|
|
|
2935
3294
|
const probeable = !SKIP_IS_DEFINITIONAL.has(e.name);
|
|
2936
3295
|
if (probeable && !e.isDirectory() && e.isSymbolicLink()) stats.skippedUnknown++;
|
|
2937
3296
|
else if (probeable && e.isDirectory()) {
|
|
2938
|
-
const held = probeForSource(
|
|
2939
|
-
if (held === true) stats.skippedWithSource.push(
|
|
3297
|
+
const held = probeForSource(join3(dir, e.name), probeBudget);
|
|
3298
|
+
if (held === true) stats.skippedWithSource.push(join3(dir, e.name));
|
|
2940
3299
|
else if (held === "unknown") stats.skippedUnknown++;
|
|
2941
3300
|
}
|
|
2942
3301
|
continue;
|
|
2943
3302
|
}
|
|
2944
|
-
const full =
|
|
3303
|
+
const full = join3(dir, e.name);
|
|
2945
3304
|
if (e.isDirectory()) collectSources(full, boundary, opts, out, seen, stats, probeBudget);
|
|
2946
3305
|
else if (e.isSymbolicLink()) {
|
|
2947
3306
|
let st, real;
|
|
2948
3307
|
try {
|
|
2949
|
-
st =
|
|
2950
|
-
real =
|
|
3308
|
+
st = statSync3(full);
|
|
3309
|
+
real = realpathSync3(full);
|
|
2951
3310
|
} catch {
|
|
2952
3311
|
stats.unwalked++;
|
|
2953
3312
|
continue;
|
|
@@ -2970,8 +3329,8 @@ function collectSources(dir, boundary, opts, out = [], seen = /* @__PURE__ */ ne
|
|
|
2970
3329
|
}
|
|
2971
3330
|
function isInside(candidate, boundary) {
|
|
2972
3331
|
if (candidate === boundary) return true;
|
|
2973
|
-
const rel =
|
|
2974
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
3332
|
+
const rel = relative2(boundary, candidate);
|
|
3333
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
2975
3334
|
}
|
|
2976
3335
|
var DEPLOYMENT_SHAPES = [
|
|
2977
3336
|
// Config first: these are declarations by the project itself, and they survive a build output being
|
|
@@ -2997,13 +3356,13 @@ var DEPLOYMENT_SHAPES = [
|
|
|
2997
3356
|
function detectDeploymentShapes(cwd, opts = {}) {
|
|
2998
3357
|
let boundary = opts.boundary ?? cwd;
|
|
2999
3358
|
try {
|
|
3000
|
-
boundary =
|
|
3359
|
+
boundary = realpathSync3(boundary);
|
|
3001
3360
|
} catch {
|
|
3002
3361
|
}
|
|
3003
|
-
const inProject = (
|
|
3362
|
+
const inProject = (path18) => {
|
|
3004
3363
|
if (opts.followOutside) return true;
|
|
3005
3364
|
try {
|
|
3006
|
-
return isInside(
|
|
3365
|
+
return isInside(realpathSync3(path18), boundary);
|
|
3007
3366
|
} catch {
|
|
3008
3367
|
return false;
|
|
3009
3368
|
}
|
|
@@ -3011,9 +3370,9 @@ function detectDeploymentShapes(cwd, opts = {}) {
|
|
|
3011
3370
|
const found = [];
|
|
3012
3371
|
for (const candidate of DEPLOYMENT_SHAPES) {
|
|
3013
3372
|
for (const file of candidate.files ?? []) {
|
|
3014
|
-
const full =
|
|
3373
|
+
const full = join3(cwd, file);
|
|
3015
3374
|
try {
|
|
3016
|
-
if (
|
|
3375
|
+
if (statSync3(full).isFile() && inProject(full)) {
|
|
3017
3376
|
found.push({ shape: candidate.shape, source: file, evidence: candidate.evidence });
|
|
3018
3377
|
break;
|
|
3019
3378
|
}
|
|
@@ -3021,9 +3380,9 @@ function detectDeploymentShapes(cwd, opts = {}) {
|
|
|
3021
3380
|
}
|
|
3022
3381
|
}
|
|
3023
3382
|
for (const dir of candidate.dirs ?? []) {
|
|
3024
|
-
const full =
|
|
3383
|
+
const full = join3(cwd, dir);
|
|
3025
3384
|
try {
|
|
3026
|
-
if (!
|
|
3385
|
+
if (!statSync3(full).isDirectory() || !inProject(full)) continue;
|
|
3027
3386
|
} catch {
|
|
3028
3387
|
continue;
|
|
3029
3388
|
}
|
|
@@ -3040,7 +3399,7 @@ function holdsFunctionEntry(dir) {
|
|
|
3040
3399
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
3041
3400
|
if (!entry.isDirectory() || entry.name.startsWith("_")) continue;
|
|
3042
3401
|
try {
|
|
3043
|
-
for (const nested of readdirSync2(
|
|
3402
|
+
for (const nested of readdirSync2(join3(dir, entry.name), { withFileTypes: true })) {
|
|
3044
3403
|
if (nested.isFile() && /^index\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(nested.name)) return true;
|
|
3045
3404
|
}
|
|
3046
3405
|
} catch {
|
|
@@ -3056,7 +3415,7 @@ function holdsSourceFile(dir) {
|
|
|
3056
3415
|
if (entry.isFile() && isSourceFile(entry.name)) return true;
|
|
3057
3416
|
if (entry.isDirectory()) {
|
|
3058
3417
|
try {
|
|
3059
|
-
for (const nested of readdirSync2(
|
|
3418
|
+
for (const nested of readdirSync2(join3(dir, entry.name), { withFileTypes: true })) {
|
|
3060
3419
|
if (nested.isFile() && isSourceFile(nested.name)) return true;
|
|
3061
3420
|
}
|
|
3062
3421
|
} catch {
|
|
@@ -3069,8 +3428,8 @@ function holdsSourceFile(dir) {
|
|
|
3069
3428
|
}
|
|
3070
3429
|
|
|
3071
3430
|
// src/map/surface.ts
|
|
3072
|
-
import { readFileSync as
|
|
3073
|
-
import { join as
|
|
3431
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
3432
|
+
import { join as join4 } from "path";
|
|
3074
3433
|
var STATIC_GENERATORS = [
|
|
3075
3434
|
{ dep: "vite", label: "vite" },
|
|
3076
3435
|
{ dep: "react-scripts", label: "create-react-app" },
|
|
@@ -3111,7 +3470,7 @@ var SHAPE_SIGNAL = {
|
|
|
3111
3470
|
};
|
|
3112
3471
|
function readManifest(cwd) {
|
|
3113
3472
|
try {
|
|
3114
|
-
const parsed = JSON.parse(
|
|
3473
|
+
const parsed = JSON.parse(readFileSync5(join4(cwd, "package.json"), "utf8"));
|
|
3115
3474
|
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
3116
3475
|
} catch {
|
|
3117
3476
|
return null;
|
|
@@ -3124,7 +3483,7 @@ function nextExportsStatically(cwd, manifest, ts) {
|
|
|
3124
3483
|
for (const file of ["next.config.js", "next.config.mjs", "next.config.ts", "next.config.cjs"]) {
|
|
3125
3484
|
let text;
|
|
3126
3485
|
try {
|
|
3127
|
-
text =
|
|
3486
|
+
text = readFileSync5(join4(cwd, file), "utf8");
|
|
3128
3487
|
} catch {
|
|
3129
3488
|
continue;
|
|
3130
3489
|
}
|
|
@@ -3283,7 +3642,7 @@ var SERVING_CALL = /\b(?:createServer|createSecureServer|Bun\.serve|Deno\.serve|
|
|
|
3283
3642
|
var MAX_ENTRY_BYTES = 256 * 1024;
|
|
3284
3643
|
function readManifest2(cwd) {
|
|
3285
3644
|
try {
|
|
3286
|
-
const parsed = JSON.parse(
|
|
3645
|
+
const parsed = JSON.parse(readFileSync6(path10.join(cwd, "package.json"), "utf8"));
|
|
3287
3646
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
3288
3647
|
} catch {
|
|
3289
3648
|
return {};
|
|
@@ -3317,12 +3676,12 @@ function serverEntries(cwd, manifest) {
|
|
|
3317
3676
|
if (!main2.startsWith("..")) candidates.add(main2);
|
|
3318
3677
|
}
|
|
3319
3678
|
const found = [];
|
|
3320
|
-
for (const
|
|
3321
|
-
const file = path10.join(cwd,
|
|
3679
|
+
for (const relative8 of candidates) {
|
|
3680
|
+
const file = path10.join(cwd, relative8);
|
|
3322
3681
|
try {
|
|
3323
|
-
if (!existsSync2(file) || !
|
|
3324
|
-
const text =
|
|
3325
|
-
if (SERVING_CALL.test(text)) found.push(
|
|
3682
|
+
if (!existsSync2(file) || !statSync4(file).isFile()) continue;
|
|
3683
|
+
const text = readFileSync6(file, "utf8").slice(0, MAX_ENTRY_BYTES);
|
|
3684
|
+
if (SERVING_CALL.test(text)) found.push(relative8);
|
|
3326
3685
|
} catch {
|
|
3327
3686
|
}
|
|
3328
3687
|
}
|
|
@@ -3375,14 +3734,14 @@ function classifyArchitecture(cwd) {
|
|
|
3375
3734
|
}
|
|
3376
3735
|
|
|
3377
3736
|
// src/protect/install/util.ts
|
|
3378
|
-
import { readFileSync as
|
|
3379
|
-
import { join as
|
|
3737
|
+
import { readFileSync as readFileSync7, existsSync as existsSync3 } from "fs";
|
|
3738
|
+
import { join as join5, dirname as dirname2 } from "path";
|
|
3380
3739
|
import { fileURLToPath } from "url";
|
|
3381
|
-
var read = (p) =>
|
|
3740
|
+
var read = (p) => readFileSync7(p, "utf8");
|
|
3382
3741
|
var log = (msg) => console.log(`patchstack protect: ${msg}`);
|
|
3383
3742
|
function hasDependency(cwd, name) {
|
|
3384
3743
|
try {
|
|
3385
|
-
const pkg = JSON.parse(read(
|
|
3744
|
+
const pkg = JSON.parse(read(join5(cwd, "package.json")));
|
|
3386
3745
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }[name]);
|
|
3387
3746
|
} catch {
|
|
3388
3747
|
return false;
|
|
@@ -3394,7 +3753,7 @@ function hasResolvableCredential(cwd, env = process.env) {
|
|
|
3394
3753
|
}
|
|
3395
3754
|
for (const file of [".patchstackrc.local.json", ".patchstackrc.json"]) {
|
|
3396
3755
|
try {
|
|
3397
|
-
const parsed = JSON.parse(read(
|
|
3756
|
+
const parsed = JSON.parse(read(join5(cwd, file)));
|
|
3398
3757
|
if (typeof parsed.apiKey === "string" && parsed.apiKey !== "") return true;
|
|
3399
3758
|
if (typeof parsed.pulseAuth === "string" && parsed.pulseAuth !== "") return true;
|
|
3400
3759
|
} catch {
|
|
@@ -3408,7 +3767,7 @@ function isSiteUuid(value) {
|
|
|
3408
3767
|
return typeof value === "string" && UUID_RE.test(value);
|
|
3409
3768
|
}
|
|
3410
3769
|
function bakeSiteUuid(cwd, guardRelPath) {
|
|
3411
|
-
const rc =
|
|
3770
|
+
const rc = join5(cwd, ".patchstackrc.json");
|
|
3412
3771
|
if (!existsSync3(rc)) {
|
|
3413
3772
|
log("no .patchstackrc.json \u2014 guard uses PATCHSTACK_SITE_UUID env or the bundled fallback");
|
|
3414
3773
|
return false;
|
|
@@ -3424,32 +3783,32 @@ function bakeSiteUuid(cwd, guardRelPath) {
|
|
|
3424
3783
|
log(".patchstackrc.json siteUuid missing or malformed \u2014 guard uses PATCHSTACK_SITE_UUID env or the bundled fallback");
|
|
3425
3784
|
return false;
|
|
3426
3785
|
}
|
|
3427
|
-
const guardPath =
|
|
3786
|
+
const guardPath = join5(cwd, guardRelPath);
|
|
3428
3787
|
if (!existsSync3(guardPath)) return false;
|
|
3429
3788
|
const source = read(guardPath);
|
|
3430
3789
|
if (!source.includes(SITE_UUID_PLACEHOLDER)) {
|
|
3431
3790
|
log(`${guardRelPath} site UUID already baked`);
|
|
3432
3791
|
return false;
|
|
3433
3792
|
}
|
|
3434
|
-
|
|
3793
|
+
writeProjectFileSync(cwd, guardPath, source.replace(SITE_UUID_PLACEHOLDER, uuid), { encoding: "utf8" });
|
|
3435
3794
|
log(`baked site UUID into ${guardRelPath} \u2014 live rules from the Patchstack API`);
|
|
3436
3795
|
return true;
|
|
3437
3796
|
}
|
|
3438
|
-
var HERE =
|
|
3797
|
+
var HERE = dirname2(fileURLToPath(import.meta.url));
|
|
3439
3798
|
function templatesDir() {
|
|
3440
|
-
const builtLayout =
|
|
3799
|
+
const builtLayout = join5(HERE, "protect", "templates");
|
|
3441
3800
|
const candidates = [
|
|
3442
3801
|
builtLayout,
|
|
3443
|
-
|
|
3802
|
+
join5(HERE, "..", "templates"),
|
|
3444
3803
|
// source: src/protect/install/ → src/protect/templates
|
|
3445
|
-
|
|
3804
|
+
join5(HERE, "templates")
|
|
3446
3805
|
];
|
|
3447
3806
|
return candidates.find((p) => existsSync3(p)) ?? builtLayout;
|
|
3448
3807
|
}
|
|
3449
3808
|
|
|
3450
3809
|
// src/protect/install/adapters/tanstack-supabase.ts
|
|
3451
|
-
import {
|
|
3452
|
-
import { join as
|
|
3810
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3811
|
+
import { join as join6 } from "path";
|
|
3453
3812
|
var CLIENT_TUNNEL = [
|
|
3454
3813
|
"",
|
|
3455
3814
|
" // PATCHSTACK: in the browser, tunnel Supabase traffic through the app's own server guard",
|
|
@@ -3519,7 +3878,7 @@ function reconcileBlock(s, region, block, legacyConst, insertBefore) {
|
|
|
3519
3878
|
return s.replace(insertBefore, block + "\n\n" + insertBefore);
|
|
3520
3879
|
}
|
|
3521
3880
|
function detect(cwd) {
|
|
3522
|
-
const pkgPath =
|
|
3881
|
+
const pkgPath = join6(cwd, "package.json");
|
|
3523
3882
|
if (!existsSync4(pkgPath)) return false;
|
|
3524
3883
|
let pkg;
|
|
3525
3884
|
try {
|
|
@@ -3528,22 +3887,22 @@ function detect(cwd) {
|
|
|
3528
3887
|
return false;
|
|
3529
3888
|
}
|
|
3530
3889
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3531
|
-
return Boolean(deps["@tanstack/react-start"]) && existsSync4(
|
|
3890
|
+
return Boolean(deps["@tanstack/react-start"]) && existsSync4(join6(cwd, "src/start.ts")) && existsSync4(join6(cwd, "src/integrations/supabase/client.ts"));
|
|
3532
3891
|
}
|
|
3533
3892
|
var GUARD_FILE = "src/integrations/patchstack/guard.ts";
|
|
3534
3893
|
function scaffold(cwd, opts) {
|
|
3535
3894
|
const templates = templatesDir();
|
|
3536
|
-
const dst =
|
|
3537
|
-
|
|
3538
|
-
|
|
3895
|
+
const dst = join6(cwd, "src/integrations/patchstack");
|
|
3896
|
+
ensureProjectDirectorySync(cwd, dst);
|
|
3897
|
+
copyProjectFileSync(cwd, join6(templates, "guard.ts"), join6(dst, "guard.ts"));
|
|
3539
3898
|
const changed = [GUARD_FILE];
|
|
3540
|
-
const rulesDst =
|
|
3899
|
+
const rulesDst = join6(dst, "rules.json");
|
|
3541
3900
|
if (opts.demo) {
|
|
3542
|
-
|
|
3901
|
+
copyProjectFileSync(cwd, join6(templates, "demo-rules.json"), rulesDst);
|
|
3543
3902
|
changed.push("src/integrations/patchstack/rules.json");
|
|
3544
3903
|
log("scaffolded guard.ts + rules.json (demo sample rule set)");
|
|
3545
3904
|
} else if (!existsSync4(rulesDst)) {
|
|
3546
|
-
|
|
3905
|
+
copyProjectFileSync(cwd, join6(templates, "rules.json"), rulesDst);
|
|
3547
3906
|
changed.push("src/integrations/patchstack/rules.json");
|
|
3548
3907
|
log("scaffolded guard.ts + rules.json (starter rules)");
|
|
3549
3908
|
} else {
|
|
@@ -3552,7 +3911,7 @@ function scaffold(cwd, opts) {
|
|
|
3552
3911
|
return changed;
|
|
3553
3912
|
}
|
|
3554
3913
|
function patchClient(cwd) {
|
|
3555
|
-
const p =
|
|
3914
|
+
const p = join6(cwd, "src/integrations/supabase/client.ts");
|
|
3556
3915
|
const s = read(p);
|
|
3557
3916
|
if (s.includes("x-ps-target")) {
|
|
3558
3917
|
log("client.ts already wired");
|
|
@@ -3563,12 +3922,12 @@ function patchClient(cwd) {
|
|
|
3563
3922
|
log("client.ts anchor not found \u2014 skipping (template changed?)");
|
|
3564
3923
|
return false;
|
|
3565
3924
|
}
|
|
3566
|
-
|
|
3925
|
+
writeProjectFileSync(cwd, p, s.replace(anchor, anchor + "\n" + CLIENT_TUNNEL), { encoding: "utf8" });
|
|
3567
3926
|
log("patched client.ts (tunnel Supabase through the guard)");
|
|
3568
3927
|
return true;
|
|
3569
3928
|
}
|
|
3570
3929
|
function patchStart(cwd) {
|
|
3571
|
-
const p =
|
|
3930
|
+
const p = join6(cwd, "src/start.ts");
|
|
3572
3931
|
let s = read(p);
|
|
3573
3932
|
const importAnchor = 'import { createStart, createMiddleware } from "@tanstack/react-start";';
|
|
3574
3933
|
const exportAnchor = "export const startInstance";
|
|
@@ -3600,7 +3959,7 @@ function patchStart(cwd) {
|
|
|
3600
3959
|
log("start.ts already wired");
|
|
3601
3960
|
return false;
|
|
3602
3961
|
}
|
|
3603
|
-
|
|
3962
|
+
writeProjectFileSync(cwd, p, s, { encoding: "utf8" });
|
|
3604
3963
|
log("patched start.ts (guard registered as request + function middleware)");
|
|
3605
3964
|
return true;
|
|
3606
3965
|
}
|
|
@@ -3615,9 +3974,9 @@ function wire(cwd, opts) {
|
|
|
3615
3974
|
return { ok: true, changed: [...new Set(changed)] };
|
|
3616
3975
|
}
|
|
3617
3976
|
function verify(cwd) {
|
|
3618
|
-
const guardPath =
|
|
3619
|
-
const clientPath =
|
|
3620
|
-
const startPath =
|
|
3977
|
+
const guardPath = join6(cwd, GUARD_FILE);
|
|
3978
|
+
const clientPath = join6(cwd, "src/integrations/supabase/client.ts");
|
|
3979
|
+
const startPath = join6(cwd, "src/start.ts");
|
|
3621
3980
|
const guard = existsSync4(guardPath) ? read(guardPath) : "";
|
|
3622
3981
|
const client = existsSync4(clientPath) ? read(clientPath) : "";
|
|
3623
3982
|
const start = existsSync4(startPath) ? read(startPath) : "";
|
|
@@ -3638,11 +3997,11 @@ var tanstackSupabaseAdapter = {
|
|
|
3638
3997
|
};
|
|
3639
3998
|
|
|
3640
3999
|
// src/protect/install/adapters/next.ts
|
|
3641
|
-
import { existsSync as existsSync5
|
|
3642
|
-
import { join as
|
|
4000
|
+
import { existsSync as existsSync5 } from "fs";
|
|
4001
|
+
import { join as join7 } from "path";
|
|
3643
4002
|
function hasNextDep(cwd) {
|
|
3644
4003
|
try {
|
|
3645
|
-
const pkg = JSON.parse(read(
|
|
4004
|
+
const pkg = JSON.parse(read(join7(cwd, "package.json")));
|
|
3646
4005
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }.next);
|
|
3647
4006
|
} catch {
|
|
3648
4007
|
return false;
|
|
@@ -3651,9 +4010,9 @@ function hasNextDep(cwd) {
|
|
|
3651
4010
|
function middlewareInfo(cwd) {
|
|
3652
4011
|
const candidates = ["middleware.ts", "middleware.js", "src/middleware.ts", "src/middleware.js"];
|
|
3653
4012
|
for (const rel of candidates) {
|
|
3654
|
-
if (existsSync5(
|
|
4013
|
+
if (existsSync5(join7(cwd, rel))) return { relDir: rel.includes("/") ? "src" : ".", relFile: rel, exists: true };
|
|
3655
4014
|
}
|
|
3656
|
-
const useSrc = existsSync5(
|
|
4015
|
+
const useSrc = existsSync5(join7(cwd, "src"));
|
|
3657
4016
|
return { relDir: useSrc ? "src" : ".", relFile: useSrc ? "src/middleware.ts" : "middleware.ts", exists: false };
|
|
3658
4017
|
}
|
|
3659
4018
|
function detect2(cwd) {
|
|
@@ -3665,15 +4024,15 @@ function rulesFile(relDir) {
|
|
|
3665
4024
|
function wire2(cwd, opts) {
|
|
3666
4025
|
const templates = templatesDir();
|
|
3667
4026
|
const mw = middlewareInfo(cwd);
|
|
3668
|
-
const dir =
|
|
3669
|
-
|
|
3670
|
-
const rulesDst =
|
|
4027
|
+
const dir = join7(cwd, mw.relDir === "." ? "" : mw.relDir);
|
|
4028
|
+
ensureProjectDirectorySync(cwd, dir);
|
|
4029
|
+
const rulesDst = join7(dir, "patchstack.rules.json");
|
|
3671
4030
|
const changed = [];
|
|
3672
4031
|
if (opts.demo || !existsSync5(rulesDst)) {
|
|
3673
|
-
|
|
4032
|
+
copyProjectFileSync(cwd, join7(templates, opts.demo ? "demo-rules.json" : "rules.json"), rulesDst);
|
|
3674
4033
|
changed.push(rulesFile(mw.relDir));
|
|
3675
4034
|
}
|
|
3676
|
-
const mwPath =
|
|
4035
|
+
const mwPath = join7(cwd, mw.relFile);
|
|
3677
4036
|
const existing = mw.exists ? read(mwPath) : "";
|
|
3678
4037
|
if (mw.exists && existing.includes("patchstack-next")) {
|
|
3679
4038
|
log(`${mw.relFile} already has the Patchstack middleware \u2014 left as-is`);
|
|
@@ -3685,7 +4044,7 @@ function wire2(cwd, opts) {
|
|
|
3685
4044
|
);
|
|
3686
4045
|
return { ok: true, changed };
|
|
3687
4046
|
}
|
|
3688
|
-
|
|
4047
|
+
copyProjectFileSync(cwd, join7(templates, "next-middleware.ts"), mwPath);
|
|
3689
4048
|
if (!opts.demo) bakeSiteUuid(cwd, mw.relFile);
|
|
3690
4049
|
changed.push(mw.relFile);
|
|
3691
4050
|
log(`scaffolded ${mw.relFile} (Patchstack middleware)`);
|
|
@@ -3693,8 +4052,8 @@ function wire2(cwd, opts) {
|
|
|
3693
4052
|
}
|
|
3694
4053
|
function verify2(cwd) {
|
|
3695
4054
|
const mw = middlewareInfo(cwd);
|
|
3696
|
-
const present2 = mw.exists && read(
|
|
3697
|
-
const rulesPresent = existsSync5(
|
|
4055
|
+
const present2 = mw.exists && read(join7(cwd, mw.relFile)).includes("patchstack-next");
|
|
4056
|
+
const rulesPresent = existsSync5(join7(cwd, rulesFile(mw.relDir)));
|
|
3698
4057
|
return {
|
|
3699
4058
|
wired: present2 && rulesPresent,
|
|
3700
4059
|
checks: [
|
|
@@ -3712,27 +4071,27 @@ var nextAdapter = {
|
|
|
3712
4071
|
};
|
|
3713
4072
|
|
|
3714
4073
|
// src/protect/install/adapters/sveltekit.ts
|
|
3715
|
-
import { join as
|
|
4074
|
+
import { join as join9 } from "path";
|
|
3716
4075
|
|
|
3717
4076
|
// src/protect/install/seam.ts
|
|
3718
|
-
import { existsSync as existsSync6
|
|
3719
|
-
import { join as
|
|
4077
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4078
|
+
import { join as join8, dirname as dirname3 } from "path";
|
|
3720
4079
|
function rulesRel(seamRel) {
|
|
3721
|
-
const d =
|
|
4080
|
+
const d = dirname3(seamRel);
|
|
3722
4081
|
return (d === "." ? "patchstack.rules.json" : `${d}/patchstack.rules.json`).replace(/\\/g, "/");
|
|
3723
4082
|
}
|
|
3724
4083
|
function wireSeam(cwd, opts, spec) {
|
|
3725
4084
|
const templates = templatesDir();
|
|
3726
|
-
const existing = spec.candidates.find((c) => existsSync6(
|
|
4085
|
+
const existing = spec.candidates.find((c) => existsSync6(join8(cwd, c)));
|
|
3727
4086
|
const seamRel = existing ?? spec.target;
|
|
3728
|
-
|
|
3729
|
-
const rulesDst =
|
|
4087
|
+
ensureProjectDirectorySync(cwd, dirname3(join8(cwd, seamRel)));
|
|
4088
|
+
const rulesDst = join8(cwd, rulesRel(seamRel));
|
|
3730
4089
|
const changed = [];
|
|
3731
4090
|
if (opts.demo || !existsSync6(rulesDst)) {
|
|
3732
|
-
|
|
4091
|
+
copyProjectFileSync(cwd, join8(templates, opts.demo ? "demo-rules.json" : "rules.json"), rulesDst);
|
|
3733
4092
|
changed.push(rulesRel(seamRel));
|
|
3734
4093
|
}
|
|
3735
|
-
const current = existing ? read(
|
|
4094
|
+
const current = existing ? read(join8(cwd, existing)) : "";
|
|
3736
4095
|
if (existing && current.includes(spec.marker)) {
|
|
3737
4096
|
log(`${existing} already has the Patchstack guard \u2014 left as-is`);
|
|
3738
4097
|
return { ok: true, changed };
|
|
@@ -3741,17 +4100,17 @@ function wireSeam(cwd, opts, spec) {
|
|
|
3741
4100
|
log(`existing ${existing} left untouched \u2014 scaffolded ${rulesRel(seamRel)}; ${spec.planHint}`);
|
|
3742
4101
|
return { ok: true, changed };
|
|
3743
4102
|
}
|
|
3744
|
-
|
|
4103
|
+
copyProjectFileSync(cwd, join8(templates, spec.templateName), join8(cwd, seamRel));
|
|
3745
4104
|
if (!opts.demo) bakeSiteUuid(cwd, seamRel);
|
|
3746
4105
|
changed.push(seamRel);
|
|
3747
4106
|
log(`scaffolded ${seamRel}`);
|
|
3748
4107
|
return { ok: true, changed: [...new Set(changed)] };
|
|
3749
4108
|
}
|
|
3750
4109
|
function verifySeam(cwd, spec) {
|
|
3751
|
-
const existing = spec.candidates.find((c) => existsSync6(
|
|
4110
|
+
const existing = spec.candidates.find((c) => existsSync6(join8(cwd, c)));
|
|
3752
4111
|
const seamRel = existing ?? spec.target;
|
|
3753
|
-
const present2 = existing ? read(
|
|
3754
|
-
const rulesPresent = existsSync6(
|
|
4112
|
+
const present2 = existing ? read(join8(cwd, existing)).includes(spec.marker) : false;
|
|
4113
|
+
const rulesPresent = existsSync6(join8(cwd, rulesRel(seamRel)));
|
|
3755
4114
|
return {
|
|
3756
4115
|
wired: present2 && rulesPresent,
|
|
3757
4116
|
checks: [
|
|
@@ -3772,7 +4131,7 @@ var SPEC = {
|
|
|
3772
4131
|
};
|
|
3773
4132
|
function detect3(cwd) {
|
|
3774
4133
|
try {
|
|
3775
|
-
const pkg = JSON.parse(read(
|
|
4134
|
+
const pkg = JSON.parse(read(join9(cwd, "package.json")));
|
|
3776
4135
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }["@sveltejs/kit"]);
|
|
3777
4136
|
} catch {
|
|
3778
4137
|
return false;
|
|
@@ -3793,7 +4152,7 @@ var sveltekitAdapter = {
|
|
|
3793
4152
|
};
|
|
3794
4153
|
|
|
3795
4154
|
// src/protect/install/adapters/astro.ts
|
|
3796
|
-
import { join as
|
|
4155
|
+
import { join as join10 } from "path";
|
|
3797
4156
|
var SPEC2 = {
|
|
3798
4157
|
templateName: "astro-middleware.ts",
|
|
3799
4158
|
candidates: ["src/middleware.ts", "src/middleware.js", "src/middleware/index.ts", "src/middleware/index.js"],
|
|
@@ -3804,7 +4163,7 @@ var SPEC2 = {
|
|
|
3804
4163
|
};
|
|
3805
4164
|
function detect4(cwd) {
|
|
3806
4165
|
try {
|
|
3807
|
-
const pkg = JSON.parse(read(
|
|
4166
|
+
const pkg = JSON.parse(read(join10(cwd, "package.json")));
|
|
3808
4167
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }.astro);
|
|
3809
4168
|
} catch {
|
|
3810
4169
|
return false;
|
|
@@ -3851,8 +4210,8 @@ var nuxtAdapter = {
|
|
|
3851
4210
|
};
|
|
3852
4211
|
|
|
3853
4212
|
// src/protect/install/find-app.ts
|
|
3854
|
-
import { readFileSync as
|
|
3855
|
-
import { join as
|
|
4213
|
+
import { readFileSync as readFileSync8, existsSync as existsSync7, readdirSync as readdirSync3, lstatSync as lstatSync2 } from "fs";
|
|
4214
|
+
import { join as join11, relative as relative3, dirname as dirname4 } from "path";
|
|
3856
4215
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
3857
4216
|
"node_modules",
|
|
3858
4217
|
"patchstack",
|
|
@@ -3873,7 +4232,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
|
3873
4232
|
]);
|
|
3874
4233
|
var MAX_DEPTH = 8;
|
|
3875
4234
|
function findAppInstance(cwd, re) {
|
|
3876
|
-
const root = existsSync7(
|
|
4235
|
+
const root = existsSync7(join11(cwd, "src")) ? join11(cwd, "src") : cwd;
|
|
3877
4236
|
const found = [];
|
|
3878
4237
|
const walk2 = (dir, depth) => {
|
|
3879
4238
|
if (depth > MAX_DEPTH) return;
|
|
@@ -3885,10 +4244,10 @@ function findAppInstance(cwd, re) {
|
|
|
3885
4244
|
}
|
|
3886
4245
|
for (const name of entries) {
|
|
3887
4246
|
if (SKIP_DIRS2.has(name) || name.startsWith(".")) continue;
|
|
3888
|
-
const p =
|
|
4247
|
+
const p = join11(dir, name);
|
|
3889
4248
|
let st;
|
|
3890
4249
|
try {
|
|
3891
|
-
st =
|
|
4250
|
+
st = lstatSync2(p);
|
|
3892
4251
|
} catch {
|
|
3893
4252
|
continue;
|
|
3894
4253
|
}
|
|
@@ -3897,7 +4256,7 @@ function findAppInstance(cwd, re) {
|
|
|
3897
4256
|
else if (/\.(?:ts|js|mjs|cjs)$/.test(name)) {
|
|
3898
4257
|
let src;
|
|
3899
4258
|
try {
|
|
3900
|
-
src =
|
|
4259
|
+
src = readFileSync8(p, "utf8");
|
|
3901
4260
|
} catch {
|
|
3902
4261
|
continue;
|
|
3903
4262
|
}
|
|
@@ -3905,7 +4264,7 @@ function findAppInstance(cwd, re) {
|
|
|
3905
4264
|
if (!m) continue;
|
|
3906
4265
|
const appVar = m[1];
|
|
3907
4266
|
found.push({
|
|
3908
|
-
relPath:
|
|
4267
|
+
relPath: relative3(cwd, p).replace(/\\/g, "/"),
|
|
3909
4268
|
appVar,
|
|
3910
4269
|
listens: listensOn(src, appVar)
|
|
3911
4270
|
});
|
|
@@ -3944,7 +4303,7 @@ function declaredEntries(cwd) {
|
|
|
3944
4303
|
const paths = /* @__PURE__ */ new Set();
|
|
3945
4304
|
let pkg;
|
|
3946
4305
|
try {
|
|
3947
|
-
pkg = JSON.parse(
|
|
4306
|
+
pkg = JSON.parse(readFileSync8(join11(cwd, "package.json"), "utf8"));
|
|
3948
4307
|
} catch {
|
|
3949
4308
|
return paths;
|
|
3950
4309
|
}
|
|
@@ -3963,24 +4322,24 @@ function declaredEntries(cwd) {
|
|
|
3963
4322
|
return paths;
|
|
3964
4323
|
}
|
|
3965
4324
|
function importSpecifier(fromRel, toRel, preserveExtension = false) {
|
|
3966
|
-
let spec =
|
|
4325
|
+
let spec = relative3(dirname4(fromRel), toRel).replace(/\\/g, "/");
|
|
3967
4326
|
if (!preserveExtension) spec = spec.replace(/\.(?:ts|js)$/, "");
|
|
3968
4327
|
if (!spec.startsWith(".")) spec = `./${spec}`;
|
|
3969
4328
|
return spec;
|
|
3970
4329
|
}
|
|
3971
4330
|
|
|
3972
4331
|
// src/protect/install/register.ts
|
|
3973
|
-
import {
|
|
3974
|
-
import { join as
|
|
4332
|
+
import { existsSync as existsSync10 } from "fs";
|
|
4333
|
+
import { join as join14, resolve as resolve3 } from "path";
|
|
3975
4334
|
|
|
3976
4335
|
// src/protect/install/generic.ts
|
|
3977
|
-
import { readFileSync as
|
|
3978
|
-
import { join as
|
|
4336
|
+
import { readFileSync as readFileSync9, existsSync as existsSync9, readdirSync as readdirSync4, lstatSync as lstatSync3 } from "fs";
|
|
4337
|
+
import { join as join13, dirname as dirname6 } from "path";
|
|
3979
4338
|
|
|
3980
4339
|
// src/protect/install/source-scope.ts
|
|
3981
4340
|
import { execFileSync } from "child_process";
|
|
3982
4341
|
import { existsSync as existsSync8 } from "fs";
|
|
3983
|
-
import { dirname as
|
|
4342
|
+
import { dirname as dirname5, join as join12, resolve as resolve2 } from "path";
|
|
3984
4343
|
|
|
3985
4344
|
// src/protect/install/node-flags.ts
|
|
3986
4345
|
var flagName = (token) => token.split("=")[0];
|
|
@@ -4269,9 +4628,9 @@ function moduleKindOf(filePath, packageIsModule) {
|
|
|
4269
4628
|
return packageIsModule ? "esm" : "cjs";
|
|
4270
4629
|
}
|
|
4271
4630
|
function resolvesToGuardModule(specifier, query) {
|
|
4272
|
-
const resolved =
|
|
4273
|
-
if (withoutModuleExtension(resolved) !==
|
|
4274
|
-
const existing = GUARD_FILENAMES.filter((name) => existsSync8(
|
|
4631
|
+
const resolved = resolve2(dirname5(query.fromFile), specifier);
|
|
4632
|
+
if (withoutModuleExtension(resolved) !== join12(query.guardDir, "guard")) return false;
|
|
4633
|
+
const existing = GUARD_FILENAMES.filter((name) => existsSync8(join12(query.guardDir, name)));
|
|
4275
4634
|
if (existing.length === 0) return false;
|
|
4276
4635
|
const extension = /(\.[A-Za-z0-9]+)$/.exec(specifier)?.[1] ?? "";
|
|
4277
4636
|
if (!query.strictExtensions) return true;
|
|
@@ -4290,16 +4649,16 @@ function withoutModuleExtension(filePath) {
|
|
|
4290
4649
|
// src/protect/install/generic.ts
|
|
4291
4650
|
function packageIsEsm(cwd) {
|
|
4292
4651
|
try {
|
|
4293
|
-
return JSON.parse(read(
|
|
4652
|
+
return JSON.parse(read(join13(cwd, "package.json"))).type === "module";
|
|
4294
4653
|
} catch {
|
|
4295
4654
|
return false;
|
|
4296
4655
|
}
|
|
4297
4656
|
}
|
|
4298
4657
|
function genericDir(cwd) {
|
|
4299
|
-
return existsSync9(
|
|
4658
|
+
return existsSync9(join13(cwd, "src")) ? "src/patchstack" : "patchstack";
|
|
4300
4659
|
}
|
|
4301
4660
|
function hasTypeScript(cwd) {
|
|
4302
|
-
return existsSync9(
|
|
4661
|
+
return existsSync9(join13(cwd, "tsconfig.json")) || candidateEntries(cwd).some((entry) => /\.tsx?$/.test(entry));
|
|
4303
4662
|
}
|
|
4304
4663
|
function genericGuardTarget(cwd) {
|
|
4305
4664
|
if (hasTypeScript(cwd)) {
|
|
@@ -4313,7 +4672,7 @@ function genericGuardTarget(cwd) {
|
|
|
4313
4672
|
function genericScaffoldFiles(cwd) {
|
|
4314
4673
|
const dir = genericDir(cwd);
|
|
4315
4674
|
const candidates = ["guard.ts", "guard.js", "guard.cjs", "rules.json"];
|
|
4316
|
-
return candidates.map((file) => `${dir}/${file}`).filter((
|
|
4675
|
+
return candidates.map((file) => `${dir}/${file}`).filter((relative8) => existsSync9(join13(cwd, relative8)));
|
|
4317
4676
|
}
|
|
4318
4677
|
function scaffoldGeneric(cwd, opts, guardTemplate, guardFile) {
|
|
4319
4678
|
const target = genericGuardTarget(cwd);
|
|
@@ -4321,15 +4680,15 @@ function scaffoldGeneric(cwd, opts, guardTemplate, guardFile) {
|
|
|
4321
4680
|
guardFile ??= target.file;
|
|
4322
4681
|
const templates = templatesDir();
|
|
4323
4682
|
const dir = genericDir(cwd);
|
|
4324
|
-
const dst =
|
|
4325
|
-
|
|
4326
|
-
|
|
4683
|
+
const dst = join13(cwd, dir);
|
|
4684
|
+
ensureProjectDirectorySync(cwd, dst);
|
|
4685
|
+
copyProjectFileSync(cwd, join13(templates, guardTemplate), join13(dst, guardFile));
|
|
4327
4686
|
const guardRel = `${dir}/${guardFile}`;
|
|
4328
4687
|
const changed = [guardRel];
|
|
4329
4688
|
if (!opts.demo) bakeSiteUuid(cwd, guardRel);
|
|
4330
|
-
const rulesDst =
|
|
4689
|
+
const rulesDst = join13(dst, "rules.json");
|
|
4331
4690
|
if (opts.demo || !existsSync9(rulesDst)) {
|
|
4332
|
-
|
|
4691
|
+
copyProjectFileSync(cwd, join13(templates, opts.demo ? "demo-rules.json" : "rules.json"), rulesDst);
|
|
4333
4692
|
changed.push(`${dir}/rules.json`);
|
|
4334
4693
|
}
|
|
4335
4694
|
return { changed, dir };
|
|
@@ -4350,17 +4709,17 @@ function candidateEntries(cwd) {
|
|
|
4350
4709
|
"app.ts",
|
|
4351
4710
|
"app.js"
|
|
4352
4711
|
];
|
|
4353
|
-
const hits = names.filter((n) => existsSync9(
|
|
4712
|
+
const hits = names.filter((n) => existsSync9(join13(cwd, n)));
|
|
4354
4713
|
try {
|
|
4355
|
-
const pkg = JSON.parse(read(
|
|
4356
|
-
if (typeof pkg.main === "string" && existsSync9(
|
|
4714
|
+
const pkg = JSON.parse(read(join13(cwd, "package.json")));
|
|
4715
|
+
if (typeof pkg.main === "string" && existsSync9(join13(cwd, pkg.main))) hits.push(pkg.main);
|
|
4357
4716
|
} catch {
|
|
4358
4717
|
}
|
|
4359
4718
|
return [...new Set(hits)];
|
|
4360
4719
|
}
|
|
4361
4720
|
function usesExpress(cwd) {
|
|
4362
4721
|
try {
|
|
4363
|
-
const pkg = JSON.parse(read(
|
|
4722
|
+
const pkg = JSON.parse(read(join13(cwd, "package.json")));
|
|
4364
4723
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }.express);
|
|
4365
4724
|
} catch {
|
|
4366
4725
|
return false;
|
|
@@ -4464,9 +4823,9 @@ function escapeForRegExp(value) {
|
|
|
4464
4823
|
function genericVerify(cwd) {
|
|
4465
4824
|
const dir = genericDir(cwd);
|
|
4466
4825
|
const target = genericGuardTarget(cwd);
|
|
4467
|
-
const present2 = GUARD_FILENAMES.find((name) => existsSync9(
|
|
4826
|
+
const present2 = GUARD_FILENAMES.find((name) => existsSync9(join13(cwd, dir, name)));
|
|
4468
4827
|
const scaffolded = present2 !== void 0;
|
|
4469
|
-
const imported = scaffolded && guardIsImported(cwd,
|
|
4828
|
+
const imported = scaffolded && guardIsImported(cwd, join13(cwd, dir, present2));
|
|
4470
4829
|
return {
|
|
4471
4830
|
wired: scaffolded && imported,
|
|
4472
4831
|
checks: [
|
|
@@ -4500,9 +4859,9 @@ var NON_RUNTIME_DIRS = /* @__PURE__ */ new Set([
|
|
|
4500
4859
|
"__mocks__"
|
|
4501
4860
|
]);
|
|
4502
4861
|
function guardIsImported(cwd, guardPath) {
|
|
4503
|
-
const guardDir =
|
|
4862
|
+
const guardDir = dirname6(guardPath);
|
|
4504
4863
|
const packageIsModule = packageIsEsm(cwd);
|
|
4505
|
-
const root = existsSync9(
|
|
4864
|
+
const root = existsSync9(join13(cwd, "src")) ? join13(cwd, "src") : cwd;
|
|
4506
4865
|
let found = false;
|
|
4507
4866
|
const walk2 = (d, depth) => {
|
|
4508
4867
|
if (found || depth > 8) return;
|
|
@@ -4515,10 +4874,10 @@ function guardIsImported(cwd, guardPath) {
|
|
|
4515
4874
|
for (const name of entries) {
|
|
4516
4875
|
if (found) return;
|
|
4517
4876
|
if (NON_RUNTIME_DIRS.has(name) || name.startsWith(".")) continue;
|
|
4518
|
-
const p =
|
|
4877
|
+
const p = join13(d, name);
|
|
4519
4878
|
let st;
|
|
4520
4879
|
try {
|
|
4521
|
-
st =
|
|
4880
|
+
st = lstatSync3(p);
|
|
4522
4881
|
} catch {
|
|
4523
4882
|
continue;
|
|
4524
4883
|
}
|
|
@@ -4526,7 +4885,7 @@ function guardIsImported(cwd, guardPath) {
|
|
|
4526
4885
|
if (st.isDirectory()) walk2(p, depth + 1);
|
|
4527
4886
|
else if (/\.(?:ts|tsx|js|jsx|mjs|cjs)$/.test(name) && p !== guardPath) {
|
|
4528
4887
|
try {
|
|
4529
|
-
if (importsAndUsesGuard(
|
|
4888
|
+
if (importsAndUsesGuard(readFileSync9(p, "utf8"), p, guardDir, packageIsModule)) found = true;
|
|
4530
4889
|
} catch {
|
|
4531
4890
|
}
|
|
4532
4891
|
}
|
|
@@ -4574,7 +4933,7 @@ function guardTarget(cwd, entryRel, spec) {
|
|
|
4574
4933
|
}
|
|
4575
4934
|
function packageType(cwd) {
|
|
4576
4935
|
try {
|
|
4577
|
-
return JSON.parse(read(
|
|
4936
|
+
return JSON.parse(read(join14(cwd, "package.json"))).type;
|
|
4578
4937
|
} catch {
|
|
4579
4938
|
return void 0;
|
|
4580
4939
|
}
|
|
@@ -4610,8 +4969,8 @@ function bindsGuard(line, maskedLine, importName, call, fileRel, guard) {
|
|
|
4610
4969
|
}
|
|
4611
4970
|
function namesGuardModule(specifier, fileRel, guard) {
|
|
4612
4971
|
return resolvesToGuardModule(specifier, {
|
|
4613
|
-
fromFile:
|
|
4614
|
-
guardDir:
|
|
4972
|
+
fromFile: join14(guard.cwd, fileRel),
|
|
4973
|
+
guardDir: resolve3(join14(guard.cwd, guard.guardDir)),
|
|
4615
4974
|
kind: moduleKindOf(fileRel, packageType(guard.cwd) === "module"),
|
|
4616
4975
|
strictExtensions: true
|
|
4617
4976
|
});
|
|
@@ -4630,7 +4989,7 @@ function wireRegister(cwd, opts, spec) {
|
|
|
4630
4989
|
}
|
|
4631
4990
|
const target = guardTarget(cwd, entry.relPath, spec);
|
|
4632
4991
|
const { changed, dir } = scaffoldGeneric(cwd, opts, target.template, target.file);
|
|
4633
|
-
const p =
|
|
4992
|
+
const p = join14(cwd, entry.relPath);
|
|
4634
4993
|
const s = read(p);
|
|
4635
4994
|
const state = wiringState(s, spec, entry.appVar, entry.relPath, { cwd, guardDir: dir });
|
|
4636
4995
|
if (state.importAtTopLevel && state.callInAppScope) {
|
|
@@ -4669,7 +5028,7 @@ function wireRegister(cwd, opts, spec) {
|
|
|
4669
5028
|
lines.splice(callIdx + 1, 0, REGION, spec.call(entry.appVar), "// #endregion patchstack");
|
|
4670
5029
|
}
|
|
4671
5030
|
const patched = lines.join("\n");
|
|
4672
|
-
|
|
5031
|
+
writeProjectFileSync(cwd, p, patched, { encoding: "utf8" });
|
|
4673
5032
|
if (spec.callAfter) {
|
|
4674
5033
|
const guardIndex = lines.findIndex((line) => line.includes(spec.call(entry.appVar)));
|
|
4675
5034
|
const early = routesBefore(patched, entry.appVar, guardIndex);
|
|
@@ -4681,7 +5040,7 @@ function wireRegister(cwd, opts, spec) {
|
|
|
4681
5040
|
}
|
|
4682
5041
|
const parsed = parses(p);
|
|
4683
5042
|
if (parsed === false) {
|
|
4684
|
-
|
|
5043
|
+
writeProjectFileSync(cwd, p, s, { encoding: "utf8" });
|
|
4685
5044
|
log(`${entry.relPath} would not parse after patching \u2014 reverted; ${spec.manualHint}`);
|
|
4686
5045
|
return { ok: true, changed };
|
|
4687
5046
|
}
|
|
@@ -4700,17 +5059,17 @@ function firstStatementLine(lines) {
|
|
|
4700
5059
|
return i;
|
|
4701
5060
|
}
|
|
4702
5061
|
function serverIsGuarded(cwd, spec, server, guardDir) {
|
|
4703
|
-
const source = read(
|
|
5062
|
+
const source = read(join14(cwd, server.relPath));
|
|
4704
5063
|
const state = wiringState(source, spec, server.appVar, server.relPath, { cwd, guardDir });
|
|
4705
5064
|
if (!state.importAtTopLevel || !state.callInAppScope || !state.ordered) return false;
|
|
4706
5065
|
return !spec.callAfter || routesBefore(source, server.appVar, state.callIndex).length === 0;
|
|
4707
5066
|
}
|
|
4708
5067
|
function verifyRegister(cwd, spec) {
|
|
4709
|
-
const dir = existsSync10(
|
|
5068
|
+
const dir = existsSync10(join14(cwd, "src")) ? "src/patchstack" : "patchstack";
|
|
4710
5069
|
const entry = findAppInstance(cwd, spec.appRe);
|
|
4711
5070
|
const target = entry ? guardTarget(cwd, entry.relPath, spec) : null;
|
|
4712
|
-
const scaffolded = target ? existsSync10(
|
|
4713
|
-
const entrySource = entry ? read(
|
|
5071
|
+
const scaffolded = target ? existsSync10(join14(cwd, dir, target.file)) : false;
|
|
5072
|
+
const entrySource = entry ? read(join14(cwd, entry.relPath)) : "";
|
|
4714
5073
|
const state = entry ? wiringState(entrySource, spec, entry.appVar, entry.relPath, { cwd, guardDir: dir }) : { importAtTopLevel: false, callInAppScope: false, ordered: false, callIndex: -1, appIndex: -1 };
|
|
4715
5074
|
const wired = state.importAtTopLevel && state.callInAppScope && state.ordered;
|
|
4716
5075
|
const early = spec.callAfter && entry ? routesBefore(entrySource, entry.appVar, state.callIndex) : [];
|
|
@@ -4801,7 +5160,7 @@ var expressAdapter = {
|
|
|
4801
5160
|
|
|
4802
5161
|
// src/protect/install/reporting.ts
|
|
4803
5162
|
import { existsSync as existsSync11 } from "fs";
|
|
4804
|
-
import { join as
|
|
5163
|
+
import { join as join15 } from "path";
|
|
4805
5164
|
|
|
4806
5165
|
// src/protect/reporting-state.js
|
|
4807
5166
|
var REPORTING_STATES = Object.freeze([
|
|
@@ -4859,7 +5218,7 @@ function explainReportingState(state) {
|
|
|
4859
5218
|
|
|
4860
5219
|
// src/protect/install/reporting.ts
|
|
4861
5220
|
function siteIdentity(cwd, env) {
|
|
4862
|
-
const rc =
|
|
5221
|
+
const rc = join15(cwd, ".patchstackrc.json");
|
|
4863
5222
|
let recorded;
|
|
4864
5223
|
if (existsSync11(rc)) {
|
|
4865
5224
|
try {
|
|
@@ -5010,7 +5369,7 @@ function runVerify(cwd) {
|
|
|
5010
5369
|
}
|
|
5011
5370
|
|
|
5012
5371
|
// src/widget.ts
|
|
5013
|
-
import { existsSync as existsSync12, readFileSync as
|
|
5372
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
5014
5373
|
import path11 from "path";
|
|
5015
5374
|
var WIDGET_SCRIPT_URL = "https://cdn.patchstack.com/patchstack-widget.js";
|
|
5016
5375
|
var WIDGET_MARKER_ATTR = "data-patchstack-connect-widget";
|
|
@@ -5064,10 +5423,10 @@ function ensureSourceWidget(cwd, siteUuid, jsxShell = null) {
|
|
|
5064
5423
|
if (!existsSync12(file)) {
|
|
5065
5424
|
return { shell: null, action: "no-shell" };
|
|
5066
5425
|
}
|
|
5067
|
-
const before =
|
|
5426
|
+
const before = readFileSync10(file, "utf8");
|
|
5068
5427
|
const { html, action } = ensureWidgetInHtml(before, siteUuid);
|
|
5069
5428
|
if (html !== before) {
|
|
5070
|
-
|
|
5429
|
+
writeProjectFileSync(cwd, file, html, { encoding: "utf8" });
|
|
5071
5430
|
}
|
|
5072
5431
|
return { shell, action };
|
|
5073
5432
|
}
|
|
@@ -5157,7 +5516,7 @@ function installCommand(pm) {
|
|
|
5157
5516
|
}
|
|
5158
5517
|
function readPackageJson(cwd) {
|
|
5159
5518
|
try {
|
|
5160
|
-
return JSON.parse(
|
|
5519
|
+
return JSON.parse(readFileSync11(path12.join(cwd, "package.json"), "utf8"));
|
|
5161
5520
|
} catch {
|
|
5162
5521
|
return null;
|
|
5163
5522
|
}
|
|
@@ -5165,7 +5524,7 @@ function readPackageJson(cwd) {
|
|
|
5165
5524
|
function installedVersion(cwd, declaredRange) {
|
|
5166
5525
|
try {
|
|
5167
5526
|
const pkg = JSON.parse(
|
|
5168
|
-
|
|
5527
|
+
readFileSync11(path12.join(cwd, "node_modules", "@patchstack", "connect", "package.json"), "utf8")
|
|
5169
5528
|
);
|
|
5170
5529
|
if (typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
5171
5530
|
return pkg.version;
|
|
@@ -5206,10 +5565,10 @@ function findWidgetMarker(cwd, siteUuid) {
|
|
|
5206
5565
|
}
|
|
5207
5566
|
budget -= 1;
|
|
5208
5567
|
try {
|
|
5209
|
-
if (
|
|
5568
|
+
if (statSync5(full).size > WIDGET_SCAN_MAX_BYTES) {
|
|
5210
5569
|
continue;
|
|
5211
5570
|
}
|
|
5212
|
-
const content =
|
|
5571
|
+
const content = readFileSync11(full, "utf8");
|
|
5213
5572
|
if (!content.includes(WIDGET_NEEDLE2)) {
|
|
5214
5573
|
continue;
|
|
5215
5574
|
}
|
|
@@ -5232,7 +5591,7 @@ function findProductionMarker(cwd, widgetFileHint) {
|
|
|
5232
5591
|
return false;
|
|
5233
5592
|
}
|
|
5234
5593
|
try {
|
|
5235
|
-
return
|
|
5594
|
+
return readFileSync11(path12.join(cwd, widgetFileHint), "utf8").includes(PROD_MARKER_NEEDLE);
|
|
5236
5595
|
} catch {
|
|
5237
5596
|
return false;
|
|
5238
5597
|
}
|
|
@@ -5271,7 +5630,7 @@ async function collectGuideState(cwd) {
|
|
|
5271
5630
|
try {
|
|
5272
5631
|
const config = await resolveConfig({ cwd });
|
|
5273
5632
|
siteUuid = config.siteUuid;
|
|
5274
|
-
if (siteUuid !== null) {
|
|
5633
|
+
if (siteUuid !== null && config.endpointTrusted !== false) {
|
|
5275
5634
|
claimUrl = buildClaimUrl(config.endpoint, siteUuid);
|
|
5276
5635
|
}
|
|
5277
5636
|
if (config.endpoint !== DEFAULT_ENDPOINT) {
|
|
@@ -5535,10 +5894,19 @@ import { tmpdir as tmpdir2 } from "os";
|
|
|
5535
5894
|
import path14 from "path";
|
|
5536
5895
|
|
|
5537
5896
|
// src/device-flow.ts
|
|
5538
|
-
import {
|
|
5897
|
+
import {
|
|
5898
|
+
closeSync as closeSync2,
|
|
5899
|
+
constants,
|
|
5900
|
+
fstatSync,
|
|
5901
|
+
openSync as openSync2,
|
|
5902
|
+
readFileSync as readFileSync12,
|
|
5903
|
+
renameSync as renameSync2,
|
|
5904
|
+
rmSync,
|
|
5905
|
+
writeFileSync as writeFileSync2
|
|
5906
|
+
} from "fs";
|
|
5539
5907
|
import { tmpdir } from "os";
|
|
5540
5908
|
import path13 from "path";
|
|
5541
|
-
import { createHash as createHash2 } from "crypto";
|
|
5909
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
5542
5910
|
function baseFrom(manifestEndpoint) {
|
|
5543
5911
|
const url = new URL(manifestEndpoint);
|
|
5544
5912
|
const p = url.pathname.replace(/\/$/, "");
|
|
@@ -5552,16 +5920,61 @@ function pendingPath(siteUuid, intent) {
|
|
|
5552
5920
|
return path13.join(tmpdir(), `patchstack-${intent}-${key}.json`);
|
|
5553
5921
|
}
|
|
5554
5922
|
function savePending(siteUuid, intent, pending) {
|
|
5555
|
-
|
|
5556
|
-
|
|
5557
|
-
|
|
5558
|
-
|
|
5923
|
+
const target = pendingPath(siteUuid, intent);
|
|
5924
|
+
const temporary = `${target}.${process.pid}.${randomUUID3()}.tmp`;
|
|
5925
|
+
let descriptor = null;
|
|
5926
|
+
try {
|
|
5927
|
+
descriptor = openSync2(temporary, "wx", 384);
|
|
5928
|
+
writeFileSync2(descriptor, JSON.stringify(pending), "utf8");
|
|
5929
|
+
closeSync2(descriptor);
|
|
5930
|
+
descriptor = null;
|
|
5931
|
+
try {
|
|
5932
|
+
renameSync2(temporary, target);
|
|
5933
|
+
} catch (error) {
|
|
5934
|
+
const code = error.code;
|
|
5935
|
+
if (process.platform !== "win32" || code !== "EEXIST" && code !== "EPERM") throw error;
|
|
5936
|
+
try {
|
|
5937
|
+
rmSync(target);
|
|
5938
|
+
} catch (removeError) {
|
|
5939
|
+
if (removeError.code !== "ENOENT") throw removeError;
|
|
5940
|
+
}
|
|
5941
|
+
renameSync2(temporary, target);
|
|
5942
|
+
}
|
|
5943
|
+
} catch (error) {
|
|
5944
|
+
if (descriptor !== null) {
|
|
5945
|
+
try {
|
|
5946
|
+
closeSync2(descriptor);
|
|
5947
|
+
} catch {
|
|
5948
|
+
}
|
|
5949
|
+
}
|
|
5950
|
+
try {
|
|
5951
|
+
rmSync(temporary);
|
|
5952
|
+
} catch {
|
|
5953
|
+
}
|
|
5954
|
+
throw error;
|
|
5955
|
+
}
|
|
5559
5956
|
}
|
|
5560
5957
|
function readPending(siteUuid, intent) {
|
|
5958
|
+
let descriptor = null;
|
|
5561
5959
|
try {
|
|
5562
|
-
|
|
5960
|
+
const noFollow = typeof constants.O_NOFOLLOW === "number" ? constants.O_NOFOLLOW : 0;
|
|
5961
|
+
descriptor = openSync2(pendingPath(siteUuid, intent), constants.O_RDONLY | noFollow);
|
|
5962
|
+
const stat2 = fstatSync(descriptor);
|
|
5963
|
+
if (!stat2.isFile()) return null;
|
|
5964
|
+
if (typeof process.getuid === "function" && typeof stat2.uid === "number" && stat2.uid !== process.getuid()) {
|
|
5965
|
+
return null;
|
|
5966
|
+
}
|
|
5967
|
+
if (process.platform !== "win32" && (stat2.mode & 63) !== 0) return null;
|
|
5968
|
+
return JSON.parse(readFileSync12(descriptor, "utf8"));
|
|
5563
5969
|
} catch {
|
|
5564
5970
|
return null;
|
|
5971
|
+
} finally {
|
|
5972
|
+
if (descriptor !== null) {
|
|
5973
|
+
try {
|
|
5974
|
+
closeSync2(descriptor);
|
|
5975
|
+
} catch {
|
|
5976
|
+
}
|
|
5977
|
+
}
|
|
5565
5978
|
}
|
|
5566
5979
|
}
|
|
5567
5980
|
function clearPending(siteUuid, intent) {
|
|
@@ -5579,17 +5992,25 @@ async function requestDeviceCode(config, intent, conflictMessage, deps = {}) {
|
|
|
5579
5992
|
message: "No site UUID configured \u2014 run `patchstack-connect scan` first."
|
|
5580
5993
|
};
|
|
5581
5994
|
}
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
5586
|
-
|
|
5587
|
-
|
|
5588
|
-
|
|
5589
|
-
|
|
5590
|
-
|
|
5591
|
-
|
|
5592
|
-
|
|
5995
|
+
let base;
|
|
5996
|
+
let started;
|
|
5997
|
+
try {
|
|
5998
|
+
base = baseFrom(config.endpoint);
|
|
5999
|
+
assertConnectableEndpoint(config, base);
|
|
6000
|
+
started = await pulseFetch(
|
|
6001
|
+
config,
|
|
6002
|
+
`${base}/device/code`,
|
|
6003
|
+
{
|
|
6004
|
+
method: "POST",
|
|
6005
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
6006
|
+
body: JSON.stringify({ site_uuid: config.siteUuid, intent }),
|
|
6007
|
+
signal: AbortSignal.timeout(config.timeoutMs)
|
|
6008
|
+
},
|
|
6009
|
+
fetchImpl
|
|
6010
|
+
);
|
|
6011
|
+
} catch {
|
|
6012
|
+
return { status: "failed", message: "Could not start the request." };
|
|
6013
|
+
}
|
|
5593
6014
|
if (started.status === 409) return { status: "conflict", message: conflictMessage };
|
|
5594
6015
|
if (started.status === 404) {
|
|
5595
6016
|
return { status: "not-found", message: "Patchstack does not recognise this site UUID." };
|
|
@@ -5597,34 +6018,76 @@ async function requestDeviceCode(config, intent, conflictMessage, deps = {}) {
|
|
|
5597
6018
|
if (!started.ok) {
|
|
5598
6019
|
return { status: "failed", message: `Could not start the request (HTTP ${started.status}).` };
|
|
5599
6020
|
}
|
|
5600
|
-
|
|
6021
|
+
let body;
|
|
6022
|
+
try {
|
|
6023
|
+
const raw = await readBoundedJson(started);
|
|
6024
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) throw new Error("unexpected response");
|
|
6025
|
+
body = raw;
|
|
6026
|
+
} catch {
|
|
6027
|
+
return { status: "failed", message: "Patchstack returned an unexpected response." };
|
|
6028
|
+
}
|
|
6029
|
+
const deviceCode = safeDisplayString(body.device_code, 2048);
|
|
6030
|
+
const userCode = safeDisplayString(body.user_code, 128);
|
|
6031
|
+
const expiresIn = Number(body.expires_in);
|
|
6032
|
+
const interval = Number(body.interval);
|
|
6033
|
+
if (deviceCode === null || userCode === null || !Number.isInteger(expiresIn) || expiresIn < 1 || expiresIn > 86400 || !Number.isFinite(interval) || interval < 1 || interval > 300) {
|
|
6034
|
+
return { status: "failed", message: "Patchstack returned an unexpected response." };
|
|
6035
|
+
}
|
|
5601
6036
|
const pending = {
|
|
5602
|
-
deviceCode
|
|
5603
|
-
userCode
|
|
6037
|
+
deviceCode,
|
|
6038
|
+
userCode,
|
|
5604
6039
|
// Points at the API, which redirects to the dashboard SPA — the CLI only knows the API origin,
|
|
5605
6040
|
// and the approval page lives on the app.
|
|
5606
|
-
verificationUri: `${base}/device?code=${encodeURIComponent(
|
|
5607
|
-
expiresAt: now() +
|
|
5608
|
-
intervalMs:
|
|
6041
|
+
verificationUri: `${base}/device?code=${encodeURIComponent(userCode)}`,
|
|
6042
|
+
expiresAt: now() + expiresIn * 1e3,
|
|
6043
|
+
intervalMs: interval * 1e3
|
|
5609
6044
|
};
|
|
5610
6045
|
savePending(config.siteUuid, intent, pending);
|
|
5611
6046
|
return { status: "started", pending };
|
|
5612
6047
|
}
|
|
5613
6048
|
async function pollDeviceToken(config, intent, pending, deps = {}) {
|
|
5614
6049
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
6050
|
+
let polled;
|
|
6051
|
+
try {
|
|
6052
|
+
const url = `${baseFrom(config.endpoint)}/device/token`;
|
|
6053
|
+
assertConnectableEndpoint(config, url);
|
|
6054
|
+
polled = await fetchImpl(url, {
|
|
6055
|
+
method: "POST",
|
|
6056
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
6057
|
+
body: JSON.stringify({ device_code: pending.deviceCode }),
|
|
6058
|
+
signal: AbortSignal.timeout(config.timeoutMs)
|
|
6059
|
+
});
|
|
6060
|
+
} catch {
|
|
6061
|
+
return { state: "expired" };
|
|
6062
|
+
}
|
|
5620
6063
|
if (polled.status === 428) return { state: "pending" };
|
|
5621
6064
|
if (!polled.ok) {
|
|
5622
6065
|
if (config.siteUuid !== null) clearPending(config.siteUuid, intent);
|
|
5623
6066
|
return { state: "expired" };
|
|
5624
6067
|
}
|
|
5625
|
-
|
|
6068
|
+
let raw;
|
|
6069
|
+
try {
|
|
6070
|
+
raw = await readBoundedJson(polled);
|
|
6071
|
+
} catch {
|
|
6072
|
+
raw = null;
|
|
6073
|
+
}
|
|
5626
6074
|
if (config.siteUuid !== null) clearPending(config.siteUuid, intent);
|
|
5627
|
-
return { state: "
|
|
6075
|
+
if (typeof raw !== "object" || raw === null || Array.isArray(raw)) return { state: "expired" };
|
|
6076
|
+
const value = raw;
|
|
6077
|
+
const apiKey = value.api_key === void 0 ? void 0 : isCredential(value.api_key) ? value.api_key : null;
|
|
6078
|
+
if (apiKey === null) return { state: "expired" };
|
|
6079
|
+
const dashboardUrl = value.dashboard_url === void 0 ? void 0 : safeRemoteUrl(value.dashboard_url);
|
|
6080
|
+
if (value.dashboard_url !== void 0 && dashboardUrl === null) return { state: "expired" };
|
|
6081
|
+
const account = value.account === void 0 ? void 0 : safeDisplayString(value.account, 256);
|
|
6082
|
+
if (value.account !== void 0 && account === null) return { state: "expired" };
|
|
6083
|
+
return {
|
|
6084
|
+
state: "approved",
|
|
6085
|
+
body: {
|
|
6086
|
+
...apiKey !== void 0 ? { api_key: apiKey } : {},
|
|
6087
|
+
...dashboardUrl !== void 0 ? { dashboard_url: dashboardUrl } : {},
|
|
6088
|
+
...account !== void 0 ? { account } : {}
|
|
6089
|
+
}
|
|
6090
|
+
};
|
|
5628
6091
|
}
|
|
5629
6092
|
async function pollUntilApproved(config, intent, pending, deps = {}) {
|
|
5630
6093
|
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
@@ -5726,11 +6189,11 @@ async function claim2(config, onPrompt, deps = {}) {
|
|
|
5726
6189
|
}
|
|
5727
6190
|
|
|
5728
6191
|
// src/protect/install/runtime/check.ts
|
|
5729
|
-
import { relative as
|
|
6192
|
+
import { relative as relative4 } from "path";
|
|
5730
6193
|
|
|
5731
6194
|
// src/protect/install/runtime/entry.ts
|
|
5732
|
-
import { existsSync as existsSync14, readFileSync as
|
|
5733
|
-
import { join as
|
|
6195
|
+
import { existsSync as existsSync14, readFileSync as readFileSync13, realpathSync as realpathSync4, statSync as statSync6 } from "fs";
|
|
6196
|
+
import { join as join16, resolve as resolve4, sep as sep2 } from "path";
|
|
5734
6197
|
var CONVENTIONAL = [
|
|
5735
6198
|
"server.js",
|
|
5736
6199
|
"server.mjs",
|
|
@@ -5756,31 +6219,31 @@ var CONVENTIONAL = [
|
|
|
5756
6219
|
var LOADABLE = /\.(?:js|mjs|cjs)$/;
|
|
5757
6220
|
function readPackage2(cwd) {
|
|
5758
6221
|
try {
|
|
5759
|
-
const parsed = JSON.parse(
|
|
6222
|
+
const parsed = JSON.parse(readFileSync13(join16(cwd, "package.json"), "utf8"));
|
|
5760
6223
|
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
5761
6224
|
} catch {
|
|
5762
6225
|
return null;
|
|
5763
6226
|
}
|
|
5764
6227
|
}
|
|
5765
|
-
function isFile(
|
|
6228
|
+
function isFile(path18) {
|
|
5766
6229
|
try {
|
|
5767
|
-
return
|
|
6230
|
+
return statSync6(path18).isFile();
|
|
5768
6231
|
} catch {
|
|
5769
6232
|
return false;
|
|
5770
6233
|
}
|
|
5771
6234
|
}
|
|
5772
6235
|
function loadable(cwd, candidate) {
|
|
5773
|
-
const written =
|
|
6236
|
+
const written = resolve4(cwd, candidate);
|
|
5774
6237
|
if (!LOADABLE.test(written) || !isFile(written)) return null;
|
|
5775
6238
|
let root;
|
|
5776
|
-
let
|
|
6239
|
+
let path18;
|
|
5777
6240
|
try {
|
|
5778
|
-
root =
|
|
5779
|
-
|
|
6241
|
+
root = realpathSync4(cwd);
|
|
6242
|
+
path18 = realpathSync4(written);
|
|
5780
6243
|
} catch {
|
|
5781
6244
|
return null;
|
|
5782
6245
|
}
|
|
5783
|
-
if (
|
|
6246
|
+
if (path18 !== root && !path18.startsWith(root + sep2)) return null;
|
|
5784
6247
|
return written;
|
|
5785
6248
|
}
|
|
5786
6249
|
function passable(token) {
|
|
@@ -5826,7 +6289,7 @@ function resolveEntry(cwd) {
|
|
|
5826
6289
|
}
|
|
5827
6290
|
return {
|
|
5828
6291
|
kind: "unavailable",
|
|
5829
|
-
reason: existsSync14(
|
|
6292
|
+
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
6293
|
};
|
|
5831
6294
|
}
|
|
5832
6295
|
|
|
@@ -5860,8 +6323,8 @@ function preloadPath() {
|
|
|
5860
6323
|
// beside this file, in the repository
|
|
5861
6324
|
];
|
|
5862
6325
|
for (const candidate of candidates) {
|
|
5863
|
-
const
|
|
5864
|
-
if (existsSync15(
|
|
6326
|
+
const path18 = fileURLToPath2(candidate);
|
|
6327
|
+
if (existsSync15(path18)) return path18;
|
|
5865
6328
|
}
|
|
5866
6329
|
return null;
|
|
5867
6330
|
}
|
|
@@ -5895,7 +6358,7 @@ async function askListener(listener, challenge, answer, budgetMs) {
|
|
|
5895
6358
|
timeout: budgetMs
|
|
5896
6359
|
};
|
|
5897
6360
|
if (listener.scheme === "https") options.agent = new HttpsAgent({ rejectUnauthorized: false });
|
|
5898
|
-
return new Promise((
|
|
6361
|
+
return new Promise((resolve5) => {
|
|
5899
6362
|
const req = send(options, (res) => {
|
|
5900
6363
|
const body = [];
|
|
5901
6364
|
let read2 = 0;
|
|
@@ -5909,7 +6372,7 @@ async function askListener(listener, challenge, answer, budgetMs) {
|
|
|
5909
6372
|
});
|
|
5910
6373
|
const finish = () => {
|
|
5911
6374
|
const text = Buffer.concat(body).toString("utf8").trim();
|
|
5912
|
-
|
|
6375
|
+
resolve5({
|
|
5913
6376
|
...listener,
|
|
5914
6377
|
outcome: text === answer ? "traversed" : "answered-without-sentinel",
|
|
5915
6378
|
detail: text === answer ? void 0 : `answered with ${res.statusCode ?? "?"} and no sentinel`
|
|
@@ -5919,7 +6382,7 @@ async function askListener(listener, challenge, answer, budgetMs) {
|
|
|
5919
6382
|
res.on("close", finish);
|
|
5920
6383
|
});
|
|
5921
6384
|
req.on("timeout", () => req.destroy(new Error("timed out")));
|
|
5922
|
-
req.on("error", (err) =>
|
|
6385
|
+
req.on("error", (err) => resolve5({ ...listener, outcome: "unreachable", detail: err.message }));
|
|
5923
6386
|
req.end();
|
|
5924
6387
|
});
|
|
5925
6388
|
}
|
|
@@ -6013,12 +6476,12 @@ async function probeRuntimeTraversal(options) {
|
|
|
6013
6476
|
let onFrozen = null;
|
|
6014
6477
|
const freeze = async () => {
|
|
6015
6478
|
if (!child.connected) return false;
|
|
6016
|
-
return new Promise((
|
|
6479
|
+
return new Promise((resolve5) => {
|
|
6017
6480
|
const settle2 = (confirmed2) => {
|
|
6018
6481
|
clearTimeout(timer);
|
|
6019
6482
|
onFrozen = null;
|
|
6020
6483
|
child.removeListener("exit", onGone);
|
|
6021
|
-
|
|
6484
|
+
resolve5(confirmed2);
|
|
6022
6485
|
};
|
|
6023
6486
|
const onGone = () => settle2(false);
|
|
6024
6487
|
const timer = setTimeout(() => settle2(false), Math.min(FREEZE_MS, remaining()));
|
|
@@ -6031,13 +6494,13 @@ async function probeRuntimeTraversal(options) {
|
|
|
6031
6494
|
}
|
|
6032
6495
|
});
|
|
6033
6496
|
};
|
|
6034
|
-
await new Promise((
|
|
6497
|
+
await new Promise((resolve5) => {
|
|
6035
6498
|
let settleTimer = null;
|
|
6036
6499
|
const done = () => {
|
|
6037
6500
|
discovering = false;
|
|
6038
6501
|
clearTimeout(runTimer);
|
|
6039
6502
|
if (settleTimer) clearTimeout(settleTimer);
|
|
6040
|
-
|
|
6503
|
+
resolve5();
|
|
6041
6504
|
};
|
|
6042
6505
|
const runTimer = setTimeout(done, remaining());
|
|
6043
6506
|
const settle2 = () => {
|
|
@@ -6177,7 +6640,7 @@ async function runRuntimeCheck(cwd, options = {}) {
|
|
|
6177
6640
|
settleMs: options.settleMs,
|
|
6178
6641
|
platform: options.platform
|
|
6179
6642
|
});
|
|
6180
|
-
return { ...result, entry: { file:
|
|
6643
|
+
return { ...result, entry: { file: relative4(cwd, entry.file).replace(/\\/g, "/"), from: entry.from } };
|
|
6181
6644
|
}
|
|
6182
6645
|
function runtimeExitCode(report) {
|
|
6183
6646
|
if (report.outcome === "proven") return 0;
|
|
@@ -6208,15 +6671,15 @@ function formatRuntimeCheck(report) {
|
|
|
6208
6671
|
}
|
|
6209
6672
|
|
|
6210
6673
|
// src/map-command.ts
|
|
6211
|
-
import
|
|
6674
|
+
import path15 from "path";
|
|
6212
6675
|
|
|
6213
6676
|
// src/map/ts-loader.ts
|
|
6214
6677
|
import { createRequire } from "module";
|
|
6215
6678
|
import { pathToFileURL } from "url";
|
|
6216
|
-
import { join as
|
|
6679
|
+
import { join as join17 } from "path";
|
|
6217
6680
|
async function loadTypeScript(cwd) {
|
|
6218
6681
|
try {
|
|
6219
|
-
const req = createRequire(pathToFileURL(
|
|
6682
|
+
const req = createRequire(pathToFileURL(join17(cwd, "package.json")));
|
|
6220
6683
|
const resolved = req.resolve("typescript");
|
|
6221
6684
|
const mod = await import(pathToFileURL(resolved).href);
|
|
6222
6685
|
return mod.default ?? mod;
|
|
@@ -6231,9 +6694,9 @@ async function loadTypeScript(cwd) {
|
|
|
6231
6694
|
}
|
|
6232
6695
|
|
|
6233
6696
|
// src/map/extract.ts
|
|
6234
|
-
import { readFileSync as
|
|
6697
|
+
import { readFileSync as readFileSync16, realpathSync as realpathSync6 } from "fs";
|
|
6235
6698
|
import { createHash as createHash5 } from "crypto";
|
|
6236
|
-
import { relative as
|
|
6699
|
+
import { relative as relative6 } from "path";
|
|
6237
6700
|
|
|
6238
6701
|
// src/map/bindings.ts
|
|
6239
6702
|
import { builtinModules } from "module";
|
|
@@ -6461,14 +6924,14 @@ function directSinks(node, ts, bindings, ctx) {
|
|
|
6461
6924
|
const root = base ? rootIdentifier(base, ts) : void 0;
|
|
6462
6925
|
if (!root) return {};
|
|
6463
6926
|
const spec = bindings.resolve(root);
|
|
6464
|
-
const
|
|
6927
|
+
const relative8 = spec !== void 0 && (spec.startsWith(".") || spec.startsWith("/"));
|
|
6465
6928
|
const pkg = npmPackageOf(spec);
|
|
6466
6929
|
if (pkg) return { pkg, root, spec };
|
|
6467
|
-
if (
|
|
6930
|
+
if (relative8 && ctx && spec) {
|
|
6468
6931
|
const viaModule = ctx.graph.importedBinding(ctx.file, spec, bindings.exportNameOf(root) ?? root);
|
|
6469
6932
|
if (viaModule) return { pkg: viaModule.package, root, spec };
|
|
6470
6933
|
}
|
|
6471
|
-
return { local: bindings.locals.has(root), root, spec, relative:
|
|
6934
|
+
return { local: bindings.locals.has(root), root, spec, relative: relative8 };
|
|
6472
6935
|
};
|
|
6473
6936
|
const attributionOf = (b, pkg) => b.pkg ? "import" : pkg ? "inferred" : void 0;
|
|
6474
6937
|
const dbApi = (pkg, attribution) => {
|
|
@@ -6645,8 +7108,8 @@ function dedupeSinks(sinks, owner) {
|
|
|
6645
7108
|
}
|
|
6646
7109
|
|
|
6647
7110
|
// src/map/module-graph.ts
|
|
6648
|
-
import { readFileSync as
|
|
6649
|
-
import { dirname as
|
|
7111
|
+
import { readFileSync as readFileSync14, realpathSync as realpathSync5, statSync as statSync7 } from "fs";
|
|
7112
|
+
import { dirname as dirname8, join as join18, relative as relative5, resolve as resolvePath } from "path";
|
|
6650
7113
|
var RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
6651
7114
|
function createModuleGraph(ts, opts) {
|
|
6652
7115
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -6654,7 +7117,7 @@ function createModuleGraph(ts, opts) {
|
|
|
6654
7117
|
if (cache.has(file)) return cache.get(file) ?? null;
|
|
6655
7118
|
let entry = null;
|
|
6656
7119
|
try {
|
|
6657
|
-
const text =
|
|
7120
|
+
const text = readFileSync14(file, "utf8");
|
|
6658
7121
|
const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, file));
|
|
6659
7122
|
const bindings = buildModuleBindings(sf, ts);
|
|
6660
7123
|
entry = { fnSinks: collectLocalSinks(sf, ts, bindings), calleesOf: collectCallees(sf, ts), bindings };
|
|
@@ -6670,7 +7133,7 @@ function createModuleGraph(ts, opts) {
|
|
|
6670
7133
|
if (!opts.followOutside) {
|
|
6671
7134
|
let real = target;
|
|
6672
7135
|
try {
|
|
6673
|
-
real =
|
|
7136
|
+
real = realpathSync5(target);
|
|
6674
7137
|
} catch {
|
|
6675
7138
|
}
|
|
6676
7139
|
if (!isInside(real, opts.boundary)) return void 0;
|
|
@@ -6688,7 +7151,7 @@ function createModuleGraph(ts, opts) {
|
|
|
6688
7151
|
for (const callee of mod.calleesOf.get(exportName) ?? []) {
|
|
6689
7152
|
for (const s of mod.fnSinks.get(callee) ?? []) collected.push(s);
|
|
6690
7153
|
}
|
|
6691
|
-
const rel =
|
|
7154
|
+
const rel = relative5(opts.cwd, target);
|
|
6692
7155
|
return collected.map((s) => ({ ...s, file: rel }));
|
|
6693
7156
|
},
|
|
6694
7157
|
// A different question about the same module: not "what sinks are in there" but "what does this
|
|
@@ -6714,7 +7177,7 @@ function createModuleGraph(ts, opts) {
|
|
|
6714
7177
|
}
|
|
6715
7178
|
function resolveRelativeModule(fromFile, spec) {
|
|
6716
7179
|
if (!spec.startsWith(".")) return void 0;
|
|
6717
|
-
const dir =
|
|
7180
|
+
const dir = dirname8(fromFile);
|
|
6718
7181
|
const base = resolvePath(dir, spec);
|
|
6719
7182
|
const candidates = [];
|
|
6720
7183
|
const jsLike = /\.(js|jsx|mjs|cjs)$/.exec(base);
|
|
@@ -6724,10 +7187,10 @@ function resolveRelativeModule(fromFile, spec) {
|
|
|
6724
7187
|
}
|
|
6725
7188
|
candidates.push(base);
|
|
6726
7189
|
for (const e of RESOLVE_EXTS) candidates.push(base + e);
|
|
6727
|
-
for (const e of RESOLVE_EXTS) candidates.push(
|
|
7190
|
+
for (const e of RESOLVE_EXTS) candidates.push(join18(base, "index" + e));
|
|
6728
7191
|
for (const c of candidates) {
|
|
6729
7192
|
try {
|
|
6730
|
-
if (
|
|
7193
|
+
if (statSync7(c).isFile()) return c;
|
|
6731
7194
|
} catch {
|
|
6732
7195
|
}
|
|
6733
7196
|
}
|
|
@@ -6753,8 +7216,8 @@ function collectCallees(sf, ts) {
|
|
|
6753
7216
|
}
|
|
6754
7217
|
|
|
6755
7218
|
// src/map/coordinates.ts
|
|
6756
|
-
function runtimeCoordinate(source,
|
|
6757
|
-
if (/\[\d*\]/.test(
|
|
7219
|
+
function runtimeCoordinate(source, path18) {
|
|
7220
|
+
if (/\[\d*\]/.test(path18)) {
|
|
6758
7221
|
return { runtimeParameter: null, runtimeParameterReason: "array traversal: needs an array_key_value rule, not a dotted parameter" };
|
|
6759
7222
|
}
|
|
6760
7223
|
switch (source) {
|
|
@@ -6763,15 +7226,15 @@ function runtimeCoordinate(source, path17) {
|
|
|
6763
7226
|
case "multipart":
|
|
6764
7227
|
case "body":
|
|
6765
7228
|
case "server-fn-data":
|
|
6766
|
-
return { runtimeParameter: `post.${
|
|
7229
|
+
return { runtimeParameter: `post.${path18}` };
|
|
6767
7230
|
case "query":
|
|
6768
|
-
return { runtimeParameter: `get.${
|
|
7231
|
+
return { runtimeParameter: `get.${path18}` };
|
|
6769
7232
|
case "cookie":
|
|
6770
|
-
return { runtimeParameter: `cookie.${
|
|
7233
|
+
return { runtimeParameter: `cookie.${path18}` };
|
|
6771
7234
|
case "file":
|
|
6772
|
-
return { runtimeParameter: `files.${
|
|
7235
|
+
return { runtimeParameter: `files.${path18}` };
|
|
6773
7236
|
case "header":
|
|
6774
|
-
return { runtimeParameter: `server.HTTP_${
|
|
7237
|
+
return { runtimeParameter: `server.HTTP_${path18.toUpperCase().replace(/-/g, "_")}` };
|
|
6775
7238
|
case "route-param":
|
|
6776
7239
|
return { runtimeParameter: null, runtimeParameterReason: "route parameters are not exposed by the runtime resolver" };
|
|
6777
7240
|
default:
|
|
@@ -6803,8 +7266,8 @@ function addressSpaceOf(source) {
|
|
|
6803
7266
|
function isProvenFlow(confidence) {
|
|
6804
7267
|
return confidence === "exact-local" || confidence === "transformed-local";
|
|
6805
7268
|
}
|
|
6806
|
-
function inputIdOf(source,
|
|
6807
|
-
return `${addressSpaceOf(source)}:${
|
|
7269
|
+
function inputIdOf(source, path18) {
|
|
7270
|
+
return `${addressSpaceOf(source)}:${path18}`;
|
|
6808
7271
|
}
|
|
6809
7272
|
function withCoordinates(fields, source) {
|
|
6810
7273
|
return fields.map((f) => {
|
|
@@ -6996,6 +7459,7 @@ function requestMemberAccesses(params, body, ts, opts = {}) {
|
|
|
6996
7459
|
}
|
|
6997
7460
|
|
|
6998
7461
|
// src/map/flows.ts
|
|
7462
|
+
var MAX_DEPENDENCY_INPUT_FLOWS_PER_ENDPOINT = 100;
|
|
6999
7463
|
function spaceOfKey(key) {
|
|
7000
7464
|
if (key === "query") return "get";
|
|
7001
7465
|
if (key === "params") return "route-param";
|
|
@@ -7006,16 +7470,23 @@ function spaceOfKey(key) {
|
|
|
7006
7470
|
if (key === "files") return "files";
|
|
7007
7471
|
return void 0;
|
|
7008
7472
|
}
|
|
7009
|
-
function linkedFlows(body, params, inputs, sinks, ts) {
|
|
7010
|
-
const { flows, limitations } = linkFlows(body, params, inputs, sinks, ts);
|
|
7011
|
-
return
|
|
7473
|
+
function linkedFlows(body, params, inputs, sinks, ts, invocations = []) {
|
|
7474
|
+
const { flows, limitations, dependencyInputFlows, dependencyInputFlowsTruncated } = linkFlows(body, params, inputs, sinks, ts, invocations);
|
|
7475
|
+
return {
|
|
7476
|
+
flows,
|
|
7477
|
+
...limitations.length > 0 ? { limitations } : {},
|
|
7478
|
+
...dependencyInputFlows.length > 0 ? { dependencyInputFlows } : {},
|
|
7479
|
+
...dependencyInputFlowsTruncated ? { dependencyInputFlowsTruncated: true } : {}
|
|
7480
|
+
};
|
|
7012
7481
|
}
|
|
7013
|
-
function linkFlows(bodyNode, params, inputs, sinks, ts) {
|
|
7014
|
-
if (!bodyNode ||
|
|
7482
|
+
function linkFlows(bodyNode, params, inputs, sinks, ts, invocations) {
|
|
7483
|
+
if (!bodyNode || inputs.length === 0 || sinks.length === 0 && invocations.length === 0) {
|
|
7484
|
+
return { flows: [], limitations: [], dependencyInputFlows: [], dependencyInputFlowsTruncated: false };
|
|
7485
|
+
}
|
|
7015
7486
|
const CONTAINER_KEYS = /* @__PURE__ */ new Set([...REQ_SOURCES, "data", "input", "payload"]);
|
|
7016
7487
|
const rootPath = /* @__PURE__ */ new Map();
|
|
7017
|
-
const addRoot = (name,
|
|
7018
|
-
if (!rootPath.has(name)) rootPath.set(name, { path:
|
|
7488
|
+
const addRoot = (name, path18, space) => {
|
|
7489
|
+
if (!rootPath.has(name)) rootPath.set(name, { path: path18, space });
|
|
7019
7490
|
};
|
|
7020
7491
|
for (const p of params ?? []) {
|
|
7021
7492
|
if (!p?.name) continue;
|
|
@@ -7146,7 +7617,57 @@ function linkFlows(bodyNode, params, inputs, sinks, ts) {
|
|
|
7146
7617
|
}
|
|
7147
7618
|
for (const l of sinkLimits) allLimits.push(l);
|
|
7148
7619
|
}
|
|
7149
|
-
|
|
7620
|
+
const dependencyInputFlows = [];
|
|
7621
|
+
let dependencyInputFlowsTruncated = false;
|
|
7622
|
+
const seenDependencyInputs = /* @__PURE__ */ new Set();
|
|
7623
|
+
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}`));
|
|
7624
|
+
for (const invocation of invocations) {
|
|
7625
|
+
if (invocation.package.startsWith("node:")) continue;
|
|
7626
|
+
for (const site of invocation.sites) {
|
|
7627
|
+
if (site.start === void 0 || site.end === void 0) continue;
|
|
7628
|
+
const span = `${site.start}:${site.end}`;
|
|
7629
|
+
if (modeledCalls.has(span)) continue;
|
|
7630
|
+
const call = callBySpan.get(span);
|
|
7631
|
+
if (!call) continue;
|
|
7632
|
+
const args = call.arguments ?? [];
|
|
7633
|
+
for (let argumentIndex = 0; argumentIndex < args.length; argumentIndex++) {
|
|
7634
|
+
const argument = args[argumentIndex];
|
|
7635
|
+
if (sinkArgumentLimitations(argument, ts, rootPath).length > 0) continue;
|
|
7636
|
+
const whole = pathFromTainted(argument, ts, rootPath);
|
|
7637
|
+
for (const read2 of taintedReadPaths(argument, ts, rootPath, false)) {
|
|
7638
|
+
if (read2.space === void 0) continue;
|
|
7639
|
+
for (const input of inputs) {
|
|
7640
|
+
if (addressSpaceOf(input.source) !== read2.space) continue;
|
|
7641
|
+
const inputPath = normalizePath(input.name);
|
|
7642
|
+
if (read2.path !== inputPath && !read2.path.startsWith(inputPath + ".")) continue;
|
|
7643
|
+
const key = `${span}:${argumentIndex}:${input.id}:${invocation.package}:${invocation.symbol}`;
|
|
7644
|
+
if (seenDependencyInputs.has(key)) continue;
|
|
7645
|
+
seenDependencyInputs.add(key);
|
|
7646
|
+
if (dependencyInputFlows.length >= MAX_DEPENDENCY_INPUT_FLOWS_PER_ENDPOINT) {
|
|
7647
|
+
dependencyInputFlowsTruncated = true;
|
|
7648
|
+
continue;
|
|
7649
|
+
}
|
|
7650
|
+
dependencyInputFlows.push({
|
|
7651
|
+
input: input.name,
|
|
7652
|
+
inputId: input.id,
|
|
7653
|
+
package: invocation.package,
|
|
7654
|
+
specifier: invocation.specifiers[0],
|
|
7655
|
+
api: invocation.api,
|
|
7656
|
+
symbol: invocation.symbol,
|
|
7657
|
+
kind: invocation.kind,
|
|
7658
|
+
resolution: invocation.resolution,
|
|
7659
|
+
argumentIndex,
|
|
7660
|
+
argumentUse: whole?.path === read2.path && whole.space === read2.space ? "direct" : "within-expression",
|
|
7661
|
+
line: site.line,
|
|
7662
|
+
start: site.start,
|
|
7663
|
+
end: site.end
|
|
7664
|
+
});
|
|
7665
|
+
}
|
|
7666
|
+
}
|
|
7667
|
+
}
|
|
7668
|
+
}
|
|
7669
|
+
}
|
|
7670
|
+
return { flows, limitations: dedupeLimitations(allLimits), dependencyInputFlows, dependencyInputFlowsTruncated };
|
|
7150
7671
|
}
|
|
7151
7672
|
function dedupeLimitations(list) {
|
|
7152
7673
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7160,8 +7681,8 @@ function dedupeLimitations(list) {
|
|
|
7160
7681
|
function join22(base, seg) {
|
|
7161
7682
|
return base ? `${base}.${seg}` : seg;
|
|
7162
7683
|
}
|
|
7163
|
-
function normalizePath(
|
|
7164
|
-
return
|
|
7684
|
+
function normalizePath(path18) {
|
|
7685
|
+
return path18.replace(/\[\d*\]/g, "").split(".").filter(Boolean).join(".");
|
|
7165
7686
|
}
|
|
7166
7687
|
function fluentChainCalls(call, ts) {
|
|
7167
7688
|
let root = call;
|
|
@@ -7192,7 +7713,7 @@ function fluentChainCalls(call, ts) {
|
|
|
7192
7713
|
collect(root);
|
|
7193
7714
|
return out;
|
|
7194
7715
|
}
|
|
7195
|
-
function taintedReadPaths(node, ts, rootPath) {
|
|
7716
|
+
function taintedReadPaths(node, ts, rootPath, includeDeferredBodies = true) {
|
|
7196
7717
|
const out = [];
|
|
7197
7718
|
const seen = /* @__PURE__ */ new Set();
|
|
7198
7719
|
const add = (r) => {
|
|
@@ -7204,6 +7725,7 @@ function taintedReadPaths(node, ts, rootPath) {
|
|
|
7204
7725
|
};
|
|
7205
7726
|
const visit = (n) => {
|
|
7206
7727
|
if (!n) return;
|
|
7728
|
+
if (!includeDeferredBodies && (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n))) return;
|
|
7207
7729
|
if (ts.isPropertyAccessExpression(n) || ts.isElementAccessExpression(n)) {
|
|
7208
7730
|
const read2 = pathFromTainted(n, ts, rootPath);
|
|
7209
7731
|
if (read2 !== void 0) {
|
|
@@ -7286,6 +7808,120 @@ function pathFromTainted(node, ts, rootPath) {
|
|
|
7286
7808
|
return { path: normalizePath([base.path, ...segs].filter(Boolean).join(".")), space };
|
|
7287
7809
|
}
|
|
7288
7810
|
|
|
7811
|
+
// src/map/invocations.ts
|
|
7812
|
+
var MAX_SITES = 3;
|
|
7813
|
+
function collectInvocations(sf, ts, bindings, ctx, root = sf) {
|
|
7814
|
+
const found = [];
|
|
7815
|
+
const counts = { total: 0, dependency: 0, local: 0, ambiguous: 0 };
|
|
7816
|
+
const isLocalName = (name, node) => bindings.locals.has(name) || isShadowedByEnclosingBinding(node, name, ts);
|
|
7817
|
+
const lineOf2 = (node) => {
|
|
7818
|
+
try {
|
|
7819
|
+
return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
7820
|
+
} catch {
|
|
7821
|
+
return void 0;
|
|
7822
|
+
}
|
|
7823
|
+
};
|
|
7824
|
+
const traceRoot = (root2) => {
|
|
7825
|
+
const specifier = bindings.resolve(root2);
|
|
7826
|
+
if (specifier === void 0) return null;
|
|
7827
|
+
const direct = npmPackageOf(specifier);
|
|
7828
|
+
if (direct !== void 0) {
|
|
7829
|
+
const resolution = bindings.originOf(root2) ?? "direct";
|
|
7830
|
+
return { pkg: direct, specifier, resolution, named: resolution === "direct" };
|
|
7831
|
+
}
|
|
7832
|
+
const exportName = bindings.exportNameOf(root2) ?? root2;
|
|
7833
|
+
const viaModule = ctx.graph.importedBinding(ctx.file, specifier, exportName);
|
|
7834
|
+
if (viaModule !== void 0) {
|
|
7835
|
+
return {
|
|
7836
|
+
pkg: viaModule.package,
|
|
7837
|
+
specifier,
|
|
7838
|
+
resolution: "reexport",
|
|
7839
|
+
named: viaModule.origin === "direct",
|
|
7840
|
+
apiName: viaModule.name
|
|
7841
|
+
};
|
|
7842
|
+
}
|
|
7843
|
+
return null;
|
|
7844
|
+
};
|
|
7845
|
+
const push = (traced, api, receiver, kind, node) => {
|
|
7846
|
+
const span = spanOf(node);
|
|
7847
|
+
found.push({
|
|
7848
|
+
package: traced.pkg,
|
|
7849
|
+
specifiers: [traced.specifier],
|
|
7850
|
+
api,
|
|
7851
|
+
...receiver !== void 0 ? { receiver } : {},
|
|
7852
|
+
symbol: receiver !== void 0 ? `${receiver}.${api}` : api,
|
|
7853
|
+
kind,
|
|
7854
|
+
// Only ever `import`: a global has no package to correlate against, and an inferred package is not
|
|
7855
|
+
// evidence about the value being called.
|
|
7856
|
+
attribution: "import",
|
|
7857
|
+
resolution: traced.resolution,
|
|
7858
|
+
callCount: 1,
|
|
7859
|
+
sites: [{ file: ctx.owner, line: lineOf2(node), start: span.start, end: span.end }]
|
|
7860
|
+
});
|
|
7861
|
+
};
|
|
7862
|
+
const visit = (node) => {
|
|
7863
|
+
if (root !== sf && node !== root && isUninvokedFunctionDeclaration(node, ts)) return;
|
|
7864
|
+
if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
|
|
7865
|
+
const callee = node.expression;
|
|
7866
|
+
counts.total++;
|
|
7867
|
+
if (ts.isIdentifier(callee)) {
|
|
7868
|
+
const traced = traceRoot(callee.text);
|
|
7869
|
+
if (traced?.named) {
|
|
7870
|
+
const api = traced.apiName ?? bindings.exportNameOf(callee.text) ?? callee.text;
|
|
7871
|
+
push(traced, api, void 0, ts.isNewExpression(node) ? "construct" : "call", node);
|
|
7872
|
+
counts.dependency++;
|
|
7873
|
+
} else if (traced) {
|
|
7874
|
+
counts.ambiguous++;
|
|
7875
|
+
} else if (isLocalName(callee.text, node)) {
|
|
7876
|
+
counts.local++;
|
|
7877
|
+
} else {
|
|
7878
|
+
counts.ambiguous++;
|
|
7879
|
+
}
|
|
7880
|
+
} else if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name)) {
|
|
7881
|
+
const root2 = rootIdentifier(callee.expression, ts);
|
|
7882
|
+
const traced = root2 !== void 0 ? traceRoot(root2) : null;
|
|
7883
|
+
if (traced && root2 !== void 0) {
|
|
7884
|
+
counts.dependency++;
|
|
7885
|
+
const receiver = traced.resolution === "direct" && bindings.isPackageMember(root2) ? bindings.exportNameOf(root2) ?? root2 : void 0;
|
|
7886
|
+
push(traced, callee.name.text, receiver, "member", node);
|
|
7887
|
+
} else if (root2 !== void 0 && ts.isIdentifier(callee.expression) && isLocalName(root2, node)) {
|
|
7888
|
+
counts.local++;
|
|
7889
|
+
} else {
|
|
7890
|
+
counts.ambiguous++;
|
|
7891
|
+
}
|
|
7892
|
+
} else {
|
|
7893
|
+
counts.ambiguous++;
|
|
7894
|
+
}
|
|
7895
|
+
}
|
|
7896
|
+
ts.forEachChild(node, visit);
|
|
7897
|
+
};
|
|
7898
|
+
visit(root);
|
|
7899
|
+
return { invocations: found, counts };
|
|
7900
|
+
}
|
|
7901
|
+
function createInvocationInventory() {
|
|
7902
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
7903
|
+
return {
|
|
7904
|
+
add(invocations) {
|
|
7905
|
+
for (const invocation of invocations) {
|
|
7906
|
+
const key = `${invocation.package}|${invocation.symbol}|${invocation.kind}|${invocation.resolution}`;
|
|
7907
|
+
const existing = byKey.get(key);
|
|
7908
|
+
if (existing === void 0) {
|
|
7909
|
+
byKey.set(key, { ...invocation });
|
|
7910
|
+
continue;
|
|
7911
|
+
}
|
|
7912
|
+
existing.callCount += invocation.callCount;
|
|
7913
|
+
for (const specifier of invocation.specifiers) {
|
|
7914
|
+
if (!existing.specifiers.includes(specifier)) existing.specifiers.push(specifier);
|
|
7915
|
+
}
|
|
7916
|
+
if (existing.sites.length < MAX_SITES) existing.sites.push(...invocation.sites.slice(0, MAX_SITES - existing.sites.length));
|
|
7917
|
+
}
|
|
7918
|
+
},
|
|
7919
|
+
list() {
|
|
7920
|
+
return [...byKey.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([, invocation]) => ({ ...invocation, specifiers: [...invocation.specifiers].sort() }));
|
|
7921
|
+
}
|
|
7922
|
+
};
|
|
7923
|
+
}
|
|
7924
|
+
|
|
7289
7925
|
// src/map/entries.ts
|
|
7290
7926
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
7291
7927
|
function extractFromFile(sf, ts, localSinks, bindings, ctx) {
|
|
@@ -7309,7 +7945,7 @@ function extractFromFile(sf, ts, localSinks, bindings, ctx) {
|
|
|
7309
7945
|
...spanOf(decl),
|
|
7310
7946
|
inputs,
|
|
7311
7947
|
sinks,
|
|
7312
|
-
...linkedFlows(handlerBody, handlerFn?.parameters, inputs, sinks, ts)
|
|
7948
|
+
...linkedFlows(handlerBody, handlerFn?.parameters, inputs, sinks, ts, handlerInvocations(handlerBody, ts, bindings, ctx))
|
|
7313
7949
|
};
|
|
7314
7950
|
if (validatorCall && inputs.length === 0) ep.inputsResolved = false;
|
|
7315
7951
|
out.push(ep);
|
|
@@ -7384,6 +8020,10 @@ function hasUseServerDirective(fn, ts) {
|
|
|
7384
8020
|
const first = fn.body?.statements?.[0];
|
|
7385
8021
|
return Boolean(first && ts.isExpressionStatement(first) && ts.isStringLiteralLike(first.expression) && first.expression.text === "use server");
|
|
7386
8022
|
}
|
|
8023
|
+
function handlerInvocations(body, ts, bindings, ctx) {
|
|
8024
|
+
const sf = body?.getSourceFile?.();
|
|
8025
|
+
return sf ? collectInvocations(sf, ts, bindings, ctx, body).invocations : [];
|
|
8026
|
+
}
|
|
7387
8027
|
function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, ctx, extra = {}) {
|
|
7388
8028
|
const entryKind = kindLabel === "route-registration" || kindLabel === "server-action" || kindLabel === "edge-function" ? kindLabel : "route-handler";
|
|
7389
8029
|
const payloadStyle = kindLabel === "server-action";
|
|
@@ -7402,14 +8042,14 @@ function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, c
|
|
|
7402
8042
|
end: extra.end,
|
|
7403
8043
|
inputs,
|
|
7404
8044
|
sinks,
|
|
7405
|
-
...linkedFlows(body, params, inputs, sinks, ts)
|
|
8045
|
+
...linkedFlows(body, params, inputs, sinks, ts, handlerInvocations(body, ts, bindings, ctx))
|
|
7406
8046
|
};
|
|
7407
8047
|
}
|
|
7408
8048
|
|
|
7409
8049
|
// src/map/imports.ts
|
|
7410
|
-
import { readFileSync as
|
|
7411
|
-
import { join as
|
|
7412
|
-
var
|
|
8050
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
8051
|
+
import { join as join19 } from "path";
|
|
8052
|
+
var MAX_SITES2 = 5;
|
|
7413
8053
|
var NPM_NAME = /^(?:@[^/@\s~][^/@\s]*\/)?[^/@\s.~][^/@\s]*$/;
|
|
7414
8054
|
function isNpmPackageName(pkg) {
|
|
7415
8055
|
return pkg.startsWith("node:") || NPM_NAME.test(pkg);
|
|
@@ -7419,7 +8059,7 @@ function readPathAliases(cwd) {
|
|
|
7419
8059
|
for (const name of ["tsconfig.json", "jsconfig.json"]) {
|
|
7420
8060
|
let paths;
|
|
7421
8061
|
try {
|
|
7422
|
-
const raw =
|
|
8062
|
+
const raw = readFileSync15(join19(cwd, name), "utf8");
|
|
7423
8063
|
const parsed = JSON.parse(stripJsonComments(raw));
|
|
7424
8064
|
paths = parsed.compilerOptions?.paths;
|
|
7425
8065
|
} catch {
|
|
@@ -7623,7 +8263,7 @@ function createImportInventory(aliases = []) {
|
|
|
7623
8263
|
for (const n of edge.names) acc.names.add(n);
|
|
7624
8264
|
if (!parsed) acc.namesComplete = false;
|
|
7625
8265
|
acc.siteCount++;
|
|
7626
|
-
if (acc.sites.length <
|
|
8266
|
+
if (acc.sites.length < MAX_SITES2) acc.sites.push({ file: relFile, line: edge.line });
|
|
7627
8267
|
}
|
|
7628
8268
|
},
|
|
7629
8269
|
list() {
|
|
@@ -7659,127 +8299,15 @@ function lineAt(starts, pos) {
|
|
|
7659
8299
|
return lo + 1;
|
|
7660
8300
|
}
|
|
7661
8301
|
|
|
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
8302
|
// src/map/extract.ts
|
|
8303
|
+
var MAX_DEPENDENCY_INPUT_FLOWS_PER_MAP = 500;
|
|
7776
8304
|
async function extractInputMap(cwd, ts, options = {}) {
|
|
7777
8305
|
const notes = [];
|
|
7778
8306
|
const endpoints = [];
|
|
7779
8307
|
const failed = [];
|
|
7780
8308
|
let boundary = cwd;
|
|
7781
8309
|
try {
|
|
7782
|
-
boundary =
|
|
8310
|
+
boundary = realpathSync6(cwd);
|
|
7783
8311
|
} catch {
|
|
7784
8312
|
}
|
|
7785
8313
|
const graph = createModuleGraph(ts, { cwd, boundary, followOutside: options.followSymlinks });
|
|
@@ -7787,6 +8315,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7787
8315
|
const files = collectSources(cwd, boundary, { followOutside: options.followSymlinks }, [], /* @__PURE__ */ new Set(), stats);
|
|
7788
8316
|
const imports = createImportInventory(readPathAliases(cwd));
|
|
7789
8317
|
const invocations = createInvocationInventory();
|
|
8318
|
+
let dependencyInputFlowCount = 0;
|
|
7790
8319
|
let parsed = 0;
|
|
7791
8320
|
let preFiltered = 0;
|
|
7792
8321
|
let importScanFailures = 0;
|
|
@@ -7798,8 +8327,8 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7798
8327
|
const hopTargets = /* @__PURE__ */ new Set();
|
|
7799
8328
|
for (const file of files) {
|
|
7800
8329
|
try {
|
|
7801
|
-
const text =
|
|
7802
|
-
const relFile =
|
|
8330
|
+
const text = readFileSync16(file, "utf8");
|
|
8331
|
+
const relFile = relative6(cwd, file);
|
|
7803
8332
|
sourceBytes += text.length;
|
|
7804
8333
|
if (!hasEntrySignal(text)) {
|
|
7805
8334
|
preFiltered++;
|
|
@@ -7830,6 +8359,13 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7830
8359
|
calls.ambiguous += called.counts.ambiguous;
|
|
7831
8360
|
const localSinks = collectLocalSinks(sf, ts, bindings, ctx);
|
|
7832
8361
|
for (const ep of extractFromFile(sf, ts, localSinks, bindings, ctx)) {
|
|
8362
|
+
const links = ep.dependencyInputFlows ?? [];
|
|
8363
|
+
const remaining = Math.max(0, MAX_DEPENDENCY_INPUT_FLOWS_PER_MAP - dependencyInputFlowCount);
|
|
8364
|
+
if (links.length > remaining) {
|
|
8365
|
+
ep.dependencyInputFlows = links.slice(0, remaining);
|
|
8366
|
+
ep.dependencyInputFlowsTruncated = true;
|
|
8367
|
+
}
|
|
8368
|
+
dependencyInputFlowCount += ep.dependencyInputFlows?.length ?? 0;
|
|
7833
8369
|
if (ep.route === void 0 && ep.entryKind === "edge-function") {
|
|
7834
8370
|
const fn = functionNameFromPath(relFile);
|
|
7835
8371
|
if (fn) ep.route = "/" + fn;
|
|
@@ -7845,7 +8381,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7845
8381
|
}
|
|
7846
8382
|
} catch (e) {
|
|
7847
8383
|
if (typeof process !== "undefined" && process.env?.PS_MAP_DEBUG) console.error("[patchstack] map error", file, e);
|
|
7848
|
-
failed.push(
|
|
8384
|
+
failed.push(relative6(cwd, file));
|
|
7849
8385
|
}
|
|
7850
8386
|
}
|
|
7851
8387
|
let hopParsed = 0;
|
|
@@ -7853,10 +8389,10 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7853
8389
|
for (const target of hopTargets) {
|
|
7854
8390
|
if (entryFiles.has(target)) continue;
|
|
7855
8391
|
try {
|
|
7856
|
-
const text =
|
|
8392
|
+
const text = readFileSync16(target, "utf8");
|
|
7857
8393
|
const sf = ts.createSourceFile(target, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, target));
|
|
7858
8394
|
const bindings = buildModuleBindings(sf, ts);
|
|
7859
|
-
const called = collectInvocations(sf, ts, bindings, { file: target, owner:
|
|
8395
|
+
const called = collectInvocations(sf, ts, bindings, { file: target, owner: relative6(cwd, target), graph });
|
|
7860
8396
|
invocations.add(called.invocations);
|
|
7861
8397
|
calls.total += called.counts.total;
|
|
7862
8398
|
calls.dependency += called.counts.dependency;
|
|
@@ -7870,6 +8406,9 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7870
8406
|
}
|
|
7871
8407
|
notes.push("Static analysis is best-effort \u2014 this is the DETECTED surface, not a proof of completeness.");
|
|
7872
8408
|
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.');
|
|
8409
|
+
if (dependencyInputFlowCount > 0) {
|
|
8410
|
+
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.");
|
|
8411
|
+
}
|
|
7873
8412
|
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
8413
|
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
8414
|
if (!options.followSymlinks) notes.push("Symlinks leaving the project directory were not followed (use --follow-symlinks to include them).");
|
|
@@ -7878,6 +8417,10 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7878
8417
|
notes.push(`${failed.length} file(s) could not be analyzed and were skipped (fail-open): ${sample}${failed.length > 5 ? ", \u2026" : ""}.`);
|
|
7879
8418
|
}
|
|
7880
8419
|
const unresolved = endpoints.filter((e) => e.inputsResolved === false).length;
|
|
8420
|
+
const truncatedDependencyFlows = endpoints.filter((e) => e.dependencyInputFlowsTruncated).length;
|
|
8421
|
+
if (truncatedDependencyFlows > 0) {
|
|
8422
|
+
notes.push(`${truncatedDependencyFlows} endpoint(s) had more dependency-input links than the bounded map carries; those endpoint records are marked dependencyInputFlowsTruncated.`);
|
|
8423
|
+
}
|
|
7881
8424
|
if (unresolved > 0) {
|
|
7882
8425
|
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
8426
|
}
|
|
@@ -7888,7 +8431,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7888
8431
|
if (endpoints.length === 0) notes.push("No recognized server-side entry points found under the analyzed roots.");
|
|
7889
8432
|
const importList = imports.list();
|
|
7890
8433
|
const unmodelled = importList.filter((d) => d.recognizedSinkKinds.length === 0).length;
|
|
7891
|
-
const skippedDirsWithSource = stats.skippedWithSource.map((d) =>
|
|
8434
|
+
const skippedDirsWithSource = stats.skippedWithSource.map((d) => relative6(cwd, d));
|
|
7892
8435
|
const importCoverageGaps = {
|
|
7893
8436
|
unreadableFiles: failed.length,
|
|
7894
8437
|
unscannableFiles: importScanFailures,
|
|
@@ -8002,8 +8545,8 @@ function getStringFlag(flags, name) {
|
|
|
8002
8545
|
}
|
|
8003
8546
|
|
|
8004
8547
|
// src/build-stamp.ts
|
|
8005
|
-
import {
|
|
8006
|
-
import { join as
|
|
8548
|
+
import { lstatSync as lstatSync4, readFileSync as readFileSync17, readdirSync as readdirSync6, realpathSync as realpathSync7 } from "fs";
|
|
8549
|
+
import { join as join20, relative as relative7, sep as sep3 } from "path";
|
|
8007
8550
|
var RULES_FILE_NAMES = /* @__PURE__ */ new Set(["rules.json", "patchstack.rules.json"]);
|
|
8008
8551
|
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "coverage", "tmp", "vendor"]);
|
|
8009
8552
|
var MAX_DEPTH2 = 6;
|
|
@@ -8021,11 +8564,11 @@ function guardImportsRulesFile(dir, rulesName) {
|
|
|
8021
8564
|
}
|
|
8022
8565
|
for (const name of siblings) {
|
|
8023
8566
|
if (!GUARD_SOURCE.test(name)) continue;
|
|
8024
|
-
const
|
|
8567
|
+
const path18 = join20(dir, name);
|
|
8025
8568
|
try {
|
|
8026
|
-
const entry =
|
|
8569
|
+
const entry = lstatSync4(path18);
|
|
8027
8570
|
if (!entry.isFile() || entry.isSymbolicLink() || entry.size > MAX_GUARD_SOURCE_BYTES) continue;
|
|
8028
|
-
const source = stripComments(
|
|
8571
|
+
const source = stripComments(readFileSync17(path18, "utf8"));
|
|
8029
8572
|
if (source.includes("@patchstack/connect/protect") && importsRules.test(source)) return true;
|
|
8030
8573
|
} catch {
|
|
8031
8574
|
}
|
|
@@ -8042,14 +8585,14 @@ function findRulesFile(cwd) {
|
|
|
8042
8585
|
const unreadable = [];
|
|
8043
8586
|
let root;
|
|
8044
8587
|
try {
|
|
8045
|
-
root =
|
|
8588
|
+
root = realpathSync7(cwd);
|
|
8046
8589
|
} catch {
|
|
8047
8590
|
return { kind: "none" };
|
|
8048
8591
|
}
|
|
8049
|
-
const contained = (
|
|
8592
|
+
const contained = (path18) => {
|
|
8050
8593
|
try {
|
|
8051
|
-
const real =
|
|
8052
|
-
return real === root || real.startsWith(root +
|
|
8594
|
+
const real = realpathSync7(path18);
|
|
8595
|
+
return real === root || real.startsWith(root + sep3);
|
|
8053
8596
|
} catch {
|
|
8054
8597
|
return false;
|
|
8055
8598
|
}
|
|
@@ -8064,32 +8607,32 @@ function findRulesFile(cwd) {
|
|
|
8064
8607
|
}
|
|
8065
8608
|
for (const name of entries) {
|
|
8066
8609
|
if (name.startsWith(".")) continue;
|
|
8067
|
-
const
|
|
8610
|
+
const path18 = join20(dir, name);
|
|
8068
8611
|
let entry;
|
|
8069
8612
|
try {
|
|
8070
|
-
entry =
|
|
8613
|
+
entry = lstatSync4(path18);
|
|
8071
8614
|
} catch {
|
|
8072
8615
|
continue;
|
|
8073
8616
|
}
|
|
8074
8617
|
if (entry.isSymbolicLink()) continue;
|
|
8075
8618
|
if (entry.isDirectory()) {
|
|
8076
|
-
if (!SKIP_DIRECTORIES.has(name)) walk2(
|
|
8619
|
+
if (!SKIP_DIRECTORIES.has(name)) walk2(path18, depth + 1);
|
|
8077
8620
|
continue;
|
|
8078
8621
|
}
|
|
8079
8622
|
if (!entry.isFile() || !RULES_FILE_NAMES.has(name)) continue;
|
|
8080
|
-
if (!contained(
|
|
8623
|
+
if (!contained(path18)) continue;
|
|
8081
8624
|
if (!guardImportsRulesFile(dir, name)) continue;
|
|
8082
8625
|
try {
|
|
8083
|
-
if (isRuleBundle(JSON.parse(
|
|
8626
|
+
if (isRuleBundle(JSON.parse(readFileSync17(path18, "utf8")))) found.push(path18);
|
|
8084
8627
|
} catch {
|
|
8085
|
-
unreadable.push(
|
|
8628
|
+
unreadable.push(path18);
|
|
8086
8629
|
}
|
|
8087
8630
|
}
|
|
8088
8631
|
};
|
|
8089
8632
|
walk2(cwd, 0);
|
|
8090
|
-
if (found.length > 1) return { kind: "ambiguous", paths: found.map((p) =>
|
|
8633
|
+
if (found.length > 1) return { kind: "ambiguous", paths: found.map((p) => relative7(cwd, p)) };
|
|
8091
8634
|
if (found.length === 1) return { kind: "one", path: found[0] };
|
|
8092
|
-
if (unreadable.length > 0) return { kind: "unreadable", paths: unreadable.map((p) =>
|
|
8635
|
+
if (unreadable.length > 0) return { kind: "unreadable", paths: unreadable.map((p) => relative7(cwd, p)) };
|
|
8093
8636
|
return { kind: "none" };
|
|
8094
8637
|
}
|
|
8095
8638
|
function detectIndent(text) {
|
|
@@ -8110,10 +8653,10 @@ function applyBuildStamp(cwd, id) {
|
|
|
8110
8653
|
reason: `more than one candidate guard rules file (${location.paths.join(", ")}) \u2014 none was stamped`
|
|
8111
8654
|
};
|
|
8112
8655
|
}
|
|
8113
|
-
const file =
|
|
8656
|
+
const file = relative7(cwd, location.path);
|
|
8114
8657
|
let text;
|
|
8115
8658
|
try {
|
|
8116
|
-
text =
|
|
8659
|
+
text = readFileSync17(location.path, "utf8");
|
|
8117
8660
|
} catch (err) {
|
|
8118
8661
|
return { kind: "skipped", reason: `${file} could not be read (${err.message})` };
|
|
8119
8662
|
}
|
|
@@ -8133,17 +8676,9 @@ function applyBuildStamp(cwd, id) {
|
|
|
8133
8676
|
if (Object.keys(namespace).length === 0) delete bundle[BUILD_STAMP_KEY];
|
|
8134
8677
|
else bundle[BUILD_STAMP_KEY] = namespace;
|
|
8135
8678
|
const serialised = JSON.stringify(bundle, null, detectIndent(text)) + (text.endsWith("\n") ? "\n" : "");
|
|
8136
|
-
const temporary = `${location.path}.patchstack-${process.pid}.tmp`;
|
|
8137
8679
|
try {
|
|
8138
|
-
|
|
8139
|
-
writeFileSync8(temporary, serialised, { encoding: "utf8", mode });
|
|
8140
|
-
chmodSync(temporary, mode);
|
|
8141
|
-
renameSync(temporary, location.path);
|
|
8680
|
+
writeProjectFileSync(cwd, location.path, serialised, { encoding: "utf8" });
|
|
8142
8681
|
} catch (err) {
|
|
8143
|
-
try {
|
|
8144
|
-
unlinkSync(temporary);
|
|
8145
|
-
} catch {
|
|
8146
|
-
}
|
|
8147
8682
|
return { kind: "skipped", reason: `${file} could not be written (${err.message})` };
|
|
8148
8683
|
}
|
|
8149
8684
|
return id === null ? { kind: "cleared", file } : { kind: "stamped", file, id };
|
|
@@ -8282,7 +8817,7 @@ async function runMap(flags) {
|
|
|
8282
8817
|
const json = JSON.stringify(map, null, 2);
|
|
8283
8818
|
const out = getStringFlag(flags, "out");
|
|
8284
8819
|
if (out) {
|
|
8285
|
-
|
|
8820
|
+
atomicWriteFileSync(path15.resolve(out), json, { encoding: "utf8" });
|
|
8286
8821
|
console.error(`patchstack: wrote ${out}`);
|
|
8287
8822
|
} else if (flags.get("upload") !== true) {
|
|
8288
8823
|
console.log(json);
|
|
@@ -8331,8 +8866,8 @@ async function runMap(flags) {
|
|
|
8331
8866
|
}
|
|
8332
8867
|
|
|
8333
8868
|
// src/setup.ts
|
|
8334
|
-
import { readFileSync as
|
|
8335
|
-
import
|
|
8869
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
8870
|
+
import path16 from "path";
|
|
8336
8871
|
var SCAN_COMMAND = "patchstack-connect scan";
|
|
8337
8872
|
var MARK_BUILD_COMMAND = "patchstack-connect mark-build";
|
|
8338
8873
|
function setupProtection(cwd) {
|
|
@@ -8357,8 +8892,8 @@ function prependHook(existing, command) {
|
|
|
8357
8892
|
return [command, ...remaining].join(" && ");
|
|
8358
8893
|
}
|
|
8359
8894
|
function wireBuildScripts(cwd, packageManager) {
|
|
8360
|
-
const target =
|
|
8361
|
-
const raw =
|
|
8895
|
+
const target = path16.join(cwd, "package.json");
|
|
8896
|
+
const raw = readFileSync18(target, "utf8");
|
|
8362
8897
|
const pkg = JSON.parse(raw);
|
|
8363
8898
|
const scripts = pkg.scripts ?? {};
|
|
8364
8899
|
const build = scripts.build;
|
|
@@ -8404,7 +8939,7 @@ function wireBuildScripts(cwd, packageManager) {
|
|
|
8404
8939
|
const indentMatch = raw.match(/^[\t ]+(?=")/m)?.[0];
|
|
8405
8940
|
const indent = indentMatch?.includes(" ") ? " " : indentMatch?.length ?? 2;
|
|
8406
8941
|
const trailingNewline = raw.endsWith("\n") ? "\n" : "";
|
|
8407
|
-
|
|
8942
|
+
writeProjectFileSync(cwd, target, `${JSON.stringify(pkg, null, indent)}${trailingNewline}`, { encoding: "utf8" });
|
|
8408
8943
|
if (build === void 0 || build.trim().length === 0) {
|
|
8409
8944
|
return {
|
|
8410
8945
|
changed: true,
|
|
@@ -9241,7 +9776,7 @@ async function runGuide(args) {
|
|
|
9241
9776
|
console.log("\u2014\u2014\u2014\u2014 Full reference guide (ships as AGENT-INSTALL.md) \u2014\u2014\u2014\u2014");
|
|
9242
9777
|
console.log("");
|
|
9243
9778
|
const guidePath = new URL("../AGENT-INSTALL.md", import.meta.url);
|
|
9244
|
-
console.log(
|
|
9779
|
+
console.log(readFileSync19(guidePath, "utf8"));
|
|
9245
9780
|
return 0;
|
|
9246
9781
|
}
|
|
9247
9782
|
async function runSetup(args) {
|
|
@@ -9416,7 +9951,7 @@ function describeStack(stack) {
|
|
|
9416
9951
|
}
|
|
9417
9952
|
function readBuildScript(cwd) {
|
|
9418
9953
|
try {
|
|
9419
|
-
const pkg = JSON.parse(
|
|
9954
|
+
const pkg = JSON.parse(readFileSync19(path17.join(cwd, "package.json"), "utf8"));
|
|
9420
9955
|
return pkg.scripts?.build;
|
|
9421
9956
|
} catch {
|
|
9422
9957
|
return void 0;
|
|
@@ -9496,7 +10031,7 @@ async function runMarkBuild(args) {
|
|
|
9496
10031
|
let marked = 0;
|
|
9497
10032
|
let widgetTouched = 0;
|
|
9498
10033
|
for (const file of files) {
|
|
9499
|
-
const before =
|
|
10034
|
+
const before = readFileSync19(file, "utf8");
|
|
9500
10035
|
let after = injectMarker(before, snippet);
|
|
9501
10036
|
if (widgetUuid !== null) {
|
|
9502
10037
|
const ensured = ensureWidgetInHtml(after, widgetUuid);
|
|
@@ -9506,7 +10041,7 @@ async function runMarkBuild(args) {
|
|
|
9506
10041
|
after = ensured.html;
|
|
9507
10042
|
}
|
|
9508
10043
|
if (after !== before) {
|
|
9509
|
-
|
|
10044
|
+
writeProjectFileSync(cwd, file, after, { encoding: "utf8" });
|
|
9510
10045
|
marked += 1;
|
|
9511
10046
|
}
|
|
9512
10047
|
}
|