@patchstack/connect 0.3.25 → 0.3.27
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 +62 -5
- package/README.md +6 -5
- package/dist/cli.js +203 -83
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +1 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +12 -4
- package/dist/index.d.ts +12 -4
- package/dist/index.js +1 -2
- package/dist/index.js.map +1 -1
- package/dist/protect.cjs +33 -1
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.edge.js +33 -1
- package/dist/protect.edge.js.map +2 -2
- package/dist/protect.js +34 -2
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-MUWVIPFR.js → refresh-manifest-KZSQD2X3.js} +1 -1
- package/dist/refresh-manifest-KZSQD2X3.js.map +1 -0
- package/package.json +1 -1
- package/dist/refresh-manifest-MUWVIPFR.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
|
|
5
5
|
|
|
6
6
|
// src/parsers/index.ts
|
|
7
7
|
import { access } from "fs/promises";
|
|
@@ -931,8 +931,8 @@ function computeManifestChecksum(packages) {
|
|
|
931
931
|
var TOKEN_SKEW_MS = 6e4;
|
|
932
932
|
function buildTokenUrl(manifestEndpoint) {
|
|
933
933
|
const url = new URL(manifestEndpoint);
|
|
934
|
-
const
|
|
935
|
-
url.pathname =
|
|
934
|
+
const path12 = url.pathname.replace(/\/$/, "");
|
|
935
|
+
url.pathname = path12.endsWith("/manifest") ? `${path12.slice(0, -"/manifest".length)}/token` : "/monitor/pulse/token";
|
|
936
936
|
url.search = "";
|
|
937
937
|
url.hash = "";
|
|
938
938
|
return url.toString();
|
|
@@ -1018,8 +1018,8 @@ function buildEndpointUrl(base, siteUuid) {
|
|
|
1018
1018
|
}
|
|
1019
1019
|
function buildRulesUrl(manifestEndpoint, siteUuid) {
|
|
1020
1020
|
const url = new URL(manifestEndpoint);
|
|
1021
|
-
const
|
|
1022
|
-
url.pathname =
|
|
1021
|
+
const path12 = url.pathname.replace(/\/$/, "");
|
|
1022
|
+
url.pathname = path12.endsWith("/manifest") ? `${path12.slice(0, -"/manifest".length)}/rules/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/rules/${encodeURIComponent(siteUuid)}`;
|
|
1023
1023
|
url.search = "";
|
|
1024
1024
|
url.hash = "";
|
|
1025
1025
|
return url.toString();
|
|
@@ -1034,16 +1034,16 @@ function buildSettingsUrl(endpoint, siteUuid) {
|
|
|
1034
1034
|
}
|
|
1035
1035
|
function buildPackageRemovedUrl(manifestEndpoint, siteUuid) {
|
|
1036
1036
|
const url = new URL(manifestEndpoint);
|
|
1037
|
-
const
|
|
1038
|
-
url.pathname =
|
|
1037
|
+
const path12 = url.pathname.replace(/\/$/, "");
|
|
1038
|
+
url.pathname = path12.endsWith("/manifest") ? `${path12.slice(0, -"/manifest".length)}/package-removed/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/package-removed/${encodeURIComponent(siteUuid)}`;
|
|
1039
1039
|
url.search = "";
|
|
1040
1040
|
url.hash = "";
|
|
1041
1041
|
return url.toString();
|
|
1042
1042
|
}
|
|
1043
1043
|
function buildInputMapUrl(manifestEndpoint, siteUuid) {
|
|
1044
1044
|
const url = new URL(manifestEndpoint);
|
|
1045
|
-
const
|
|
1046
|
-
url.pathname =
|
|
1045
|
+
const path12 = url.pathname.replace(/\/$/, "");
|
|
1046
|
+
url.pathname = path12.endsWith("/manifest") ? `${path12.slice(0, -"/manifest".length)}/input-map/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/input-map/${encodeURIComponent(siteUuid)}`;
|
|
1047
1047
|
url.search = "";
|
|
1048
1048
|
url.hash = "";
|
|
1049
1049
|
return url.toString();
|
|
@@ -1457,13 +1457,9 @@ async function persistSiteUuid(cwd, siteUuid) {
|
|
|
1457
1457
|
return writeConfigFile(cwd, { ...existing, siteUuid });
|
|
1458
1458
|
}
|
|
1459
1459
|
async function persistApiKey(cwd, apiKey) {
|
|
1460
|
-
const existing = await readConfigFile(cwd);
|
|
1460
|
+
const { pulseAuth: _dropped, ...existing } = await readConfigFile(cwd);
|
|
1461
1461
|
return writeConfigFile(cwd, { ...existing, apiKey });
|
|
1462
1462
|
}
|
|
1463
|
-
async function persistPulseAuth(cwd, pulseAuth) {
|
|
1464
|
-
const existing = await readConfigFile(cwd);
|
|
1465
|
-
return writeConfigFile(cwd, { ...existing, pulseAuth });
|
|
1466
|
-
}
|
|
1467
1463
|
async function readConfigFile(cwd) {
|
|
1468
1464
|
const target = path6.join(cwd, CONFIG_FILENAME);
|
|
1469
1465
|
let raw;
|
|
@@ -2953,17 +2949,40 @@ function renderGuideChecklist(state, useColor) {
|
|
|
2953
2949
|
}
|
|
2954
2950
|
|
|
2955
2951
|
// src/login.ts
|
|
2952
|
+
import { mkdtempSync, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync6 } from "fs";
|
|
2953
|
+
import { tmpdir } from "os";
|
|
2954
|
+
import path10 from "path";
|
|
2955
|
+
import { createHash as createHash2 } from "crypto";
|
|
2956
2956
|
function baseFrom(manifestEndpoint) {
|
|
2957
2957
|
const url = new URL(manifestEndpoint);
|
|
2958
|
-
const
|
|
2959
|
-
url.pathname =
|
|
2958
|
+
const p = url.pathname.replace(/\/$/, "");
|
|
2959
|
+
url.pathname = p.endsWith("/manifest") ? p.slice(0, -"/manifest".length) : "/monitor/pulse";
|
|
2960
2960
|
url.search = "";
|
|
2961
2961
|
url.hash = "";
|
|
2962
2962
|
return url.toString().replace(/\/$/, "");
|
|
2963
2963
|
}
|
|
2964
|
-
|
|
2964
|
+
function pendingPath(siteUuid) {
|
|
2965
|
+
const key = createHash2("sha256").update(siteUuid).digest("hex").slice(0, 16);
|
|
2966
|
+
return path10.join(tmpdir(), `patchstack-login-${key}.json`);
|
|
2967
|
+
}
|
|
2968
|
+
function savePendingLogin(siteUuid, pending) {
|
|
2969
|
+
writeFileSync6(pendingPath(siteUuid), JSON.stringify(pending), { encoding: "utf8", mode: 384 });
|
|
2970
|
+
}
|
|
2971
|
+
function readPendingLogin(siteUuid) {
|
|
2972
|
+
try {
|
|
2973
|
+
return JSON.parse(readFileSync6(pendingPath(siteUuid), "utf8"));
|
|
2974
|
+
} catch {
|
|
2975
|
+
return null;
|
|
2976
|
+
}
|
|
2977
|
+
}
|
|
2978
|
+
function clearPendingLogin(siteUuid) {
|
|
2979
|
+
try {
|
|
2980
|
+
rmSync(pendingPath(siteUuid));
|
|
2981
|
+
} catch {
|
|
2982
|
+
}
|
|
2983
|
+
}
|
|
2984
|
+
async function startLogin(config, deps = {}) {
|
|
2965
2985
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
2966
|
-
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
2967
2986
|
const now = deps.now ?? (() => Date.now());
|
|
2968
2987
|
if (config.siteUuid === null) {
|
|
2969
2988
|
return { status: "failed", message: "No site UUID configured \u2014 run `patchstack-connect scan` first." };
|
|
@@ -2986,29 +3005,72 @@ async function login(config, onPrompt, deps = {}) {
|
|
|
2986
3005
|
if (!started.ok) {
|
|
2987
3006
|
return { status: "failed", message: `Could not start the login (HTTP ${started.status}).` };
|
|
2988
3007
|
}
|
|
2989
|
-
const
|
|
2990
|
-
const
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
3008
|
+
const body = await started.json();
|
|
3009
|
+
const pending = {
|
|
3010
|
+
deviceCode: body.device_code,
|
|
3011
|
+
userCode: body.user_code,
|
|
3012
|
+
// Points at the API, which redirects to the dashboard SPA — the CLI only
|
|
3013
|
+
// knows the API origin, and the approval page lives on the app.
|
|
3014
|
+
verificationUri: `${base}/device?code=${encodeURIComponent(body.user_code)}`,
|
|
3015
|
+
expiresAt: now() + body.expires_in * 1e3,
|
|
3016
|
+
intervalMs: Math.max(1, body.interval) * 1e3
|
|
3017
|
+
};
|
|
3018
|
+
savePendingLogin(config.siteUuid, pending);
|
|
3019
|
+
return { status: "started", pending };
|
|
3020
|
+
}
|
|
3021
|
+
async function redeemIfApproved(config, pending, deps = {}) {
|
|
3022
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
3023
|
+
const polled = await fetchImpl(`${baseFrom(config.endpoint)}/device/token`, {
|
|
3024
|
+
method: "POST",
|
|
3025
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
3026
|
+
body: JSON.stringify({ device_code: pending.deviceCode })
|
|
3027
|
+
});
|
|
3028
|
+
if (polled.status === 428) return "pending";
|
|
3029
|
+
if (!polled.ok) {
|
|
3030
|
+
if (config.siteUuid !== null) clearPendingLogin(config.siteUuid);
|
|
3031
|
+
return "expired";
|
|
3032
|
+
}
|
|
3033
|
+
const { api_key: apiKey } = await polled.json();
|
|
3034
|
+
if (typeof apiKey !== "string" || apiKey.length === 0) return "expired";
|
|
3035
|
+
await persistApiKey(process.cwd(), apiKey);
|
|
3036
|
+
if (config.siteUuid !== null) clearPendingLogin(config.siteUuid);
|
|
3037
|
+
return "approved";
|
|
3038
|
+
}
|
|
3039
|
+
async function waitForApproval(config, pending, deps = {}) {
|
|
3040
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
3041
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
3042
|
+
const now = deps.now ?? (() => Date.now());
|
|
3043
|
+
const deadline = Math.min(pending.expiresAt, deps.until ?? pending.expiresAt);
|
|
3044
|
+
const expired = { status: "expired", message: "The login request expired. Run the command again." };
|
|
2994
3045
|
while (now() < deadline) {
|
|
2995
|
-
await sleep(intervalMs);
|
|
2996
|
-
const polled = await fetchImpl(`${
|
|
3046
|
+
await sleep(pending.intervalMs);
|
|
3047
|
+
const polled = await fetchImpl(`${baseFrom(config.endpoint)}/device/token`, {
|
|
2997
3048
|
method: "POST",
|
|
2998
3049
|
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
2999
|
-
body: JSON.stringify({ device_code: deviceCode })
|
|
3050
|
+
body: JSON.stringify({ device_code: pending.deviceCode })
|
|
3000
3051
|
});
|
|
3001
3052
|
if (polled.status === 428) continue;
|
|
3002
|
-
if (!polled.ok)
|
|
3053
|
+
if (!polled.ok) {
|
|
3054
|
+
if (config.siteUuid !== null) clearPendingLogin(config.siteUuid);
|
|
3055
|
+
return expired;
|
|
3056
|
+
}
|
|
3003
3057
|
const { api_key: apiKey } = await polled.json();
|
|
3004
3058
|
if (typeof apiKey !== "string" || apiKey.length === 0) {
|
|
3005
3059
|
return { status: "failed", message: "Patchstack approved the request but returned no credential." };
|
|
3006
3060
|
}
|
|
3007
|
-
await persistPulseAuth(process.cwd(), apiKey);
|
|
3008
3061
|
await persistApiKey(process.cwd(), apiKey);
|
|
3009
|
-
|
|
3062
|
+
if (config.siteUuid !== null) clearPendingLogin(config.siteUuid);
|
|
3063
|
+
return { status: "approved", userCode: pending.userCode, verificationUri: pending.verificationUri };
|
|
3064
|
+
}
|
|
3065
|
+
return expired;
|
|
3066
|
+
}
|
|
3067
|
+
async function login(config, onPrompt, deps = {}) {
|
|
3068
|
+
const started = await startLogin(config, deps);
|
|
3069
|
+
if (started.status !== "started" || started.pending === void 0) {
|
|
3070
|
+
return { status: started.status === "started" ? "failed" : started.status, message: started.message };
|
|
3010
3071
|
}
|
|
3011
|
-
|
|
3072
|
+
onPrompt(started.pending.userCode, started.pending.verificationUri);
|
|
3073
|
+
return waitForApproval(config, started.pending, deps);
|
|
3012
3074
|
}
|
|
3013
3075
|
|
|
3014
3076
|
// src/map/ts-loader.ts
|
|
@@ -3032,8 +3094,8 @@ async function loadTypeScript(cwd) {
|
|
|
3032
3094
|
}
|
|
3033
3095
|
|
|
3034
3096
|
// src/map/extract.ts
|
|
3035
|
-
import { readFileSync as
|
|
3036
|
-
import { createHash as
|
|
3097
|
+
import { readFileSync as readFileSync11, realpathSync as realpathSync3 } from "fs";
|
|
3098
|
+
import { createHash as createHash4 } from "crypto";
|
|
3037
3099
|
import { relative as relative4 } from "path";
|
|
3038
3100
|
|
|
3039
3101
|
// src/map/ast.ts
|
|
@@ -3288,7 +3350,7 @@ function npmPackageOf(spec) {
|
|
|
3288
3350
|
}
|
|
3289
3351
|
|
|
3290
3352
|
// src/map/sources.ts
|
|
3291
|
-
import { readFileSync as
|
|
3353
|
+
import { readFileSync as readFileSync7, readdirSync as readdirSync5, realpathSync, statSync as statSync3 } from "fs";
|
|
3292
3354
|
import { join as join12, relative as relative2, isAbsolute } from "path";
|
|
3293
3355
|
|
|
3294
3356
|
// src/map/routes.ts
|
|
@@ -3373,7 +3435,7 @@ function hasEntrySignal(text) {
|
|
|
3373
3435
|
}
|
|
3374
3436
|
function detectFramework(cwd) {
|
|
3375
3437
|
try {
|
|
3376
|
-
const pkg = JSON.parse(
|
|
3438
|
+
const pkg = JSON.parse(readFileSync7(join12(cwd, "package.json"), "utf8"));
|
|
3377
3439
|
const d = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3378
3440
|
if (d["@tanstack/react-start"] || d["@tanstack/start"] || d["@tanstack/solid-start"]) return "tanstack-start";
|
|
3379
3441
|
if (d["next"]) return "next";
|
|
@@ -3501,10 +3563,10 @@ function detectDeploymentShapes(cwd, opts = {}) {
|
|
|
3501
3563
|
boundary = realpathSync(boundary);
|
|
3502
3564
|
} catch {
|
|
3503
3565
|
}
|
|
3504
|
-
const inProject = (
|
|
3566
|
+
const inProject = (path12) => {
|
|
3505
3567
|
if (opts.followOutside) return true;
|
|
3506
3568
|
try {
|
|
3507
|
-
return isInside(realpathSync(
|
|
3569
|
+
return isInside(realpathSync(path12), boundary);
|
|
3508
3570
|
} catch {
|
|
3509
3571
|
return false;
|
|
3510
3572
|
}
|
|
@@ -3570,7 +3632,7 @@ function holdsSourceFile(dir) {
|
|
|
3570
3632
|
}
|
|
3571
3633
|
|
|
3572
3634
|
// src/map/surface.ts
|
|
3573
|
-
import { readFileSync as
|
|
3635
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
3574
3636
|
import { join as join13 } from "path";
|
|
3575
3637
|
var STATIC_GENERATORS = [
|
|
3576
3638
|
{ dep: "vite", label: "vite" },
|
|
@@ -3609,7 +3671,7 @@ var SHAPE_SIGNAL = {
|
|
|
3609
3671
|
};
|
|
3610
3672
|
function readManifest(cwd) {
|
|
3611
3673
|
try {
|
|
3612
|
-
const parsed = JSON.parse(
|
|
3674
|
+
const parsed = JSON.parse(readFileSync8(join13(cwd, "package.json"), "utf8"));
|
|
3613
3675
|
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
3614
3676
|
} catch {
|
|
3615
3677
|
return null;
|
|
@@ -3622,7 +3684,7 @@ function nextExportsStatically(cwd, manifest, ts) {
|
|
|
3622
3684
|
for (const file of ["next.config.js", "next.config.mjs", "next.config.ts", "next.config.cjs"]) {
|
|
3623
3685
|
let text;
|
|
3624
3686
|
try {
|
|
3625
|
-
text =
|
|
3687
|
+
text = readFileSync8(join13(cwd, file), "utf8");
|
|
3626
3688
|
} catch {
|
|
3627
3689
|
continue;
|
|
3628
3690
|
}
|
|
@@ -3741,7 +3803,7 @@ function classifyServerSurface(cwd, endpointCount, deploymentShapes, ts) {
|
|
|
3741
3803
|
}
|
|
3742
3804
|
function surfaceNote(surface) {
|
|
3743
3805
|
if (surface.state === "server-runtime-detected") {
|
|
3744
|
-
return "serverSurface: a server runtime was recognized in the analysed source (see its evidence). Request-path
|
|
3806
|
+
return "serverSurface: a server runtime was recognized in the analysed source (see its evidence). Request-path shielding is APPLICABLE to these paths \u2014 this is not a statement that a guard is installed, fetching rules, or enforcing them.";
|
|
3745
3807
|
}
|
|
3746
3808
|
if (surface.state === "static-build-detected") {
|
|
3747
3809
|
return "serverSurface: a static build was identified and no server runtime was recognized in the analysed source. This is NOT deployment attestation \u2014 it describes the source, not what is deployed, and a serverless function added at the platform level would not appear here. Advisories against this app are dependency and bundle concerns rather than request-path risk.";
|
|
@@ -3750,7 +3812,7 @@ function surfaceNote(surface) {
|
|
|
3750
3812
|
}
|
|
3751
3813
|
|
|
3752
3814
|
// src/map/sinks.ts
|
|
3753
|
-
import { createHash as
|
|
3815
|
+
import { createHash as createHash3 } from "crypto";
|
|
3754
3816
|
var DB_OPS = /* @__PURE__ */ new Set(["insert", "update", "delete", "select", "upsert", "rpc"]);
|
|
3755
3817
|
var PRISMA_OPS = /* @__PURE__ */ new Set(["create", "createMany", "update", "updateMany", "delete", "deleteMany", "upsert", "findFirst", "findUnique", "findMany", "count", "aggregate"]);
|
|
3756
3818
|
var FS_CALLS = /^(readFile|writeFile|readFileSync|writeFileSync|appendFile|createReadStream|createWriteStream|unlink|rm|rmSync|mkdir|readdir|stat|open)$/;
|
|
@@ -4006,7 +4068,7 @@ function argumentRoleOf(sinkKind, method, index, total = 0) {
|
|
|
4006
4068
|
return table?.[index] ?? "unknown";
|
|
4007
4069
|
}
|
|
4008
4070
|
function sinkId(s, owner) {
|
|
4009
|
-
return
|
|
4071
|
+
return createHash3("sha256").update([s.kind, s.provider, s.package, s.table, s.op, s.file ?? owner, s.start, s.end].join("|")).digest("hex").slice(0, 12);
|
|
4010
4072
|
}
|
|
4011
4073
|
function dedupeSinks(sinks, owner) {
|
|
4012
4074
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -4022,7 +4084,7 @@ function dedupeSinks(sinks, owner) {
|
|
|
4022
4084
|
}
|
|
4023
4085
|
|
|
4024
4086
|
// src/map/module-graph.ts
|
|
4025
|
-
import { readFileSync as
|
|
4087
|
+
import { readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
|
|
4026
4088
|
import { dirname as dirname4, join as join14, relative as relative3, resolve as resolvePath } from "path";
|
|
4027
4089
|
var RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
4028
4090
|
function createModuleGraph(ts, opts) {
|
|
@@ -4031,7 +4093,7 @@ function createModuleGraph(ts, opts) {
|
|
|
4031
4093
|
if (cache.has(file)) return cache.get(file) ?? null;
|
|
4032
4094
|
let entry = null;
|
|
4033
4095
|
try {
|
|
4034
|
-
const text =
|
|
4096
|
+
const text = readFileSync9(file, "utf8");
|
|
4035
4097
|
const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, file));
|
|
4036
4098
|
const bindings = buildModuleBindings(sf, ts);
|
|
4037
4099
|
entry = { fnSinks: collectLocalSinks(sf, ts, bindings), calleesOf: collectCallees(sf, ts), bindings };
|
|
@@ -4130,8 +4192,8 @@ function collectCallees(sf, ts) {
|
|
|
4130
4192
|
}
|
|
4131
4193
|
|
|
4132
4194
|
// src/map/coordinates.ts
|
|
4133
|
-
function runtimeCoordinate(source,
|
|
4134
|
-
if (/\[\d*\]/.test(
|
|
4195
|
+
function runtimeCoordinate(source, path12) {
|
|
4196
|
+
if (/\[\d*\]/.test(path12)) {
|
|
4135
4197
|
return { runtimeParameter: null, runtimeParameterReason: "array traversal: needs an array_key_value rule, not a dotted parameter" };
|
|
4136
4198
|
}
|
|
4137
4199
|
switch (source) {
|
|
@@ -4140,15 +4202,15 @@ function runtimeCoordinate(source, path11) {
|
|
|
4140
4202
|
case "multipart":
|
|
4141
4203
|
case "body":
|
|
4142
4204
|
case "server-fn-data":
|
|
4143
|
-
return { runtimeParameter: `post.${
|
|
4205
|
+
return { runtimeParameter: `post.${path12}` };
|
|
4144
4206
|
case "query":
|
|
4145
|
-
return { runtimeParameter: `get.${
|
|
4207
|
+
return { runtimeParameter: `get.${path12}` };
|
|
4146
4208
|
case "cookie":
|
|
4147
|
-
return { runtimeParameter: `cookie.${
|
|
4209
|
+
return { runtimeParameter: `cookie.${path12}` };
|
|
4148
4210
|
case "file":
|
|
4149
|
-
return { runtimeParameter: `files.${
|
|
4211
|
+
return { runtimeParameter: `files.${path12}` };
|
|
4150
4212
|
case "header":
|
|
4151
|
-
return { runtimeParameter: `server.HTTP_${
|
|
4213
|
+
return { runtimeParameter: `server.HTTP_${path12.toUpperCase().replace(/-/g, "_")}` };
|
|
4152
4214
|
case "route-param":
|
|
4153
4215
|
return { runtimeParameter: null, runtimeParameterReason: "route parameters are not exposed by the runtime resolver" };
|
|
4154
4216
|
default:
|
|
@@ -4180,8 +4242,8 @@ function addressSpaceOf(source) {
|
|
|
4180
4242
|
function isProvenFlow(confidence) {
|
|
4181
4243
|
return confidence === "exact-local" || confidence === "transformed-local";
|
|
4182
4244
|
}
|
|
4183
|
-
function inputIdOf(source,
|
|
4184
|
-
return `${addressSpaceOf(source)}:${
|
|
4245
|
+
function inputIdOf(source, path12) {
|
|
4246
|
+
return `${addressSpaceOf(source)}:${path12}`;
|
|
4185
4247
|
}
|
|
4186
4248
|
function withCoordinates(fields, source) {
|
|
4187
4249
|
return fields.map((f) => {
|
|
@@ -4380,8 +4442,8 @@ function linkFlows(bodyNode, params, inputs, sinks, ts) {
|
|
|
4380
4442
|
if (!bodyNode || sinks.length === 0 || inputs.length === 0) return { flows: [], limitations: [] };
|
|
4381
4443
|
const CONTAINER_KEYS = /* @__PURE__ */ new Set([...REQ_SOURCES, "data", "input", "payload"]);
|
|
4382
4444
|
const rootPath = /* @__PURE__ */ new Map();
|
|
4383
|
-
const addRoot = (name,
|
|
4384
|
-
if (!rootPath.has(name)) rootPath.set(name, { path:
|
|
4445
|
+
const addRoot = (name, path12, space) => {
|
|
4446
|
+
if (!rootPath.has(name)) rootPath.set(name, { path: path12, space });
|
|
4385
4447
|
};
|
|
4386
4448
|
for (const p of params ?? []) {
|
|
4387
4449
|
if (!p?.name) continue;
|
|
@@ -4526,8 +4588,8 @@ function dedupeLimitations(list) {
|
|
|
4526
4588
|
function join22(base, seg) {
|
|
4527
4589
|
return base ? `${base}.${seg}` : seg;
|
|
4528
4590
|
}
|
|
4529
|
-
function normalizePath(
|
|
4530
|
-
return
|
|
4591
|
+
function normalizePath(path12) {
|
|
4592
|
+
return path12.replace(/\[\d*\]/g, "").split(".").filter(Boolean).join(".");
|
|
4531
4593
|
}
|
|
4532
4594
|
function fluentChainCalls(call, ts) {
|
|
4533
4595
|
let root = call;
|
|
@@ -4773,7 +4835,7 @@ function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, c
|
|
|
4773
4835
|
}
|
|
4774
4836
|
|
|
4775
4837
|
// src/map/imports.ts
|
|
4776
|
-
import { readFileSync as
|
|
4838
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
4777
4839
|
import { join as join15 } from "path";
|
|
4778
4840
|
var MAX_SITES = 5;
|
|
4779
4841
|
var NPM_NAME = /^(?:@[^/@\s~][^/@\s]*\/)?[^/@\s.~][^/@\s]*$/;
|
|
@@ -4785,7 +4847,7 @@ function readPathAliases(cwd) {
|
|
|
4785
4847
|
for (const name of ["tsconfig.json", "jsconfig.json"]) {
|
|
4786
4848
|
let paths;
|
|
4787
4849
|
try {
|
|
4788
|
-
const raw =
|
|
4850
|
+
const raw = readFileSync10(join15(cwd, name), "utf8");
|
|
4789
4851
|
const parsed = JSON.parse(stripJsonComments(raw));
|
|
4790
4852
|
paths = parsed.compilerOptions?.paths;
|
|
4791
4853
|
} catch {
|
|
@@ -5164,7 +5226,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
5164
5226
|
const hopTargets = /* @__PURE__ */ new Set();
|
|
5165
5227
|
for (const file of files) {
|
|
5166
5228
|
try {
|
|
5167
|
-
const text =
|
|
5229
|
+
const text = readFileSync11(file, "utf8");
|
|
5168
5230
|
const relFile = relative4(cwd, file);
|
|
5169
5231
|
sourceBytes += text.length;
|
|
5170
5232
|
if (!hasEntrySignal(text)) {
|
|
@@ -5177,7 +5239,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
5177
5239
|
}
|
|
5178
5240
|
parsed++;
|
|
5179
5241
|
entryFiles.add(file);
|
|
5180
|
-
const fingerprint =
|
|
5242
|
+
const fingerprint = createHash4("sha256").update(text).digest("hex").slice(0, 16);
|
|
5181
5243
|
const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, file));
|
|
5182
5244
|
const bindings = buildModuleBindings(sf, ts);
|
|
5183
5245
|
for (const specifier of bindings.imports) {
|
|
@@ -5219,7 +5281,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
5219
5281
|
for (const target of hopTargets) {
|
|
5220
5282
|
if (entryFiles.has(target)) continue;
|
|
5221
5283
|
try {
|
|
5222
|
-
const text =
|
|
5284
|
+
const text = readFileSync11(target, "utf8");
|
|
5223
5285
|
const sf = ts.createSourceFile(target, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, target));
|
|
5224
5286
|
const bindings = buildModuleBindings(sf, ts);
|
|
5225
5287
|
const called = collectInvocations(sf, ts, bindings, { file: target, owner: relative4(cwd, target), graph });
|
|
@@ -5350,8 +5412,8 @@ async function buildInputMap(cwd, options = {}) {
|
|
|
5350
5412
|
}
|
|
5351
5413
|
|
|
5352
5414
|
// src/setup.ts
|
|
5353
|
-
import { readFileSync as
|
|
5354
|
-
import
|
|
5415
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
|
|
5416
|
+
import path11 from "path";
|
|
5355
5417
|
var SCAN_COMMAND = "patchstack-connect scan";
|
|
5356
5418
|
var MARK_BUILD_COMMAND = "patchstack-connect mark-build";
|
|
5357
5419
|
function setupProtection(cwd) {
|
|
@@ -5369,8 +5431,8 @@ function appendHook(existing, command) {
|
|
|
5369
5431
|
return `${existing} && ${command}`;
|
|
5370
5432
|
}
|
|
5371
5433
|
function wireBuildScripts(cwd, packageManager) {
|
|
5372
|
-
const target =
|
|
5373
|
-
const raw =
|
|
5434
|
+
const target = path11.join(cwd, "package.json");
|
|
5435
|
+
const raw = readFileSync12(target, "utf8");
|
|
5374
5436
|
const pkg = JSON.parse(raw);
|
|
5375
5437
|
const scripts = pkg.scripts ?? {};
|
|
5376
5438
|
const build = scripts.build;
|
|
@@ -5419,7 +5481,7 @@ function wireBuildScripts(cwd, packageManager) {
|
|
|
5419
5481
|
const indentMatch = raw.match(/^[\t ]+(?=")/m)?.[0];
|
|
5420
5482
|
const indent = indentMatch?.includes(" ") ? " " : indentMatch?.length ?? 2;
|
|
5421
5483
|
const trailingNewline = raw.endsWith("\n") ? "\n" : "";
|
|
5422
|
-
|
|
5484
|
+
writeFileSync7(target, `${JSON.stringify(pkg, null, indent)}${trailingNewline}`, "utf8");
|
|
5423
5485
|
if (build === void 0 || build.trim().length === 0) {
|
|
5424
5486
|
return {
|
|
5425
5487
|
changed: true,
|
|
@@ -5497,11 +5559,21 @@ Usage:
|
|
|
5497
5559
|
what's missing, with tailored commands), then
|
|
5498
5560
|
print the full setup guide. --full prints the
|
|
5499
5561
|
guide even when setup is complete
|
|
5500
|
-
patchstack-connect login [
|
|
5501
|
-
.patchstackrc.json has been lost. Prints a
|
|
5502
|
-
|
|
5503
|
-
|
|
5504
|
-
|
|
5562
|
+
patchstack-connect login [--wait] Recover this site's credential when
|
|
5563
|
+
.patchstackrc.json has been lost. Prints a link
|
|
5564
|
+
for the site's OWNER to approve in the dashboard.
|
|
5565
|
+
In a terminal it then waits. When the output is
|
|
5566
|
+
piped or captured \u2014 an assistant running it \u2014 it
|
|
5567
|
+
prints the link and EXITS, so the link is visible
|
|
5568
|
+
immediately. Run it AGAIN once the user confirms
|
|
5569
|
+
they approved: it resumes the same request rather
|
|
5570
|
+
than starting a new one, and finishes the flow.
|
|
5571
|
+
--wait blocks instead of returning. Use this
|
|
5572
|
+
instead of deleting
|
|
5573
|
+
.patchstackrc.json and re-scanning, which would
|
|
5574
|
+
provision a second site. Approving ROTATES the
|
|
5575
|
+
credential: CI, deploys and other machines using
|
|
5576
|
+
the old one must be updated. Not usable in CI
|
|
5505
5577
|
patchstack-connect help Print this message
|
|
5506
5578
|
|
|
5507
5579
|
Options (for scan, setup, status, and uninstall):
|
|
@@ -5520,7 +5592,7 @@ Options (for demo and demo-guide):
|
|
|
5520
5592
|
Environment:
|
|
5521
5593
|
PATCHSTACK_SITE_UUID Site UUID
|
|
5522
5594
|
PATCHSTACK_API_KEY WP-format site API key for block-log reporting (never put in the widget)
|
|
5523
|
-
PATCHSTACK_PULSE_AUTH
|
|
5595
|
+
PATCHSTACK_PULSE_AUTH Only if Pulse ingest uses a different credential from block-logs
|
|
5524
5596
|
PATCHSTACK_TELEMETRY Set to off to disable block-log reporting
|
|
5525
5597
|
PATCHSTACK_API_BASE API origin for /oauth/token and /api/logs/log (default: https://api.patchstack.com)
|
|
5526
5598
|
PATCHSTACK_ENDPOINT API endpoint (default: https://api.patchstack.com/monitor/pulse/manifest)
|
|
@@ -5602,17 +5674,66 @@ async function runLogin(args) {
|
|
|
5602
5674
|
cliSiteUuid: getStringFlag(args.flags, "site-uuid"),
|
|
5603
5675
|
cliEndpoint: getStringFlag(args.flags, "endpoint")
|
|
5604
5676
|
});
|
|
5605
|
-
const
|
|
5677
|
+
const approved = () => {
|
|
5678
|
+
console.log("\n \u2713 Credential restored and saved to .patchstackrc.json.");
|
|
5679
|
+
console.log(" The previous credential no longer works. Update it anywhere else it was set:");
|
|
5680
|
+
console.log(" CI secrets, hosting env vars, preview environments, other checkouts.\n");
|
|
5681
|
+
return 0;
|
|
5682
|
+
};
|
|
5683
|
+
if (args.flags.has("wait")) {
|
|
5684
|
+
const pending = config.siteUuid === null ? null : readPendingLogin(config.siteUuid);
|
|
5685
|
+
if (pending === null) {
|
|
5686
|
+
console.error("\n No login is waiting for approval. Run `patchstack-connect login` first.\n");
|
|
5687
|
+
return 1;
|
|
5688
|
+
}
|
|
5689
|
+
const resumed = await waitForApproval(config, pending);
|
|
5690
|
+
if (resumed.status === "approved") return approved();
|
|
5691
|
+
console.error(`
|
|
5692
|
+
${resumed.message ?? "Login failed."}
|
|
5693
|
+
`);
|
|
5694
|
+
return 1;
|
|
5695
|
+
}
|
|
5696
|
+
const prompt = (userCode, verificationUri) => {
|
|
5606
5697
|
console.log(`
|
|
5607
5698
|
Your code: ${userCode}`);
|
|
5608
5699
|
console.log(` Approve at: ${verificationUri}
|
|
5609
5700
|
`);
|
|
5610
|
-
console.log("
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5701
|
+
console.log(" Open that link and approve it as the site's owner. Approving issues a new");
|
|
5702
|
+
console.log(" credential and stops the current one working \u2014 CI, deploys and any other");
|
|
5703
|
+
console.log(" machine using it will need the new value.\n");
|
|
5704
|
+
};
|
|
5705
|
+
if (process.stdout.isTTY !== true) {
|
|
5706
|
+
const existing = config.siteUuid === null ? null : readPendingLogin(config.siteUuid);
|
|
5707
|
+
if (existing !== null && Date.now() < existing.expiresAt) {
|
|
5708
|
+
const outcome = await redeemIfApproved(config, existing);
|
|
5709
|
+
if (outcome === "approved") return approved();
|
|
5710
|
+
if (outcome === "pending") {
|
|
5711
|
+
const secondsLeft = Math.round((existing.expiresAt - Date.now()) / 1e3);
|
|
5712
|
+
console.log(`
|
|
5713
|
+
Still waiting for approval of code ${existing.userCode}.`);
|
|
5714
|
+
console.log(` Approve at: ${existing.verificationUri}`);
|
|
5715
|
+
console.log(` (valid for another ${secondsLeft}s \u2014 run this again once the user confirms)
|
|
5716
|
+
`);
|
|
5717
|
+
return 0;
|
|
5718
|
+
}
|
|
5719
|
+
}
|
|
5720
|
+
const started = await startLogin(config);
|
|
5721
|
+
if (started.status !== "started" || started.pending === void 0) {
|
|
5722
|
+
console.error(`
|
|
5723
|
+
${started.message ?? "Login failed."}
|
|
5724
|
+
`);
|
|
5725
|
+
return 1;
|
|
5726
|
+
}
|
|
5727
|
+
prompt(started.pending.userCode, started.pending.verificationUri);
|
|
5728
|
+
console.log(" Give that link to the user. When they confirm they have approved it, run");
|
|
5729
|
+
console.log(" this same command again (or `login --wait` to block until they do).\n");
|
|
5614
5730
|
return 0;
|
|
5615
5731
|
}
|
|
5732
|
+
const result = await login(config, (userCode, verificationUri) => {
|
|
5733
|
+
prompt(userCode, verificationUri);
|
|
5734
|
+
console.log(" Waiting for approval (the code expires in 10 minutes)\u2026");
|
|
5735
|
+
});
|
|
5736
|
+
if (result.status === "approved") return approved();
|
|
5616
5737
|
console.error(`
|
|
5617
5738
|
${result.message ?? "Login failed."}
|
|
5618
5739
|
`);
|
|
@@ -5661,7 +5782,7 @@ async function runMap(args) {
|
|
|
5661
5782
|
const json = JSON.stringify(map, null, 2);
|
|
5662
5783
|
const out = getStringFlag(args.flags, "out");
|
|
5663
5784
|
if (out) {
|
|
5664
|
-
|
|
5785
|
+
writeFileSync8(out, json);
|
|
5665
5786
|
console.error(`patchstack: wrote ${out}`);
|
|
5666
5787
|
} else if (args.flags.get("upload") !== true) {
|
|
5667
5788
|
console.log(json);
|
|
@@ -5745,7 +5866,6 @@ async function runScan(args, options = {}) {
|
|
|
5745
5866
|
}
|
|
5746
5867
|
if (typeof response.api_key === "string" && response.api_key.length > 0) {
|
|
5747
5868
|
const target = await persistApiKey(process.cwd(), response.api_key);
|
|
5748
|
-
await persistPulseAuth(process.cwd(), response.api_key);
|
|
5749
5869
|
console.log(`Saved API key to ${target} (authenticates Pulse ingest and block-log reporting; keep out of the public widget).`);
|
|
5750
5870
|
}
|
|
5751
5871
|
if (response.stored) {
|
|
@@ -5948,7 +6068,7 @@ async function runGuide(args) {
|
|
|
5948
6068
|
console.log("\u2014\u2014\u2014\u2014 Full reference guide (ships as AGENT-INSTALL.md) \u2014\u2014\u2014\u2014");
|
|
5949
6069
|
console.log("");
|
|
5950
6070
|
const guidePath = new URL("../AGENT-INSTALL.md", import.meta.url);
|
|
5951
|
-
console.log(
|
|
6071
|
+
console.log(readFileSync13(guidePath, "utf8"));
|
|
5952
6072
|
return 0;
|
|
5953
6073
|
}
|
|
5954
6074
|
async function runSetup(args) {
|
|
@@ -6129,7 +6249,7 @@ async function runMarkBuild(args) {
|
|
|
6129
6249
|
let marked = 0;
|
|
6130
6250
|
let widgetTouched = 0;
|
|
6131
6251
|
for (const file of files) {
|
|
6132
|
-
const before =
|
|
6252
|
+
const before = readFileSync13(file, "utf8");
|
|
6133
6253
|
let after = injectMarker(before, snippet);
|
|
6134
6254
|
if (widgetUuid !== null) {
|
|
6135
6255
|
const ensured = ensureWidgetInHtml(after, widgetUuid);
|
|
@@ -6139,7 +6259,7 @@ async function runMarkBuild(args) {
|
|
|
6139
6259
|
after = ensured.html;
|
|
6140
6260
|
}
|
|
6141
6261
|
if (after !== before) {
|
|
6142
|
-
|
|
6262
|
+
writeFileSync8(file, after);
|
|
6143
6263
|
marked += 1;
|
|
6144
6264
|
}
|
|
6145
6265
|
}
|