@patchstack/connect 0.3.26 → 0.3.28
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 +86 -7
- package/dist/cli.js +220 -74
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +17 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -1
- package/dist/protect.cjs +262 -60
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.ts +17 -0
- package/dist/protect.edge.js +244 -60
- package/dist/protect.edge.js.map +4 -4
- package/dist/protect.js +245 -61
- package/dist/protect.js.map +1 -1
- package/dist/{refresh-manifest-KZSQD2X3.js → refresh-manifest-VRBE6RH6.js} +18 -1
- package/dist/refresh-manifest-VRBE6RH6.js.map +1 -0
- package/package.json +1 -1
- package/dist/refresh-manifest-KZSQD2X3.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();
|
|
@@ -1016,10 +1016,23 @@ function buildEndpointUrl(base, siteUuid) {
|
|
|
1016
1016
|
const trimmed = base.replace(/\/$/, "");
|
|
1017
1017
|
return siteUuid !== void 0 && siteUuid !== null && siteUuid.length > 0 ? `${trimmed}/${encodeURIComponent(siteUuid)}` : trimmed;
|
|
1018
1018
|
}
|
|
1019
|
+
function authFailureMessage(status, config) {
|
|
1020
|
+
const hasCredential = typeof config.pulseAuth === "string" && config.pulseAuth.length > 0;
|
|
1021
|
+
if (status === 401 && !hasCredential) {
|
|
1022
|
+
return "Patchstack requires an API credential for this site and none is configured. Run `npx patchstack-connect login`, or set PATCHSTACK_API_KEY.";
|
|
1023
|
+
}
|
|
1024
|
+
if (status === 401) {
|
|
1025
|
+
return "Patchstack rejected this API credential. It may have expired, been revoked, or the site may no longer exist. Run `npx patchstack-connect login` to issue a new one.";
|
|
1026
|
+
}
|
|
1027
|
+
if (status === 403) {
|
|
1028
|
+
return "This API credential is not permitted to act on this site. Check that siteUuid in .patchstackrc.json matches the credential (a credential is issued for one site).";
|
|
1029
|
+
}
|
|
1030
|
+
return null;
|
|
1031
|
+
}
|
|
1019
1032
|
function buildRulesUrl(manifestEndpoint, siteUuid) {
|
|
1020
1033
|
const url = new URL(manifestEndpoint);
|
|
1021
|
-
const
|
|
1022
|
-
url.pathname =
|
|
1034
|
+
const path12 = url.pathname.replace(/\/$/, "");
|
|
1035
|
+
url.pathname = path12.endsWith("/manifest") ? `${path12.slice(0, -"/manifest".length)}/rules/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/rules/${encodeURIComponent(siteUuid)}`;
|
|
1023
1036
|
url.search = "";
|
|
1024
1037
|
url.hash = "";
|
|
1025
1038
|
return url.toString();
|
|
@@ -1034,16 +1047,16 @@ function buildSettingsUrl(endpoint, siteUuid) {
|
|
|
1034
1047
|
}
|
|
1035
1048
|
function buildPackageRemovedUrl(manifestEndpoint, siteUuid) {
|
|
1036
1049
|
const url = new URL(manifestEndpoint);
|
|
1037
|
-
const
|
|
1038
|
-
url.pathname =
|
|
1050
|
+
const path12 = url.pathname.replace(/\/$/, "");
|
|
1051
|
+
url.pathname = path12.endsWith("/manifest") ? `${path12.slice(0, -"/manifest".length)}/package-removed/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/package-removed/${encodeURIComponent(siteUuid)}`;
|
|
1039
1052
|
url.search = "";
|
|
1040
1053
|
url.hash = "";
|
|
1041
1054
|
return url.toString();
|
|
1042
1055
|
}
|
|
1043
1056
|
function buildInputMapUrl(manifestEndpoint, siteUuid) {
|
|
1044
1057
|
const url = new URL(manifestEndpoint);
|
|
1045
|
-
const
|
|
1046
|
-
url.pathname =
|
|
1058
|
+
const path12 = url.pathname.replace(/\/$/, "");
|
|
1059
|
+
url.pathname = path12.endsWith("/manifest") ? `${path12.slice(0, -"/manifest".length)}/input-map/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/input-map/${encodeURIComponent(siteUuid)}`;
|
|
1047
1060
|
url.search = "";
|
|
1048
1061
|
url.hash = "";
|
|
1049
1062
|
return url.toString();
|
|
@@ -1070,6 +1083,10 @@ async function postInputMap(config, map) {
|
|
|
1070
1083
|
if (response.status === 422) {
|
|
1071
1084
|
return { result: "failed", message: `Patchstack does not accept this map schema (version ${map.version}). Update @patchstack/connect.` };
|
|
1072
1085
|
}
|
|
1086
|
+
const refused = authFailureMessage(response.status, config);
|
|
1087
|
+
if (refused !== null) {
|
|
1088
|
+
return { result: "failed", message: refused };
|
|
1089
|
+
}
|
|
1073
1090
|
if (!response.ok) {
|
|
1074
1091
|
return { result: "failed", message: `Patchstack returned ${response.status}.` };
|
|
1075
1092
|
}
|
|
@@ -1100,6 +1117,13 @@ async function postPackageRemoved(config) {
|
|
|
1100
1117
|
if (response.status === 404) {
|
|
1101
1118
|
return { result: "gone", message: null };
|
|
1102
1119
|
}
|
|
1120
|
+
if (response.status === 401 && await fetchSiteStatus(config) === "removed") {
|
|
1121
|
+
return { result: "gone", message: null };
|
|
1122
|
+
}
|
|
1123
|
+
const refused = authFailureMessage(response.status, config);
|
|
1124
|
+
if (refused !== null) {
|
|
1125
|
+
return { result: "failed", message: refused };
|
|
1126
|
+
}
|
|
1103
1127
|
if (!response.ok) {
|
|
1104
1128
|
return { result: "failed", message: `Patchstack returned ${response.status}.` };
|
|
1105
1129
|
}
|
|
@@ -1181,6 +1205,10 @@ async function postManifest(config, payload) {
|
|
|
1181
1205
|
"VALIDATION_ERROR"
|
|
1182
1206
|
);
|
|
1183
1207
|
}
|
|
1208
|
+
const refused = authFailureMessage(response.status, config);
|
|
1209
|
+
if (refused !== null) {
|
|
1210
|
+
throw new PatchstackError(refused, "UNAUTHORIZED");
|
|
1211
|
+
}
|
|
1184
1212
|
if (response.status < 200 || response.status >= 300) {
|
|
1185
1213
|
throw new PatchstackError(
|
|
1186
1214
|
`Patchstack returned ${response.status}: ${text.slice(0, 200)}`,
|
|
@@ -2949,17 +2977,40 @@ function renderGuideChecklist(state, useColor) {
|
|
|
2949
2977
|
}
|
|
2950
2978
|
|
|
2951
2979
|
// src/login.ts
|
|
2980
|
+
import { mkdtempSync, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync6 } from "fs";
|
|
2981
|
+
import { tmpdir } from "os";
|
|
2982
|
+
import path10 from "path";
|
|
2983
|
+
import { createHash as createHash2 } from "crypto";
|
|
2952
2984
|
function baseFrom(manifestEndpoint) {
|
|
2953
2985
|
const url = new URL(manifestEndpoint);
|
|
2954
|
-
const
|
|
2955
|
-
url.pathname =
|
|
2986
|
+
const p = url.pathname.replace(/\/$/, "");
|
|
2987
|
+
url.pathname = p.endsWith("/manifest") ? p.slice(0, -"/manifest".length) : "/monitor/pulse";
|
|
2956
2988
|
url.search = "";
|
|
2957
2989
|
url.hash = "";
|
|
2958
2990
|
return url.toString().replace(/\/$/, "");
|
|
2959
2991
|
}
|
|
2960
|
-
|
|
2992
|
+
function pendingPath(siteUuid) {
|
|
2993
|
+
const key = createHash2("sha256").update(siteUuid).digest("hex").slice(0, 16);
|
|
2994
|
+
return path10.join(tmpdir(), `patchstack-login-${key}.json`);
|
|
2995
|
+
}
|
|
2996
|
+
function savePendingLogin(siteUuid, pending) {
|
|
2997
|
+
writeFileSync6(pendingPath(siteUuid), JSON.stringify(pending), { encoding: "utf8", mode: 384 });
|
|
2998
|
+
}
|
|
2999
|
+
function readPendingLogin(siteUuid) {
|
|
3000
|
+
try {
|
|
3001
|
+
return JSON.parse(readFileSync6(pendingPath(siteUuid), "utf8"));
|
|
3002
|
+
} catch {
|
|
3003
|
+
return null;
|
|
3004
|
+
}
|
|
3005
|
+
}
|
|
3006
|
+
function clearPendingLogin(siteUuid) {
|
|
3007
|
+
try {
|
|
3008
|
+
rmSync(pendingPath(siteUuid));
|
|
3009
|
+
} catch {
|
|
3010
|
+
}
|
|
3011
|
+
}
|
|
3012
|
+
async function startLogin(config, deps = {}) {
|
|
2961
3013
|
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
2962
|
-
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
2963
3014
|
const now = deps.now ?? (() => Date.now());
|
|
2964
3015
|
if (config.siteUuid === null) {
|
|
2965
3016
|
return { status: "failed", message: "No site UUID configured \u2014 run `patchstack-connect scan` first." };
|
|
@@ -2982,28 +3033,72 @@ async function login(config, onPrompt, deps = {}) {
|
|
|
2982
3033
|
if (!started.ok) {
|
|
2983
3034
|
return { status: "failed", message: `Could not start the login (HTTP ${started.status}).` };
|
|
2984
3035
|
}
|
|
2985
|
-
const
|
|
2986
|
-
const
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
3036
|
+
const body = await started.json();
|
|
3037
|
+
const pending = {
|
|
3038
|
+
deviceCode: body.device_code,
|
|
3039
|
+
userCode: body.user_code,
|
|
3040
|
+
// Points at the API, which redirects to the dashboard SPA — the CLI only
|
|
3041
|
+
// knows the API origin, and the approval page lives on the app.
|
|
3042
|
+
verificationUri: `${base}/device?code=${encodeURIComponent(body.user_code)}`,
|
|
3043
|
+
expiresAt: now() + body.expires_in * 1e3,
|
|
3044
|
+
intervalMs: Math.max(1, body.interval) * 1e3
|
|
3045
|
+
};
|
|
3046
|
+
savePendingLogin(config.siteUuid, pending);
|
|
3047
|
+
return { status: "started", pending };
|
|
3048
|
+
}
|
|
3049
|
+
async function redeemIfApproved(config, pending, deps = {}) {
|
|
3050
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
3051
|
+
const polled = await fetchImpl(`${baseFrom(config.endpoint)}/device/token`, {
|
|
3052
|
+
method: "POST",
|
|
3053
|
+
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
3054
|
+
body: JSON.stringify({ device_code: pending.deviceCode })
|
|
3055
|
+
});
|
|
3056
|
+
if (polled.status === 428) return "pending";
|
|
3057
|
+
if (!polled.ok) {
|
|
3058
|
+
if (config.siteUuid !== null) clearPendingLogin(config.siteUuid);
|
|
3059
|
+
return "expired";
|
|
3060
|
+
}
|
|
3061
|
+
const { api_key: apiKey } = await polled.json();
|
|
3062
|
+
if (typeof apiKey !== "string" || apiKey.length === 0) return "expired";
|
|
3063
|
+
await persistApiKey(process.cwd(), apiKey);
|
|
3064
|
+
if (config.siteUuid !== null) clearPendingLogin(config.siteUuid);
|
|
3065
|
+
return "approved";
|
|
3066
|
+
}
|
|
3067
|
+
async function waitForApproval(config, pending, deps = {}) {
|
|
3068
|
+
const fetchImpl = deps.fetchImpl ?? fetch;
|
|
3069
|
+
const sleep = deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms)));
|
|
3070
|
+
const now = deps.now ?? (() => Date.now());
|
|
3071
|
+
const deadline = Math.min(pending.expiresAt, deps.until ?? pending.expiresAt);
|
|
3072
|
+
const expired = { status: "expired", message: "The login request expired. Run the command again." };
|
|
2990
3073
|
while (now() < deadline) {
|
|
2991
|
-
await sleep(intervalMs);
|
|
2992
|
-
const polled = await fetchImpl(`${
|
|
3074
|
+
await sleep(pending.intervalMs);
|
|
3075
|
+
const polled = await fetchImpl(`${baseFrom(config.endpoint)}/device/token`, {
|
|
2993
3076
|
method: "POST",
|
|
2994
3077
|
headers: { "Content-Type": "application/json", Accept: "application/json" },
|
|
2995
|
-
body: JSON.stringify({ device_code: deviceCode })
|
|
3078
|
+
body: JSON.stringify({ device_code: pending.deviceCode })
|
|
2996
3079
|
});
|
|
2997
3080
|
if (polled.status === 428) continue;
|
|
2998
|
-
if (!polled.ok)
|
|
3081
|
+
if (!polled.ok) {
|
|
3082
|
+
if (config.siteUuid !== null) clearPendingLogin(config.siteUuid);
|
|
3083
|
+
return expired;
|
|
3084
|
+
}
|
|
2999
3085
|
const { api_key: apiKey } = await polled.json();
|
|
3000
3086
|
if (typeof apiKey !== "string" || apiKey.length === 0) {
|
|
3001
3087
|
return { status: "failed", message: "Patchstack approved the request but returned no credential." };
|
|
3002
3088
|
}
|
|
3003
3089
|
await persistApiKey(process.cwd(), apiKey);
|
|
3004
|
-
|
|
3090
|
+
if (config.siteUuid !== null) clearPendingLogin(config.siteUuid);
|
|
3091
|
+
return { status: "approved", userCode: pending.userCode, verificationUri: pending.verificationUri };
|
|
3005
3092
|
}
|
|
3006
|
-
return
|
|
3093
|
+
return expired;
|
|
3094
|
+
}
|
|
3095
|
+
async function login(config, onPrompt, deps = {}) {
|
|
3096
|
+
const started = await startLogin(config, deps);
|
|
3097
|
+
if (started.status !== "started" || started.pending === void 0) {
|
|
3098
|
+
return { status: started.status === "started" ? "failed" : started.status, message: started.message };
|
|
3099
|
+
}
|
|
3100
|
+
onPrompt(started.pending.userCode, started.pending.verificationUri);
|
|
3101
|
+
return waitForApproval(config, started.pending, deps);
|
|
3007
3102
|
}
|
|
3008
3103
|
|
|
3009
3104
|
// src/map/ts-loader.ts
|
|
@@ -3027,8 +3122,8 @@ async function loadTypeScript(cwd) {
|
|
|
3027
3122
|
}
|
|
3028
3123
|
|
|
3029
3124
|
// src/map/extract.ts
|
|
3030
|
-
import { readFileSync as
|
|
3031
|
-
import { createHash as
|
|
3125
|
+
import { readFileSync as readFileSync11, realpathSync as realpathSync3 } from "fs";
|
|
3126
|
+
import { createHash as createHash4 } from "crypto";
|
|
3032
3127
|
import { relative as relative4 } from "path";
|
|
3033
3128
|
|
|
3034
3129
|
// src/map/ast.ts
|
|
@@ -3283,7 +3378,7 @@ function npmPackageOf(spec) {
|
|
|
3283
3378
|
}
|
|
3284
3379
|
|
|
3285
3380
|
// src/map/sources.ts
|
|
3286
|
-
import { readFileSync as
|
|
3381
|
+
import { readFileSync as readFileSync7, readdirSync as readdirSync5, realpathSync, statSync as statSync3 } from "fs";
|
|
3287
3382
|
import { join as join12, relative as relative2, isAbsolute } from "path";
|
|
3288
3383
|
|
|
3289
3384
|
// src/map/routes.ts
|
|
@@ -3368,7 +3463,7 @@ function hasEntrySignal(text) {
|
|
|
3368
3463
|
}
|
|
3369
3464
|
function detectFramework(cwd) {
|
|
3370
3465
|
try {
|
|
3371
|
-
const pkg = JSON.parse(
|
|
3466
|
+
const pkg = JSON.parse(readFileSync7(join12(cwd, "package.json"), "utf8"));
|
|
3372
3467
|
const d = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3373
3468
|
if (d["@tanstack/react-start"] || d["@tanstack/start"] || d["@tanstack/solid-start"]) return "tanstack-start";
|
|
3374
3469
|
if (d["next"]) return "next";
|
|
@@ -3496,10 +3591,10 @@ function detectDeploymentShapes(cwd, opts = {}) {
|
|
|
3496
3591
|
boundary = realpathSync(boundary);
|
|
3497
3592
|
} catch {
|
|
3498
3593
|
}
|
|
3499
|
-
const inProject = (
|
|
3594
|
+
const inProject = (path12) => {
|
|
3500
3595
|
if (opts.followOutside) return true;
|
|
3501
3596
|
try {
|
|
3502
|
-
return isInside(realpathSync(
|
|
3597
|
+
return isInside(realpathSync(path12), boundary);
|
|
3503
3598
|
} catch {
|
|
3504
3599
|
return false;
|
|
3505
3600
|
}
|
|
@@ -3565,7 +3660,7 @@ function holdsSourceFile(dir) {
|
|
|
3565
3660
|
}
|
|
3566
3661
|
|
|
3567
3662
|
// src/map/surface.ts
|
|
3568
|
-
import { readFileSync as
|
|
3663
|
+
import { readFileSync as readFileSync8 } from "fs";
|
|
3569
3664
|
import { join as join13 } from "path";
|
|
3570
3665
|
var STATIC_GENERATORS = [
|
|
3571
3666
|
{ dep: "vite", label: "vite" },
|
|
@@ -3604,7 +3699,7 @@ var SHAPE_SIGNAL = {
|
|
|
3604
3699
|
};
|
|
3605
3700
|
function readManifest(cwd) {
|
|
3606
3701
|
try {
|
|
3607
|
-
const parsed = JSON.parse(
|
|
3702
|
+
const parsed = JSON.parse(readFileSync8(join13(cwd, "package.json"), "utf8"));
|
|
3608
3703
|
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
3609
3704
|
} catch {
|
|
3610
3705
|
return null;
|
|
@@ -3617,7 +3712,7 @@ function nextExportsStatically(cwd, manifest, ts) {
|
|
|
3617
3712
|
for (const file of ["next.config.js", "next.config.mjs", "next.config.ts", "next.config.cjs"]) {
|
|
3618
3713
|
let text;
|
|
3619
3714
|
try {
|
|
3620
|
-
text =
|
|
3715
|
+
text = readFileSync8(join13(cwd, file), "utf8");
|
|
3621
3716
|
} catch {
|
|
3622
3717
|
continue;
|
|
3623
3718
|
}
|
|
@@ -3745,7 +3840,7 @@ function surfaceNote(surface) {
|
|
|
3745
3840
|
}
|
|
3746
3841
|
|
|
3747
3842
|
// src/map/sinks.ts
|
|
3748
|
-
import { createHash as
|
|
3843
|
+
import { createHash as createHash3 } from "crypto";
|
|
3749
3844
|
var DB_OPS = /* @__PURE__ */ new Set(["insert", "update", "delete", "select", "upsert", "rpc"]);
|
|
3750
3845
|
var PRISMA_OPS = /* @__PURE__ */ new Set(["create", "createMany", "update", "updateMany", "delete", "deleteMany", "upsert", "findFirst", "findUnique", "findMany", "count", "aggregate"]);
|
|
3751
3846
|
var FS_CALLS = /^(readFile|writeFile|readFileSync|writeFileSync|appendFile|createReadStream|createWriteStream|unlink|rm|rmSync|mkdir|readdir|stat|open)$/;
|
|
@@ -4001,7 +4096,7 @@ function argumentRoleOf(sinkKind, method, index, total = 0) {
|
|
|
4001
4096
|
return table?.[index] ?? "unknown";
|
|
4002
4097
|
}
|
|
4003
4098
|
function sinkId(s, owner) {
|
|
4004
|
-
return
|
|
4099
|
+
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);
|
|
4005
4100
|
}
|
|
4006
4101
|
function dedupeSinks(sinks, owner) {
|
|
4007
4102
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -4017,7 +4112,7 @@ function dedupeSinks(sinks, owner) {
|
|
|
4017
4112
|
}
|
|
4018
4113
|
|
|
4019
4114
|
// src/map/module-graph.ts
|
|
4020
|
-
import { readFileSync as
|
|
4115
|
+
import { readFileSync as readFileSync9, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
|
|
4021
4116
|
import { dirname as dirname4, join as join14, relative as relative3, resolve as resolvePath } from "path";
|
|
4022
4117
|
var RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
4023
4118
|
function createModuleGraph(ts, opts) {
|
|
@@ -4026,7 +4121,7 @@ function createModuleGraph(ts, opts) {
|
|
|
4026
4121
|
if (cache.has(file)) return cache.get(file) ?? null;
|
|
4027
4122
|
let entry = null;
|
|
4028
4123
|
try {
|
|
4029
|
-
const text =
|
|
4124
|
+
const text = readFileSync9(file, "utf8");
|
|
4030
4125
|
const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, file));
|
|
4031
4126
|
const bindings = buildModuleBindings(sf, ts);
|
|
4032
4127
|
entry = { fnSinks: collectLocalSinks(sf, ts, bindings), calleesOf: collectCallees(sf, ts), bindings };
|
|
@@ -4125,8 +4220,8 @@ function collectCallees(sf, ts) {
|
|
|
4125
4220
|
}
|
|
4126
4221
|
|
|
4127
4222
|
// src/map/coordinates.ts
|
|
4128
|
-
function runtimeCoordinate(source,
|
|
4129
|
-
if (/\[\d*\]/.test(
|
|
4223
|
+
function runtimeCoordinate(source, path12) {
|
|
4224
|
+
if (/\[\d*\]/.test(path12)) {
|
|
4130
4225
|
return { runtimeParameter: null, runtimeParameterReason: "array traversal: needs an array_key_value rule, not a dotted parameter" };
|
|
4131
4226
|
}
|
|
4132
4227
|
switch (source) {
|
|
@@ -4135,15 +4230,15 @@ function runtimeCoordinate(source, path11) {
|
|
|
4135
4230
|
case "multipart":
|
|
4136
4231
|
case "body":
|
|
4137
4232
|
case "server-fn-data":
|
|
4138
|
-
return { runtimeParameter: `post.${
|
|
4233
|
+
return { runtimeParameter: `post.${path12}` };
|
|
4139
4234
|
case "query":
|
|
4140
|
-
return { runtimeParameter: `get.${
|
|
4235
|
+
return { runtimeParameter: `get.${path12}` };
|
|
4141
4236
|
case "cookie":
|
|
4142
|
-
return { runtimeParameter: `cookie.${
|
|
4237
|
+
return { runtimeParameter: `cookie.${path12}` };
|
|
4143
4238
|
case "file":
|
|
4144
|
-
return { runtimeParameter: `files.${
|
|
4239
|
+
return { runtimeParameter: `files.${path12}` };
|
|
4145
4240
|
case "header":
|
|
4146
|
-
return { runtimeParameter: `server.HTTP_${
|
|
4241
|
+
return { runtimeParameter: `server.HTTP_${path12.toUpperCase().replace(/-/g, "_")}` };
|
|
4147
4242
|
case "route-param":
|
|
4148
4243
|
return { runtimeParameter: null, runtimeParameterReason: "route parameters are not exposed by the runtime resolver" };
|
|
4149
4244
|
default:
|
|
@@ -4175,8 +4270,8 @@ function addressSpaceOf(source) {
|
|
|
4175
4270
|
function isProvenFlow(confidence) {
|
|
4176
4271
|
return confidence === "exact-local" || confidence === "transformed-local";
|
|
4177
4272
|
}
|
|
4178
|
-
function inputIdOf(source,
|
|
4179
|
-
return `${addressSpaceOf(source)}:${
|
|
4273
|
+
function inputIdOf(source, path12) {
|
|
4274
|
+
return `${addressSpaceOf(source)}:${path12}`;
|
|
4180
4275
|
}
|
|
4181
4276
|
function withCoordinates(fields, source) {
|
|
4182
4277
|
return fields.map((f) => {
|
|
@@ -4375,8 +4470,8 @@ function linkFlows(bodyNode, params, inputs, sinks, ts) {
|
|
|
4375
4470
|
if (!bodyNode || sinks.length === 0 || inputs.length === 0) return { flows: [], limitations: [] };
|
|
4376
4471
|
const CONTAINER_KEYS = /* @__PURE__ */ new Set([...REQ_SOURCES, "data", "input", "payload"]);
|
|
4377
4472
|
const rootPath = /* @__PURE__ */ new Map();
|
|
4378
|
-
const addRoot = (name,
|
|
4379
|
-
if (!rootPath.has(name)) rootPath.set(name, { path:
|
|
4473
|
+
const addRoot = (name, path12, space) => {
|
|
4474
|
+
if (!rootPath.has(name)) rootPath.set(name, { path: path12, space });
|
|
4380
4475
|
};
|
|
4381
4476
|
for (const p of params ?? []) {
|
|
4382
4477
|
if (!p?.name) continue;
|
|
@@ -4521,8 +4616,8 @@ function dedupeLimitations(list) {
|
|
|
4521
4616
|
function join22(base, seg) {
|
|
4522
4617
|
return base ? `${base}.${seg}` : seg;
|
|
4523
4618
|
}
|
|
4524
|
-
function normalizePath(
|
|
4525
|
-
return
|
|
4619
|
+
function normalizePath(path12) {
|
|
4620
|
+
return path12.replace(/\[\d*\]/g, "").split(".").filter(Boolean).join(".");
|
|
4526
4621
|
}
|
|
4527
4622
|
function fluentChainCalls(call, ts) {
|
|
4528
4623
|
let root = call;
|
|
@@ -4768,7 +4863,7 @@ function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, c
|
|
|
4768
4863
|
}
|
|
4769
4864
|
|
|
4770
4865
|
// src/map/imports.ts
|
|
4771
|
-
import { readFileSync as
|
|
4866
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
4772
4867
|
import { join as join15 } from "path";
|
|
4773
4868
|
var MAX_SITES = 5;
|
|
4774
4869
|
var NPM_NAME = /^(?:@[^/@\s~][^/@\s]*\/)?[^/@\s.~][^/@\s]*$/;
|
|
@@ -4780,7 +4875,7 @@ function readPathAliases(cwd) {
|
|
|
4780
4875
|
for (const name of ["tsconfig.json", "jsconfig.json"]) {
|
|
4781
4876
|
let paths;
|
|
4782
4877
|
try {
|
|
4783
|
-
const raw =
|
|
4878
|
+
const raw = readFileSync10(join15(cwd, name), "utf8");
|
|
4784
4879
|
const parsed = JSON.parse(stripJsonComments(raw));
|
|
4785
4880
|
paths = parsed.compilerOptions?.paths;
|
|
4786
4881
|
} catch {
|
|
@@ -5159,7 +5254,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
5159
5254
|
const hopTargets = /* @__PURE__ */ new Set();
|
|
5160
5255
|
for (const file of files) {
|
|
5161
5256
|
try {
|
|
5162
|
-
const text =
|
|
5257
|
+
const text = readFileSync11(file, "utf8");
|
|
5163
5258
|
const relFile = relative4(cwd, file);
|
|
5164
5259
|
sourceBytes += text.length;
|
|
5165
5260
|
if (!hasEntrySignal(text)) {
|
|
@@ -5172,7 +5267,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
5172
5267
|
}
|
|
5173
5268
|
parsed++;
|
|
5174
5269
|
entryFiles.add(file);
|
|
5175
|
-
const fingerprint =
|
|
5270
|
+
const fingerprint = createHash4("sha256").update(text).digest("hex").slice(0, 16);
|
|
5176
5271
|
const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, file));
|
|
5177
5272
|
const bindings = buildModuleBindings(sf, ts);
|
|
5178
5273
|
for (const specifier of bindings.imports) {
|
|
@@ -5214,7 +5309,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
5214
5309
|
for (const target of hopTargets) {
|
|
5215
5310
|
if (entryFiles.has(target)) continue;
|
|
5216
5311
|
try {
|
|
5217
|
-
const text =
|
|
5312
|
+
const text = readFileSync11(target, "utf8");
|
|
5218
5313
|
const sf = ts.createSourceFile(target, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, target));
|
|
5219
5314
|
const bindings = buildModuleBindings(sf, ts);
|
|
5220
5315
|
const called = collectInvocations(sf, ts, bindings, { file: target, owner: relative4(cwd, target), graph });
|
|
@@ -5345,8 +5440,8 @@ async function buildInputMap(cwd, options = {}) {
|
|
|
5345
5440
|
}
|
|
5346
5441
|
|
|
5347
5442
|
// src/setup.ts
|
|
5348
|
-
import { readFileSync as
|
|
5349
|
-
import
|
|
5443
|
+
import { readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
|
|
5444
|
+
import path11 from "path";
|
|
5350
5445
|
var SCAN_COMMAND = "patchstack-connect scan";
|
|
5351
5446
|
var MARK_BUILD_COMMAND = "patchstack-connect mark-build";
|
|
5352
5447
|
function setupProtection(cwd) {
|
|
@@ -5364,8 +5459,8 @@ function appendHook(existing, command) {
|
|
|
5364
5459
|
return `${existing} && ${command}`;
|
|
5365
5460
|
}
|
|
5366
5461
|
function wireBuildScripts(cwd, packageManager) {
|
|
5367
|
-
const target =
|
|
5368
|
-
const raw =
|
|
5462
|
+
const target = path11.join(cwd, "package.json");
|
|
5463
|
+
const raw = readFileSync12(target, "utf8");
|
|
5369
5464
|
const pkg = JSON.parse(raw);
|
|
5370
5465
|
const scripts = pkg.scripts ?? {};
|
|
5371
5466
|
const build = scripts.build;
|
|
@@ -5414,7 +5509,7 @@ function wireBuildScripts(cwd, packageManager) {
|
|
|
5414
5509
|
const indentMatch = raw.match(/^[\t ]+(?=")/m)?.[0];
|
|
5415
5510
|
const indent = indentMatch?.includes(" ") ? " " : indentMatch?.length ?? 2;
|
|
5416
5511
|
const trailingNewline = raw.endsWith("\n") ? "\n" : "";
|
|
5417
|
-
|
|
5512
|
+
writeFileSync7(target, `${JSON.stringify(pkg, null, indent)}${trailingNewline}`, "utf8");
|
|
5418
5513
|
if (build === void 0 || build.trim().length === 0) {
|
|
5419
5514
|
return {
|
|
5420
5515
|
changed: true,
|
|
@@ -5492,10 +5587,17 @@ Usage:
|
|
|
5492
5587
|
what's missing, with tailored commands), then
|
|
5493
5588
|
print the full setup guide. --full prints the
|
|
5494
5589
|
guide even when setup is complete
|
|
5495
|
-
patchstack-connect login [
|
|
5590
|
+
patchstack-connect login [--wait] Recover this site's credential when
|
|
5496
5591
|
.patchstackrc.json has been lost. Prints a link
|
|
5497
|
-
for the site's OWNER to approve in the dashboard
|
|
5498
|
-
then waits
|
|
5592
|
+
for the site's OWNER to approve in the dashboard.
|
|
5593
|
+
In a terminal it then waits. When the output is
|
|
5594
|
+
piped or captured \u2014 an assistant running it \u2014 it
|
|
5595
|
+
prints the link and EXITS, so the link is visible
|
|
5596
|
+
immediately. Run it AGAIN once the user confirms
|
|
5597
|
+
they approved: it resumes the same request rather
|
|
5598
|
+
than starting a new one, and finishes the flow.
|
|
5599
|
+
--wait blocks instead of returning. Use this
|
|
5600
|
+
instead of deleting
|
|
5499
5601
|
.patchstackrc.json and re-scanning, which would
|
|
5500
5602
|
provision a second site. Approving ROTATES the
|
|
5501
5603
|
credential: CI, deploys and other machines using
|
|
@@ -5600,7 +5702,26 @@ async function runLogin(args) {
|
|
|
5600
5702
|
cliSiteUuid: getStringFlag(args.flags, "site-uuid"),
|
|
5601
5703
|
cliEndpoint: getStringFlag(args.flags, "endpoint")
|
|
5602
5704
|
});
|
|
5603
|
-
const
|
|
5705
|
+
const approved = () => {
|
|
5706
|
+
console.log("\n \u2713 Credential restored and saved to .patchstackrc.json.");
|
|
5707
|
+
console.log(" The previous credential no longer works. Update it anywhere else it was set:");
|
|
5708
|
+
console.log(" CI secrets, hosting env vars, preview environments, other checkouts.\n");
|
|
5709
|
+
return 0;
|
|
5710
|
+
};
|
|
5711
|
+
if (args.flags.has("wait")) {
|
|
5712
|
+
const pending = config.siteUuid === null ? null : readPendingLogin(config.siteUuid);
|
|
5713
|
+
if (pending === null) {
|
|
5714
|
+
console.error("\n No login is waiting for approval. Run `patchstack-connect login` first.\n");
|
|
5715
|
+
return 1;
|
|
5716
|
+
}
|
|
5717
|
+
const resumed = await waitForApproval(config, pending);
|
|
5718
|
+
if (resumed.status === "approved") return approved();
|
|
5719
|
+
console.error(`
|
|
5720
|
+
${resumed.message ?? "Login failed."}
|
|
5721
|
+
`);
|
|
5722
|
+
return 1;
|
|
5723
|
+
}
|
|
5724
|
+
const prompt = (userCode, verificationUri) => {
|
|
5604
5725
|
console.log(`
|
|
5605
5726
|
Your code: ${userCode}`);
|
|
5606
5727
|
console.log(` Approve at: ${verificationUri}
|
|
@@ -5608,14 +5729,39 @@ async function runLogin(args) {
|
|
|
5608
5729
|
console.log(" Open that link and approve it as the site's owner. Approving issues a new");
|
|
5609
5730
|
console.log(" credential and stops the current one working \u2014 CI, deploys and any other");
|
|
5610
5731
|
console.log(" machine using it will need the new value.\n");
|
|
5611
|
-
|
|
5612
|
-
|
|
5613
|
-
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5732
|
+
};
|
|
5733
|
+
if (process.stdout.isTTY !== true) {
|
|
5734
|
+
const existing = config.siteUuid === null ? null : readPendingLogin(config.siteUuid);
|
|
5735
|
+
if (existing !== null && Date.now() < existing.expiresAt) {
|
|
5736
|
+
const outcome = await redeemIfApproved(config, existing);
|
|
5737
|
+
if (outcome === "approved") return approved();
|
|
5738
|
+
if (outcome === "pending") {
|
|
5739
|
+
const secondsLeft = Math.round((existing.expiresAt - Date.now()) / 1e3);
|
|
5740
|
+
console.log(`
|
|
5741
|
+
Still waiting for approval of code ${existing.userCode}.`);
|
|
5742
|
+
console.log(` Approve at: ${existing.verificationUri}`);
|
|
5743
|
+
console.log(` (valid for another ${secondsLeft}s \u2014 run this again once the user confirms)
|
|
5744
|
+
`);
|
|
5745
|
+
return 0;
|
|
5746
|
+
}
|
|
5747
|
+
}
|
|
5748
|
+
const started = await startLogin(config);
|
|
5749
|
+
if (started.status !== "started" || started.pending === void 0) {
|
|
5750
|
+
console.error(`
|
|
5751
|
+
${started.message ?? "Login failed."}
|
|
5752
|
+
`);
|
|
5753
|
+
return 1;
|
|
5754
|
+
}
|
|
5755
|
+
prompt(started.pending.userCode, started.pending.verificationUri);
|
|
5756
|
+
console.log(" Give that link to the user. When they confirm they have approved it, run");
|
|
5757
|
+
console.log(" this same command again (or `login --wait` to block until they do).\n");
|
|
5617
5758
|
return 0;
|
|
5618
5759
|
}
|
|
5760
|
+
const result = await login(config, (userCode, verificationUri) => {
|
|
5761
|
+
prompt(userCode, verificationUri);
|
|
5762
|
+
console.log(" Waiting for approval (the code expires in 10 minutes)\u2026");
|
|
5763
|
+
});
|
|
5764
|
+
if (result.status === "approved") return approved();
|
|
5619
5765
|
console.error(`
|
|
5620
5766
|
${result.message ?? "Login failed."}
|
|
5621
5767
|
`);
|
|
@@ -5664,7 +5810,7 @@ async function runMap(args) {
|
|
|
5664
5810
|
const json = JSON.stringify(map, null, 2);
|
|
5665
5811
|
const out = getStringFlag(args.flags, "out");
|
|
5666
5812
|
if (out) {
|
|
5667
|
-
|
|
5813
|
+
writeFileSync8(out, json);
|
|
5668
5814
|
console.error(`patchstack: wrote ${out}`);
|
|
5669
5815
|
} else if (args.flags.get("upload") !== true) {
|
|
5670
5816
|
console.log(json);
|
|
@@ -5950,7 +6096,7 @@ async function runGuide(args) {
|
|
|
5950
6096
|
console.log("\u2014\u2014\u2014\u2014 Full reference guide (ships as AGENT-INSTALL.md) \u2014\u2014\u2014\u2014");
|
|
5951
6097
|
console.log("");
|
|
5952
6098
|
const guidePath = new URL("../AGENT-INSTALL.md", import.meta.url);
|
|
5953
|
-
console.log(
|
|
6099
|
+
console.log(readFileSync13(guidePath, "utf8"));
|
|
5954
6100
|
return 0;
|
|
5955
6101
|
}
|
|
5956
6102
|
async function runSetup(args) {
|
|
@@ -6131,7 +6277,7 @@ async function runMarkBuild(args) {
|
|
|
6131
6277
|
let marked = 0;
|
|
6132
6278
|
let widgetTouched = 0;
|
|
6133
6279
|
for (const file of files) {
|
|
6134
|
-
const before =
|
|
6280
|
+
const before = readFileSync13(file, "utf8");
|
|
6135
6281
|
let after = injectMarker(before, snippet);
|
|
6136
6282
|
if (widgetUuid !== null) {
|
|
6137
6283
|
const ensured = ensureWidgetInHtml(after, widgetUuid);
|
|
@@ -6141,7 +6287,7 @@ async function runMarkBuild(args) {
|
|
|
6141
6287
|
after = ensured.html;
|
|
6142
6288
|
}
|
|
6143
6289
|
if (after !== before) {
|
|
6144
|
-
|
|
6290
|
+
writeFileSync8(file, after);
|
|
6145
6291
|
marked += 1;
|
|
6146
6292
|
}
|
|
6147
6293
|
}
|