@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/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 readFileSync13, writeFileSync as writeFileSync8 } from "fs";
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 readFileSync5, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
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) => readFileSync(p, "utf8");
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
- writeFileSync(guardPath, source.replace(SITE_UUID_PLACEHOLDER, uuid));
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 writeFileSync2, existsSync as existsSync3, mkdirSync, copyFileSync } from "fs";
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
- writeFileSync2(p, s.replace(anchor, anchor + "\n" + CLIENT_TUNNEL));
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
- writeFileSync2(p, s);
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 readFileSync2, existsSync as existsSync6, readdirSync as readdirSync2, lstatSync } from "fs";
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 = readFileSync2(p, "utf8");
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 writeFileSync4, existsSync as existsSync8 } from "fs";
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 readFileSync3, existsSync as existsSync7, mkdirSync as mkdirSync4, copyFileSync as copyFileSync4, readdirSync as readdirSync3, lstatSync as lstatSync2 } from "fs";
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 (readFileSync3(p, "utf8").includes(GUARD_MARKER)) found = true;
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
- writeFileSync4(p, lines.join("\n"));
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 readFileSync4, writeFileSync as writeFileSync5 } from "fs";
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 = readFileSync4(file, "utf8");
2674
+ const before = readFileSync5(file, "utf8");
2569
2675
  const { html, action } = ensureWidgetInHtml(before, siteUuid);
2570
2676
  if (html !== before) {
2571
- writeFileSync5(file, html);
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(readFileSync5(path9.join(cwd, "package.json"), "utf8"));
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
- readFileSync5(path9.join(cwd, "node_modules", "@patchstack", "connect", "package.json"), "utf8")
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 = readFileSync5(full, "utf8");
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: resolveWidgetFileHint(cwd, stack.framework),
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 readFileSync6, rmSync, writeFileSync as writeFileSync6 } from "fs";
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
- writeFileSync6(pendingPath(siteUuid), JSON.stringify(pending), { encoding: "utf8", mode: 384 });
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(readFileSync6(pendingPath(siteUuid), "utf8"));
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 readFileSync11, realpathSync as realpathSync3 } from "fs";
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 readFileSync7, readdirSync as readdirSync5, realpathSync, statSync as statSync3 } from "fs";
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(readFileSync7(join12(cwd, "package.json"), "utf8"));
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 readFileSync8 } from "fs";
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(readFileSync8(join13(cwd, "package.json"), "utf8"));
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 = readFileSync8(join13(cwd, file), "utf8");
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 readFileSync9, realpathSync as realpathSync2, statSync as statSync4 } from "fs";
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 = readFileSync9(file, "utf8");
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 readFileSync10 } from "fs";
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 = readFileSync10(join15(cwd, name), "utf8");
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 = readFileSync11(file, "utf8");
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 = readFileSync11(target, "utf8");
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 readFileSync12, writeFileSync as writeFileSync7 } from "fs";
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 = readFileSync12(target, "utf8");
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
- writeFileSync7(target, `${JSON.stringify(pkg, null, indent)}${trailingNewline}`, "utf8");
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
- writeFileSync8(out, json);
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(readFileSync13(guidePath, "utf8"));
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: no HTML files found under ${dir}. Nothing to mark.`);
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 = readFileSync13(file, "utf8");
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
- writeFileSync8(file, after);
6460
+ writeFileSync9(file, after);
6263
6461
  marked += 1;
6264
6462
  }
6265
6463
  }