@patchstack/connect 0.5.5 → 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/AGENT-INSTALL.md +1 -1
- package/README.md +1 -0
- package/dist/chunk-2K5SHFUM.js +295 -0
- package/dist/chunk-2K5SHFUM.js.map +1 -0
- package/dist/cli.js +1026 -481
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +368 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +23 -3
- package/dist/index.d.ts +23 -3
- package/dist/index.js +380 -39
- 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;
|
|
1531
|
+
}
|
|
1532
|
+
if (!isSafeOrigin(tokenUrl)) return null;
|
|
1533
|
+
const key = cacheKey(tokenUrl, config.pulseAuth);
|
|
1534
|
+
const existing = cached.get(key);
|
|
1535
|
+
if (existing !== void 0 && Date.now() < existing.expiresAt - TOKEN_SKEW_MS) {
|
|
1536
|
+
return existing.token;
|
|
1270
1537
|
}
|
|
1271
|
-
|
|
1538
|
+
const pending = inflight.get(key);
|
|
1539
|
+
if (pending !== void 0) return pending;
|
|
1272
1540
|
const credentials = parsePulseAuth(config.pulseAuth);
|
|
1273
1541
|
if (credentials === null) return null;
|
|
1274
|
-
|
|
1542
|
+
let exchange;
|
|
1543
|
+
exchange = (async () => {
|
|
1275
1544
|
try {
|
|
1276
|
-
const response = await fetchImpl(
|
|
1545
|
+
const response = await fetchImpl(tokenUrl, {
|
|
1277
1546
|
method: "POST",
|
|
1278
1547
|
headers: {
|
|
1279
1548
|
"Content-Type": "application/json",
|
|
@@ -1288,25 +1557,27 @@ async function getPulseToken(config, fetchImpl = fetch) {
|
|
|
1288
1557
|
signal: AbortSignal.timeout(config.timeoutMs)
|
|
1289
1558
|
});
|
|
1290
1559
|
if (!response.ok) return null;
|
|
1291
|
-
const body = await response
|
|
1292
|
-
if (
|
|
1560
|
+
const body = await readBoundedJson(response);
|
|
1561
|
+
if (!isHeaderValue(body.access_token)) return null;
|
|
1293
1562
|
const expiresIn = Number(body.expires_in);
|
|
1294
1563
|
const ttlMs = Number.isFinite(expiresIn) && expiresIn > 0 ? expiresIn * 1e3 : 36e5;
|
|
1295
|
-
cached
|
|
1564
|
+
cached.set(key, { token: body.access_token, expiresAt: Date.now() + ttlMs });
|
|
1296
1565
|
return body.access_token;
|
|
1297
1566
|
} catch {
|
|
1298
1567
|
return null;
|
|
1299
1568
|
} finally {
|
|
1300
|
-
inflight
|
|
1569
|
+
if (inflight.get(key) === exchange) inflight.delete(key);
|
|
1301
1570
|
}
|
|
1302
1571
|
})();
|
|
1303
|
-
|
|
1572
|
+
inflight.set(key, exchange);
|
|
1573
|
+
return exchange;
|
|
1304
1574
|
}
|
|
1305
1575
|
async function pulseAuthHeader(config, fetchImpl = fetch) {
|
|
1306
1576
|
const token = await getPulseToken(config, fetchImpl);
|
|
1307
1577
|
return token === null ? {} : { Authorization: `Bearer ${token}` };
|
|
1308
1578
|
}
|
|
1309
1579
|
async function pulseFetch(config, url, init, fetchImpl = fetch) {
|
|
1580
|
+
assertConnectableEndpoint(config, url);
|
|
1310
1581
|
const send = async () => {
|
|
1311
1582
|
const auth = await pulseAuthHeader(config, fetchImpl);
|
|
1312
1583
|
const response = await fetchImpl(url, {
|
|
@@ -1317,7 +1588,7 @@ async function pulseFetch(config, url, init, fetchImpl = fetch) {
|
|
|
1317
1588
|
};
|
|
1318
1589
|
const first = await send();
|
|
1319
1590
|
if (first.response.status === 401 && first.authenticated) {
|
|
1320
|
-
clearPulseToken();
|
|
1591
|
+
clearPulseToken(config);
|
|
1321
1592
|
return (await send()).response;
|
|
1322
1593
|
}
|
|
1323
1594
|
return first.response;
|
|
@@ -1349,12 +1620,13 @@ var DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest";
|
|
|
1349
1620
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
1350
1621
|
var CLAIM_TOKEN_HEADER = "X-Patchstack-Claim-Token";
|
|
1351
1622
|
function claimTokenHeader(config) {
|
|
1352
|
-
return
|
|
1623
|
+
return config.endpointTrusted !== false && isHeaderValue(config.claimToken) ? { [CLAIM_TOKEN_HEADER]: config.claimToken } : {};
|
|
1353
1624
|
}
|
|
1354
1625
|
function claimOutcomeLines(claim3, config) {
|
|
1355
1626
|
if (typeof config.claimToken !== "string" || config.claimToken === "") return [];
|
|
1356
1627
|
if (claim3?.state === "claimed" || claim3?.state === "owned-by-you") {
|
|
1357
|
-
const
|
|
1628
|
+
const dashboardUrl = safeRemoteUrl(claim3.dashboard_url);
|
|
1629
|
+
const dashboard = dashboardUrl === null ? [] : [`Dashboard: ${dashboardUrl}`];
|
|
1358
1630
|
return [
|
|
1359
1631
|
claim3.state === "claimed" ? "Connected to your Patchstack account." : "This site is already connected to your Patchstack account.",
|
|
1360
1632
|
...dashboard
|
|
@@ -1385,8 +1657,8 @@ function authFailureMessage(status, config) {
|
|
|
1385
1657
|
}
|
|
1386
1658
|
function buildRulesUrl(manifestEndpoint, siteUuid) {
|
|
1387
1659
|
const url = new URL(manifestEndpoint);
|
|
1388
|
-
const
|
|
1389
|
-
url.pathname =
|
|
1660
|
+
const path18 = url.pathname.replace(/\/$/, "");
|
|
1661
|
+
url.pathname = path18.endsWith("/manifest") ? `${path18.slice(0, -"/manifest".length)}/rules/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/rules/${encodeURIComponent(siteUuid)}`;
|
|
1390
1662
|
url.search = "";
|
|
1391
1663
|
url.hash = "";
|
|
1392
1664
|
return url.toString();
|
|
@@ -1401,16 +1673,16 @@ function buildSettingsUrl(endpoint, siteUuid) {
|
|
|
1401
1673
|
}
|
|
1402
1674
|
function buildPackageRemovedUrl(manifestEndpoint, siteUuid) {
|
|
1403
1675
|
const url = new URL(manifestEndpoint);
|
|
1404
|
-
const
|
|
1405
|
-
url.pathname =
|
|
1676
|
+
const path18 = url.pathname.replace(/\/$/, "");
|
|
1677
|
+
url.pathname = path18.endsWith("/manifest") ? `${path18.slice(0, -"/manifest".length)}/package-removed/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/package-removed/${encodeURIComponent(siteUuid)}`;
|
|
1406
1678
|
url.search = "";
|
|
1407
1679
|
url.hash = "";
|
|
1408
1680
|
return url.toString();
|
|
1409
1681
|
}
|
|
1410
1682
|
function buildInputMapUrl(manifestEndpoint, siteUuid) {
|
|
1411
1683
|
const url = new URL(manifestEndpoint);
|
|
1412
|
-
const
|
|
1413
|
-
url.pathname =
|
|
1684
|
+
const path18 = url.pathname.replace(/\/$/, "");
|
|
1685
|
+
url.pathname = path18.endsWith("/manifest") ? `${path18.slice(0, -"/manifest".length)}/input-map/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/input-map/${encodeURIComponent(siteUuid)}`;
|
|
1414
1686
|
url.search = "";
|
|
1415
1687
|
url.hash = "";
|
|
1416
1688
|
return url.toString();
|
|
@@ -1450,7 +1722,7 @@ async function postInputMap(config, map, buildId) {
|
|
|
1450
1722
|
if (!response.ok) {
|
|
1451
1723
|
return { result: "failed", message: `Patchstack returned ${response.status}.` };
|
|
1452
1724
|
}
|
|
1453
|
-
const body = await response
|
|
1725
|
+
const body = await readBoundedJson(response);
|
|
1454
1726
|
const revision = Number(body.revision);
|
|
1455
1727
|
if ((body.result === "stored" || body.result === "unchanged") && Number.isInteger(revision) && revision > 0) {
|
|
1456
1728
|
return { result: body.result, revision };
|
|
@@ -1487,9 +1759,9 @@ async function postPackageRemoved(config) {
|
|
|
1487
1759
|
if (!response.ok) {
|
|
1488
1760
|
return { result: "failed", message: `Patchstack returned ${response.status}.` };
|
|
1489
1761
|
}
|
|
1490
|
-
const body = await response
|
|
1762
|
+
const body = await readBoundedJson(response);
|
|
1491
1763
|
if (body.status === "deleted" || body.status === "flagged") {
|
|
1492
|
-
return { result: body.status, message: body.message
|
|
1764
|
+
return { result: body.status, message: safeDisplayString(body.message) };
|
|
1493
1765
|
}
|
|
1494
1766
|
return { result: "failed", message: "Patchstack returned an unexpected response." };
|
|
1495
1767
|
} catch {
|
|
@@ -1501,6 +1773,7 @@ async function fetchSiteStatus(config) {
|
|
|
1501
1773
|
const url = new URL(buildSettingsUrl(config.endpoint, config.siteUuid));
|
|
1502
1774
|
url.searchParams.set("t", Date.now().toString());
|
|
1503
1775
|
try {
|
|
1776
|
+
assertConnectableEndpoint(config, url.toString());
|
|
1504
1777
|
const response = await fetch(url.toString(), {
|
|
1505
1778
|
method: "GET",
|
|
1506
1779
|
headers: {
|
|
@@ -1533,6 +1806,42 @@ function validationFields(body) {
|
|
|
1533
1806
|
if (typeof errors !== "object" || errors === null || Array.isArray(errors)) return [];
|
|
1534
1807
|
return Object.keys(errors);
|
|
1535
1808
|
}
|
|
1809
|
+
function manifestResponse(body, provisioning) {
|
|
1810
|
+
if (typeof body !== "object" || body === null || Array.isArray(body)) return null;
|
|
1811
|
+
const value = body;
|
|
1812
|
+
if (typeof value.stored !== "boolean") return null;
|
|
1813
|
+
const uuid = value.uuid === void 0 ? void 0 : isCanonicalUuid(value.uuid) ? value.uuid : null;
|
|
1814
|
+
if (uuid === null || provisioning && uuid === void 0) return null;
|
|
1815
|
+
if (value.api_key !== void 0 && !isCredential(value.api_key)) return null;
|
|
1816
|
+
if (value.manifest_id !== void 0 && (!Number.isInteger(value.manifest_id) || Number(value.manifest_id) <= 0)) return null;
|
|
1817
|
+
const claimValue = value.claim;
|
|
1818
|
+
let claim3;
|
|
1819
|
+
if (claimValue !== void 0) {
|
|
1820
|
+
if (typeof claimValue !== "object" || claimValue === null || Array.isArray(claimValue)) return null;
|
|
1821
|
+
const raw = claimValue;
|
|
1822
|
+
if (!["claimed", "owned-by-you", "owned-by-other", "rejected"].includes(String(raw.state))) return null;
|
|
1823
|
+
const dashboardUrl = raw.dashboard_url === void 0 || raw.dashboard_url === null ? null : safeRemoteUrl(raw.dashboard_url);
|
|
1824
|
+
if (raw.dashboard_url !== void 0 && raw.dashboard_url !== null && dashboardUrl === null) return null;
|
|
1825
|
+
claim3 = {
|
|
1826
|
+
state: raw.state,
|
|
1827
|
+
...Number.isInteger(raw.site_id) ? { site_id: Number(raw.site_id) } : {},
|
|
1828
|
+
...dashboardUrl !== null ? { dashboard_url: dashboardUrl } : {},
|
|
1829
|
+
...raw.reason === "expired" || raw.reason === "invalid" ? { reason: raw.reason } : {}
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
const result = {
|
|
1833
|
+
stored: value.stored,
|
|
1834
|
+
...uuid !== void 0 ? { uuid } : {},
|
|
1835
|
+
...typeof value.api_key === "string" ? { api_key: value.api_key } : {},
|
|
1836
|
+
...Number.isInteger(value.manifest_id) ? { manifest_id: Number(value.manifest_id) } : {},
|
|
1837
|
+
...safeDisplayString(value.checksum, 256) !== null ? { checksum: safeDisplayString(value.checksum, 256) } : {},
|
|
1838
|
+
...safeDisplayString(value.reason) !== null ? { reason: safeDisplayString(value.reason) } : {},
|
|
1839
|
+
...safeDisplayString(value.message) !== null ? { message: safeDisplayString(value.message) } : {},
|
|
1840
|
+
...safeDisplayString(value.error) !== null ? { error: safeDisplayString(value.error) } : {},
|
|
1841
|
+
...claim3 !== void 0 ? { claim: claim3 } : {}
|
|
1842
|
+
};
|
|
1843
|
+
return result;
|
|
1844
|
+
}
|
|
1536
1845
|
function environmentRejected(err) {
|
|
1537
1846
|
return err instanceof PatchstackError && err.code === "VALIDATION_ERROR" && err.fields.includes("environment");
|
|
1538
1847
|
}
|
|
@@ -1551,6 +1860,7 @@ async function postManifestWithEnvironmentFallback(config, payload) {
|
|
|
1551
1860
|
async function postManifest(config, payload) {
|
|
1552
1861
|
const url = buildEndpointUrl(config.endpoint, config.siteUuid);
|
|
1553
1862
|
const timeoutMs = config.timeoutMs;
|
|
1863
|
+
assertConnectableEndpoint(config, url);
|
|
1554
1864
|
let response;
|
|
1555
1865
|
try {
|
|
1556
1866
|
response = await pulseFetch(config, url, {
|
|
@@ -1578,25 +1888,27 @@ async function postManifest(config, payload) {
|
|
|
1578
1888
|
cause
|
|
1579
1889
|
);
|
|
1580
1890
|
}
|
|
1581
|
-
|
|
1582
|
-
let
|
|
1891
|
+
let text = "";
|
|
1892
|
+
let parsed = null;
|
|
1583
1893
|
try {
|
|
1584
|
-
|
|
1894
|
+
text = await readBoundedText(response);
|
|
1895
|
+
parsed = text.length > 0 ? JSON.parse(text) : null;
|
|
1585
1896
|
} catch {
|
|
1586
|
-
|
|
1897
|
+
parsed = null;
|
|
1587
1898
|
}
|
|
1899
|
+
const body = manifestResponse(parsed, config.siteUuid === null);
|
|
1588
1900
|
if (response.status === 404) {
|
|
1589
1901
|
throw new PatchstackError(
|
|
1590
|
-
|
|
1902
|
+
safeDisplayString(parsed?.error) ?? "Site not found. Check that your site UUID is correct and that the app is registered as a Pulse app in your Patchstack dashboard.",
|
|
1591
1903
|
"SITE_NOT_FOUND"
|
|
1592
1904
|
);
|
|
1593
1905
|
}
|
|
1594
1906
|
if (response.status === 422) {
|
|
1595
1907
|
const refused2 = new PatchstackError(
|
|
1596
|
-
|
|
1908
|
+
safeDisplayString(parsed?.message) ?? "Patchstack rejected the manifest payload (validation failed).",
|
|
1597
1909
|
"VALIDATION_ERROR"
|
|
1598
1910
|
);
|
|
1599
|
-
refused2.fields = validationFields(
|
|
1911
|
+
refused2.fields = validationFields(parsed);
|
|
1600
1912
|
throw refused2;
|
|
1601
1913
|
}
|
|
1602
1914
|
const refused = authFailureMessage(response.status, config);
|
|
@@ -1605,12 +1917,12 @@ async function postManifest(config, payload) {
|
|
|
1605
1917
|
}
|
|
1606
1918
|
if (response.status < 200 || response.status >= 300) {
|
|
1607
1919
|
throw new PatchstackError(
|
|
1608
|
-
`Patchstack returned ${response.status}: ${text
|
|
1920
|
+
`Patchstack returned ${response.status}: ${safeDisplayString(text, 200) ?? "no response details"}`,
|
|
1609
1921
|
"SERVER_ERROR"
|
|
1610
1922
|
);
|
|
1611
1923
|
}
|
|
1612
1924
|
if (body === null) {
|
|
1613
|
-
throw new PatchstackError("Patchstack returned an
|
|
1925
|
+
throw new PatchstackError("Patchstack returned an unexpected response.", "SERVER_ERROR");
|
|
1614
1926
|
}
|
|
1615
1927
|
return body;
|
|
1616
1928
|
}
|
|
@@ -1623,7 +1935,7 @@ function isTimeoutError(cause) {
|
|
|
1623
1935
|
|
|
1624
1936
|
// src/demo.ts
|
|
1625
1937
|
import { readFile as readFile7 } from "fs/promises";
|
|
1626
|
-
import { join } from "path";
|
|
1938
|
+
import { join as join2 } from "path";
|
|
1627
1939
|
var NODE_SERIALIZE_DEMO = {
|
|
1628
1940
|
name: "node-serialize",
|
|
1629
1941
|
packageName: "node-serialize",
|
|
@@ -1692,7 +2004,7 @@ Add this deliberately vulnerable package first: npm install --save-exact ${scena
|
|
|
1692
2004
|
}
|
|
1693
2005
|
async function readPersistedSiteUuid(cwd) {
|
|
1694
2006
|
try {
|
|
1695
|
-
const value = JSON.parse(await readFile7(
|
|
2007
|
+
const value = JSON.parse(await readFile7(join2(cwd, ".patchstackrc.json"), "utf8")).siteUuid;
|
|
1696
2008
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
1697
2009
|
} catch {
|
|
1698
2010
|
return null;
|
|
@@ -1777,7 +2089,7 @@ async function waitForDemoRule(manifestEndpoint, siteUuid, scenario, options = {
|
|
|
1777
2089
|
const pollIntervalMs = options.pollIntervalMs ?? 1e3;
|
|
1778
2090
|
const requestTimeoutMs = options.requestTimeoutMs ?? 1e4;
|
|
1779
2091
|
const fetchFn = options.fetchFn ?? fetch;
|
|
1780
|
-
const sleep = options.sleep ?? ((ms) => new Promise((
|
|
2092
|
+
const sleep = options.sleep ?? ((ms) => new Promise((resolve5) => setTimeout(resolve5, ms)));
|
|
1781
2093
|
const now = options.now ?? Date.now;
|
|
1782
2094
|
const started = now();
|
|
1783
2095
|
const rulesUrl = buildRulesUrl(manifestEndpoint, siteUuid);
|
|
@@ -1826,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
|
}
|
|
@@ -2513,12 +2872,16 @@ function productionGate(framework) {
|
|
|
2513
2872
|
function hasJsxShell(framework) {
|
|
2514
2873
|
return framework !== null && JSX_SHELL_FRAMEWORKS.has(framework);
|
|
2515
2874
|
}
|
|
2516
|
-
function buildSourceMarkerSnippet(framework) {
|
|
2875
|
+
function buildSourceMarkerSnippet(framework, checksum = null) {
|
|
2517
2876
|
const gate = productionGate(framework);
|
|
2877
|
+
const statements = ["window.__PATCHSTACK_PROD__=true;"];
|
|
2878
|
+
if (checksum !== null && checksum !== "") {
|
|
2879
|
+
statements.push(`window.__PATCHSTACK_BUILD__=${JSON.stringify(checksum)};`);
|
|
2880
|
+
}
|
|
2518
2881
|
return `{${gate} && (
|
|
2519
2882
|
<script
|
|
2520
2883
|
${MARKER_ATTR}="true"
|
|
2521
|
-
dangerouslySetInnerHTML={{ __html: '
|
|
2884
|
+
dangerouslySetInnerHTML={{ __html: '${statements.join("")}' }}
|
|
2522
2885
|
/>
|
|
2523
2886
|
)}`;
|
|
2524
2887
|
}
|
|
@@ -2581,7 +2944,7 @@ function findJsxShellAnchor(source, tagName) {
|
|
|
2581
2944
|
}
|
|
2582
2945
|
return null;
|
|
2583
2946
|
}
|
|
2584
|
-
function ensureMarkerInJsxShell(source, framework) {
|
|
2947
|
+
function ensureMarkerInJsxShell(source, framework, checksum = null) {
|
|
2585
2948
|
if (!hasJsxShell(framework)) {
|
|
2586
2949
|
return { source, action: "unsupported" };
|
|
2587
2950
|
}
|
|
@@ -2589,7 +2952,7 @@ function ensureMarkerInJsxShell(source, framework) {
|
|
|
2589
2952
|
if (stripped.includes(PROD_MARKER_GLOBAL)) {
|
|
2590
2953
|
return { source: stripped, action: "manual" };
|
|
2591
2954
|
}
|
|
2592
|
-
const block = (indent) => [REGION_OPEN, ...buildSourceMarkerSnippet(framework).split("\n"), REGION_CLOSE].map((line) => `${indent}${line}`).join("\n");
|
|
2955
|
+
const block = (indent) => [REGION_OPEN, ...buildSourceMarkerSnippet(framework, checksum).split("\n"), REGION_CLOSE].map((line) => `${indent}${line}`).join("\n");
|
|
2593
2956
|
for (const tagName of ["head", "body"]) {
|
|
2594
2957
|
const anchor = findJsxShellAnchor(stripped, tagName);
|
|
2595
2958
|
if (anchor === null) continue;
|
|
@@ -2604,30 +2967,30 @@ ${block(indent)}${separator}${remainder}`,
|
|
|
2604
2967
|
}
|
|
2605
2968
|
return { source: stripped, action: "no-anchor" };
|
|
2606
2969
|
}
|
|
2607
|
-
function ensureSourceMarker(cwd, shell, framework) {
|
|
2970
|
+
function ensureSourceMarker(cwd, shell, framework, checksum = null) {
|
|
2608
2971
|
if (shell === null) {
|
|
2609
2972
|
return { shell: null, source: "", action: "no-anchor" };
|
|
2610
2973
|
}
|
|
2611
2974
|
const file = path9.resolve(cwd, shell);
|
|
2612
|
-
const before =
|
|
2613
|
-
const result = ensureMarkerInJsxShell(before, framework);
|
|
2975
|
+
const before = readFileSync3(file, "utf8");
|
|
2976
|
+
const result = ensureMarkerInJsxShell(before, framework, checksum);
|
|
2614
2977
|
if (result.source !== before) {
|
|
2615
|
-
|
|
2978
|
+
writeProjectFileSync(cwd, file, result.source, { encoding: "utf8" });
|
|
2616
2979
|
}
|
|
2617
2980
|
return { ...result, shell };
|
|
2618
2981
|
}
|
|
2619
2982
|
|
|
2620
2983
|
// src/guide.ts
|
|
2621
|
-
import { existsSync as existsSync13, readFileSync as
|
|
2984
|
+
import { existsSync as existsSync13, readFileSync as readFileSync11, readdirSync as readdirSync5, statSync as statSync5 } from "fs";
|
|
2622
2985
|
import path12 from "path";
|
|
2623
2986
|
|
|
2624
2987
|
// src/architecture.ts
|
|
2625
|
-
import { existsSync as existsSync2, readFileSync as
|
|
2988
|
+
import { existsSync as existsSync2, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
2626
2989
|
import path10 from "path";
|
|
2627
2990
|
|
|
2628
2991
|
// src/map/sources.ts
|
|
2629
|
-
import { readFileSync as
|
|
2630
|
-
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";
|
|
2631
2994
|
|
|
2632
2995
|
// src/map/ast.ts
|
|
2633
2996
|
function rootIdentifier(node, ts) {
|
|
@@ -2836,7 +3199,7 @@ function hasEntrySignal(text) {
|
|
|
2836
3199
|
}
|
|
2837
3200
|
function detectFramework(cwd) {
|
|
2838
3201
|
try {
|
|
2839
|
-
const pkg = JSON.parse(
|
|
3202
|
+
const pkg = JSON.parse(readFileSync4(join3(cwd, "package.json"), "utf8"));
|
|
2840
3203
|
const d = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
2841
3204
|
if (d["@tanstack/react-start"] || d["@tanstack/start"] || d["@tanstack/solid-start"]) return "tanstack-start";
|
|
2842
3205
|
if (d["next"]) return "next";
|
|
@@ -2848,11 +3211,11 @@ function detectFramework(cwd) {
|
|
|
2848
3211
|
} catch {
|
|
2849
3212
|
}
|
|
2850
3213
|
try {
|
|
2851
|
-
if (
|
|
3214
|
+
if (statSync3(join3(cwd, "supabase", "functions")).isDirectory()) return "supabase-functions";
|
|
2852
3215
|
} catch {
|
|
2853
3216
|
}
|
|
2854
3217
|
try {
|
|
2855
|
-
if (
|
|
3218
|
+
if (statSync3(join3(cwd, "functions")).isDirectory()) return "deno-functions";
|
|
2856
3219
|
} catch {
|
|
2857
3220
|
}
|
|
2858
3221
|
return "unknown";
|
|
@@ -2894,7 +3257,7 @@ function probeForSource(dir, budget) {
|
|
|
2894
3257
|
for (const e of entries) {
|
|
2895
3258
|
if (budget.left-- <= 0) return "unknown";
|
|
2896
3259
|
if (e.isDirectory()) {
|
|
2897
|
-
if (!SKIP_IS_DEFINITIONAL.has(e.name)) dirs.push(
|
|
3260
|
+
if (!SKIP_IS_DEFINITIONAL.has(e.name)) dirs.push(join3(dir, e.name));
|
|
2898
3261
|
} else if (isSourceFile(e.name)) return true;
|
|
2899
3262
|
else if (e.isSymbolicLink()) return "unknown";
|
|
2900
3263
|
}
|
|
@@ -2907,7 +3270,7 @@ function probeForSource(dir, budget) {
|
|
|
2907
3270
|
function collectSources(dir, boundary, opts, out = [], seen = /* @__PURE__ */ new Set(), stats = { discovered: 0, unwalked: 0, skippedWithSource: [], skippedUnknown: 0 }, probeBudget = { left: PROBE_BUDGET }) {
|
|
2908
3271
|
let key;
|
|
2909
3272
|
try {
|
|
2910
|
-
key =
|
|
3273
|
+
key = realpathSync3(dir);
|
|
2911
3274
|
} catch {
|
|
2912
3275
|
stats.unwalked++;
|
|
2913
3276
|
return out;
|
|
@@ -2931,19 +3294,19 @@ function collectSources(dir, boundary, opts, out = [], seen = /* @__PURE__ */ ne
|
|
|
2931
3294
|
const probeable = !SKIP_IS_DEFINITIONAL.has(e.name);
|
|
2932
3295
|
if (probeable && !e.isDirectory() && e.isSymbolicLink()) stats.skippedUnknown++;
|
|
2933
3296
|
else if (probeable && e.isDirectory()) {
|
|
2934
|
-
const held = probeForSource(
|
|
2935
|
-
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));
|
|
2936
3299
|
else if (held === "unknown") stats.skippedUnknown++;
|
|
2937
3300
|
}
|
|
2938
3301
|
continue;
|
|
2939
3302
|
}
|
|
2940
|
-
const full =
|
|
3303
|
+
const full = join3(dir, e.name);
|
|
2941
3304
|
if (e.isDirectory()) collectSources(full, boundary, opts, out, seen, stats, probeBudget);
|
|
2942
3305
|
else if (e.isSymbolicLink()) {
|
|
2943
3306
|
let st, real;
|
|
2944
3307
|
try {
|
|
2945
|
-
st =
|
|
2946
|
-
real =
|
|
3308
|
+
st = statSync3(full);
|
|
3309
|
+
real = realpathSync3(full);
|
|
2947
3310
|
} catch {
|
|
2948
3311
|
stats.unwalked++;
|
|
2949
3312
|
continue;
|
|
@@ -2966,8 +3329,8 @@ function collectSources(dir, boundary, opts, out = [], seen = /* @__PURE__ */ ne
|
|
|
2966
3329
|
}
|
|
2967
3330
|
function isInside(candidate, boundary) {
|
|
2968
3331
|
if (candidate === boundary) return true;
|
|
2969
|
-
const rel =
|
|
2970
|
-
return rel !== "" && !rel.startsWith("..") && !
|
|
3332
|
+
const rel = relative2(boundary, candidate);
|
|
3333
|
+
return rel !== "" && !rel.startsWith("..") && !isAbsolute2(rel);
|
|
2971
3334
|
}
|
|
2972
3335
|
var DEPLOYMENT_SHAPES = [
|
|
2973
3336
|
// Config first: these are declarations by the project itself, and they survive a build output being
|
|
@@ -2993,13 +3356,13 @@ var DEPLOYMENT_SHAPES = [
|
|
|
2993
3356
|
function detectDeploymentShapes(cwd, opts = {}) {
|
|
2994
3357
|
let boundary = opts.boundary ?? cwd;
|
|
2995
3358
|
try {
|
|
2996
|
-
boundary =
|
|
3359
|
+
boundary = realpathSync3(boundary);
|
|
2997
3360
|
} catch {
|
|
2998
3361
|
}
|
|
2999
|
-
const inProject = (
|
|
3362
|
+
const inProject = (path18) => {
|
|
3000
3363
|
if (opts.followOutside) return true;
|
|
3001
3364
|
try {
|
|
3002
|
-
return isInside(
|
|
3365
|
+
return isInside(realpathSync3(path18), boundary);
|
|
3003
3366
|
} catch {
|
|
3004
3367
|
return false;
|
|
3005
3368
|
}
|
|
@@ -3007,9 +3370,9 @@ function detectDeploymentShapes(cwd, opts = {}) {
|
|
|
3007
3370
|
const found = [];
|
|
3008
3371
|
for (const candidate of DEPLOYMENT_SHAPES) {
|
|
3009
3372
|
for (const file of candidate.files ?? []) {
|
|
3010
|
-
const full =
|
|
3373
|
+
const full = join3(cwd, file);
|
|
3011
3374
|
try {
|
|
3012
|
-
if (
|
|
3375
|
+
if (statSync3(full).isFile() && inProject(full)) {
|
|
3013
3376
|
found.push({ shape: candidate.shape, source: file, evidence: candidate.evidence });
|
|
3014
3377
|
break;
|
|
3015
3378
|
}
|
|
@@ -3017,9 +3380,9 @@ function detectDeploymentShapes(cwd, opts = {}) {
|
|
|
3017
3380
|
}
|
|
3018
3381
|
}
|
|
3019
3382
|
for (const dir of candidate.dirs ?? []) {
|
|
3020
|
-
const full =
|
|
3383
|
+
const full = join3(cwd, dir);
|
|
3021
3384
|
try {
|
|
3022
|
-
if (!
|
|
3385
|
+
if (!statSync3(full).isDirectory() || !inProject(full)) continue;
|
|
3023
3386
|
} catch {
|
|
3024
3387
|
continue;
|
|
3025
3388
|
}
|
|
@@ -3036,7 +3399,7 @@ function holdsFunctionEntry(dir) {
|
|
|
3036
3399
|
for (const entry of readdirSync2(dir, { withFileTypes: true })) {
|
|
3037
3400
|
if (!entry.isDirectory() || entry.name.startsWith("_")) continue;
|
|
3038
3401
|
try {
|
|
3039
|
-
for (const nested of readdirSync2(
|
|
3402
|
+
for (const nested of readdirSync2(join3(dir, entry.name), { withFileTypes: true })) {
|
|
3040
3403
|
if (nested.isFile() && /^index\.(ts|tsx|mts|cts|js|jsx|mjs|cjs)$/.test(nested.name)) return true;
|
|
3041
3404
|
}
|
|
3042
3405
|
} catch {
|
|
@@ -3052,7 +3415,7 @@ function holdsSourceFile(dir) {
|
|
|
3052
3415
|
if (entry.isFile() && isSourceFile(entry.name)) return true;
|
|
3053
3416
|
if (entry.isDirectory()) {
|
|
3054
3417
|
try {
|
|
3055
|
-
for (const nested of readdirSync2(
|
|
3418
|
+
for (const nested of readdirSync2(join3(dir, entry.name), { withFileTypes: true })) {
|
|
3056
3419
|
if (nested.isFile() && isSourceFile(nested.name)) return true;
|
|
3057
3420
|
}
|
|
3058
3421
|
} catch {
|
|
@@ -3065,8 +3428,8 @@ function holdsSourceFile(dir) {
|
|
|
3065
3428
|
}
|
|
3066
3429
|
|
|
3067
3430
|
// src/map/surface.ts
|
|
3068
|
-
import { readFileSync as
|
|
3069
|
-
import { join as
|
|
3431
|
+
import { readFileSync as readFileSync5 } from "fs";
|
|
3432
|
+
import { join as join4 } from "path";
|
|
3070
3433
|
var STATIC_GENERATORS = [
|
|
3071
3434
|
{ dep: "vite", label: "vite" },
|
|
3072
3435
|
{ dep: "react-scripts", label: "create-react-app" },
|
|
@@ -3107,7 +3470,7 @@ var SHAPE_SIGNAL = {
|
|
|
3107
3470
|
};
|
|
3108
3471
|
function readManifest(cwd) {
|
|
3109
3472
|
try {
|
|
3110
|
-
const parsed = JSON.parse(
|
|
3473
|
+
const parsed = JSON.parse(readFileSync5(join4(cwd, "package.json"), "utf8"));
|
|
3111
3474
|
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
3112
3475
|
} catch {
|
|
3113
3476
|
return null;
|
|
@@ -3120,7 +3483,7 @@ function nextExportsStatically(cwd, manifest, ts) {
|
|
|
3120
3483
|
for (const file of ["next.config.js", "next.config.mjs", "next.config.ts", "next.config.cjs"]) {
|
|
3121
3484
|
let text;
|
|
3122
3485
|
try {
|
|
3123
|
-
text =
|
|
3486
|
+
text = readFileSync5(join4(cwd, file), "utf8");
|
|
3124
3487
|
} catch {
|
|
3125
3488
|
continue;
|
|
3126
3489
|
}
|
|
@@ -3279,7 +3642,7 @@ var SERVING_CALL = /\b(?:createServer|createSecureServer|Bun\.serve|Deno\.serve|
|
|
|
3279
3642
|
var MAX_ENTRY_BYTES = 256 * 1024;
|
|
3280
3643
|
function readManifest2(cwd) {
|
|
3281
3644
|
try {
|
|
3282
|
-
const parsed = JSON.parse(
|
|
3645
|
+
const parsed = JSON.parse(readFileSync6(path10.join(cwd, "package.json"), "utf8"));
|
|
3283
3646
|
return typeof parsed === "object" && parsed !== null ? parsed : {};
|
|
3284
3647
|
} catch {
|
|
3285
3648
|
return {};
|
|
@@ -3313,12 +3676,12 @@ function serverEntries(cwd, manifest) {
|
|
|
3313
3676
|
if (!main2.startsWith("..")) candidates.add(main2);
|
|
3314
3677
|
}
|
|
3315
3678
|
const found = [];
|
|
3316
|
-
for (const
|
|
3317
|
-
const file = path10.join(cwd,
|
|
3679
|
+
for (const relative8 of candidates) {
|
|
3680
|
+
const file = path10.join(cwd, relative8);
|
|
3318
3681
|
try {
|
|
3319
|
-
if (!existsSync2(file) || !
|
|
3320
|
-
const text =
|
|
3321
|
-
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);
|
|
3322
3685
|
} catch {
|
|
3323
3686
|
}
|
|
3324
3687
|
}
|
|
@@ -3371,14 +3734,14 @@ function classifyArchitecture(cwd) {
|
|
|
3371
3734
|
}
|
|
3372
3735
|
|
|
3373
3736
|
// src/protect/install/util.ts
|
|
3374
|
-
import { readFileSync as
|
|
3375
|
-
import { join as
|
|
3737
|
+
import { readFileSync as readFileSync7, existsSync as existsSync3 } from "fs";
|
|
3738
|
+
import { join as join5, dirname as dirname2 } from "path";
|
|
3376
3739
|
import { fileURLToPath } from "url";
|
|
3377
|
-
var read = (p) =>
|
|
3740
|
+
var read = (p) => readFileSync7(p, "utf8");
|
|
3378
3741
|
var log = (msg) => console.log(`patchstack protect: ${msg}`);
|
|
3379
3742
|
function hasDependency(cwd, name) {
|
|
3380
3743
|
try {
|
|
3381
|
-
const pkg = JSON.parse(read(
|
|
3744
|
+
const pkg = JSON.parse(read(join5(cwd, "package.json")));
|
|
3382
3745
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }[name]);
|
|
3383
3746
|
} catch {
|
|
3384
3747
|
return false;
|
|
@@ -3390,7 +3753,7 @@ function hasResolvableCredential(cwd, env = process.env) {
|
|
|
3390
3753
|
}
|
|
3391
3754
|
for (const file of [".patchstackrc.local.json", ".patchstackrc.json"]) {
|
|
3392
3755
|
try {
|
|
3393
|
-
const parsed = JSON.parse(read(
|
|
3756
|
+
const parsed = JSON.parse(read(join5(cwd, file)));
|
|
3394
3757
|
if (typeof parsed.apiKey === "string" && parsed.apiKey !== "") return true;
|
|
3395
3758
|
if (typeof parsed.pulseAuth === "string" && parsed.pulseAuth !== "") return true;
|
|
3396
3759
|
} catch {
|
|
@@ -3404,7 +3767,7 @@ function isSiteUuid(value) {
|
|
|
3404
3767
|
return typeof value === "string" && UUID_RE.test(value);
|
|
3405
3768
|
}
|
|
3406
3769
|
function bakeSiteUuid(cwd, guardRelPath) {
|
|
3407
|
-
const rc =
|
|
3770
|
+
const rc = join5(cwd, ".patchstackrc.json");
|
|
3408
3771
|
if (!existsSync3(rc)) {
|
|
3409
3772
|
log("no .patchstackrc.json \u2014 guard uses PATCHSTACK_SITE_UUID env or the bundled fallback");
|
|
3410
3773
|
return false;
|
|
@@ -3420,32 +3783,32 @@ function bakeSiteUuid(cwd, guardRelPath) {
|
|
|
3420
3783
|
log(".patchstackrc.json siteUuid missing or malformed \u2014 guard uses PATCHSTACK_SITE_UUID env or the bundled fallback");
|
|
3421
3784
|
return false;
|
|
3422
3785
|
}
|
|
3423
|
-
const guardPath =
|
|
3786
|
+
const guardPath = join5(cwd, guardRelPath);
|
|
3424
3787
|
if (!existsSync3(guardPath)) return false;
|
|
3425
3788
|
const source = read(guardPath);
|
|
3426
3789
|
if (!source.includes(SITE_UUID_PLACEHOLDER)) {
|
|
3427
3790
|
log(`${guardRelPath} site UUID already baked`);
|
|
3428
3791
|
return false;
|
|
3429
3792
|
}
|
|
3430
|
-
|
|
3793
|
+
writeProjectFileSync(cwd, guardPath, source.replace(SITE_UUID_PLACEHOLDER, uuid), { encoding: "utf8" });
|
|
3431
3794
|
log(`baked site UUID into ${guardRelPath} \u2014 live rules from the Patchstack API`);
|
|
3432
3795
|
return true;
|
|
3433
3796
|
}
|
|
3434
|
-
var HERE =
|
|
3797
|
+
var HERE = dirname2(fileURLToPath(import.meta.url));
|
|
3435
3798
|
function templatesDir() {
|
|
3436
|
-
const builtLayout =
|
|
3799
|
+
const builtLayout = join5(HERE, "protect", "templates");
|
|
3437
3800
|
const candidates = [
|
|
3438
3801
|
builtLayout,
|
|
3439
|
-
|
|
3802
|
+
join5(HERE, "..", "templates"),
|
|
3440
3803
|
// source: src/protect/install/ → src/protect/templates
|
|
3441
|
-
|
|
3804
|
+
join5(HERE, "templates")
|
|
3442
3805
|
];
|
|
3443
3806
|
return candidates.find((p) => existsSync3(p)) ?? builtLayout;
|
|
3444
3807
|
}
|
|
3445
3808
|
|
|
3446
3809
|
// src/protect/install/adapters/tanstack-supabase.ts
|
|
3447
|
-
import {
|
|
3448
|
-
import { join as
|
|
3810
|
+
import { existsSync as existsSync4 } from "fs";
|
|
3811
|
+
import { join as join6 } from "path";
|
|
3449
3812
|
var CLIENT_TUNNEL = [
|
|
3450
3813
|
"",
|
|
3451
3814
|
" // PATCHSTACK: in the browser, tunnel Supabase traffic through the app's own server guard",
|
|
@@ -3515,7 +3878,7 @@ function reconcileBlock(s, region, block, legacyConst, insertBefore) {
|
|
|
3515
3878
|
return s.replace(insertBefore, block + "\n\n" + insertBefore);
|
|
3516
3879
|
}
|
|
3517
3880
|
function detect(cwd) {
|
|
3518
|
-
const pkgPath =
|
|
3881
|
+
const pkgPath = join6(cwd, "package.json");
|
|
3519
3882
|
if (!existsSync4(pkgPath)) return false;
|
|
3520
3883
|
let pkg;
|
|
3521
3884
|
try {
|
|
@@ -3524,22 +3887,22 @@ function detect(cwd) {
|
|
|
3524
3887
|
return false;
|
|
3525
3888
|
}
|
|
3526
3889
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3527
|
-
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"));
|
|
3528
3891
|
}
|
|
3529
3892
|
var GUARD_FILE = "src/integrations/patchstack/guard.ts";
|
|
3530
3893
|
function scaffold(cwd, opts) {
|
|
3531
3894
|
const templates = templatesDir();
|
|
3532
|
-
const dst =
|
|
3533
|
-
|
|
3534
|
-
|
|
3895
|
+
const dst = join6(cwd, "src/integrations/patchstack");
|
|
3896
|
+
ensureProjectDirectorySync(cwd, dst);
|
|
3897
|
+
copyProjectFileSync(cwd, join6(templates, "guard.ts"), join6(dst, "guard.ts"));
|
|
3535
3898
|
const changed = [GUARD_FILE];
|
|
3536
|
-
const rulesDst =
|
|
3899
|
+
const rulesDst = join6(dst, "rules.json");
|
|
3537
3900
|
if (opts.demo) {
|
|
3538
|
-
|
|
3901
|
+
copyProjectFileSync(cwd, join6(templates, "demo-rules.json"), rulesDst);
|
|
3539
3902
|
changed.push("src/integrations/patchstack/rules.json");
|
|
3540
3903
|
log("scaffolded guard.ts + rules.json (demo sample rule set)");
|
|
3541
3904
|
} else if (!existsSync4(rulesDst)) {
|
|
3542
|
-
|
|
3905
|
+
copyProjectFileSync(cwd, join6(templates, "rules.json"), rulesDst);
|
|
3543
3906
|
changed.push("src/integrations/patchstack/rules.json");
|
|
3544
3907
|
log("scaffolded guard.ts + rules.json (starter rules)");
|
|
3545
3908
|
} else {
|
|
@@ -3548,7 +3911,7 @@ function scaffold(cwd, opts) {
|
|
|
3548
3911
|
return changed;
|
|
3549
3912
|
}
|
|
3550
3913
|
function patchClient(cwd) {
|
|
3551
|
-
const p =
|
|
3914
|
+
const p = join6(cwd, "src/integrations/supabase/client.ts");
|
|
3552
3915
|
const s = read(p);
|
|
3553
3916
|
if (s.includes("x-ps-target")) {
|
|
3554
3917
|
log("client.ts already wired");
|
|
@@ -3559,12 +3922,12 @@ function patchClient(cwd) {
|
|
|
3559
3922
|
log("client.ts anchor not found \u2014 skipping (template changed?)");
|
|
3560
3923
|
return false;
|
|
3561
3924
|
}
|
|
3562
|
-
|
|
3925
|
+
writeProjectFileSync(cwd, p, s.replace(anchor, anchor + "\n" + CLIENT_TUNNEL), { encoding: "utf8" });
|
|
3563
3926
|
log("patched client.ts (tunnel Supabase through the guard)");
|
|
3564
3927
|
return true;
|
|
3565
3928
|
}
|
|
3566
3929
|
function patchStart(cwd) {
|
|
3567
|
-
const p =
|
|
3930
|
+
const p = join6(cwd, "src/start.ts");
|
|
3568
3931
|
let s = read(p);
|
|
3569
3932
|
const importAnchor = 'import { createStart, createMiddleware } from "@tanstack/react-start";';
|
|
3570
3933
|
const exportAnchor = "export const startInstance";
|
|
@@ -3596,7 +3959,7 @@ function patchStart(cwd) {
|
|
|
3596
3959
|
log("start.ts already wired");
|
|
3597
3960
|
return false;
|
|
3598
3961
|
}
|
|
3599
|
-
|
|
3962
|
+
writeProjectFileSync(cwd, p, s, { encoding: "utf8" });
|
|
3600
3963
|
log("patched start.ts (guard registered as request + function middleware)");
|
|
3601
3964
|
return true;
|
|
3602
3965
|
}
|
|
@@ -3611,9 +3974,9 @@ function wire(cwd, opts) {
|
|
|
3611
3974
|
return { ok: true, changed: [...new Set(changed)] };
|
|
3612
3975
|
}
|
|
3613
3976
|
function verify(cwd) {
|
|
3614
|
-
const guardPath =
|
|
3615
|
-
const clientPath =
|
|
3616
|
-
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");
|
|
3617
3980
|
const guard = existsSync4(guardPath) ? read(guardPath) : "";
|
|
3618
3981
|
const client = existsSync4(clientPath) ? read(clientPath) : "";
|
|
3619
3982
|
const start = existsSync4(startPath) ? read(startPath) : "";
|
|
@@ -3634,11 +3997,11 @@ var tanstackSupabaseAdapter = {
|
|
|
3634
3997
|
};
|
|
3635
3998
|
|
|
3636
3999
|
// src/protect/install/adapters/next.ts
|
|
3637
|
-
import { existsSync as existsSync5
|
|
3638
|
-
import { join as
|
|
4000
|
+
import { existsSync as existsSync5 } from "fs";
|
|
4001
|
+
import { join as join7 } from "path";
|
|
3639
4002
|
function hasNextDep(cwd) {
|
|
3640
4003
|
try {
|
|
3641
|
-
const pkg = JSON.parse(read(
|
|
4004
|
+
const pkg = JSON.parse(read(join7(cwd, "package.json")));
|
|
3642
4005
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }.next);
|
|
3643
4006
|
} catch {
|
|
3644
4007
|
return false;
|
|
@@ -3647,9 +4010,9 @@ function hasNextDep(cwd) {
|
|
|
3647
4010
|
function middlewareInfo(cwd) {
|
|
3648
4011
|
const candidates = ["middleware.ts", "middleware.js", "src/middleware.ts", "src/middleware.js"];
|
|
3649
4012
|
for (const rel of candidates) {
|
|
3650
|
-
if (existsSync5(
|
|
4013
|
+
if (existsSync5(join7(cwd, rel))) return { relDir: rel.includes("/") ? "src" : ".", relFile: rel, exists: true };
|
|
3651
4014
|
}
|
|
3652
|
-
const useSrc = existsSync5(
|
|
4015
|
+
const useSrc = existsSync5(join7(cwd, "src"));
|
|
3653
4016
|
return { relDir: useSrc ? "src" : ".", relFile: useSrc ? "src/middleware.ts" : "middleware.ts", exists: false };
|
|
3654
4017
|
}
|
|
3655
4018
|
function detect2(cwd) {
|
|
@@ -3661,15 +4024,15 @@ function rulesFile(relDir) {
|
|
|
3661
4024
|
function wire2(cwd, opts) {
|
|
3662
4025
|
const templates = templatesDir();
|
|
3663
4026
|
const mw = middlewareInfo(cwd);
|
|
3664
|
-
const dir =
|
|
3665
|
-
|
|
3666
|
-
const rulesDst =
|
|
4027
|
+
const dir = join7(cwd, mw.relDir === "." ? "" : mw.relDir);
|
|
4028
|
+
ensureProjectDirectorySync(cwd, dir);
|
|
4029
|
+
const rulesDst = join7(dir, "patchstack.rules.json");
|
|
3667
4030
|
const changed = [];
|
|
3668
4031
|
if (opts.demo || !existsSync5(rulesDst)) {
|
|
3669
|
-
|
|
4032
|
+
copyProjectFileSync(cwd, join7(templates, opts.demo ? "demo-rules.json" : "rules.json"), rulesDst);
|
|
3670
4033
|
changed.push(rulesFile(mw.relDir));
|
|
3671
4034
|
}
|
|
3672
|
-
const mwPath =
|
|
4035
|
+
const mwPath = join7(cwd, mw.relFile);
|
|
3673
4036
|
const existing = mw.exists ? read(mwPath) : "";
|
|
3674
4037
|
if (mw.exists && existing.includes("patchstack-next")) {
|
|
3675
4038
|
log(`${mw.relFile} already has the Patchstack middleware \u2014 left as-is`);
|
|
@@ -3681,7 +4044,7 @@ function wire2(cwd, opts) {
|
|
|
3681
4044
|
);
|
|
3682
4045
|
return { ok: true, changed };
|
|
3683
4046
|
}
|
|
3684
|
-
|
|
4047
|
+
copyProjectFileSync(cwd, join7(templates, "next-middleware.ts"), mwPath);
|
|
3685
4048
|
if (!opts.demo) bakeSiteUuid(cwd, mw.relFile);
|
|
3686
4049
|
changed.push(mw.relFile);
|
|
3687
4050
|
log(`scaffolded ${mw.relFile} (Patchstack middleware)`);
|
|
@@ -3689,8 +4052,8 @@ function wire2(cwd, opts) {
|
|
|
3689
4052
|
}
|
|
3690
4053
|
function verify2(cwd) {
|
|
3691
4054
|
const mw = middlewareInfo(cwd);
|
|
3692
|
-
const present2 = mw.exists && read(
|
|
3693
|
-
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)));
|
|
3694
4057
|
return {
|
|
3695
4058
|
wired: present2 && rulesPresent,
|
|
3696
4059
|
checks: [
|
|
@@ -3708,27 +4071,27 @@ var nextAdapter = {
|
|
|
3708
4071
|
};
|
|
3709
4072
|
|
|
3710
4073
|
// src/protect/install/adapters/sveltekit.ts
|
|
3711
|
-
import { join as
|
|
4074
|
+
import { join as join9 } from "path";
|
|
3712
4075
|
|
|
3713
4076
|
// src/protect/install/seam.ts
|
|
3714
|
-
import { existsSync as existsSync6
|
|
3715
|
-
import { join as
|
|
4077
|
+
import { existsSync as existsSync6 } from "fs";
|
|
4078
|
+
import { join as join8, dirname as dirname3 } from "path";
|
|
3716
4079
|
function rulesRel(seamRel) {
|
|
3717
|
-
const d =
|
|
4080
|
+
const d = dirname3(seamRel);
|
|
3718
4081
|
return (d === "." ? "patchstack.rules.json" : `${d}/patchstack.rules.json`).replace(/\\/g, "/");
|
|
3719
4082
|
}
|
|
3720
4083
|
function wireSeam(cwd, opts, spec) {
|
|
3721
4084
|
const templates = templatesDir();
|
|
3722
|
-
const existing = spec.candidates.find((c) => existsSync6(
|
|
4085
|
+
const existing = spec.candidates.find((c) => existsSync6(join8(cwd, c)));
|
|
3723
4086
|
const seamRel = existing ?? spec.target;
|
|
3724
|
-
|
|
3725
|
-
const rulesDst =
|
|
4087
|
+
ensureProjectDirectorySync(cwd, dirname3(join8(cwd, seamRel)));
|
|
4088
|
+
const rulesDst = join8(cwd, rulesRel(seamRel));
|
|
3726
4089
|
const changed = [];
|
|
3727
4090
|
if (opts.demo || !existsSync6(rulesDst)) {
|
|
3728
|
-
|
|
4091
|
+
copyProjectFileSync(cwd, join8(templates, opts.demo ? "demo-rules.json" : "rules.json"), rulesDst);
|
|
3729
4092
|
changed.push(rulesRel(seamRel));
|
|
3730
4093
|
}
|
|
3731
|
-
const current = existing ? read(
|
|
4094
|
+
const current = existing ? read(join8(cwd, existing)) : "";
|
|
3732
4095
|
if (existing && current.includes(spec.marker)) {
|
|
3733
4096
|
log(`${existing} already has the Patchstack guard \u2014 left as-is`);
|
|
3734
4097
|
return { ok: true, changed };
|
|
@@ -3737,17 +4100,17 @@ function wireSeam(cwd, opts, spec) {
|
|
|
3737
4100
|
log(`existing ${existing} left untouched \u2014 scaffolded ${rulesRel(seamRel)}; ${spec.planHint}`);
|
|
3738
4101
|
return { ok: true, changed };
|
|
3739
4102
|
}
|
|
3740
|
-
|
|
4103
|
+
copyProjectFileSync(cwd, join8(templates, spec.templateName), join8(cwd, seamRel));
|
|
3741
4104
|
if (!opts.demo) bakeSiteUuid(cwd, seamRel);
|
|
3742
4105
|
changed.push(seamRel);
|
|
3743
4106
|
log(`scaffolded ${seamRel}`);
|
|
3744
4107
|
return { ok: true, changed: [...new Set(changed)] };
|
|
3745
4108
|
}
|
|
3746
4109
|
function verifySeam(cwd, spec) {
|
|
3747
|
-
const existing = spec.candidates.find((c) => existsSync6(
|
|
4110
|
+
const existing = spec.candidates.find((c) => existsSync6(join8(cwd, c)));
|
|
3748
4111
|
const seamRel = existing ?? spec.target;
|
|
3749
|
-
const present2 = existing ? read(
|
|
3750
|
-
const rulesPresent = existsSync6(
|
|
4112
|
+
const present2 = existing ? read(join8(cwd, existing)).includes(spec.marker) : false;
|
|
4113
|
+
const rulesPresent = existsSync6(join8(cwd, rulesRel(seamRel)));
|
|
3751
4114
|
return {
|
|
3752
4115
|
wired: present2 && rulesPresent,
|
|
3753
4116
|
checks: [
|
|
@@ -3768,7 +4131,7 @@ var SPEC = {
|
|
|
3768
4131
|
};
|
|
3769
4132
|
function detect3(cwd) {
|
|
3770
4133
|
try {
|
|
3771
|
-
const pkg = JSON.parse(read(
|
|
4134
|
+
const pkg = JSON.parse(read(join9(cwd, "package.json")));
|
|
3772
4135
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }["@sveltejs/kit"]);
|
|
3773
4136
|
} catch {
|
|
3774
4137
|
return false;
|
|
@@ -3789,7 +4152,7 @@ var sveltekitAdapter = {
|
|
|
3789
4152
|
};
|
|
3790
4153
|
|
|
3791
4154
|
// src/protect/install/adapters/astro.ts
|
|
3792
|
-
import { join as
|
|
4155
|
+
import { join as join10 } from "path";
|
|
3793
4156
|
var SPEC2 = {
|
|
3794
4157
|
templateName: "astro-middleware.ts",
|
|
3795
4158
|
candidates: ["src/middleware.ts", "src/middleware.js", "src/middleware/index.ts", "src/middleware/index.js"],
|
|
@@ -3800,7 +4163,7 @@ var SPEC2 = {
|
|
|
3800
4163
|
};
|
|
3801
4164
|
function detect4(cwd) {
|
|
3802
4165
|
try {
|
|
3803
|
-
const pkg = JSON.parse(read(
|
|
4166
|
+
const pkg = JSON.parse(read(join10(cwd, "package.json")));
|
|
3804
4167
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }.astro);
|
|
3805
4168
|
} catch {
|
|
3806
4169
|
return false;
|
|
@@ -3847,8 +4210,8 @@ var nuxtAdapter = {
|
|
|
3847
4210
|
};
|
|
3848
4211
|
|
|
3849
4212
|
// src/protect/install/find-app.ts
|
|
3850
|
-
import { readFileSync as
|
|
3851
|
-
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";
|
|
3852
4215
|
var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
3853
4216
|
"node_modules",
|
|
3854
4217
|
"patchstack",
|
|
@@ -3869,7 +4232,7 @@ var SKIP_DIRS2 = /* @__PURE__ */ new Set([
|
|
|
3869
4232
|
]);
|
|
3870
4233
|
var MAX_DEPTH = 8;
|
|
3871
4234
|
function findAppInstance(cwd, re) {
|
|
3872
|
-
const root = existsSync7(
|
|
4235
|
+
const root = existsSync7(join11(cwd, "src")) ? join11(cwd, "src") : cwd;
|
|
3873
4236
|
const found = [];
|
|
3874
4237
|
const walk2 = (dir, depth) => {
|
|
3875
4238
|
if (depth > MAX_DEPTH) return;
|
|
@@ -3881,10 +4244,10 @@ function findAppInstance(cwd, re) {
|
|
|
3881
4244
|
}
|
|
3882
4245
|
for (const name of entries) {
|
|
3883
4246
|
if (SKIP_DIRS2.has(name) || name.startsWith(".")) continue;
|
|
3884
|
-
const p =
|
|
4247
|
+
const p = join11(dir, name);
|
|
3885
4248
|
let st;
|
|
3886
4249
|
try {
|
|
3887
|
-
st =
|
|
4250
|
+
st = lstatSync2(p);
|
|
3888
4251
|
} catch {
|
|
3889
4252
|
continue;
|
|
3890
4253
|
}
|
|
@@ -3893,7 +4256,7 @@ function findAppInstance(cwd, re) {
|
|
|
3893
4256
|
else if (/\.(?:ts|js|mjs|cjs)$/.test(name)) {
|
|
3894
4257
|
let src;
|
|
3895
4258
|
try {
|
|
3896
|
-
src =
|
|
4259
|
+
src = readFileSync8(p, "utf8");
|
|
3897
4260
|
} catch {
|
|
3898
4261
|
continue;
|
|
3899
4262
|
}
|
|
@@ -3901,7 +4264,7 @@ function findAppInstance(cwd, re) {
|
|
|
3901
4264
|
if (!m) continue;
|
|
3902
4265
|
const appVar = m[1];
|
|
3903
4266
|
found.push({
|
|
3904
|
-
relPath:
|
|
4267
|
+
relPath: relative3(cwd, p).replace(/\\/g, "/"),
|
|
3905
4268
|
appVar,
|
|
3906
4269
|
listens: listensOn(src, appVar)
|
|
3907
4270
|
});
|
|
@@ -3940,7 +4303,7 @@ function declaredEntries(cwd) {
|
|
|
3940
4303
|
const paths = /* @__PURE__ */ new Set();
|
|
3941
4304
|
let pkg;
|
|
3942
4305
|
try {
|
|
3943
|
-
pkg = JSON.parse(
|
|
4306
|
+
pkg = JSON.parse(readFileSync8(join11(cwd, "package.json"), "utf8"));
|
|
3944
4307
|
} catch {
|
|
3945
4308
|
return paths;
|
|
3946
4309
|
}
|
|
@@ -3959,24 +4322,24 @@ function declaredEntries(cwd) {
|
|
|
3959
4322
|
return paths;
|
|
3960
4323
|
}
|
|
3961
4324
|
function importSpecifier(fromRel, toRel, preserveExtension = false) {
|
|
3962
|
-
let spec =
|
|
4325
|
+
let spec = relative3(dirname4(fromRel), toRel).replace(/\\/g, "/");
|
|
3963
4326
|
if (!preserveExtension) spec = spec.replace(/\.(?:ts|js)$/, "");
|
|
3964
4327
|
if (!spec.startsWith(".")) spec = `./${spec}`;
|
|
3965
4328
|
return spec;
|
|
3966
4329
|
}
|
|
3967
4330
|
|
|
3968
4331
|
// src/protect/install/register.ts
|
|
3969
|
-
import {
|
|
3970
|
-
import { join as
|
|
4332
|
+
import { existsSync as existsSync10 } from "fs";
|
|
4333
|
+
import { join as join14, resolve as resolve3 } from "path";
|
|
3971
4334
|
|
|
3972
4335
|
// src/protect/install/generic.ts
|
|
3973
|
-
import { readFileSync as
|
|
3974
|
-
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";
|
|
3975
4338
|
|
|
3976
4339
|
// src/protect/install/source-scope.ts
|
|
3977
4340
|
import { execFileSync } from "child_process";
|
|
3978
4341
|
import { existsSync as existsSync8 } from "fs";
|
|
3979
|
-
import { dirname as
|
|
4342
|
+
import { dirname as dirname5, join as join12, resolve as resolve2 } from "path";
|
|
3980
4343
|
|
|
3981
4344
|
// src/protect/install/node-flags.ts
|
|
3982
4345
|
var flagName = (token) => token.split("=")[0];
|
|
@@ -4265,9 +4628,9 @@ function moduleKindOf(filePath, packageIsModule) {
|
|
|
4265
4628
|
return packageIsModule ? "esm" : "cjs";
|
|
4266
4629
|
}
|
|
4267
4630
|
function resolvesToGuardModule(specifier, query) {
|
|
4268
|
-
const resolved =
|
|
4269
|
-
if (withoutModuleExtension(resolved) !==
|
|
4270
|
-
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)));
|
|
4271
4634
|
if (existing.length === 0) return false;
|
|
4272
4635
|
const extension = /(\.[A-Za-z0-9]+)$/.exec(specifier)?.[1] ?? "";
|
|
4273
4636
|
if (!query.strictExtensions) return true;
|
|
@@ -4286,16 +4649,16 @@ function withoutModuleExtension(filePath) {
|
|
|
4286
4649
|
// src/protect/install/generic.ts
|
|
4287
4650
|
function packageIsEsm(cwd) {
|
|
4288
4651
|
try {
|
|
4289
|
-
return JSON.parse(read(
|
|
4652
|
+
return JSON.parse(read(join13(cwd, "package.json"))).type === "module";
|
|
4290
4653
|
} catch {
|
|
4291
4654
|
return false;
|
|
4292
4655
|
}
|
|
4293
4656
|
}
|
|
4294
4657
|
function genericDir(cwd) {
|
|
4295
|
-
return existsSync9(
|
|
4658
|
+
return existsSync9(join13(cwd, "src")) ? "src/patchstack" : "patchstack";
|
|
4296
4659
|
}
|
|
4297
4660
|
function hasTypeScript(cwd) {
|
|
4298
|
-
return existsSync9(
|
|
4661
|
+
return existsSync9(join13(cwd, "tsconfig.json")) || candidateEntries(cwd).some((entry) => /\.tsx?$/.test(entry));
|
|
4299
4662
|
}
|
|
4300
4663
|
function genericGuardTarget(cwd) {
|
|
4301
4664
|
if (hasTypeScript(cwd)) {
|
|
@@ -4309,7 +4672,7 @@ function genericGuardTarget(cwd) {
|
|
|
4309
4672
|
function genericScaffoldFiles(cwd) {
|
|
4310
4673
|
const dir = genericDir(cwd);
|
|
4311
4674
|
const candidates = ["guard.ts", "guard.js", "guard.cjs", "rules.json"];
|
|
4312
|
-
return candidates.map((file) => `${dir}/${file}`).filter((
|
|
4675
|
+
return candidates.map((file) => `${dir}/${file}`).filter((relative8) => existsSync9(join13(cwd, relative8)));
|
|
4313
4676
|
}
|
|
4314
4677
|
function scaffoldGeneric(cwd, opts, guardTemplate, guardFile) {
|
|
4315
4678
|
const target = genericGuardTarget(cwd);
|
|
@@ -4317,15 +4680,15 @@ function scaffoldGeneric(cwd, opts, guardTemplate, guardFile) {
|
|
|
4317
4680
|
guardFile ??= target.file;
|
|
4318
4681
|
const templates = templatesDir();
|
|
4319
4682
|
const dir = genericDir(cwd);
|
|
4320
|
-
const dst =
|
|
4321
|
-
|
|
4322
|
-
|
|
4683
|
+
const dst = join13(cwd, dir);
|
|
4684
|
+
ensureProjectDirectorySync(cwd, dst);
|
|
4685
|
+
copyProjectFileSync(cwd, join13(templates, guardTemplate), join13(dst, guardFile));
|
|
4323
4686
|
const guardRel = `${dir}/${guardFile}`;
|
|
4324
4687
|
const changed = [guardRel];
|
|
4325
4688
|
if (!opts.demo) bakeSiteUuid(cwd, guardRel);
|
|
4326
|
-
const rulesDst =
|
|
4689
|
+
const rulesDst = join13(dst, "rules.json");
|
|
4327
4690
|
if (opts.demo || !existsSync9(rulesDst)) {
|
|
4328
|
-
|
|
4691
|
+
copyProjectFileSync(cwd, join13(templates, opts.demo ? "demo-rules.json" : "rules.json"), rulesDst);
|
|
4329
4692
|
changed.push(`${dir}/rules.json`);
|
|
4330
4693
|
}
|
|
4331
4694
|
return { changed, dir };
|
|
@@ -4346,17 +4709,17 @@ function candidateEntries(cwd) {
|
|
|
4346
4709
|
"app.ts",
|
|
4347
4710
|
"app.js"
|
|
4348
4711
|
];
|
|
4349
|
-
const hits = names.filter((n) => existsSync9(
|
|
4712
|
+
const hits = names.filter((n) => existsSync9(join13(cwd, n)));
|
|
4350
4713
|
try {
|
|
4351
|
-
const pkg = JSON.parse(read(
|
|
4352
|
-
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);
|
|
4353
4716
|
} catch {
|
|
4354
4717
|
}
|
|
4355
4718
|
return [...new Set(hits)];
|
|
4356
4719
|
}
|
|
4357
4720
|
function usesExpress(cwd) {
|
|
4358
4721
|
try {
|
|
4359
|
-
const pkg = JSON.parse(read(
|
|
4722
|
+
const pkg = JSON.parse(read(join13(cwd, "package.json")));
|
|
4360
4723
|
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }.express);
|
|
4361
4724
|
} catch {
|
|
4362
4725
|
return false;
|
|
@@ -4460,9 +4823,9 @@ function escapeForRegExp(value) {
|
|
|
4460
4823
|
function genericVerify(cwd) {
|
|
4461
4824
|
const dir = genericDir(cwd);
|
|
4462
4825
|
const target = genericGuardTarget(cwd);
|
|
4463
|
-
const present2 = GUARD_FILENAMES.find((name) => existsSync9(
|
|
4826
|
+
const present2 = GUARD_FILENAMES.find((name) => existsSync9(join13(cwd, dir, name)));
|
|
4464
4827
|
const scaffolded = present2 !== void 0;
|
|
4465
|
-
const imported = scaffolded && guardIsImported(cwd,
|
|
4828
|
+
const imported = scaffolded && guardIsImported(cwd, join13(cwd, dir, present2));
|
|
4466
4829
|
return {
|
|
4467
4830
|
wired: scaffolded && imported,
|
|
4468
4831
|
checks: [
|
|
@@ -4496,9 +4859,9 @@ var NON_RUNTIME_DIRS = /* @__PURE__ */ new Set([
|
|
|
4496
4859
|
"__mocks__"
|
|
4497
4860
|
]);
|
|
4498
4861
|
function guardIsImported(cwd, guardPath) {
|
|
4499
|
-
const guardDir =
|
|
4862
|
+
const guardDir = dirname6(guardPath);
|
|
4500
4863
|
const packageIsModule = packageIsEsm(cwd);
|
|
4501
|
-
const root = existsSync9(
|
|
4864
|
+
const root = existsSync9(join13(cwd, "src")) ? join13(cwd, "src") : cwd;
|
|
4502
4865
|
let found = false;
|
|
4503
4866
|
const walk2 = (d, depth) => {
|
|
4504
4867
|
if (found || depth > 8) return;
|
|
@@ -4511,10 +4874,10 @@ function guardIsImported(cwd, guardPath) {
|
|
|
4511
4874
|
for (const name of entries) {
|
|
4512
4875
|
if (found) return;
|
|
4513
4876
|
if (NON_RUNTIME_DIRS.has(name) || name.startsWith(".")) continue;
|
|
4514
|
-
const p =
|
|
4877
|
+
const p = join13(d, name);
|
|
4515
4878
|
let st;
|
|
4516
4879
|
try {
|
|
4517
|
-
st =
|
|
4880
|
+
st = lstatSync3(p);
|
|
4518
4881
|
} catch {
|
|
4519
4882
|
continue;
|
|
4520
4883
|
}
|
|
@@ -4522,7 +4885,7 @@ function guardIsImported(cwd, guardPath) {
|
|
|
4522
4885
|
if (st.isDirectory()) walk2(p, depth + 1);
|
|
4523
4886
|
else if (/\.(?:ts|tsx|js|jsx|mjs|cjs)$/.test(name) && p !== guardPath) {
|
|
4524
4887
|
try {
|
|
4525
|
-
if (importsAndUsesGuard(
|
|
4888
|
+
if (importsAndUsesGuard(readFileSync9(p, "utf8"), p, guardDir, packageIsModule)) found = true;
|
|
4526
4889
|
} catch {
|
|
4527
4890
|
}
|
|
4528
4891
|
}
|
|
@@ -4570,7 +4933,7 @@ function guardTarget(cwd, entryRel, spec) {
|
|
|
4570
4933
|
}
|
|
4571
4934
|
function packageType(cwd) {
|
|
4572
4935
|
try {
|
|
4573
|
-
return JSON.parse(read(
|
|
4936
|
+
return JSON.parse(read(join14(cwd, "package.json"))).type;
|
|
4574
4937
|
} catch {
|
|
4575
4938
|
return void 0;
|
|
4576
4939
|
}
|
|
@@ -4606,8 +4969,8 @@ function bindsGuard(line, maskedLine, importName, call, fileRel, guard) {
|
|
|
4606
4969
|
}
|
|
4607
4970
|
function namesGuardModule(specifier, fileRel, guard) {
|
|
4608
4971
|
return resolvesToGuardModule(specifier, {
|
|
4609
|
-
fromFile:
|
|
4610
|
-
guardDir:
|
|
4972
|
+
fromFile: join14(guard.cwd, fileRel),
|
|
4973
|
+
guardDir: resolve3(join14(guard.cwd, guard.guardDir)),
|
|
4611
4974
|
kind: moduleKindOf(fileRel, packageType(guard.cwd) === "module"),
|
|
4612
4975
|
strictExtensions: true
|
|
4613
4976
|
});
|
|
@@ -4626,7 +4989,7 @@ function wireRegister(cwd, opts, spec) {
|
|
|
4626
4989
|
}
|
|
4627
4990
|
const target = guardTarget(cwd, entry.relPath, spec);
|
|
4628
4991
|
const { changed, dir } = scaffoldGeneric(cwd, opts, target.template, target.file);
|
|
4629
|
-
const p =
|
|
4992
|
+
const p = join14(cwd, entry.relPath);
|
|
4630
4993
|
const s = read(p);
|
|
4631
4994
|
const state = wiringState(s, spec, entry.appVar, entry.relPath, { cwd, guardDir: dir });
|
|
4632
4995
|
if (state.importAtTopLevel && state.callInAppScope) {
|
|
@@ -4665,7 +5028,7 @@ function wireRegister(cwd, opts, spec) {
|
|
|
4665
5028
|
lines.splice(callIdx + 1, 0, REGION, spec.call(entry.appVar), "// #endregion patchstack");
|
|
4666
5029
|
}
|
|
4667
5030
|
const patched = lines.join("\n");
|
|
4668
|
-
|
|
5031
|
+
writeProjectFileSync(cwd, p, patched, { encoding: "utf8" });
|
|
4669
5032
|
if (spec.callAfter) {
|
|
4670
5033
|
const guardIndex = lines.findIndex((line) => line.includes(spec.call(entry.appVar)));
|
|
4671
5034
|
const early = routesBefore(patched, entry.appVar, guardIndex);
|
|
@@ -4677,7 +5040,7 @@ function wireRegister(cwd, opts, spec) {
|
|
|
4677
5040
|
}
|
|
4678
5041
|
const parsed = parses(p);
|
|
4679
5042
|
if (parsed === false) {
|
|
4680
|
-
|
|
5043
|
+
writeProjectFileSync(cwd, p, s, { encoding: "utf8" });
|
|
4681
5044
|
log(`${entry.relPath} would not parse after patching \u2014 reverted; ${spec.manualHint}`);
|
|
4682
5045
|
return { ok: true, changed };
|
|
4683
5046
|
}
|
|
@@ -4696,17 +5059,17 @@ function firstStatementLine(lines) {
|
|
|
4696
5059
|
return i;
|
|
4697
5060
|
}
|
|
4698
5061
|
function serverIsGuarded(cwd, spec, server, guardDir) {
|
|
4699
|
-
const source = read(
|
|
5062
|
+
const source = read(join14(cwd, server.relPath));
|
|
4700
5063
|
const state = wiringState(source, spec, server.appVar, server.relPath, { cwd, guardDir });
|
|
4701
5064
|
if (!state.importAtTopLevel || !state.callInAppScope || !state.ordered) return false;
|
|
4702
5065
|
return !spec.callAfter || routesBefore(source, server.appVar, state.callIndex).length === 0;
|
|
4703
5066
|
}
|
|
4704
5067
|
function verifyRegister(cwd, spec) {
|
|
4705
|
-
const dir = existsSync10(
|
|
5068
|
+
const dir = existsSync10(join14(cwd, "src")) ? "src/patchstack" : "patchstack";
|
|
4706
5069
|
const entry = findAppInstance(cwd, spec.appRe);
|
|
4707
5070
|
const target = entry ? guardTarget(cwd, entry.relPath, spec) : null;
|
|
4708
|
-
const scaffolded = target ? existsSync10(
|
|
4709
|
-
const entrySource = entry ? read(
|
|
5071
|
+
const scaffolded = target ? existsSync10(join14(cwd, dir, target.file)) : false;
|
|
5072
|
+
const entrySource = entry ? read(join14(cwd, entry.relPath)) : "";
|
|
4710
5073
|
const state = entry ? wiringState(entrySource, spec, entry.appVar, entry.relPath, { cwd, guardDir: dir }) : { importAtTopLevel: false, callInAppScope: false, ordered: false, callIndex: -1, appIndex: -1 };
|
|
4711
5074
|
const wired = state.importAtTopLevel && state.callInAppScope && state.ordered;
|
|
4712
5075
|
const early = spec.callAfter && entry ? routesBefore(entrySource, entry.appVar, state.callIndex) : [];
|
|
@@ -4797,7 +5160,7 @@ var expressAdapter = {
|
|
|
4797
5160
|
|
|
4798
5161
|
// src/protect/install/reporting.ts
|
|
4799
5162
|
import { existsSync as existsSync11 } from "fs";
|
|
4800
|
-
import { join as
|
|
5163
|
+
import { join as join15 } from "path";
|
|
4801
5164
|
|
|
4802
5165
|
// src/protect/reporting-state.js
|
|
4803
5166
|
var REPORTING_STATES = Object.freeze([
|
|
@@ -4855,7 +5218,7 @@ function explainReportingState(state) {
|
|
|
4855
5218
|
|
|
4856
5219
|
// src/protect/install/reporting.ts
|
|
4857
5220
|
function siteIdentity(cwd, env) {
|
|
4858
|
-
const rc =
|
|
5221
|
+
const rc = join15(cwd, ".patchstackrc.json");
|
|
4859
5222
|
let recorded;
|
|
4860
5223
|
if (existsSync11(rc)) {
|
|
4861
5224
|
try {
|
|
@@ -5006,7 +5369,7 @@ function runVerify(cwd) {
|
|
|
5006
5369
|
}
|
|
5007
5370
|
|
|
5008
5371
|
// src/widget.ts
|
|
5009
|
-
import { existsSync as existsSync12, readFileSync as
|
|
5372
|
+
import { existsSync as existsSync12, readFileSync as readFileSync10 } from "fs";
|
|
5010
5373
|
import path11 from "path";
|
|
5011
5374
|
var WIDGET_SCRIPT_URL = "https://cdn.patchstack.com/patchstack-widget.js";
|
|
5012
5375
|
var WIDGET_MARKER_ATTR = "data-patchstack-connect-widget";
|
|
@@ -5051,16 +5414,19 @@ function findSourceShell(cwd) {
|
|
|
5051
5414
|
}
|
|
5052
5415
|
return null;
|
|
5053
5416
|
}
|
|
5054
|
-
function ensureSourceWidget(cwd, siteUuid) {
|
|
5055
|
-
const shell = findSourceShell(cwd);
|
|
5417
|
+
function ensureSourceWidget(cwd, siteUuid, jsxShell = null) {
|
|
5418
|
+
const shell = findSourceShell(cwd) ?? jsxShell;
|
|
5056
5419
|
if (shell === null) {
|
|
5057
5420
|
return { shell: null, action: "no-shell" };
|
|
5058
5421
|
}
|
|
5059
5422
|
const file = path11.join(cwd, shell);
|
|
5060
|
-
|
|
5423
|
+
if (!existsSync12(file)) {
|
|
5424
|
+
return { shell: null, action: "no-shell" };
|
|
5425
|
+
}
|
|
5426
|
+
const before = readFileSync10(file, "utf8");
|
|
5061
5427
|
const { html, action } = ensureWidgetInHtml(before, siteUuid);
|
|
5062
5428
|
if (html !== before) {
|
|
5063
|
-
|
|
5429
|
+
writeProjectFileSync(cwd, file, html, { encoding: "utf8" });
|
|
5064
5430
|
}
|
|
5065
5431
|
return { shell, action };
|
|
5066
5432
|
}
|
|
@@ -5150,7 +5516,7 @@ function installCommand(pm) {
|
|
|
5150
5516
|
}
|
|
5151
5517
|
function readPackageJson(cwd) {
|
|
5152
5518
|
try {
|
|
5153
|
-
return JSON.parse(
|
|
5519
|
+
return JSON.parse(readFileSync11(path12.join(cwd, "package.json"), "utf8"));
|
|
5154
5520
|
} catch {
|
|
5155
5521
|
return null;
|
|
5156
5522
|
}
|
|
@@ -5158,7 +5524,7 @@ function readPackageJson(cwd) {
|
|
|
5158
5524
|
function installedVersion(cwd, declaredRange) {
|
|
5159
5525
|
try {
|
|
5160
5526
|
const pkg = JSON.parse(
|
|
5161
|
-
|
|
5527
|
+
readFileSync11(path12.join(cwd, "node_modules", "@patchstack", "connect", "package.json"), "utf8")
|
|
5162
5528
|
);
|
|
5163
5529
|
if (typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
5164
5530
|
return pkg.version;
|
|
@@ -5199,10 +5565,10 @@ function findWidgetMarker(cwd, siteUuid) {
|
|
|
5199
5565
|
}
|
|
5200
5566
|
budget -= 1;
|
|
5201
5567
|
try {
|
|
5202
|
-
if (
|
|
5568
|
+
if (statSync5(full).size > WIDGET_SCAN_MAX_BYTES) {
|
|
5203
5569
|
continue;
|
|
5204
5570
|
}
|
|
5205
|
-
const content =
|
|
5571
|
+
const content = readFileSync11(full, "utf8");
|
|
5206
5572
|
if (!content.includes(WIDGET_NEEDLE2)) {
|
|
5207
5573
|
continue;
|
|
5208
5574
|
}
|
|
@@ -5225,7 +5591,7 @@ function findProductionMarker(cwd, widgetFileHint) {
|
|
|
5225
5591
|
return false;
|
|
5226
5592
|
}
|
|
5227
5593
|
try {
|
|
5228
|
-
return
|
|
5594
|
+
return readFileSync11(path12.join(cwd, widgetFileHint), "utf8").includes(PROD_MARKER_NEEDLE);
|
|
5229
5595
|
} catch {
|
|
5230
5596
|
return false;
|
|
5231
5597
|
}
|
|
@@ -5264,7 +5630,7 @@ async function collectGuideState(cwd) {
|
|
|
5264
5630
|
try {
|
|
5265
5631
|
const config = await resolveConfig({ cwd });
|
|
5266
5632
|
siteUuid = config.siteUuid;
|
|
5267
|
-
if (siteUuid !== null) {
|
|
5633
|
+
if (siteUuid !== null && config.endpointTrusted !== false) {
|
|
5268
5634
|
claimUrl = buildClaimUrl(config.endpoint, siteUuid);
|
|
5269
5635
|
}
|
|
5270
5636
|
if (config.endpoint !== DEFAULT_ENDPOINT) {
|
|
@@ -5528,10 +5894,19 @@ import { tmpdir as tmpdir2 } from "os";
|
|
|
5528
5894
|
import path14 from "path";
|
|
5529
5895
|
|
|
5530
5896
|
// src/device-flow.ts
|
|
5531
|
-
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";
|
|
5532
5907
|
import { tmpdir } from "os";
|
|
5533
5908
|
import path13 from "path";
|
|
5534
|
-
import { createHash as createHash2 } from "crypto";
|
|
5909
|
+
import { createHash as createHash2, randomUUID as randomUUID3 } from "crypto";
|
|
5535
5910
|
function baseFrom(manifestEndpoint) {
|
|
5536
5911
|
const url = new URL(manifestEndpoint);
|
|
5537
5912
|
const p = url.pathname.replace(/\/$/, "");
|
|
@@ -5545,16 +5920,61 @@ function pendingPath(siteUuid, intent) {
|
|
|
5545
5920
|
return path13.join(tmpdir(), `patchstack-${intent}-${key}.json`);
|
|
5546
5921
|
}
|
|
5547
5922
|
function savePending(siteUuid, intent, pending) {
|
|
5548
|
-
|
|
5549
|
-
|
|
5550
|
-
|
|
5551
|
-
|
|
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
|
+
}
|
|
5552
5956
|
}
|
|
5553
5957
|
function readPending(siteUuid, intent) {
|
|
5958
|
+
let descriptor = null;
|
|
5554
5959
|
try {
|
|
5555
|
-
|
|
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"));
|
|
5556
5969
|
} catch {
|
|
5557
5970
|
return null;
|
|
5971
|
+
} finally {
|
|
5972
|
+
if (descriptor !== null) {
|
|
5973
|
+
try {
|
|
5974
|
+
closeSync2(descriptor);
|
|
5975
|
+
} catch {
|
|
5976
|
+
}
|
|
5977
|
+
}
|
|
5558
5978
|
}
|
|
5559
5979
|
}
|
|
5560
5980
|
function clearPending(siteUuid, intent) {
|
|
@@ -5572,17 +5992,25 @@ async function requestDeviceCode(config, intent, conflictMessage, deps = {}) {
|
|
|
5572
5992
|
message: "No site UUID configured \u2014 run `patchstack-connect scan` first."
|
|
5573
5993
|
};
|
|
5574
5994
|
}
|
|
5575
|
-
|
|
5576
|
-
|
|
5577
|
-
|
|
5578
|
-
|
|
5579
|
-
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5585
|
-
|
|
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
|
+
}
|
|
5586
6014
|
if (started.status === 409) return { status: "conflict", message: conflictMessage };
|
|
5587
6015
|
if (started.status === 404) {
|
|
5588
6016
|
return { status: "not-found", message: "Patchstack does not recognise this site UUID." };
|
|
@@ -5590,34 +6018,76 @@ async function requestDeviceCode(config, intent, conflictMessage, deps = {}) {
|
|
|
5590
6018
|
if (!started.ok) {
|
|
5591
6019
|
return { status: "failed", message: `Could not start the request (HTTP ${started.status}).` };
|
|
5592
6020
|
}
|
|
5593
|
-
|
|
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
|
+
}
|
|
5594
6036
|
const pending = {
|
|
5595
|
-
deviceCode
|
|
5596
|
-
userCode
|
|
6037
|
+
deviceCode,
|
|
6038
|
+
userCode,
|
|
5597
6039
|
// Points at the API, which redirects to the dashboard SPA — the CLI only knows the API origin,
|
|
5598
6040
|
// and the approval page lives on the app.
|
|
5599
|
-
verificationUri: `${base}/device?code=${encodeURIComponent(
|
|
5600
|
-
expiresAt: now() +
|
|
5601
|
-
intervalMs:
|
|
6041
|
+
verificationUri: `${base}/device?code=${encodeURIComponent(userCode)}`,
|
|
6042
|
+
expiresAt: now() + expiresIn * 1e3,
|
|
6043
|
+
intervalMs: interval * 1e3
|
|
5602
6044
|
};
|
|
5603
6045
|
savePending(config.siteUuid, intent, pending);
|
|
5604
6046
|
return { status: "started", pending };
|
|
5605
6047
|
}
|
|
5606
6048
|
async function pollDeviceToken(config, intent, pending, deps = {}) {
|
|
5607
6049
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5611
|
-
|
|
5612
|
-
|
|
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
|
+
}
|
|
5613
6063
|
if (polled.status === 428) return { state: "pending" };
|
|
5614
6064
|
if (!polled.ok) {
|
|
5615
6065
|
if (config.siteUuid !== null) clearPending(config.siteUuid, intent);
|
|
5616
6066
|
return { state: "expired" };
|
|
5617
6067
|
}
|
|
5618
|
-
|
|
6068
|
+
let raw;
|
|
6069
|
+
try {
|
|
6070
|
+
raw = await readBoundedJson(polled);
|
|
6071
|
+
} catch {
|
|
6072
|
+
raw = null;
|
|
6073
|
+
}
|
|
5619
6074
|
if (config.siteUuid !== null) clearPending(config.siteUuid, intent);
|
|
5620
|
-
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
|
+
};
|
|
5621
6091
|
}
|
|
5622
6092
|
async function pollUntilApproved(config, intent, pending, deps = {}) {
|
|
5623
6093
|
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
@@ -5719,11 +6189,11 @@ async function claim2(config, onPrompt, deps = {}) {
|
|
|
5719
6189
|
}
|
|
5720
6190
|
|
|
5721
6191
|
// src/protect/install/runtime/check.ts
|
|
5722
|
-
import { relative as
|
|
6192
|
+
import { relative as relative4 } from "path";
|
|
5723
6193
|
|
|
5724
6194
|
// src/protect/install/runtime/entry.ts
|
|
5725
|
-
import { existsSync as existsSync14, readFileSync as
|
|
5726
|
-
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";
|
|
5727
6197
|
var CONVENTIONAL = [
|
|
5728
6198
|
"server.js",
|
|
5729
6199
|
"server.mjs",
|
|
@@ -5749,31 +6219,31 @@ var CONVENTIONAL = [
|
|
|
5749
6219
|
var LOADABLE = /\.(?:js|mjs|cjs)$/;
|
|
5750
6220
|
function readPackage2(cwd) {
|
|
5751
6221
|
try {
|
|
5752
|
-
const parsed = JSON.parse(
|
|
6222
|
+
const parsed = JSON.parse(readFileSync13(join16(cwd, "package.json"), "utf8"));
|
|
5753
6223
|
return parsed !== null && typeof parsed === "object" ? parsed : null;
|
|
5754
6224
|
} catch {
|
|
5755
6225
|
return null;
|
|
5756
6226
|
}
|
|
5757
6227
|
}
|
|
5758
|
-
function isFile(
|
|
6228
|
+
function isFile(path18) {
|
|
5759
6229
|
try {
|
|
5760
|
-
return
|
|
6230
|
+
return statSync6(path18).isFile();
|
|
5761
6231
|
} catch {
|
|
5762
6232
|
return false;
|
|
5763
6233
|
}
|
|
5764
6234
|
}
|
|
5765
6235
|
function loadable(cwd, candidate) {
|
|
5766
|
-
const written =
|
|
6236
|
+
const written = resolve4(cwd, candidate);
|
|
5767
6237
|
if (!LOADABLE.test(written) || !isFile(written)) return null;
|
|
5768
6238
|
let root;
|
|
5769
|
-
let
|
|
6239
|
+
let path18;
|
|
5770
6240
|
try {
|
|
5771
|
-
root =
|
|
5772
|
-
|
|
6241
|
+
root = realpathSync4(cwd);
|
|
6242
|
+
path18 = realpathSync4(written);
|
|
5773
6243
|
} catch {
|
|
5774
6244
|
return null;
|
|
5775
6245
|
}
|
|
5776
|
-
if (
|
|
6246
|
+
if (path18 !== root && !path18.startsWith(root + sep2)) return null;
|
|
5777
6247
|
return written;
|
|
5778
6248
|
}
|
|
5779
6249
|
function passable(token) {
|
|
@@ -5819,7 +6289,7 @@ function resolveEntry(cwd) {
|
|
|
5819
6289
|
}
|
|
5820
6290
|
return {
|
|
5821
6291
|
kind: "unavailable",
|
|
5822
|
-
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"
|
|
5823
6293
|
};
|
|
5824
6294
|
}
|
|
5825
6295
|
|
|
@@ -5853,8 +6323,8 @@ function preloadPath() {
|
|
|
5853
6323
|
// beside this file, in the repository
|
|
5854
6324
|
];
|
|
5855
6325
|
for (const candidate of candidates) {
|
|
5856
|
-
const
|
|
5857
|
-
if (existsSync15(
|
|
6326
|
+
const path18 = fileURLToPath2(candidate);
|
|
6327
|
+
if (existsSync15(path18)) return path18;
|
|
5858
6328
|
}
|
|
5859
6329
|
return null;
|
|
5860
6330
|
}
|
|
@@ -5888,7 +6358,7 @@ async function askListener(listener, challenge, answer, budgetMs) {
|
|
|
5888
6358
|
timeout: budgetMs
|
|
5889
6359
|
};
|
|
5890
6360
|
if (listener.scheme === "https") options.agent = new HttpsAgent({ rejectUnauthorized: false });
|
|
5891
|
-
return new Promise((
|
|
6361
|
+
return new Promise((resolve5) => {
|
|
5892
6362
|
const req = send(options, (res) => {
|
|
5893
6363
|
const body = [];
|
|
5894
6364
|
let read2 = 0;
|
|
@@ -5902,7 +6372,7 @@ async function askListener(listener, challenge, answer, budgetMs) {
|
|
|
5902
6372
|
});
|
|
5903
6373
|
const finish = () => {
|
|
5904
6374
|
const text = Buffer.concat(body).toString("utf8").trim();
|
|
5905
|
-
|
|
6375
|
+
resolve5({
|
|
5906
6376
|
...listener,
|
|
5907
6377
|
outcome: text === answer ? "traversed" : "answered-without-sentinel",
|
|
5908
6378
|
detail: text === answer ? void 0 : `answered with ${res.statusCode ?? "?"} and no sentinel`
|
|
@@ -5912,7 +6382,7 @@ async function askListener(listener, challenge, answer, budgetMs) {
|
|
|
5912
6382
|
res.on("close", finish);
|
|
5913
6383
|
});
|
|
5914
6384
|
req.on("timeout", () => req.destroy(new Error("timed out")));
|
|
5915
|
-
req.on("error", (err) =>
|
|
6385
|
+
req.on("error", (err) => resolve5({ ...listener, outcome: "unreachable", detail: err.message }));
|
|
5916
6386
|
req.end();
|
|
5917
6387
|
});
|
|
5918
6388
|
}
|
|
@@ -6006,12 +6476,12 @@ async function probeRuntimeTraversal(options) {
|
|
|
6006
6476
|
let onFrozen = null;
|
|
6007
6477
|
const freeze = async () => {
|
|
6008
6478
|
if (!child.connected) return false;
|
|
6009
|
-
return new Promise((
|
|
6479
|
+
return new Promise((resolve5) => {
|
|
6010
6480
|
const settle2 = (confirmed2) => {
|
|
6011
6481
|
clearTimeout(timer);
|
|
6012
6482
|
onFrozen = null;
|
|
6013
6483
|
child.removeListener("exit", onGone);
|
|
6014
|
-
|
|
6484
|
+
resolve5(confirmed2);
|
|
6015
6485
|
};
|
|
6016
6486
|
const onGone = () => settle2(false);
|
|
6017
6487
|
const timer = setTimeout(() => settle2(false), Math.min(FREEZE_MS, remaining()));
|
|
@@ -6024,13 +6494,13 @@ async function probeRuntimeTraversal(options) {
|
|
|
6024
6494
|
}
|
|
6025
6495
|
});
|
|
6026
6496
|
};
|
|
6027
|
-
await new Promise((
|
|
6497
|
+
await new Promise((resolve5) => {
|
|
6028
6498
|
let settleTimer = null;
|
|
6029
6499
|
const done = () => {
|
|
6030
6500
|
discovering = false;
|
|
6031
6501
|
clearTimeout(runTimer);
|
|
6032
6502
|
if (settleTimer) clearTimeout(settleTimer);
|
|
6033
|
-
|
|
6503
|
+
resolve5();
|
|
6034
6504
|
};
|
|
6035
6505
|
const runTimer = setTimeout(done, remaining());
|
|
6036
6506
|
const settle2 = () => {
|
|
@@ -6170,7 +6640,7 @@ async function runRuntimeCheck(cwd, options = {}) {
|
|
|
6170
6640
|
settleMs: options.settleMs,
|
|
6171
6641
|
platform: options.platform
|
|
6172
6642
|
});
|
|
6173
|
-
return { ...result, entry: { file:
|
|
6643
|
+
return { ...result, entry: { file: relative4(cwd, entry.file).replace(/\\/g, "/"), from: entry.from } };
|
|
6174
6644
|
}
|
|
6175
6645
|
function runtimeExitCode(report) {
|
|
6176
6646
|
if (report.outcome === "proven") return 0;
|
|
@@ -6201,15 +6671,15 @@ function formatRuntimeCheck(report) {
|
|
|
6201
6671
|
}
|
|
6202
6672
|
|
|
6203
6673
|
// src/map-command.ts
|
|
6204
|
-
import
|
|
6674
|
+
import path15 from "path";
|
|
6205
6675
|
|
|
6206
6676
|
// src/map/ts-loader.ts
|
|
6207
6677
|
import { createRequire } from "module";
|
|
6208
6678
|
import { pathToFileURL } from "url";
|
|
6209
|
-
import { join as
|
|
6679
|
+
import { join as join17 } from "path";
|
|
6210
6680
|
async function loadTypeScript(cwd) {
|
|
6211
6681
|
try {
|
|
6212
|
-
const req = createRequire(pathToFileURL(
|
|
6682
|
+
const req = createRequire(pathToFileURL(join17(cwd, "package.json")));
|
|
6213
6683
|
const resolved = req.resolve("typescript");
|
|
6214
6684
|
const mod = await import(pathToFileURL(resolved).href);
|
|
6215
6685
|
return mod.default ?? mod;
|
|
@@ -6224,9 +6694,9 @@ async function loadTypeScript(cwd) {
|
|
|
6224
6694
|
}
|
|
6225
6695
|
|
|
6226
6696
|
// src/map/extract.ts
|
|
6227
|
-
import { readFileSync as
|
|
6697
|
+
import { readFileSync as readFileSync16, realpathSync as realpathSync6 } from "fs";
|
|
6228
6698
|
import { createHash as createHash5 } from "crypto";
|
|
6229
|
-
import { relative as
|
|
6699
|
+
import { relative as relative6 } from "path";
|
|
6230
6700
|
|
|
6231
6701
|
// src/map/bindings.ts
|
|
6232
6702
|
import { builtinModules } from "module";
|
|
@@ -6454,14 +6924,14 @@ function directSinks(node, ts, bindings, ctx) {
|
|
|
6454
6924
|
const root = base ? rootIdentifier(base, ts) : void 0;
|
|
6455
6925
|
if (!root) return {};
|
|
6456
6926
|
const spec = bindings.resolve(root);
|
|
6457
|
-
const
|
|
6927
|
+
const relative8 = spec !== void 0 && (spec.startsWith(".") || spec.startsWith("/"));
|
|
6458
6928
|
const pkg = npmPackageOf(spec);
|
|
6459
6929
|
if (pkg) return { pkg, root, spec };
|
|
6460
|
-
if (
|
|
6930
|
+
if (relative8 && ctx && spec) {
|
|
6461
6931
|
const viaModule = ctx.graph.importedBinding(ctx.file, spec, bindings.exportNameOf(root) ?? root);
|
|
6462
6932
|
if (viaModule) return { pkg: viaModule.package, root, spec };
|
|
6463
6933
|
}
|
|
6464
|
-
return { local: bindings.locals.has(root), root, spec, relative:
|
|
6934
|
+
return { local: bindings.locals.has(root), root, spec, relative: relative8 };
|
|
6465
6935
|
};
|
|
6466
6936
|
const attributionOf = (b, pkg) => b.pkg ? "import" : pkg ? "inferred" : void 0;
|
|
6467
6937
|
const dbApi = (pkg, attribution) => {
|
|
@@ -6638,8 +7108,8 @@ function dedupeSinks(sinks, owner) {
|
|
|
6638
7108
|
}
|
|
6639
7109
|
|
|
6640
7110
|
// src/map/module-graph.ts
|
|
6641
|
-
import { readFileSync as
|
|
6642
|
-
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";
|
|
6643
7113
|
var RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
6644
7114
|
function createModuleGraph(ts, opts) {
|
|
6645
7115
|
const cache = /* @__PURE__ */ new Map();
|
|
@@ -6647,7 +7117,7 @@ function createModuleGraph(ts, opts) {
|
|
|
6647
7117
|
if (cache.has(file)) return cache.get(file) ?? null;
|
|
6648
7118
|
let entry = null;
|
|
6649
7119
|
try {
|
|
6650
|
-
const text =
|
|
7120
|
+
const text = readFileSync14(file, "utf8");
|
|
6651
7121
|
const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, file));
|
|
6652
7122
|
const bindings = buildModuleBindings(sf, ts);
|
|
6653
7123
|
entry = { fnSinks: collectLocalSinks(sf, ts, bindings), calleesOf: collectCallees(sf, ts), bindings };
|
|
@@ -6663,7 +7133,7 @@ function createModuleGraph(ts, opts) {
|
|
|
6663
7133
|
if (!opts.followOutside) {
|
|
6664
7134
|
let real = target;
|
|
6665
7135
|
try {
|
|
6666
|
-
real =
|
|
7136
|
+
real = realpathSync5(target);
|
|
6667
7137
|
} catch {
|
|
6668
7138
|
}
|
|
6669
7139
|
if (!isInside(real, opts.boundary)) return void 0;
|
|
@@ -6681,7 +7151,7 @@ function createModuleGraph(ts, opts) {
|
|
|
6681
7151
|
for (const callee of mod.calleesOf.get(exportName) ?? []) {
|
|
6682
7152
|
for (const s of mod.fnSinks.get(callee) ?? []) collected.push(s);
|
|
6683
7153
|
}
|
|
6684
|
-
const rel =
|
|
7154
|
+
const rel = relative5(opts.cwd, target);
|
|
6685
7155
|
return collected.map((s) => ({ ...s, file: rel }));
|
|
6686
7156
|
},
|
|
6687
7157
|
// A different question about the same module: not "what sinks are in there" but "what does this
|
|
@@ -6707,7 +7177,7 @@ function createModuleGraph(ts, opts) {
|
|
|
6707
7177
|
}
|
|
6708
7178
|
function resolveRelativeModule(fromFile, spec) {
|
|
6709
7179
|
if (!spec.startsWith(".")) return void 0;
|
|
6710
|
-
const dir =
|
|
7180
|
+
const dir = dirname8(fromFile);
|
|
6711
7181
|
const base = resolvePath(dir, spec);
|
|
6712
7182
|
const candidates = [];
|
|
6713
7183
|
const jsLike = /\.(js|jsx|mjs|cjs)$/.exec(base);
|
|
@@ -6717,10 +7187,10 @@ function resolveRelativeModule(fromFile, spec) {
|
|
|
6717
7187
|
}
|
|
6718
7188
|
candidates.push(base);
|
|
6719
7189
|
for (const e of RESOLVE_EXTS) candidates.push(base + e);
|
|
6720
|
-
for (const e of RESOLVE_EXTS) candidates.push(
|
|
7190
|
+
for (const e of RESOLVE_EXTS) candidates.push(join18(base, "index" + e));
|
|
6721
7191
|
for (const c of candidates) {
|
|
6722
7192
|
try {
|
|
6723
|
-
if (
|
|
7193
|
+
if (statSync7(c).isFile()) return c;
|
|
6724
7194
|
} catch {
|
|
6725
7195
|
}
|
|
6726
7196
|
}
|
|
@@ -6746,8 +7216,8 @@ function collectCallees(sf, ts) {
|
|
|
6746
7216
|
}
|
|
6747
7217
|
|
|
6748
7218
|
// src/map/coordinates.ts
|
|
6749
|
-
function runtimeCoordinate(source,
|
|
6750
|
-
if (/\[\d*\]/.test(
|
|
7219
|
+
function runtimeCoordinate(source, path18) {
|
|
7220
|
+
if (/\[\d*\]/.test(path18)) {
|
|
6751
7221
|
return { runtimeParameter: null, runtimeParameterReason: "array traversal: needs an array_key_value rule, not a dotted parameter" };
|
|
6752
7222
|
}
|
|
6753
7223
|
switch (source) {
|
|
@@ -6756,15 +7226,15 @@ function runtimeCoordinate(source, path17) {
|
|
|
6756
7226
|
case "multipart":
|
|
6757
7227
|
case "body":
|
|
6758
7228
|
case "server-fn-data":
|
|
6759
|
-
return { runtimeParameter: `post.${
|
|
7229
|
+
return { runtimeParameter: `post.${path18}` };
|
|
6760
7230
|
case "query":
|
|
6761
|
-
return { runtimeParameter: `get.${
|
|
7231
|
+
return { runtimeParameter: `get.${path18}` };
|
|
6762
7232
|
case "cookie":
|
|
6763
|
-
return { runtimeParameter: `cookie.${
|
|
7233
|
+
return { runtimeParameter: `cookie.${path18}` };
|
|
6764
7234
|
case "file":
|
|
6765
|
-
return { runtimeParameter: `files.${
|
|
7235
|
+
return { runtimeParameter: `files.${path18}` };
|
|
6766
7236
|
case "header":
|
|
6767
|
-
return { runtimeParameter: `server.HTTP_${
|
|
7237
|
+
return { runtimeParameter: `server.HTTP_${path18.toUpperCase().replace(/-/g, "_")}` };
|
|
6768
7238
|
case "route-param":
|
|
6769
7239
|
return { runtimeParameter: null, runtimeParameterReason: "route parameters are not exposed by the runtime resolver" };
|
|
6770
7240
|
default:
|
|
@@ -6796,8 +7266,8 @@ function addressSpaceOf(source) {
|
|
|
6796
7266
|
function isProvenFlow(confidence) {
|
|
6797
7267
|
return confidence === "exact-local" || confidence === "transformed-local";
|
|
6798
7268
|
}
|
|
6799
|
-
function inputIdOf(source,
|
|
6800
|
-
return `${addressSpaceOf(source)}:${
|
|
7269
|
+
function inputIdOf(source, path18) {
|
|
7270
|
+
return `${addressSpaceOf(source)}:${path18}`;
|
|
6801
7271
|
}
|
|
6802
7272
|
function withCoordinates(fields, source) {
|
|
6803
7273
|
return fields.map((f) => {
|
|
@@ -6989,6 +7459,7 @@ function requestMemberAccesses(params, body, ts, opts = {}) {
|
|
|
6989
7459
|
}
|
|
6990
7460
|
|
|
6991
7461
|
// src/map/flows.ts
|
|
7462
|
+
var MAX_DEPENDENCY_INPUT_FLOWS_PER_ENDPOINT = 100;
|
|
6992
7463
|
function spaceOfKey(key) {
|
|
6993
7464
|
if (key === "query") return "get";
|
|
6994
7465
|
if (key === "params") return "route-param";
|
|
@@ -6999,16 +7470,23 @@ function spaceOfKey(key) {
|
|
|
6999
7470
|
if (key === "files") return "files";
|
|
7000
7471
|
return void 0;
|
|
7001
7472
|
}
|
|
7002
|
-
function linkedFlows(body, params, inputs, sinks, ts) {
|
|
7003
|
-
const { flows, limitations } = linkFlows(body, params, inputs, sinks, ts);
|
|
7004
|
-
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
|
+
};
|
|
7005
7481
|
}
|
|
7006
|
-
function linkFlows(bodyNode, params, inputs, sinks, ts) {
|
|
7007
|
-
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
|
+
}
|
|
7008
7486
|
const CONTAINER_KEYS = /* @__PURE__ */ new Set([...REQ_SOURCES, "data", "input", "payload"]);
|
|
7009
7487
|
const rootPath = /* @__PURE__ */ new Map();
|
|
7010
|
-
const addRoot = (name,
|
|
7011
|
-
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 });
|
|
7012
7490
|
};
|
|
7013
7491
|
for (const p of params ?? []) {
|
|
7014
7492
|
if (!p?.name) continue;
|
|
@@ -7139,7 +7617,57 @@ function linkFlows(bodyNode, params, inputs, sinks, ts) {
|
|
|
7139
7617
|
}
|
|
7140
7618
|
for (const l of sinkLimits) allLimits.push(l);
|
|
7141
7619
|
}
|
|
7142
|
-
|
|
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 };
|
|
7143
7671
|
}
|
|
7144
7672
|
function dedupeLimitations(list) {
|
|
7145
7673
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -7153,8 +7681,8 @@ function dedupeLimitations(list) {
|
|
|
7153
7681
|
function join22(base, seg) {
|
|
7154
7682
|
return base ? `${base}.${seg}` : seg;
|
|
7155
7683
|
}
|
|
7156
|
-
function normalizePath(
|
|
7157
|
-
return
|
|
7684
|
+
function normalizePath(path18) {
|
|
7685
|
+
return path18.replace(/\[\d*\]/g, "").split(".").filter(Boolean).join(".");
|
|
7158
7686
|
}
|
|
7159
7687
|
function fluentChainCalls(call, ts) {
|
|
7160
7688
|
let root = call;
|
|
@@ -7185,7 +7713,7 @@ function fluentChainCalls(call, ts) {
|
|
|
7185
7713
|
collect(root);
|
|
7186
7714
|
return out;
|
|
7187
7715
|
}
|
|
7188
|
-
function taintedReadPaths(node, ts, rootPath) {
|
|
7716
|
+
function taintedReadPaths(node, ts, rootPath, includeDeferredBodies = true) {
|
|
7189
7717
|
const out = [];
|
|
7190
7718
|
const seen = /* @__PURE__ */ new Set();
|
|
7191
7719
|
const add = (r) => {
|
|
@@ -7197,6 +7725,7 @@ function taintedReadPaths(node, ts, rootPath) {
|
|
|
7197
7725
|
};
|
|
7198
7726
|
const visit = (n) => {
|
|
7199
7727
|
if (!n) return;
|
|
7728
|
+
if (!includeDeferredBodies && (ts.isArrowFunction(n) || ts.isFunctionExpression(n) || ts.isFunctionDeclaration(n))) return;
|
|
7200
7729
|
if (ts.isPropertyAccessExpression(n) || ts.isElementAccessExpression(n)) {
|
|
7201
7730
|
const read2 = pathFromTainted(n, ts, rootPath);
|
|
7202
7731
|
if (read2 !== void 0) {
|
|
@@ -7279,6 +7808,120 @@ function pathFromTainted(node, ts, rootPath) {
|
|
|
7279
7808
|
return { path: normalizePath([base.path, ...segs].filter(Boolean).join(".")), space };
|
|
7280
7809
|
}
|
|
7281
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
|
+
|
|
7282
7925
|
// src/map/entries.ts
|
|
7283
7926
|
var HTTP_METHODS = /* @__PURE__ */ new Set(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"]);
|
|
7284
7927
|
function extractFromFile(sf, ts, localSinks, bindings, ctx) {
|
|
@@ -7302,7 +7945,7 @@ function extractFromFile(sf, ts, localSinks, bindings, ctx) {
|
|
|
7302
7945
|
...spanOf(decl),
|
|
7303
7946
|
inputs,
|
|
7304
7947
|
sinks,
|
|
7305
|
-
...linkedFlows(handlerBody, handlerFn?.parameters, inputs, sinks, ts)
|
|
7948
|
+
...linkedFlows(handlerBody, handlerFn?.parameters, inputs, sinks, ts, handlerInvocations(handlerBody, ts, bindings, ctx))
|
|
7306
7949
|
};
|
|
7307
7950
|
if (validatorCall && inputs.length === 0) ep.inputsResolved = false;
|
|
7308
7951
|
out.push(ep);
|
|
@@ -7377,6 +8020,10 @@ function hasUseServerDirective(fn, ts) {
|
|
|
7377
8020
|
const first = fn.body?.statements?.[0];
|
|
7378
8021
|
return Boolean(first && ts.isExpressionStatement(first) && ts.isStringLiteralLike(first.expression) && first.expression.text === "use server");
|
|
7379
8022
|
}
|
|
8023
|
+
function handlerInvocations(body, ts, bindings, ctx) {
|
|
8024
|
+
const sf = body?.getSourceFile?.();
|
|
8025
|
+
return sf ? collectInvocations(sf, ts, bindings, ctx, body).invocations : [];
|
|
8026
|
+
}
|
|
7380
8027
|
function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, ctx, extra = {}) {
|
|
7381
8028
|
const entryKind = kindLabel === "route-registration" || kindLabel === "server-action" || kindLabel === "edge-function" ? kindLabel : "route-handler";
|
|
7382
8029
|
const payloadStyle = kindLabel === "server-action";
|
|
@@ -7395,14 +8042,14 @@ function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, c
|
|
|
7395
8042
|
end: extra.end,
|
|
7396
8043
|
inputs,
|
|
7397
8044
|
sinks,
|
|
7398
|
-
...linkedFlows(body, params, inputs, sinks, ts)
|
|
8045
|
+
...linkedFlows(body, params, inputs, sinks, ts, handlerInvocations(body, ts, bindings, ctx))
|
|
7399
8046
|
};
|
|
7400
8047
|
}
|
|
7401
8048
|
|
|
7402
8049
|
// src/map/imports.ts
|
|
7403
|
-
import { readFileSync as
|
|
7404
|
-
import { join as
|
|
7405
|
-
var
|
|
8050
|
+
import { readFileSync as readFileSync15 } from "fs";
|
|
8051
|
+
import { join as join19 } from "path";
|
|
8052
|
+
var MAX_SITES2 = 5;
|
|
7406
8053
|
var NPM_NAME = /^(?:@[^/@\s~][^/@\s]*\/)?[^/@\s.~][^/@\s]*$/;
|
|
7407
8054
|
function isNpmPackageName(pkg) {
|
|
7408
8055
|
return pkg.startsWith("node:") || NPM_NAME.test(pkg);
|
|
@@ -7412,7 +8059,7 @@ function readPathAliases(cwd) {
|
|
|
7412
8059
|
for (const name of ["tsconfig.json", "jsconfig.json"]) {
|
|
7413
8060
|
let paths;
|
|
7414
8061
|
try {
|
|
7415
|
-
const raw =
|
|
8062
|
+
const raw = readFileSync15(join19(cwd, name), "utf8");
|
|
7416
8063
|
const parsed = JSON.parse(stripJsonComments(raw));
|
|
7417
8064
|
paths = parsed.compilerOptions?.paths;
|
|
7418
8065
|
} catch {
|
|
@@ -7616,7 +8263,7 @@ function createImportInventory(aliases = []) {
|
|
|
7616
8263
|
for (const n of edge.names) acc.names.add(n);
|
|
7617
8264
|
if (!parsed) acc.namesComplete = false;
|
|
7618
8265
|
acc.siteCount++;
|
|
7619
|
-
if (acc.sites.length <
|
|
8266
|
+
if (acc.sites.length < MAX_SITES2) acc.sites.push({ file: relFile, line: edge.line });
|
|
7620
8267
|
}
|
|
7621
8268
|
},
|
|
7622
8269
|
list() {
|
|
@@ -7652,127 +8299,15 @@ function lineAt(starts, pos) {
|
|
|
7652
8299
|
return lo + 1;
|
|
7653
8300
|
}
|
|
7654
8301
|
|
|
7655
|
-
// src/map/invocations.ts
|
|
7656
|
-
var MAX_SITES2 = 3;
|
|
7657
|
-
function collectInvocations(sf, ts, bindings, ctx) {
|
|
7658
|
-
const found = [];
|
|
7659
|
-
const counts = { total: 0, dependency: 0, local: 0, ambiguous: 0 };
|
|
7660
|
-
const isLocalName = (name, node) => bindings.locals.has(name) || isShadowedByEnclosingBinding(node, name, ts);
|
|
7661
|
-
const lineOf2 = (node) => {
|
|
7662
|
-
try {
|
|
7663
|
-
return sf.getLineAndCharacterOfPosition(node.getStart(sf)).line + 1;
|
|
7664
|
-
} catch {
|
|
7665
|
-
return void 0;
|
|
7666
|
-
}
|
|
7667
|
-
};
|
|
7668
|
-
const traceRoot = (root) => {
|
|
7669
|
-
const specifier = bindings.resolve(root);
|
|
7670
|
-
if (specifier === void 0) return null;
|
|
7671
|
-
const direct = npmPackageOf(specifier);
|
|
7672
|
-
if (direct !== void 0) {
|
|
7673
|
-
const resolution = bindings.originOf(root) ?? "direct";
|
|
7674
|
-
return { pkg: direct, specifier, resolution, named: resolution === "direct" };
|
|
7675
|
-
}
|
|
7676
|
-
const exportName = bindings.exportNameOf(root) ?? root;
|
|
7677
|
-
const viaModule = ctx.graph.importedBinding(ctx.file, specifier, exportName);
|
|
7678
|
-
if (viaModule !== void 0) {
|
|
7679
|
-
return {
|
|
7680
|
-
pkg: viaModule.package,
|
|
7681
|
-
specifier,
|
|
7682
|
-
resolution: "reexport",
|
|
7683
|
-
named: viaModule.origin === "direct",
|
|
7684
|
-
apiName: viaModule.name
|
|
7685
|
-
};
|
|
7686
|
-
}
|
|
7687
|
-
return null;
|
|
7688
|
-
};
|
|
7689
|
-
const push = (traced, api, receiver, kind, node) => {
|
|
7690
|
-
const span = spanOf(node);
|
|
7691
|
-
found.push({
|
|
7692
|
-
package: traced.pkg,
|
|
7693
|
-
specifiers: [traced.specifier],
|
|
7694
|
-
api,
|
|
7695
|
-
...receiver !== void 0 ? { receiver } : {},
|
|
7696
|
-
symbol: receiver !== void 0 ? `${receiver}.${api}` : api,
|
|
7697
|
-
kind,
|
|
7698
|
-
// Only ever `import`: a global has no package to correlate against, and an inferred package is not
|
|
7699
|
-
// evidence about the value being called.
|
|
7700
|
-
attribution: "import",
|
|
7701
|
-
resolution: traced.resolution,
|
|
7702
|
-
callCount: 1,
|
|
7703
|
-
sites: [{ file: ctx.owner, line: lineOf2(node), start: span.start, end: span.end }]
|
|
7704
|
-
});
|
|
7705
|
-
};
|
|
7706
|
-
const visit = (node) => {
|
|
7707
|
-
if (ts.isCallExpression(node) || ts.isNewExpression(node)) {
|
|
7708
|
-
const callee = node.expression;
|
|
7709
|
-
counts.total++;
|
|
7710
|
-
if (ts.isIdentifier(callee)) {
|
|
7711
|
-
const traced = traceRoot(callee.text);
|
|
7712
|
-
if (traced?.named) {
|
|
7713
|
-
const api = traced.apiName ?? bindings.exportNameOf(callee.text) ?? callee.text;
|
|
7714
|
-
push(traced, api, void 0, ts.isNewExpression(node) ? "construct" : "call", node);
|
|
7715
|
-
counts.dependency++;
|
|
7716
|
-
} else if (traced) {
|
|
7717
|
-
counts.ambiguous++;
|
|
7718
|
-
} else if (isLocalName(callee.text, node)) {
|
|
7719
|
-
counts.local++;
|
|
7720
|
-
} else {
|
|
7721
|
-
counts.ambiguous++;
|
|
7722
|
-
}
|
|
7723
|
-
} else if (ts.isPropertyAccessExpression(callee) && ts.isIdentifier(callee.name)) {
|
|
7724
|
-
const root = rootIdentifier(callee.expression, ts);
|
|
7725
|
-
const traced = root !== void 0 ? traceRoot(root) : null;
|
|
7726
|
-
if (traced && root !== void 0) {
|
|
7727
|
-
counts.dependency++;
|
|
7728
|
-
const receiver = traced.resolution === "direct" && bindings.isPackageMember(root) ? bindings.exportNameOf(root) ?? root : void 0;
|
|
7729
|
-
push(traced, callee.name.text, receiver, "member", node);
|
|
7730
|
-
} else if (root !== void 0 && ts.isIdentifier(callee.expression) && isLocalName(root, node)) {
|
|
7731
|
-
counts.local++;
|
|
7732
|
-
} else {
|
|
7733
|
-
counts.ambiguous++;
|
|
7734
|
-
}
|
|
7735
|
-
} else {
|
|
7736
|
-
counts.ambiguous++;
|
|
7737
|
-
}
|
|
7738
|
-
}
|
|
7739
|
-
ts.forEachChild(node, visit);
|
|
7740
|
-
};
|
|
7741
|
-
visit(sf);
|
|
7742
|
-
return { invocations: found, counts };
|
|
7743
|
-
}
|
|
7744
|
-
function createInvocationInventory() {
|
|
7745
|
-
const byKey = /* @__PURE__ */ new Map();
|
|
7746
|
-
return {
|
|
7747
|
-
add(invocations) {
|
|
7748
|
-
for (const invocation of invocations) {
|
|
7749
|
-
const key = `${invocation.package}|${invocation.symbol}|${invocation.kind}|${invocation.resolution}`;
|
|
7750
|
-
const existing = byKey.get(key);
|
|
7751
|
-
if (existing === void 0) {
|
|
7752
|
-
byKey.set(key, { ...invocation });
|
|
7753
|
-
continue;
|
|
7754
|
-
}
|
|
7755
|
-
existing.callCount += invocation.callCount;
|
|
7756
|
-
for (const specifier of invocation.specifiers) {
|
|
7757
|
-
if (!existing.specifiers.includes(specifier)) existing.specifiers.push(specifier);
|
|
7758
|
-
}
|
|
7759
|
-
if (existing.sites.length < MAX_SITES2) existing.sites.push(...invocation.sites.slice(0, MAX_SITES2 - existing.sites.length));
|
|
7760
|
-
}
|
|
7761
|
-
},
|
|
7762
|
-
list() {
|
|
7763
|
-
return [...byKey.entries()].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([, invocation]) => ({ ...invocation, specifiers: [...invocation.specifiers].sort() }));
|
|
7764
|
-
}
|
|
7765
|
-
};
|
|
7766
|
-
}
|
|
7767
|
-
|
|
7768
8302
|
// src/map/extract.ts
|
|
8303
|
+
var MAX_DEPENDENCY_INPUT_FLOWS_PER_MAP = 500;
|
|
7769
8304
|
async function extractInputMap(cwd, ts, options = {}) {
|
|
7770
8305
|
const notes = [];
|
|
7771
8306
|
const endpoints = [];
|
|
7772
8307
|
const failed = [];
|
|
7773
8308
|
let boundary = cwd;
|
|
7774
8309
|
try {
|
|
7775
|
-
boundary =
|
|
8310
|
+
boundary = realpathSync6(cwd);
|
|
7776
8311
|
} catch {
|
|
7777
8312
|
}
|
|
7778
8313
|
const graph = createModuleGraph(ts, { cwd, boundary, followOutside: options.followSymlinks });
|
|
@@ -7780,6 +8315,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7780
8315
|
const files = collectSources(cwd, boundary, { followOutside: options.followSymlinks }, [], /* @__PURE__ */ new Set(), stats);
|
|
7781
8316
|
const imports = createImportInventory(readPathAliases(cwd));
|
|
7782
8317
|
const invocations = createInvocationInventory();
|
|
8318
|
+
let dependencyInputFlowCount = 0;
|
|
7783
8319
|
let parsed = 0;
|
|
7784
8320
|
let preFiltered = 0;
|
|
7785
8321
|
let importScanFailures = 0;
|
|
@@ -7791,8 +8327,8 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7791
8327
|
const hopTargets = /* @__PURE__ */ new Set();
|
|
7792
8328
|
for (const file of files) {
|
|
7793
8329
|
try {
|
|
7794
|
-
const text =
|
|
7795
|
-
const relFile =
|
|
8330
|
+
const text = readFileSync16(file, "utf8");
|
|
8331
|
+
const relFile = relative6(cwd, file);
|
|
7796
8332
|
sourceBytes += text.length;
|
|
7797
8333
|
if (!hasEntrySignal(text)) {
|
|
7798
8334
|
preFiltered++;
|
|
@@ -7823,6 +8359,13 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7823
8359
|
calls.ambiguous += called.counts.ambiguous;
|
|
7824
8360
|
const localSinks = collectLocalSinks(sf, ts, bindings, ctx);
|
|
7825
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;
|
|
7826
8369
|
if (ep.route === void 0 && ep.entryKind === "edge-function") {
|
|
7827
8370
|
const fn = functionNameFromPath(relFile);
|
|
7828
8371
|
if (fn) ep.route = "/" + fn;
|
|
@@ -7838,7 +8381,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7838
8381
|
}
|
|
7839
8382
|
} catch (e) {
|
|
7840
8383
|
if (typeof process !== "undefined" && process.env?.PS_MAP_DEBUG) console.error("[patchstack] map error", file, e);
|
|
7841
|
-
failed.push(
|
|
8384
|
+
failed.push(relative6(cwd, file));
|
|
7842
8385
|
}
|
|
7843
8386
|
}
|
|
7844
8387
|
let hopParsed = 0;
|
|
@@ -7846,10 +8389,10 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7846
8389
|
for (const target of hopTargets) {
|
|
7847
8390
|
if (entryFiles.has(target)) continue;
|
|
7848
8391
|
try {
|
|
7849
|
-
const text =
|
|
8392
|
+
const text = readFileSync16(target, "utf8");
|
|
7850
8393
|
const sf = ts.createSourceFile(target, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, target));
|
|
7851
8394
|
const bindings = buildModuleBindings(sf, ts);
|
|
7852
|
-
const called = collectInvocations(sf, ts, bindings, { file: target, owner:
|
|
8395
|
+
const called = collectInvocations(sf, ts, bindings, { file: target, owner: relative6(cwd, target), graph });
|
|
7853
8396
|
invocations.add(called.invocations);
|
|
7854
8397
|
calls.total += called.counts.total;
|
|
7855
8398
|
calls.dependency += called.counts.dependency;
|
|
@@ -7863,6 +8406,9 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7863
8406
|
}
|
|
7864
8407
|
notes.push("Static analysis is best-effort \u2014 this is the DETECTED surface, not a proof of completeness.");
|
|
7865
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
|
+
}
|
|
7866
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.");
|
|
7867
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.');
|
|
7868
8414
|
if (!options.followSymlinks) notes.push("Symlinks leaving the project directory were not followed (use --follow-symlinks to include them).");
|
|
@@ -7871,6 +8417,10 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7871
8417
|
notes.push(`${failed.length} file(s) could not be analyzed and were skipped (fail-open): ${sample}${failed.length > 5 ? ", \u2026" : ""}.`);
|
|
7872
8418
|
}
|
|
7873
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
|
+
}
|
|
7874
8424
|
if (unresolved > 0) {
|
|
7875
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).`);
|
|
7876
8426
|
}
|
|
@@ -7881,7 +8431,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
7881
8431
|
if (endpoints.length === 0) notes.push("No recognized server-side entry points found under the analyzed roots.");
|
|
7882
8432
|
const importList = imports.list();
|
|
7883
8433
|
const unmodelled = importList.filter((d) => d.recognizedSinkKinds.length === 0).length;
|
|
7884
|
-
const skippedDirsWithSource = stats.skippedWithSource.map((d) =>
|
|
8434
|
+
const skippedDirsWithSource = stats.skippedWithSource.map((d) => relative6(cwd, d));
|
|
7885
8435
|
const importCoverageGaps = {
|
|
7886
8436
|
unreadableFiles: failed.length,
|
|
7887
8437
|
unscannableFiles: importScanFailures,
|
|
@@ -7995,8 +8545,8 @@ function getStringFlag(flags, name) {
|
|
|
7995
8545
|
}
|
|
7996
8546
|
|
|
7997
8547
|
// src/build-stamp.ts
|
|
7998
|
-
import {
|
|
7999
|
-
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";
|
|
8000
8550
|
var RULES_FILE_NAMES = /* @__PURE__ */ new Set(["rules.json", "patchstack.rules.json"]);
|
|
8001
8551
|
var SKIP_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "coverage", "tmp", "vendor"]);
|
|
8002
8552
|
var MAX_DEPTH2 = 6;
|
|
@@ -8014,11 +8564,11 @@ function guardImportsRulesFile(dir, rulesName) {
|
|
|
8014
8564
|
}
|
|
8015
8565
|
for (const name of siblings) {
|
|
8016
8566
|
if (!GUARD_SOURCE.test(name)) continue;
|
|
8017
|
-
const
|
|
8567
|
+
const path18 = join20(dir, name);
|
|
8018
8568
|
try {
|
|
8019
|
-
const entry =
|
|
8569
|
+
const entry = lstatSync4(path18);
|
|
8020
8570
|
if (!entry.isFile() || entry.isSymbolicLink() || entry.size > MAX_GUARD_SOURCE_BYTES) continue;
|
|
8021
|
-
const source = stripComments(
|
|
8571
|
+
const source = stripComments(readFileSync17(path18, "utf8"));
|
|
8022
8572
|
if (source.includes("@patchstack/connect/protect") && importsRules.test(source)) return true;
|
|
8023
8573
|
} catch {
|
|
8024
8574
|
}
|
|
@@ -8035,14 +8585,14 @@ function findRulesFile(cwd) {
|
|
|
8035
8585
|
const unreadable = [];
|
|
8036
8586
|
let root;
|
|
8037
8587
|
try {
|
|
8038
|
-
root =
|
|
8588
|
+
root = realpathSync7(cwd);
|
|
8039
8589
|
} catch {
|
|
8040
8590
|
return { kind: "none" };
|
|
8041
8591
|
}
|
|
8042
|
-
const contained = (
|
|
8592
|
+
const contained = (path18) => {
|
|
8043
8593
|
try {
|
|
8044
|
-
const real =
|
|
8045
|
-
return real === root || real.startsWith(root +
|
|
8594
|
+
const real = realpathSync7(path18);
|
|
8595
|
+
return real === root || real.startsWith(root + sep3);
|
|
8046
8596
|
} catch {
|
|
8047
8597
|
return false;
|
|
8048
8598
|
}
|
|
@@ -8057,32 +8607,32 @@ function findRulesFile(cwd) {
|
|
|
8057
8607
|
}
|
|
8058
8608
|
for (const name of entries) {
|
|
8059
8609
|
if (name.startsWith(".")) continue;
|
|
8060
|
-
const
|
|
8610
|
+
const path18 = join20(dir, name);
|
|
8061
8611
|
let entry;
|
|
8062
8612
|
try {
|
|
8063
|
-
entry =
|
|
8613
|
+
entry = lstatSync4(path18);
|
|
8064
8614
|
} catch {
|
|
8065
8615
|
continue;
|
|
8066
8616
|
}
|
|
8067
8617
|
if (entry.isSymbolicLink()) continue;
|
|
8068
8618
|
if (entry.isDirectory()) {
|
|
8069
|
-
if (!SKIP_DIRECTORIES.has(name)) walk2(
|
|
8619
|
+
if (!SKIP_DIRECTORIES.has(name)) walk2(path18, depth + 1);
|
|
8070
8620
|
continue;
|
|
8071
8621
|
}
|
|
8072
8622
|
if (!entry.isFile() || !RULES_FILE_NAMES.has(name)) continue;
|
|
8073
|
-
if (!contained(
|
|
8623
|
+
if (!contained(path18)) continue;
|
|
8074
8624
|
if (!guardImportsRulesFile(dir, name)) continue;
|
|
8075
8625
|
try {
|
|
8076
|
-
if (isRuleBundle(JSON.parse(
|
|
8626
|
+
if (isRuleBundle(JSON.parse(readFileSync17(path18, "utf8")))) found.push(path18);
|
|
8077
8627
|
} catch {
|
|
8078
|
-
unreadable.push(
|
|
8628
|
+
unreadable.push(path18);
|
|
8079
8629
|
}
|
|
8080
8630
|
}
|
|
8081
8631
|
};
|
|
8082
8632
|
walk2(cwd, 0);
|
|
8083
|
-
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)) };
|
|
8084
8634
|
if (found.length === 1) return { kind: "one", path: found[0] };
|
|
8085
|
-
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)) };
|
|
8086
8636
|
return { kind: "none" };
|
|
8087
8637
|
}
|
|
8088
8638
|
function detectIndent(text) {
|
|
@@ -8103,10 +8653,10 @@ function applyBuildStamp(cwd, id) {
|
|
|
8103
8653
|
reason: `more than one candidate guard rules file (${location.paths.join(", ")}) \u2014 none was stamped`
|
|
8104
8654
|
};
|
|
8105
8655
|
}
|
|
8106
|
-
const file =
|
|
8656
|
+
const file = relative7(cwd, location.path);
|
|
8107
8657
|
let text;
|
|
8108
8658
|
try {
|
|
8109
|
-
text =
|
|
8659
|
+
text = readFileSync17(location.path, "utf8");
|
|
8110
8660
|
} catch (err) {
|
|
8111
8661
|
return { kind: "skipped", reason: `${file} could not be read (${err.message})` };
|
|
8112
8662
|
}
|
|
@@ -8126,17 +8676,9 @@ function applyBuildStamp(cwd, id) {
|
|
|
8126
8676
|
if (Object.keys(namespace).length === 0) delete bundle[BUILD_STAMP_KEY];
|
|
8127
8677
|
else bundle[BUILD_STAMP_KEY] = namespace;
|
|
8128
8678
|
const serialised = JSON.stringify(bundle, null, detectIndent(text)) + (text.endsWith("\n") ? "\n" : "");
|
|
8129
|
-
const temporary = `${location.path}.patchstack-${process.pid}.tmp`;
|
|
8130
8679
|
try {
|
|
8131
|
-
|
|
8132
|
-
writeFileSync8(temporary, serialised, { encoding: "utf8", mode });
|
|
8133
|
-
chmodSync(temporary, mode);
|
|
8134
|
-
renameSync(temporary, location.path);
|
|
8680
|
+
writeProjectFileSync(cwd, location.path, serialised, { encoding: "utf8" });
|
|
8135
8681
|
} catch (err) {
|
|
8136
|
-
try {
|
|
8137
|
-
unlinkSync(temporary);
|
|
8138
|
-
} catch {
|
|
8139
|
-
}
|
|
8140
8682
|
return { kind: "skipped", reason: `${file} could not be written (${err.message})` };
|
|
8141
8683
|
}
|
|
8142
8684
|
return id === null ? { kind: "cleared", file } : { kind: "stamped", file, id };
|
|
@@ -8275,7 +8817,7 @@ async function runMap(flags) {
|
|
|
8275
8817
|
const json = JSON.stringify(map, null, 2);
|
|
8276
8818
|
const out = getStringFlag(flags, "out");
|
|
8277
8819
|
if (out) {
|
|
8278
|
-
|
|
8820
|
+
atomicWriteFileSync(path15.resolve(out), json, { encoding: "utf8" });
|
|
8279
8821
|
console.error(`patchstack: wrote ${out}`);
|
|
8280
8822
|
} else if (flags.get("upload") !== true) {
|
|
8281
8823
|
console.log(json);
|
|
@@ -8324,8 +8866,8 @@ async function runMap(flags) {
|
|
|
8324
8866
|
}
|
|
8325
8867
|
|
|
8326
8868
|
// src/setup.ts
|
|
8327
|
-
import { readFileSync as
|
|
8328
|
-
import
|
|
8869
|
+
import { readFileSync as readFileSync18 } from "fs";
|
|
8870
|
+
import path16 from "path";
|
|
8329
8871
|
var SCAN_COMMAND = "patchstack-connect scan";
|
|
8330
8872
|
var MARK_BUILD_COMMAND = "patchstack-connect mark-build";
|
|
8331
8873
|
function setupProtection(cwd) {
|
|
@@ -8350,8 +8892,8 @@ function prependHook(existing, command) {
|
|
|
8350
8892
|
return [command, ...remaining].join(" && ");
|
|
8351
8893
|
}
|
|
8352
8894
|
function wireBuildScripts(cwd, packageManager) {
|
|
8353
|
-
const target =
|
|
8354
|
-
const raw =
|
|
8895
|
+
const target = path16.join(cwd, "package.json");
|
|
8896
|
+
const raw = readFileSync18(target, "utf8");
|
|
8355
8897
|
const pkg = JSON.parse(raw);
|
|
8356
8898
|
const scripts = pkg.scripts ?? {};
|
|
8357
8899
|
const build = scripts.build;
|
|
@@ -8397,7 +8939,7 @@ function wireBuildScripts(cwd, packageManager) {
|
|
|
8397
8939
|
const indentMatch = raw.match(/^[\t ]+(?=")/m)?.[0];
|
|
8398
8940
|
const indent = indentMatch?.includes(" ") ? " " : indentMatch?.length ?? 2;
|
|
8399
8941
|
const trailingNewline = raw.endsWith("\n") ? "\n" : "";
|
|
8400
|
-
|
|
8942
|
+
writeProjectFileSync(cwd, target, `${JSON.stringify(pkg, null, indent)}${trailingNewline}`, { encoding: "utf8" });
|
|
8401
8943
|
if (build === void 0 || build.trim().length === 0) {
|
|
8402
8944
|
return {
|
|
8403
8945
|
changed: true,
|
|
@@ -8927,8 +9469,9 @@ async function runScan(args, options = {}) {
|
|
|
8927
9469
|
const cleared = applyBuildStamp(process.cwd(), null);
|
|
8928
9470
|
if (cleared.kind === "cleared") console.log(`Removed the previous map binding from ${cleared.file}.`);
|
|
8929
9471
|
}
|
|
9472
|
+
const shellFramework = detectStack(payload.packages).framework;
|
|
8930
9473
|
if (config.widget) {
|
|
8931
|
-
reportSourceMarker(
|
|
9474
|
+
reportSourceMarker(shellFramework, computeManifestChecksum(payload.packages));
|
|
8932
9475
|
}
|
|
8933
9476
|
const provisioning = config.siteUuid === null;
|
|
8934
9477
|
if (provisioning) {
|
|
@@ -8974,7 +9517,7 @@ async function runScan(args, options = {}) {
|
|
|
8974
9517
|
const connected = response.claim?.state === "claimed" || response.claim?.state === "owned-by-you";
|
|
8975
9518
|
const effectiveUuid = config.siteUuid ?? response.uuid ?? null;
|
|
8976
9519
|
if (config.widget && effectiveUuid !== null && effectiveUuid.length > 0) {
|
|
8977
|
-
reportSourceWidget(effectiveUuid);
|
|
9520
|
+
reportSourceWidget(effectiveUuid, shellFramework);
|
|
8978
9521
|
}
|
|
8979
9522
|
const linkUuid = response.uuid ?? config.siteUuid;
|
|
8980
9523
|
if (!connected && (provisioning || claimLines.length > 0) && linkUuid !== null && linkUuid !== void 0 && linkUuid.length > 0) {
|
|
@@ -9005,9 +9548,11 @@ async function runScan(args, options = {}) {
|
|
|
9005
9548
|
}
|
|
9006
9549
|
return 0;
|
|
9007
9550
|
}
|
|
9008
|
-
function reportSourceWidget(siteUuid) {
|
|
9551
|
+
function reportSourceWidget(siteUuid, framework) {
|
|
9009
9552
|
try {
|
|
9010
|
-
const
|
|
9553
|
+
const hint = hasJsxShell(framework) ? resolveWidgetFileHint(process.cwd(), framework) : null;
|
|
9554
|
+
const jsxShell = hint !== null && !hint.toLowerCase().endsWith(".html") ? hint : null;
|
|
9555
|
+
const result = ensureSourceWidget(process.cwd(), siteUuid, jsxShell);
|
|
9011
9556
|
switch (result.action) {
|
|
9012
9557
|
case "added":
|
|
9013
9558
|
console.log(`Widget: added the "Report a vulnerability" tag to ${result.shell}. Reload your preview to see it.`);
|
|
@@ -9026,7 +9571,7 @@ function reportSourceWidget(siteUuid) {
|
|
|
9026
9571
|
console.log(` ${buildWidgetTag(siteUuid)}`);
|
|
9027
9572
|
break;
|
|
9028
9573
|
case "no-shell":
|
|
9029
|
-
console.log("Widget: no
|
|
9574
|
+
console.log("Widget: no root shell found to edit (index.html / public/index.html / src/app.html, or a JSX root).");
|
|
9030
9575
|
console.log("Add this tag to your root layout before </body> (run `guide` for framework-specific placement):");
|
|
9031
9576
|
console.log(` ${buildWidgetTag(siteUuid)}`);
|
|
9032
9577
|
break;
|
|
@@ -9038,13 +9583,13 @@ function reportSourceWidget(siteUuid) {
|
|
|
9038
9583
|
console.warn(`Widget: skipped (${err.message}).`);
|
|
9039
9584
|
}
|
|
9040
9585
|
}
|
|
9041
|
-
function reportSourceMarker(framework) {
|
|
9586
|
+
function reportSourceMarker(framework, checksum = null) {
|
|
9042
9587
|
try {
|
|
9043
9588
|
const shell = resolveWidgetFileHint(process.cwd(), framework);
|
|
9044
9589
|
if (shell === null || shell.toLowerCase().endsWith(".html")) {
|
|
9045
9590
|
return;
|
|
9046
9591
|
}
|
|
9047
|
-
const result = ensureSourceMarker(process.cwd(), shell, framework);
|
|
9592
|
+
const result = ensureSourceMarker(process.cwd(), shell, framework, checksum);
|
|
9048
9593
|
switch (result.action) {
|
|
9049
9594
|
case "added":
|
|
9050
9595
|
console.log(`Production marker: added to ${shell} (guarded by ${productionGate(framework)}).`);
|
|
@@ -9231,7 +9776,7 @@ async function runGuide(args) {
|
|
|
9231
9776
|
console.log("\u2014\u2014\u2014\u2014 Full reference guide (ships as AGENT-INSTALL.md) \u2014\u2014\u2014\u2014");
|
|
9232
9777
|
console.log("");
|
|
9233
9778
|
const guidePath = new URL("../AGENT-INSTALL.md", import.meta.url);
|
|
9234
|
-
console.log(
|
|
9779
|
+
console.log(readFileSync19(guidePath, "utf8"));
|
|
9235
9780
|
return 0;
|
|
9236
9781
|
}
|
|
9237
9782
|
async function runSetup(args) {
|
|
@@ -9406,7 +9951,7 @@ function describeStack(stack) {
|
|
|
9406
9951
|
}
|
|
9407
9952
|
function readBuildScript(cwd) {
|
|
9408
9953
|
try {
|
|
9409
|
-
const pkg = JSON.parse(
|
|
9954
|
+
const pkg = JSON.parse(readFileSync19(path17.join(cwd, "package.json"), "utf8"));
|
|
9410
9955
|
return pkg.scripts?.build;
|
|
9411
9956
|
} catch {
|
|
9412
9957
|
return void 0;
|
|
@@ -9486,7 +10031,7 @@ async function runMarkBuild(args) {
|
|
|
9486
10031
|
let marked = 0;
|
|
9487
10032
|
let widgetTouched = 0;
|
|
9488
10033
|
for (const file of files) {
|
|
9489
|
-
const before =
|
|
10034
|
+
const before = readFileSync19(file, "utf8");
|
|
9490
10035
|
let after = injectMarker(before, snippet);
|
|
9491
10036
|
if (widgetUuid !== null) {
|
|
9492
10037
|
const ensured = ensureWidgetInHtml(after, widgetUuid);
|
|
@@ -9496,7 +10041,7 @@ async function runMarkBuild(args) {
|
|
|
9496
10041
|
after = ensured.html;
|
|
9497
10042
|
}
|
|
9498
10043
|
if (after !== before) {
|
|
9499
|
-
|
|
10044
|
+
writeProjectFileSync(cwd, file, after, { encoding: "utf8" });
|
|
9500
10045
|
marked += 1;
|
|
9501
10046
|
}
|
|
9502
10047
|
}
|