@patchstack/connect 0.3.27 → 0.3.29
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 +65 -2
- package/dist/cli.js +243 -45
- 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 +229 -59
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.ts +17 -0
- package/dist/protect.edge.js +211 -59
- package/dist/protect.edge.js.map +4 -4
- package/dist/protect.js +212 -60
- 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 readFileSync14, writeFileSync as writeFileSync9 } from "fs";
|
|
5
5
|
|
|
6
6
|
// src/parsers/index.ts
|
|
7
7
|
import { access } from "fs/promises";
|
|
@@ -1016,6 +1016,19 @@ 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
1034
|
const path12 = url.pathname.replace(/\/$/, "");
|
|
@@ -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)}`,
|
|
@@ -1516,7 +1544,7 @@ function isEnvironment(value) {
|
|
|
1516
1544
|
}
|
|
1517
1545
|
|
|
1518
1546
|
// src/mark-build.ts
|
|
1519
|
-
import { existsSync, readdirSync, statSync } from "fs";
|
|
1547
|
+
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
1520
1548
|
import path7 from "path";
|
|
1521
1549
|
|
|
1522
1550
|
// src/stack.ts
|
|
@@ -1659,16 +1687,94 @@ function injectMarker(html, snippet) {
|
|
|
1659
1687
|
}
|
|
1660
1688
|
return stripped + snippet;
|
|
1661
1689
|
}
|
|
1690
|
+
var JSX_SHELL_FRAMEWORKS = /* @__PURE__ */ new Set([
|
|
1691
|
+
"next",
|
|
1692
|
+
"remix",
|
|
1693
|
+
"react-router",
|
|
1694
|
+
"tanstack-start",
|
|
1695
|
+
"gatsby"
|
|
1696
|
+
]);
|
|
1697
|
+
var PRODUCTION_GATES = {
|
|
1698
|
+
next: "process.env.NODE_ENV === 'production'",
|
|
1699
|
+
gatsby: "process.env.NODE_ENV === 'production'",
|
|
1700
|
+
nuxt: "!import.meta.dev"
|
|
1701
|
+
};
|
|
1702
|
+
var DEFAULT_PRODUCTION_GATE = "import.meta.env.PROD";
|
|
1703
|
+
function productionGate(framework) {
|
|
1704
|
+
const mapped = framework !== null ? PRODUCTION_GATES[framework] : void 0;
|
|
1705
|
+
return mapped ?? DEFAULT_PRODUCTION_GATE;
|
|
1706
|
+
}
|
|
1707
|
+
function hasJsxShell(framework) {
|
|
1708
|
+
return framework !== null && JSX_SHELL_FRAMEWORKS.has(framework);
|
|
1709
|
+
}
|
|
1710
|
+
function buildSourceMarkerSnippet(framework) {
|
|
1711
|
+
const gate = productionGate(framework);
|
|
1712
|
+
return `{${gate} && (
|
|
1713
|
+
<script
|
|
1714
|
+
${MARKER_ATTR}="true"
|
|
1715
|
+
dangerouslySetInnerHTML={{ __html: 'window.__PATCHSTACK_PROD__=true;' }}
|
|
1716
|
+
/>
|
|
1717
|
+
)}`;
|
|
1718
|
+
}
|
|
1719
|
+
var PROD_MARKER_GLOBAL = "__PATCHSTACK_PROD__";
|
|
1720
|
+
var REGION_OPEN = "{/* #region patchstack (managed by patchstack-connect \u2014 do not edit) */}";
|
|
1721
|
+
var REGION_CLOSE = "{/* #endregion patchstack */}";
|
|
1722
|
+
var REGION_RE = /[ \t]*\{\/\* #region patchstack[\s\S]*?#endregion patchstack \*\/\}\n?/g;
|
|
1723
|
+
function ensureMarkerInJsxShell(source, framework) {
|
|
1724
|
+
if (!hasJsxShell(framework)) {
|
|
1725
|
+
return { source, action: "unsupported" };
|
|
1726
|
+
}
|
|
1727
|
+
const stripped = source.replace(REGION_RE, "");
|
|
1728
|
+
if (stripped.includes(PROD_MARKER_GLOBAL)) {
|
|
1729
|
+
return { source, action: "manual" };
|
|
1730
|
+
}
|
|
1731
|
+
const block = (indent) => [REGION_OPEN, ...buildSourceMarkerSnippet(framework).split("\n"), REGION_CLOSE].map((line) => `${indent}${line}`).join("\n");
|
|
1732
|
+
const widget = stripped.match(/^([ \t]*).*patchstack-widget.*$/m);
|
|
1733
|
+
if (widget?.index !== void 0) {
|
|
1734
|
+
const indent = widget[1] ?? "";
|
|
1735
|
+
return {
|
|
1736
|
+
source: `${stripped.slice(0, widget.index)}${block(indent)}
|
|
1737
|
+
${stripped.slice(widget.index)}`,
|
|
1738
|
+
action: "added"
|
|
1739
|
+
};
|
|
1740
|
+
}
|
|
1741
|
+
for (const anchor of [/^([ \t]*)<head>[ \t]*$/m, /^([ \t]*)<body>[ \t]*$/m]) {
|
|
1742
|
+
const match = stripped.match(anchor);
|
|
1743
|
+
if (match?.index === void 0) {
|
|
1744
|
+
continue;
|
|
1745
|
+
}
|
|
1746
|
+
const end = match.index + match[0].length;
|
|
1747
|
+
const indent = `${match[1] ?? ""} `;
|
|
1748
|
+
return {
|
|
1749
|
+
source: `${stripped.slice(0, end)}
|
|
1750
|
+
${block(indent)}${stripped.slice(end)}`,
|
|
1751
|
+
action: "added"
|
|
1752
|
+
};
|
|
1753
|
+
}
|
|
1754
|
+
return { source, action: "no-anchor" };
|
|
1755
|
+
}
|
|
1756
|
+
function ensureSourceMarker(cwd, shell, framework) {
|
|
1757
|
+
if (shell === null) {
|
|
1758
|
+
return { shell: null, source: "", action: "no-anchor" };
|
|
1759
|
+
}
|
|
1760
|
+
const file = path7.resolve(cwd, shell);
|
|
1761
|
+
const before = readFileSync(file, "utf8");
|
|
1762
|
+
const result = ensureMarkerInJsxShell(before, framework);
|
|
1763
|
+
if (result.source !== before && result.action === "added") {
|
|
1764
|
+
writeFileSync(file, result.source);
|
|
1765
|
+
}
|
|
1766
|
+
return { ...result, shell };
|
|
1767
|
+
}
|
|
1662
1768
|
|
|
1663
1769
|
// src/guide.ts
|
|
1664
|
-
import { existsSync as existsSync10, readFileSync as
|
|
1770
|
+
import { existsSync as existsSync10, readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
|
|
1665
1771
|
import path9 from "path";
|
|
1666
1772
|
|
|
1667
1773
|
// src/protect/install/util.ts
|
|
1668
|
-
import { readFileSync, existsSync as existsSync2, writeFileSync } from "fs";
|
|
1774
|
+
import { readFileSync as readFileSync2, existsSync as existsSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
1669
1775
|
import { join as join2, dirname } from "path";
|
|
1670
1776
|
import { fileURLToPath } from "url";
|
|
1671
|
-
var read = (p) =>
|
|
1777
|
+
var read = (p) => readFileSync2(p, "utf8");
|
|
1672
1778
|
var log = (msg) => console.log(`patchstack protect: ${msg}`);
|
|
1673
1779
|
function hasDependency(cwd, name) {
|
|
1674
1780
|
try {
|
|
@@ -1704,7 +1810,7 @@ function bakeSiteUuid(cwd, guardRelPath) {
|
|
|
1704
1810
|
log(`${guardRelPath} site UUID already baked`);
|
|
1705
1811
|
return false;
|
|
1706
1812
|
}
|
|
1707
|
-
|
|
1813
|
+
writeFileSync2(guardPath, source.replace(SITE_UUID_PLACEHOLDER, uuid));
|
|
1708
1814
|
log(`baked site UUID into ${guardRelPath} \u2014 live rules from the Patchstack API`);
|
|
1709
1815
|
return true;
|
|
1710
1816
|
}
|
|
@@ -1721,7 +1827,7 @@ function templatesDir() {
|
|
|
1721
1827
|
}
|
|
1722
1828
|
|
|
1723
1829
|
// src/protect/install/adapters/tanstack-supabase.ts
|
|
1724
|
-
import { writeFileSync as
|
|
1830
|
+
import { writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync, copyFileSync } from "fs";
|
|
1725
1831
|
import { join as join3 } from "path";
|
|
1726
1832
|
var CLIENT_TUNNEL = [
|
|
1727
1833
|
"",
|
|
@@ -1835,7 +1941,7 @@ function patchClient(cwd) {
|
|
|
1835
1941
|
log("client.ts anchor not found \u2014 skipping (template changed?)");
|
|
1836
1942
|
return false;
|
|
1837
1943
|
}
|
|
1838
|
-
|
|
1944
|
+
writeFileSync3(p, s.replace(anchor, anchor + "\n" + CLIENT_TUNNEL));
|
|
1839
1945
|
log("patched client.ts (tunnel Supabase through the guard)");
|
|
1840
1946
|
return true;
|
|
1841
1947
|
}
|
|
@@ -1872,7 +1978,7 @@ function patchStart(cwd) {
|
|
|
1872
1978
|
log("start.ts already wired");
|
|
1873
1979
|
return false;
|
|
1874
1980
|
}
|
|
1875
|
-
|
|
1981
|
+
writeFileSync3(p, s);
|
|
1876
1982
|
log("patched start.ts (guard registered as request + function middleware)");
|
|
1877
1983
|
return true;
|
|
1878
1984
|
}
|
|
@@ -2123,7 +2229,7 @@ var nuxtAdapter = {
|
|
|
2123
2229
|
};
|
|
2124
2230
|
|
|
2125
2231
|
// src/protect/install/find-app.ts
|
|
2126
|
-
import { readFileSync as
|
|
2232
|
+
import { readFileSync as readFileSync3, existsSync as existsSync6, readdirSync as readdirSync2, lstatSync } from "fs";
|
|
2127
2233
|
import { join as join8, relative, dirname as dirname3 } from "path";
|
|
2128
2234
|
var SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
2129
2235
|
"node_modules",
|
|
@@ -2170,7 +2276,7 @@ function findAppInstance(cwd, re) {
|
|
|
2170
2276
|
else if (/\.(?:ts|js|mjs|cjs)$/.test(name)) {
|
|
2171
2277
|
let src;
|
|
2172
2278
|
try {
|
|
2173
|
-
src =
|
|
2279
|
+
src = readFileSync3(p, "utf8");
|
|
2174
2280
|
} catch {
|
|
2175
2281
|
continue;
|
|
2176
2282
|
}
|
|
@@ -2190,11 +2296,11 @@ function importSpecifier(fromRel, toRel, preserveExtension = false) {
|
|
|
2190
2296
|
}
|
|
2191
2297
|
|
|
2192
2298
|
// src/protect/install/register.ts
|
|
2193
|
-
import { writeFileSync as
|
|
2299
|
+
import { writeFileSync as writeFileSync5, existsSync as existsSync8 } from "fs";
|
|
2194
2300
|
import { join as join10 } from "path";
|
|
2195
2301
|
|
|
2196
2302
|
// src/protect/install/generic.ts
|
|
2197
|
-
import { readFileSync as
|
|
2303
|
+
import { readFileSync as readFileSync4, existsSync as existsSync7, mkdirSync as mkdirSync4, copyFileSync as copyFileSync4, readdirSync as readdirSync3, lstatSync as lstatSync2 } from "fs";
|
|
2198
2304
|
import { join as join9 } from "path";
|
|
2199
2305
|
var GUARD_MARKER = "patchstack/guard";
|
|
2200
2306
|
function genericDir(cwd) {
|
|
@@ -2302,7 +2408,7 @@ function guardIsImported(cwd, guardPath) {
|
|
|
2302
2408
|
if (st.isDirectory()) walk2(p, depth + 1);
|
|
2303
2409
|
else if (/\.(?:ts|tsx|js|jsx|mjs|cjs)$/.test(name) && p !== guardPath) {
|
|
2304
2410
|
try {
|
|
2305
|
-
if (
|
|
2411
|
+
if (readFileSync4(p, "utf8").includes(GUARD_MARKER)) found = true;
|
|
2306
2412
|
} catch {
|
|
2307
2413
|
}
|
|
2308
2414
|
}
|
|
@@ -2385,7 +2491,7 @@ function wireRegister(cwd, opts, spec) {
|
|
|
2385
2491
|
const callIdx = preferredIdx === -1 ? appIdx : preferredIdx;
|
|
2386
2492
|
lines.splice(callIdx + 1, 0, REGION, spec.call(entry.appVar), "// #endregion patchstack");
|
|
2387
2493
|
}
|
|
2388
|
-
|
|
2494
|
+
writeFileSync5(p, lines.join("\n"));
|
|
2389
2495
|
changed.push(entry.relPath);
|
|
2390
2496
|
log(`patched ${entry.relPath} (${spec.call(entry.appVar).trim()})`);
|
|
2391
2497
|
return { ok: true, changed: [...new Set(changed)] };
|
|
@@ -2514,7 +2620,7 @@ function runVerify(cwd) {
|
|
|
2514
2620
|
}
|
|
2515
2621
|
|
|
2516
2622
|
// src/widget.ts
|
|
2517
|
-
import { existsSync as existsSync9, readFileSync as
|
|
2623
|
+
import { existsSync as existsSync9, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
2518
2624
|
import path8 from "path";
|
|
2519
2625
|
var WIDGET_SCRIPT_URL = "https://cdn.patchstack.com/patchstack-widget.js";
|
|
2520
2626
|
var WIDGET_MARKER_ATTR = "data-patchstack-connect-widget";
|
|
@@ -2565,15 +2671,16 @@ function ensureSourceWidget(cwd, siteUuid) {
|
|
|
2565
2671
|
return { shell: null, action: "no-shell" };
|
|
2566
2672
|
}
|
|
2567
2673
|
const file = path8.join(cwd, shell);
|
|
2568
|
-
const before =
|
|
2674
|
+
const before = readFileSync5(file, "utf8");
|
|
2569
2675
|
const { html, action } = ensureWidgetInHtml(before, siteUuid);
|
|
2570
2676
|
if (html !== before) {
|
|
2571
|
-
|
|
2677
|
+
writeFileSync6(file, html);
|
|
2572
2678
|
}
|
|
2573
2679
|
return { shell, action };
|
|
2574
2680
|
}
|
|
2575
2681
|
|
|
2576
2682
|
// src/guide.ts
|
|
2683
|
+
var PROD_MARKER_NEEDLE = "__PATCHSTACK_PROD__";
|
|
2577
2684
|
var WIDGET_NEEDLE2 = "patchstack-widget";
|
|
2578
2685
|
var INSTALL_COMMANDS = {
|
|
2579
2686
|
npm: "npm install --save @patchstack/connect",
|
|
@@ -2657,7 +2764,7 @@ function installCommand(pm) {
|
|
|
2657
2764
|
}
|
|
2658
2765
|
function readPackageJson(cwd) {
|
|
2659
2766
|
try {
|
|
2660
|
-
return JSON.parse(
|
|
2767
|
+
return JSON.parse(readFileSync6(path9.join(cwd, "package.json"), "utf8"));
|
|
2661
2768
|
} catch {
|
|
2662
2769
|
return null;
|
|
2663
2770
|
}
|
|
@@ -2665,7 +2772,7 @@ function readPackageJson(cwd) {
|
|
|
2665
2772
|
function installedVersion(cwd, declaredRange) {
|
|
2666
2773
|
try {
|
|
2667
2774
|
const pkg = JSON.parse(
|
|
2668
|
-
|
|
2775
|
+
readFileSync6(path9.join(cwd, "node_modules", "@patchstack", "connect", "package.json"), "utf8")
|
|
2669
2776
|
);
|
|
2670
2777
|
if (typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
2671
2778
|
return pkg.version;
|
|
@@ -2709,7 +2816,7 @@ function findWidgetMarker(cwd, siteUuid) {
|
|
|
2709
2816
|
if (statSync2(full).size > WIDGET_SCAN_MAX_BYTES) {
|
|
2710
2817
|
continue;
|
|
2711
2818
|
}
|
|
2712
|
-
const content =
|
|
2819
|
+
const content = readFileSync6(full, "utf8");
|
|
2713
2820
|
if (!content.includes(WIDGET_NEEDLE2)) {
|
|
2714
2821
|
continue;
|
|
2715
2822
|
}
|
|
@@ -2727,6 +2834,16 @@ function findWidgetMarker(cwd, siteUuid) {
|
|
|
2727
2834
|
uuidMatches: sawWidget && siteUuid != null ? sawTokenMatch : null
|
|
2728
2835
|
};
|
|
2729
2836
|
}
|
|
2837
|
+
function findProductionMarker(cwd, widgetFileHint) {
|
|
2838
|
+
if (widgetFileHint === null) {
|
|
2839
|
+
return false;
|
|
2840
|
+
}
|
|
2841
|
+
try {
|
|
2842
|
+
return readFileSync6(path9.join(cwd, widgetFileHint), "utf8").includes(PROD_MARKER_NEEDLE);
|
|
2843
|
+
} catch {
|
|
2844
|
+
return false;
|
|
2845
|
+
}
|
|
2846
|
+
}
|
|
2730
2847
|
function resolveWidgetFileHint(cwd, framework) {
|
|
2731
2848
|
const candidates = [
|
|
2732
2849
|
...framework !== null ? WIDGET_FILE_CANDIDATES[framework] ?? [] : [],
|
|
@@ -2776,6 +2893,7 @@ async function collectGuideState(cwd) {
|
|
|
2776
2893
|
{}
|
|
2777
2894
|
);
|
|
2778
2895
|
const widget = findWidgetMarker(cwd, siteUuid);
|
|
2896
|
+
const widgetFileHint = resolveWidgetFileHint(cwd, stack.framework);
|
|
2779
2897
|
const protection = runVerify(cwd);
|
|
2780
2898
|
return {
|
|
2781
2899
|
projectName: pkg?.name ?? null,
|
|
@@ -2795,7 +2913,8 @@ async function collectGuideState(cwd) {
|
|
|
2795
2913
|
widgetTokenMatches: widget.uuidMatches,
|
|
2796
2914
|
widgetOptOut,
|
|
2797
2915
|
framework: stack.framework,
|
|
2798
|
-
widgetFileHint
|
|
2916
|
+
widgetFileHint,
|
|
2917
|
+
productionMarkerWired: findProductionMarker(cwd, widgetFileHint),
|
|
2799
2918
|
protectionWired: protection.wired,
|
|
2800
2919
|
protectionStack: protection.stack,
|
|
2801
2920
|
protectionChecks: protection.checks
|
|
@@ -2809,6 +2928,12 @@ var ANSI = {
|
|
|
2809
2928
|
yellow: "\x1B[33m",
|
|
2810
2929
|
cyan: "\x1B[36m"
|
|
2811
2930
|
};
|
|
2931
|
+
function needsSourceProductionMarker(state) {
|
|
2932
|
+
if (state.siteUuid === null || state.widgetOptOut || state.widgetFileHint === null) {
|
|
2933
|
+
return false;
|
|
2934
|
+
}
|
|
2935
|
+
return !state.widgetFileHint.toLowerCase().endsWith(".html");
|
|
2936
|
+
}
|
|
2812
2937
|
function countRemainingSteps(state) {
|
|
2813
2938
|
return [
|
|
2814
2939
|
state.installed?.section === "dependencies",
|
|
@@ -2816,6 +2941,7 @@ function countRemainingSteps(state) {
|
|
|
2816
2941
|
state.installScanWired,
|
|
2817
2942
|
!state.hasBuildScript || state.prebuildWired && state.postbuildWired,
|
|
2818
2943
|
state.widgetOptOut || state.widgetInstalled && state.widgetTokenMatches !== false,
|
|
2944
|
+
!needsSourceProductionMarker(state) || state.productionMarkerWired,
|
|
2819
2945
|
state.protectionWired
|
|
2820
2946
|
].filter((step) => !step).length;
|
|
2821
2947
|
}
|
|
@@ -2906,6 +3032,34 @@ function renderGuideChecklist(state, useColor) {
|
|
|
2906
3032
|
lines.push(detail(` ${buildWidgetTag(state.siteUuid)}`));
|
|
2907
3033
|
lines.push(detail("The site UUID is public by design \u2014 it ships in client-side HTML."));
|
|
2908
3034
|
}
|
|
3035
|
+
if (needsSourceProductionMarker(state)) {
|
|
3036
|
+
if (state.productionMarkerWired) {
|
|
3037
|
+
lines.push(done("Production marker wired (widget switches to report mode on the published site)"));
|
|
3038
|
+
} else {
|
|
3039
|
+
lines.push(todo("Add the production marker \u2014 this root is server-rendered, so mark-build cannot stamp it"));
|
|
3040
|
+
lines.push(
|
|
3041
|
+
detail(
|
|
3042
|
+
"Without it the widget treats the published site as build mode and shows the claim flow to visitors."
|
|
3043
|
+
)
|
|
3044
|
+
);
|
|
3045
|
+
const gate = productionGate(state.framework);
|
|
3046
|
+
if (hasJsxShell(state.framework)) {
|
|
3047
|
+
lines.push(detail(`Run \u2192 npx @patchstack/connect scan (adds it to ${state.widgetFileHint} automatically)`));
|
|
3048
|
+
lines.push(detail("Or add it by hand, in <head> above the widget tag:"));
|
|
3049
|
+
for (const snippetLine of buildSourceMarkerSnippet(state.framework).split("\n")) {
|
|
3050
|
+
lines.push(detail(` ${snippetLine}`));
|
|
3051
|
+
}
|
|
3052
|
+
} else {
|
|
3053
|
+
lines.push(
|
|
3054
|
+
detail(
|
|
3055
|
+
`Edit ${state.widgetFileHint} \u2192 using the framework's head mechanism, emit an inline`
|
|
3056
|
+
)
|
|
3057
|
+
);
|
|
3058
|
+
lines.push(detail(` <script>window.__PATCHSTACK_PROD__=true;</script> only when ${gate}.`));
|
|
3059
|
+
}
|
|
3060
|
+
lines.push(detail(`The ${gate} guard is required \u2014 an ungated marker also hides the claim flow in preview.`));
|
|
3061
|
+
}
|
|
3062
|
+
}
|
|
2909
3063
|
if (state.protectionWired) {
|
|
2910
3064
|
lines.push(done(`Runtime protection wired (${state.protectionStack})`));
|
|
2911
3065
|
} else {
|
|
@@ -2949,7 +3103,7 @@ function renderGuideChecklist(state, useColor) {
|
|
|
2949
3103
|
}
|
|
2950
3104
|
|
|
2951
3105
|
// src/login.ts
|
|
2952
|
-
import { mkdtempSync, readFileSync as
|
|
3106
|
+
import { mkdtempSync, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync7 } from "fs";
|
|
2953
3107
|
import { tmpdir } from "os";
|
|
2954
3108
|
import path10 from "path";
|
|
2955
3109
|
import { createHash as createHash2 } from "crypto";
|
|
@@ -2966,11 +3120,11 @@ function pendingPath(siteUuid) {
|
|
|
2966
3120
|
return path10.join(tmpdir(), `patchstack-login-${key}.json`);
|
|
2967
3121
|
}
|
|
2968
3122
|
function savePendingLogin(siteUuid, pending) {
|
|
2969
|
-
|
|
3123
|
+
writeFileSync7(pendingPath(siteUuid), JSON.stringify(pending), { encoding: "utf8", mode: 384 });
|
|
2970
3124
|
}
|
|
2971
3125
|
function readPendingLogin(siteUuid) {
|
|
2972
3126
|
try {
|
|
2973
|
-
return JSON.parse(
|
|
3127
|
+
return JSON.parse(readFileSync7(pendingPath(siteUuid), "utf8"));
|
|
2974
3128
|
} catch {
|
|
2975
3129
|
return null;
|
|
2976
3130
|
}
|
|
@@ -3094,7 +3248,7 @@ async function loadTypeScript(cwd) {
|
|
|
3094
3248
|
}
|
|
3095
3249
|
|
|
3096
3250
|
// src/map/extract.ts
|
|
3097
|
-
import { readFileSync as
|
|
3251
|
+
import { readFileSync as readFileSync12, realpathSync as realpathSync3 } from "fs";
|
|
3098
3252
|
import { createHash as createHash4 } from "crypto";
|
|
3099
3253
|
import { relative as relative4 } from "path";
|
|
3100
3254
|
|
|
@@ -3350,7 +3504,7 @@ function npmPackageOf(spec) {
|
|
|
3350
3504
|
}
|
|
3351
3505
|
|
|
3352
3506
|
// src/map/sources.ts
|
|
3353
|
-
import { readFileSync as
|
|
3507
|
+
import { readFileSync as readFileSync8, readdirSync as readdirSync5, realpathSync, statSync as statSync3 } from "fs";
|
|
3354
3508
|
import { join as join12, relative as relative2, isAbsolute } from "path";
|
|
3355
3509
|
|
|
3356
3510
|
// src/map/routes.ts
|
|
@@ -3435,7 +3589,7 @@ function hasEntrySignal(text) {
|
|
|
3435
3589
|
}
|
|
3436
3590
|
function detectFramework(cwd) {
|
|
3437
3591
|
try {
|
|
3438
|
-
const pkg = JSON.parse(
|
|
3592
|
+
const pkg = JSON.parse(readFileSync8(join12(cwd, "package.json"), "utf8"));
|
|
3439
3593
|
const d = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
3440
3594
|
if (d["@tanstack/react-start"] || d["@tanstack/start"] || d["@tanstack/solid-start"]) return "tanstack-start";
|
|
3441
3595
|
if (d["next"]) return "next";
|
|
@@ -3632,7 +3786,7 @@ function holdsSourceFile(dir) {
|
|
|
3632
3786
|
}
|
|
3633
3787
|
|
|
3634
3788
|
// src/map/surface.ts
|
|
3635
|
-
import { readFileSync as
|
|
3789
|
+
import { readFileSync as readFileSync9 } from "fs";
|
|
3636
3790
|
import { join as join13 } from "path";
|
|
3637
3791
|
var STATIC_GENERATORS = [
|
|
3638
3792
|
{ dep: "vite", label: "vite" },
|
|
@@ -3671,7 +3825,7 @@ var SHAPE_SIGNAL = {
|
|
|
3671
3825
|
};
|
|
3672
3826
|
function readManifest(cwd) {
|
|
3673
3827
|
try {
|
|
3674
|
-
const parsed = JSON.parse(
|
|
3828
|
+
const parsed = JSON.parse(readFileSync9(join13(cwd, "package.json"), "utf8"));
|
|
3675
3829
|
return typeof parsed === "object" && parsed !== null ? parsed : null;
|
|
3676
3830
|
} catch {
|
|
3677
3831
|
return null;
|
|
@@ -3684,7 +3838,7 @@ function nextExportsStatically(cwd, manifest, ts) {
|
|
|
3684
3838
|
for (const file of ["next.config.js", "next.config.mjs", "next.config.ts", "next.config.cjs"]) {
|
|
3685
3839
|
let text;
|
|
3686
3840
|
try {
|
|
3687
|
-
text =
|
|
3841
|
+
text = readFileSync9(join13(cwd, file), "utf8");
|
|
3688
3842
|
} catch {
|
|
3689
3843
|
continue;
|
|
3690
3844
|
}
|
|
@@ -4084,7 +4238,7 @@ function dedupeSinks(sinks, owner) {
|
|
|
4084
4238
|
}
|
|
4085
4239
|
|
|
4086
4240
|
// src/map/module-graph.ts
|
|
4087
|
-
import { readFileSync as
|
|
4241
|
+
import { readFileSync as readFileSync10, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
|
|
4088
4242
|
import { dirname as dirname4, join as join14, relative as relative3, resolve as resolvePath } from "path";
|
|
4089
4243
|
var RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
|
|
4090
4244
|
function createModuleGraph(ts, opts) {
|
|
@@ -4093,7 +4247,7 @@ function createModuleGraph(ts, opts) {
|
|
|
4093
4247
|
if (cache.has(file)) return cache.get(file) ?? null;
|
|
4094
4248
|
let entry = null;
|
|
4095
4249
|
try {
|
|
4096
|
-
const text =
|
|
4250
|
+
const text = readFileSync10(file, "utf8");
|
|
4097
4251
|
const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, file));
|
|
4098
4252
|
const bindings = buildModuleBindings(sf, ts);
|
|
4099
4253
|
entry = { fnSinks: collectLocalSinks(sf, ts, bindings), calleesOf: collectCallees(sf, ts), bindings };
|
|
@@ -4835,7 +4989,7 @@ function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, c
|
|
|
4835
4989
|
}
|
|
4836
4990
|
|
|
4837
4991
|
// src/map/imports.ts
|
|
4838
|
-
import { readFileSync as
|
|
4992
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
4839
4993
|
import { join as join15 } from "path";
|
|
4840
4994
|
var MAX_SITES = 5;
|
|
4841
4995
|
var NPM_NAME = /^(?:@[^/@\s~][^/@\s]*\/)?[^/@\s.~][^/@\s]*$/;
|
|
@@ -4847,7 +5001,7 @@ function readPathAliases(cwd) {
|
|
|
4847
5001
|
for (const name of ["tsconfig.json", "jsconfig.json"]) {
|
|
4848
5002
|
let paths;
|
|
4849
5003
|
try {
|
|
4850
|
-
const raw =
|
|
5004
|
+
const raw = readFileSync11(join15(cwd, name), "utf8");
|
|
4851
5005
|
const parsed = JSON.parse(stripJsonComments(raw));
|
|
4852
5006
|
paths = parsed.compilerOptions?.paths;
|
|
4853
5007
|
} catch {
|
|
@@ -5226,7 +5380,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
5226
5380
|
const hopTargets = /* @__PURE__ */ new Set();
|
|
5227
5381
|
for (const file of files) {
|
|
5228
5382
|
try {
|
|
5229
|
-
const text =
|
|
5383
|
+
const text = readFileSync12(file, "utf8");
|
|
5230
5384
|
const relFile = relative4(cwd, file);
|
|
5231
5385
|
sourceBytes += text.length;
|
|
5232
5386
|
if (!hasEntrySignal(text)) {
|
|
@@ -5281,7 +5435,7 @@ async function extractInputMap(cwd, ts, options = {}) {
|
|
|
5281
5435
|
for (const target of hopTargets) {
|
|
5282
5436
|
if (entryFiles.has(target)) continue;
|
|
5283
5437
|
try {
|
|
5284
|
-
const text =
|
|
5438
|
+
const text = readFileSync12(target, "utf8");
|
|
5285
5439
|
const sf = ts.createSourceFile(target, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, target));
|
|
5286
5440
|
const bindings = buildModuleBindings(sf, ts);
|
|
5287
5441
|
const called = collectInvocations(sf, ts, bindings, { file: target, owner: relative4(cwd, target), graph });
|
|
@@ -5412,7 +5566,7 @@ async function buildInputMap(cwd, options = {}) {
|
|
|
5412
5566
|
}
|
|
5413
5567
|
|
|
5414
5568
|
// src/setup.ts
|
|
5415
|
-
import { readFileSync as
|
|
5569
|
+
import { readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
|
|
5416
5570
|
import path11 from "path";
|
|
5417
5571
|
var SCAN_COMMAND = "patchstack-connect scan";
|
|
5418
5572
|
var MARK_BUILD_COMMAND = "patchstack-connect mark-build";
|
|
@@ -5432,7 +5586,7 @@ function appendHook(existing, command) {
|
|
|
5432
5586
|
}
|
|
5433
5587
|
function wireBuildScripts(cwd, packageManager) {
|
|
5434
5588
|
const target = path11.join(cwd, "package.json");
|
|
5435
|
-
const raw =
|
|
5589
|
+
const raw = readFileSync13(target, "utf8");
|
|
5436
5590
|
const pkg = JSON.parse(raw);
|
|
5437
5591
|
const scripts = pkg.scripts ?? {};
|
|
5438
5592
|
const build = scripts.build;
|
|
@@ -5481,7 +5635,7 @@ function wireBuildScripts(cwd, packageManager) {
|
|
|
5481
5635
|
const indentMatch = raw.match(/^[\t ]+(?=")/m)?.[0];
|
|
5482
5636
|
const indent = indentMatch?.includes(" ") ? " " : indentMatch?.length ?? 2;
|
|
5483
5637
|
const trailingNewline = raw.endsWith("\n") ? "\n" : "";
|
|
5484
|
-
|
|
5638
|
+
writeFileSync8(target, `${JSON.stringify(pkg, null, indent)}${trailingNewline}`, "utf8");
|
|
5485
5639
|
if (build === void 0 || build.trim().length === 0) {
|
|
5486
5640
|
return {
|
|
5487
5641
|
changed: true,
|
|
@@ -5782,7 +5936,7 @@ async function runMap(args) {
|
|
|
5782
5936
|
const json = JSON.stringify(map, null, 2);
|
|
5783
5937
|
const out = getStringFlag(args.flags, "out");
|
|
5784
5938
|
if (out) {
|
|
5785
|
-
|
|
5939
|
+
writeFileSync9(out, json);
|
|
5786
5940
|
console.error(`patchstack: wrote ${out}`);
|
|
5787
5941
|
} else if (args.flags.get("upload") !== true) {
|
|
5788
5942
|
console.log(json);
|
|
@@ -5855,6 +6009,9 @@ async function runScan(args, options = {}) {
|
|
|
5855
6009
|
}
|
|
5856
6010
|
return 0;
|
|
5857
6011
|
}
|
|
6012
|
+
if (config.widget) {
|
|
6013
|
+
reportSourceMarker(detectStack(payload.packages).framework);
|
|
6014
|
+
}
|
|
5858
6015
|
const provisioning = config.siteUuid === null;
|
|
5859
6016
|
if (provisioning) {
|
|
5860
6017
|
console.log("No site UUID configured \u2014 provisioning a new Patchstack site from this manifest\u2026");
|
|
@@ -5937,6 +6094,41 @@ function reportSourceWidget(siteUuid) {
|
|
|
5937
6094
|
console.warn(`Widget: skipped (${err.message}).`);
|
|
5938
6095
|
}
|
|
5939
6096
|
}
|
|
6097
|
+
function reportSourceMarker(framework) {
|
|
6098
|
+
try {
|
|
6099
|
+
const shell = resolveWidgetFileHint(process.cwd(), framework);
|
|
6100
|
+
if (shell === null || shell.toLowerCase().endsWith(".html")) {
|
|
6101
|
+
return;
|
|
6102
|
+
}
|
|
6103
|
+
const result = ensureSourceMarker(process.cwd(), shell, framework);
|
|
6104
|
+
switch (result.action) {
|
|
6105
|
+
case "added":
|
|
6106
|
+
console.log(`Production marker: added to ${shell} (guarded by ${productionGate(framework)}).`);
|
|
6107
|
+
console.log(" This root is server-rendered, so the marker ships in source rather than built HTML.");
|
|
6108
|
+
break;
|
|
6109
|
+
case "manual":
|
|
6110
|
+
console.log(`Production marker: already set in ${shell} \u2014 left untouched.`);
|
|
6111
|
+
break;
|
|
6112
|
+
case "no-anchor":
|
|
6113
|
+
case "unsupported":
|
|
6114
|
+
console.log(
|
|
6115
|
+
`Production marker: ${shell} needs it by hand \u2014 without it the widget reads the published site as build mode.`
|
|
6116
|
+
);
|
|
6117
|
+
if (hasJsxShell(framework)) {
|
|
6118
|
+
for (const line of buildSourceMarkerSnippet(framework).split("\n")) {
|
|
6119
|
+
console.log(` ${line}`);
|
|
6120
|
+
}
|
|
6121
|
+
} else {
|
|
6122
|
+
console.log(
|
|
6123
|
+
` Emit <script>window.__PATCHSTACK_PROD__=true;</script> only when ${productionGate(framework)}.`
|
|
6124
|
+
);
|
|
6125
|
+
}
|
|
6126
|
+
break;
|
|
6127
|
+
}
|
|
6128
|
+
} catch (err) {
|
|
6129
|
+
console.warn(`Production marker: skipped (${err.message}).`);
|
|
6130
|
+
}
|
|
6131
|
+
}
|
|
5940
6132
|
async function runProtectCommand(args) {
|
|
5941
6133
|
if (args.flags.get("check") === true) {
|
|
5942
6134
|
const report = runVerify(process.cwd());
|
|
@@ -6068,7 +6260,7 @@ async function runGuide(args) {
|
|
|
6068
6260
|
console.log("\u2014\u2014\u2014\u2014 Full reference guide (ships as AGENT-INSTALL.md) \u2014\u2014\u2014\u2014");
|
|
6069
6261
|
console.log("");
|
|
6070
6262
|
const guidePath = new URL("../AGENT-INSTALL.md", import.meta.url);
|
|
6071
|
-
console.log(
|
|
6263
|
+
console.log(readFileSync14(guidePath, "utf8"));
|
|
6072
6264
|
return 0;
|
|
6073
6265
|
}
|
|
6074
6266
|
async function runSetup(args) {
|
|
@@ -6242,14 +6434,20 @@ async function runMarkBuild(args) {
|
|
|
6242
6434
|
}
|
|
6243
6435
|
const files = findHtmlFiles(dir);
|
|
6244
6436
|
if (files.length === 0) {
|
|
6245
|
-
console.warn(`mark-build:
|
|
6437
|
+
console.warn(`mark-build: found ${dir} but no HTML files in it.`);
|
|
6438
|
+
console.warn(
|
|
6439
|
+
"mark-build: this build looks server-rendered, so the production flag has nothing to stamp."
|
|
6440
|
+
);
|
|
6441
|
+
console.warn(
|
|
6442
|
+
"mark-build: add the marker to your root shell instead \u2014 run `patchstack-connect guide` for the snippet."
|
|
6443
|
+
);
|
|
6246
6444
|
return 0;
|
|
6247
6445
|
}
|
|
6248
6446
|
const snippet = buildInjectionSnippet(checksum, stack);
|
|
6249
6447
|
let marked = 0;
|
|
6250
6448
|
let widgetTouched = 0;
|
|
6251
6449
|
for (const file of files) {
|
|
6252
|
-
const before =
|
|
6450
|
+
const before = readFileSync14(file, "utf8");
|
|
6253
6451
|
let after = injectMarker(before, snippet);
|
|
6254
6452
|
if (widgetUuid !== null) {
|
|
6255
6453
|
const ensured = ensureWidgetInHtml(after, widgetUuid);
|
|
@@ -6259,7 +6457,7 @@ async function runMarkBuild(args) {
|
|
|
6259
6457
|
after = ensured.html;
|
|
6260
6458
|
}
|
|
6261
6459
|
if (after !== before) {
|
|
6262
|
-
|
|
6460
|
+
writeFileSync9(file, after);
|
|
6263
6461
|
marked += 1;
|
|
6264
6462
|
}
|
|
6265
6463
|
}
|