@patchstack/connect 0.3.28 → 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";
@@ -1544,7 +1544,7 @@ function isEnvironment(value) {
1544
1544
  }
1545
1545
 
1546
1546
  // src/mark-build.ts
1547
- import { existsSync, readdirSync, statSync } from "fs";
1547
+ import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
1548
1548
  import path7 from "path";
1549
1549
 
1550
1550
  // src/stack.ts
@@ -1687,16 +1687,94 @@ function injectMarker(html, snippet) {
1687
1687
  }
1688
1688
  return stripped + snippet;
1689
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
+ }
1690
1768
 
1691
1769
  // src/guide.ts
1692
- 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";
1693
1771
  import path9 from "path";
1694
1772
 
1695
1773
  // src/protect/install/util.ts
1696
- import { readFileSync, existsSync as existsSync2, writeFileSync } from "fs";
1774
+ import { readFileSync as readFileSync2, existsSync as existsSync2, writeFileSync as writeFileSync2 } from "fs";
1697
1775
  import { join as join2, dirname } from "path";
1698
1776
  import { fileURLToPath } from "url";
1699
- var read = (p) => readFileSync(p, "utf8");
1777
+ var read = (p) => readFileSync2(p, "utf8");
1700
1778
  var log = (msg) => console.log(`patchstack protect: ${msg}`);
1701
1779
  function hasDependency(cwd, name) {
1702
1780
  try {
@@ -1732,7 +1810,7 @@ function bakeSiteUuid(cwd, guardRelPath) {
1732
1810
  log(`${guardRelPath} site UUID already baked`);
1733
1811
  return false;
1734
1812
  }
1735
- writeFileSync(guardPath, source.replace(SITE_UUID_PLACEHOLDER, uuid));
1813
+ writeFileSync2(guardPath, source.replace(SITE_UUID_PLACEHOLDER, uuid));
1736
1814
  log(`baked site UUID into ${guardRelPath} \u2014 live rules from the Patchstack API`);
1737
1815
  return true;
1738
1816
  }
@@ -1749,7 +1827,7 @@ function templatesDir() {
1749
1827
  }
1750
1828
 
1751
1829
  // src/protect/install/adapters/tanstack-supabase.ts
1752
- import { writeFileSync as writeFileSync2, existsSync as existsSync3, mkdirSync, copyFileSync } from "fs";
1830
+ import { writeFileSync as writeFileSync3, existsSync as existsSync3, mkdirSync, copyFileSync } from "fs";
1753
1831
  import { join as join3 } from "path";
1754
1832
  var CLIENT_TUNNEL = [
1755
1833
  "",
@@ -1863,7 +1941,7 @@ function patchClient(cwd) {
1863
1941
  log("client.ts anchor not found \u2014 skipping (template changed?)");
1864
1942
  return false;
1865
1943
  }
1866
- writeFileSync2(p, s.replace(anchor, anchor + "\n" + CLIENT_TUNNEL));
1944
+ writeFileSync3(p, s.replace(anchor, anchor + "\n" + CLIENT_TUNNEL));
1867
1945
  log("patched client.ts (tunnel Supabase through the guard)");
1868
1946
  return true;
1869
1947
  }
@@ -1900,7 +1978,7 @@ function patchStart(cwd) {
1900
1978
  log("start.ts already wired");
1901
1979
  return false;
1902
1980
  }
1903
- writeFileSync2(p, s);
1981
+ writeFileSync3(p, s);
1904
1982
  log("patched start.ts (guard registered as request + function middleware)");
1905
1983
  return true;
1906
1984
  }
@@ -2151,7 +2229,7 @@ var nuxtAdapter = {
2151
2229
  };
2152
2230
 
2153
2231
  // src/protect/install/find-app.ts
2154
- 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";
2155
2233
  import { join as join8, relative, dirname as dirname3 } from "path";
2156
2234
  var SKIP_DIRS = /* @__PURE__ */ new Set([
2157
2235
  "node_modules",
@@ -2198,7 +2276,7 @@ function findAppInstance(cwd, re) {
2198
2276
  else if (/\.(?:ts|js|mjs|cjs)$/.test(name)) {
2199
2277
  let src;
2200
2278
  try {
2201
- src = readFileSync2(p, "utf8");
2279
+ src = readFileSync3(p, "utf8");
2202
2280
  } catch {
2203
2281
  continue;
2204
2282
  }
@@ -2218,11 +2296,11 @@ function importSpecifier(fromRel, toRel, preserveExtension = false) {
2218
2296
  }
2219
2297
 
2220
2298
  // src/protect/install/register.ts
2221
- import { writeFileSync as writeFileSync4, existsSync as existsSync8 } from "fs";
2299
+ import { writeFileSync as writeFileSync5, existsSync as existsSync8 } from "fs";
2222
2300
  import { join as join10 } from "path";
2223
2301
 
2224
2302
  // src/protect/install/generic.ts
2225
- 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";
2226
2304
  import { join as join9 } from "path";
2227
2305
  var GUARD_MARKER = "patchstack/guard";
2228
2306
  function genericDir(cwd) {
@@ -2330,7 +2408,7 @@ function guardIsImported(cwd, guardPath) {
2330
2408
  if (st.isDirectory()) walk2(p, depth + 1);
2331
2409
  else if (/\.(?:ts|tsx|js|jsx|mjs|cjs)$/.test(name) && p !== guardPath) {
2332
2410
  try {
2333
- if (readFileSync3(p, "utf8").includes(GUARD_MARKER)) found = true;
2411
+ if (readFileSync4(p, "utf8").includes(GUARD_MARKER)) found = true;
2334
2412
  } catch {
2335
2413
  }
2336
2414
  }
@@ -2413,7 +2491,7 @@ function wireRegister(cwd, opts, spec) {
2413
2491
  const callIdx = preferredIdx === -1 ? appIdx : preferredIdx;
2414
2492
  lines.splice(callIdx + 1, 0, REGION, spec.call(entry.appVar), "// #endregion patchstack");
2415
2493
  }
2416
- writeFileSync4(p, lines.join("\n"));
2494
+ writeFileSync5(p, lines.join("\n"));
2417
2495
  changed.push(entry.relPath);
2418
2496
  log(`patched ${entry.relPath} (${spec.call(entry.appVar).trim()})`);
2419
2497
  return { ok: true, changed: [...new Set(changed)] };
@@ -2542,7 +2620,7 @@ function runVerify(cwd) {
2542
2620
  }
2543
2621
 
2544
2622
  // src/widget.ts
2545
- 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";
2546
2624
  import path8 from "path";
2547
2625
  var WIDGET_SCRIPT_URL = "https://cdn.patchstack.com/patchstack-widget.js";
2548
2626
  var WIDGET_MARKER_ATTR = "data-patchstack-connect-widget";
@@ -2593,15 +2671,16 @@ function ensureSourceWidget(cwd, siteUuid) {
2593
2671
  return { shell: null, action: "no-shell" };
2594
2672
  }
2595
2673
  const file = path8.join(cwd, shell);
2596
- const before = readFileSync4(file, "utf8");
2674
+ const before = readFileSync5(file, "utf8");
2597
2675
  const { html, action } = ensureWidgetInHtml(before, siteUuid);
2598
2676
  if (html !== before) {
2599
- writeFileSync5(file, html);
2677
+ writeFileSync6(file, html);
2600
2678
  }
2601
2679
  return { shell, action };
2602
2680
  }
2603
2681
 
2604
2682
  // src/guide.ts
2683
+ var PROD_MARKER_NEEDLE = "__PATCHSTACK_PROD__";
2605
2684
  var WIDGET_NEEDLE2 = "patchstack-widget";
2606
2685
  var INSTALL_COMMANDS = {
2607
2686
  npm: "npm install --save @patchstack/connect",
@@ -2685,7 +2764,7 @@ function installCommand(pm) {
2685
2764
  }
2686
2765
  function readPackageJson(cwd) {
2687
2766
  try {
2688
- return JSON.parse(readFileSync5(path9.join(cwd, "package.json"), "utf8"));
2767
+ return JSON.parse(readFileSync6(path9.join(cwd, "package.json"), "utf8"));
2689
2768
  } catch {
2690
2769
  return null;
2691
2770
  }
@@ -2693,7 +2772,7 @@ function readPackageJson(cwd) {
2693
2772
  function installedVersion(cwd, declaredRange) {
2694
2773
  try {
2695
2774
  const pkg = JSON.parse(
2696
- readFileSync5(path9.join(cwd, "node_modules", "@patchstack", "connect", "package.json"), "utf8")
2775
+ readFileSync6(path9.join(cwd, "node_modules", "@patchstack", "connect", "package.json"), "utf8")
2697
2776
  );
2698
2777
  if (typeof pkg.version === "string" && pkg.version.length > 0) {
2699
2778
  return pkg.version;
@@ -2737,7 +2816,7 @@ function findWidgetMarker(cwd, siteUuid) {
2737
2816
  if (statSync2(full).size > WIDGET_SCAN_MAX_BYTES) {
2738
2817
  continue;
2739
2818
  }
2740
- const content = readFileSync5(full, "utf8");
2819
+ const content = readFileSync6(full, "utf8");
2741
2820
  if (!content.includes(WIDGET_NEEDLE2)) {
2742
2821
  continue;
2743
2822
  }
@@ -2755,6 +2834,16 @@ function findWidgetMarker(cwd, siteUuid) {
2755
2834
  uuidMatches: sawWidget && siteUuid != null ? sawTokenMatch : null
2756
2835
  };
2757
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
+ }
2758
2847
  function resolveWidgetFileHint(cwd, framework) {
2759
2848
  const candidates = [
2760
2849
  ...framework !== null ? WIDGET_FILE_CANDIDATES[framework] ?? [] : [],
@@ -2804,6 +2893,7 @@ async function collectGuideState(cwd) {
2804
2893
  {}
2805
2894
  );
2806
2895
  const widget = findWidgetMarker(cwd, siteUuid);
2896
+ const widgetFileHint = resolveWidgetFileHint(cwd, stack.framework);
2807
2897
  const protection = runVerify(cwd);
2808
2898
  return {
2809
2899
  projectName: pkg?.name ?? null,
@@ -2823,7 +2913,8 @@ async function collectGuideState(cwd) {
2823
2913
  widgetTokenMatches: widget.uuidMatches,
2824
2914
  widgetOptOut,
2825
2915
  framework: stack.framework,
2826
- widgetFileHint: resolveWidgetFileHint(cwd, stack.framework),
2916
+ widgetFileHint,
2917
+ productionMarkerWired: findProductionMarker(cwd, widgetFileHint),
2827
2918
  protectionWired: protection.wired,
2828
2919
  protectionStack: protection.stack,
2829
2920
  protectionChecks: protection.checks
@@ -2837,6 +2928,12 @@ var ANSI = {
2837
2928
  yellow: "\x1B[33m",
2838
2929
  cyan: "\x1B[36m"
2839
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
+ }
2840
2937
  function countRemainingSteps(state) {
2841
2938
  return [
2842
2939
  state.installed?.section === "dependencies",
@@ -2844,6 +2941,7 @@ function countRemainingSteps(state) {
2844
2941
  state.installScanWired,
2845
2942
  !state.hasBuildScript || state.prebuildWired && state.postbuildWired,
2846
2943
  state.widgetOptOut || state.widgetInstalled && state.widgetTokenMatches !== false,
2944
+ !needsSourceProductionMarker(state) || state.productionMarkerWired,
2847
2945
  state.protectionWired
2848
2946
  ].filter((step) => !step).length;
2849
2947
  }
@@ -2934,6 +3032,34 @@ function renderGuideChecklist(state, useColor) {
2934
3032
  lines.push(detail(` ${buildWidgetTag(state.siteUuid)}`));
2935
3033
  lines.push(detail("The site UUID is public by design \u2014 it ships in client-side HTML."));
2936
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
+ }
2937
3063
  if (state.protectionWired) {
2938
3064
  lines.push(done(`Runtime protection wired (${state.protectionStack})`));
2939
3065
  } else {
@@ -2977,7 +3103,7 @@ function renderGuideChecklist(state, useColor) {
2977
3103
  }
2978
3104
 
2979
3105
  // src/login.ts
2980
- import { mkdtempSync, readFileSync as readFileSync6, rmSync, writeFileSync as writeFileSync6 } from "fs";
3106
+ import { mkdtempSync, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync7 } from "fs";
2981
3107
  import { tmpdir } from "os";
2982
3108
  import path10 from "path";
2983
3109
  import { createHash as createHash2 } from "crypto";
@@ -2994,11 +3120,11 @@ function pendingPath(siteUuid) {
2994
3120
  return path10.join(tmpdir(), `patchstack-login-${key}.json`);
2995
3121
  }
2996
3122
  function savePendingLogin(siteUuid, pending) {
2997
- writeFileSync6(pendingPath(siteUuid), JSON.stringify(pending), { encoding: "utf8", mode: 384 });
3123
+ writeFileSync7(pendingPath(siteUuid), JSON.stringify(pending), { encoding: "utf8", mode: 384 });
2998
3124
  }
2999
3125
  function readPendingLogin(siteUuid) {
3000
3126
  try {
3001
- return JSON.parse(readFileSync6(pendingPath(siteUuid), "utf8"));
3127
+ return JSON.parse(readFileSync7(pendingPath(siteUuid), "utf8"));
3002
3128
  } catch {
3003
3129
  return null;
3004
3130
  }
@@ -3122,7 +3248,7 @@ async function loadTypeScript(cwd) {
3122
3248
  }
3123
3249
 
3124
3250
  // src/map/extract.ts
3125
- import { readFileSync as readFileSync11, realpathSync as realpathSync3 } from "fs";
3251
+ import { readFileSync as readFileSync12, realpathSync as realpathSync3 } from "fs";
3126
3252
  import { createHash as createHash4 } from "crypto";
3127
3253
  import { relative as relative4 } from "path";
3128
3254
 
@@ -3378,7 +3504,7 @@ function npmPackageOf(spec) {
3378
3504
  }
3379
3505
 
3380
3506
  // src/map/sources.ts
3381
- 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";
3382
3508
  import { join as join12, relative as relative2, isAbsolute } from "path";
3383
3509
 
3384
3510
  // src/map/routes.ts
@@ -3463,7 +3589,7 @@ function hasEntrySignal(text) {
3463
3589
  }
3464
3590
  function detectFramework(cwd) {
3465
3591
  try {
3466
- const pkg = JSON.parse(readFileSync7(join12(cwd, "package.json"), "utf8"));
3592
+ const pkg = JSON.parse(readFileSync8(join12(cwd, "package.json"), "utf8"));
3467
3593
  const d = { ...pkg.dependencies, ...pkg.devDependencies };
3468
3594
  if (d["@tanstack/react-start"] || d["@tanstack/start"] || d["@tanstack/solid-start"]) return "tanstack-start";
3469
3595
  if (d["next"]) return "next";
@@ -3660,7 +3786,7 @@ function holdsSourceFile(dir) {
3660
3786
  }
3661
3787
 
3662
3788
  // src/map/surface.ts
3663
- import { readFileSync as readFileSync8 } from "fs";
3789
+ import { readFileSync as readFileSync9 } from "fs";
3664
3790
  import { join as join13 } from "path";
3665
3791
  var STATIC_GENERATORS = [
3666
3792
  { dep: "vite", label: "vite" },
@@ -3699,7 +3825,7 @@ var SHAPE_SIGNAL = {
3699
3825
  };
3700
3826
  function readManifest(cwd) {
3701
3827
  try {
3702
- const parsed = JSON.parse(readFileSync8(join13(cwd, "package.json"), "utf8"));
3828
+ const parsed = JSON.parse(readFileSync9(join13(cwd, "package.json"), "utf8"));
3703
3829
  return typeof parsed === "object" && parsed !== null ? parsed : null;
3704
3830
  } catch {
3705
3831
  return null;
@@ -3712,7 +3838,7 @@ function nextExportsStatically(cwd, manifest, ts) {
3712
3838
  for (const file of ["next.config.js", "next.config.mjs", "next.config.ts", "next.config.cjs"]) {
3713
3839
  let text;
3714
3840
  try {
3715
- text = readFileSync8(join13(cwd, file), "utf8");
3841
+ text = readFileSync9(join13(cwd, file), "utf8");
3716
3842
  } catch {
3717
3843
  continue;
3718
3844
  }
@@ -4112,7 +4238,7 @@ function dedupeSinks(sinks, owner) {
4112
4238
  }
4113
4239
 
4114
4240
  // src/map/module-graph.ts
4115
- 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";
4116
4242
  import { dirname as dirname4, join as join14, relative as relative3, resolve as resolvePath } from "path";
4117
4243
  var RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
4118
4244
  function createModuleGraph(ts, opts) {
@@ -4121,7 +4247,7 @@ function createModuleGraph(ts, opts) {
4121
4247
  if (cache.has(file)) return cache.get(file) ?? null;
4122
4248
  let entry = null;
4123
4249
  try {
4124
- const text = readFileSync9(file, "utf8");
4250
+ const text = readFileSync10(file, "utf8");
4125
4251
  const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, file));
4126
4252
  const bindings = buildModuleBindings(sf, ts);
4127
4253
  entry = { fnSinks: collectLocalSinks(sf, ts, bindings), calleesOf: collectCallees(sf, ts), bindings };
@@ -4863,7 +4989,7 @@ function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, c
4863
4989
  }
4864
4990
 
4865
4991
  // src/map/imports.ts
4866
- import { readFileSync as readFileSync10 } from "fs";
4992
+ import { readFileSync as readFileSync11 } from "fs";
4867
4993
  import { join as join15 } from "path";
4868
4994
  var MAX_SITES = 5;
4869
4995
  var NPM_NAME = /^(?:@[^/@\s~][^/@\s]*\/)?[^/@\s.~][^/@\s]*$/;
@@ -4875,7 +5001,7 @@ function readPathAliases(cwd) {
4875
5001
  for (const name of ["tsconfig.json", "jsconfig.json"]) {
4876
5002
  let paths;
4877
5003
  try {
4878
- const raw = readFileSync10(join15(cwd, name), "utf8");
5004
+ const raw = readFileSync11(join15(cwd, name), "utf8");
4879
5005
  const parsed = JSON.parse(stripJsonComments(raw));
4880
5006
  paths = parsed.compilerOptions?.paths;
4881
5007
  } catch {
@@ -5254,7 +5380,7 @@ async function extractInputMap(cwd, ts, options = {}) {
5254
5380
  const hopTargets = /* @__PURE__ */ new Set();
5255
5381
  for (const file of files) {
5256
5382
  try {
5257
- const text = readFileSync11(file, "utf8");
5383
+ const text = readFileSync12(file, "utf8");
5258
5384
  const relFile = relative4(cwd, file);
5259
5385
  sourceBytes += text.length;
5260
5386
  if (!hasEntrySignal(text)) {
@@ -5309,7 +5435,7 @@ async function extractInputMap(cwd, ts, options = {}) {
5309
5435
  for (const target of hopTargets) {
5310
5436
  if (entryFiles.has(target)) continue;
5311
5437
  try {
5312
- const text = readFileSync11(target, "utf8");
5438
+ const text = readFileSync12(target, "utf8");
5313
5439
  const sf = ts.createSourceFile(target, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, target));
5314
5440
  const bindings = buildModuleBindings(sf, ts);
5315
5441
  const called = collectInvocations(sf, ts, bindings, { file: target, owner: relative4(cwd, target), graph });
@@ -5440,7 +5566,7 @@ async function buildInputMap(cwd, options = {}) {
5440
5566
  }
5441
5567
 
5442
5568
  // src/setup.ts
5443
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync7 } from "fs";
5569
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync8 } from "fs";
5444
5570
  import path11 from "path";
5445
5571
  var SCAN_COMMAND = "patchstack-connect scan";
5446
5572
  var MARK_BUILD_COMMAND = "patchstack-connect mark-build";
@@ -5460,7 +5586,7 @@ function appendHook(existing, command) {
5460
5586
  }
5461
5587
  function wireBuildScripts(cwd, packageManager) {
5462
5588
  const target = path11.join(cwd, "package.json");
5463
- const raw = readFileSync12(target, "utf8");
5589
+ const raw = readFileSync13(target, "utf8");
5464
5590
  const pkg = JSON.parse(raw);
5465
5591
  const scripts = pkg.scripts ?? {};
5466
5592
  const build = scripts.build;
@@ -5509,7 +5635,7 @@ function wireBuildScripts(cwd, packageManager) {
5509
5635
  const indentMatch = raw.match(/^[\t ]+(?=")/m)?.[0];
5510
5636
  const indent = indentMatch?.includes(" ") ? " " : indentMatch?.length ?? 2;
5511
5637
  const trailingNewline = raw.endsWith("\n") ? "\n" : "";
5512
- writeFileSync7(target, `${JSON.stringify(pkg, null, indent)}${trailingNewline}`, "utf8");
5638
+ writeFileSync8(target, `${JSON.stringify(pkg, null, indent)}${trailingNewline}`, "utf8");
5513
5639
  if (build === void 0 || build.trim().length === 0) {
5514
5640
  return {
5515
5641
  changed: true,
@@ -5810,7 +5936,7 @@ async function runMap(args) {
5810
5936
  const json = JSON.stringify(map, null, 2);
5811
5937
  const out = getStringFlag(args.flags, "out");
5812
5938
  if (out) {
5813
- writeFileSync8(out, json);
5939
+ writeFileSync9(out, json);
5814
5940
  console.error(`patchstack: wrote ${out}`);
5815
5941
  } else if (args.flags.get("upload") !== true) {
5816
5942
  console.log(json);
@@ -5883,6 +6009,9 @@ async function runScan(args, options = {}) {
5883
6009
  }
5884
6010
  return 0;
5885
6011
  }
6012
+ if (config.widget) {
6013
+ reportSourceMarker(detectStack(payload.packages).framework);
6014
+ }
5886
6015
  const provisioning = config.siteUuid === null;
5887
6016
  if (provisioning) {
5888
6017
  console.log("No site UUID configured \u2014 provisioning a new Patchstack site from this manifest\u2026");
@@ -5965,6 +6094,41 @@ function reportSourceWidget(siteUuid) {
5965
6094
  console.warn(`Widget: skipped (${err.message}).`);
5966
6095
  }
5967
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
+ }
5968
6132
  async function runProtectCommand(args) {
5969
6133
  if (args.flags.get("check") === true) {
5970
6134
  const report = runVerify(process.cwd());
@@ -6096,7 +6260,7 @@ async function runGuide(args) {
6096
6260
  console.log("\u2014\u2014\u2014\u2014 Full reference guide (ships as AGENT-INSTALL.md) \u2014\u2014\u2014\u2014");
6097
6261
  console.log("");
6098
6262
  const guidePath = new URL("../AGENT-INSTALL.md", import.meta.url);
6099
- console.log(readFileSync13(guidePath, "utf8"));
6263
+ console.log(readFileSync14(guidePath, "utf8"));
6100
6264
  return 0;
6101
6265
  }
6102
6266
  async function runSetup(args) {
@@ -6270,14 +6434,20 @@ async function runMarkBuild(args) {
6270
6434
  }
6271
6435
  const files = findHtmlFiles(dir);
6272
6436
  if (files.length === 0) {
6273
- 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
+ );
6274
6444
  return 0;
6275
6445
  }
6276
6446
  const snippet = buildInjectionSnippet(checksum, stack);
6277
6447
  let marked = 0;
6278
6448
  let widgetTouched = 0;
6279
6449
  for (const file of files) {
6280
- const before = readFileSync13(file, "utf8");
6450
+ const before = readFileSync14(file, "utf8");
6281
6451
  let after = injectMarker(before, snippet);
6282
6452
  if (widgetUuid !== null) {
6283
6453
  const ensured = ensureWidgetInHtml(after, widgetUuid);
@@ -6287,7 +6457,7 @@ async function runMarkBuild(args) {
6287
6457
  after = ensured.html;
6288
6458
  }
6289
6459
  if (after !== before) {
6290
- writeFileSync8(file, after);
6460
+ writeFileSync9(file, after);
6291
6461
  marked += 1;
6292
6462
  }
6293
6463
  }