@patchstack/connect 0.5.4 → 0.5.6

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,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/cli.ts
4
- import { readFileSync as readFileSync17, writeFileSync as writeFileSync11 } from "fs";
5
- import path15 from "path";
4
+ import { readFileSync as readFileSync18, writeFileSync as writeFileSync11 } from "fs";
5
+ import path16 from "path";
6
6
  import { createRequire as createRequire2 } from "module";
7
7
 
8
8
  // src/parsers/index.ts
@@ -1245,8 +1245,8 @@ function detectHostingPlatform(env = process.env) {
1245
1245
  var TOKEN_SKEW_MS = 6e4;
1246
1246
  function buildTokenUrl(manifestEndpoint) {
1247
1247
  const url = new URL(manifestEndpoint);
1248
- const path16 = url.pathname.replace(/\/$/, "");
1249
- url.pathname = path16.endsWith("/manifest") ? `${path16.slice(0, -"/manifest".length)}/token` : "/monitor/pulse/token";
1248
+ const path17 = url.pathname.replace(/\/$/, "");
1249
+ url.pathname = path17.endsWith("/manifest") ? `${path17.slice(0, -"/manifest".length)}/token` : "/monitor/pulse/token";
1250
1250
  url.search = "";
1251
1251
  url.hash = "";
1252
1252
  return url.toString();
@@ -1385,8 +1385,8 @@ function authFailureMessage(status, config) {
1385
1385
  }
1386
1386
  function buildRulesUrl(manifestEndpoint, siteUuid) {
1387
1387
  const url = new URL(manifestEndpoint);
1388
- const path16 = url.pathname.replace(/\/$/, "");
1389
- url.pathname = path16.endsWith("/manifest") ? `${path16.slice(0, -"/manifest".length)}/rules/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/rules/${encodeURIComponent(siteUuid)}`;
1388
+ const path17 = url.pathname.replace(/\/$/, "");
1389
+ url.pathname = path17.endsWith("/manifest") ? `${path17.slice(0, -"/manifest".length)}/rules/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/rules/${encodeURIComponent(siteUuid)}`;
1390
1390
  url.search = "";
1391
1391
  url.hash = "";
1392
1392
  return url.toString();
@@ -1401,16 +1401,16 @@ function buildSettingsUrl(endpoint, siteUuid) {
1401
1401
  }
1402
1402
  function buildPackageRemovedUrl(manifestEndpoint, siteUuid) {
1403
1403
  const url = new URL(manifestEndpoint);
1404
- const path16 = url.pathname.replace(/\/$/, "");
1405
- url.pathname = path16.endsWith("/manifest") ? `${path16.slice(0, -"/manifest".length)}/package-removed/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/package-removed/${encodeURIComponent(siteUuid)}`;
1404
+ const path17 = url.pathname.replace(/\/$/, "");
1405
+ url.pathname = path17.endsWith("/manifest") ? `${path17.slice(0, -"/manifest".length)}/package-removed/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/package-removed/${encodeURIComponent(siteUuid)}`;
1406
1406
  url.search = "";
1407
1407
  url.hash = "";
1408
1408
  return url.toString();
1409
1409
  }
1410
1410
  function buildInputMapUrl(manifestEndpoint, siteUuid) {
1411
1411
  const url = new URL(manifestEndpoint);
1412
- const path16 = url.pathname.replace(/\/$/, "");
1413
- url.pathname = path16.endsWith("/manifest") ? `${path16.slice(0, -"/manifest".length)}/input-map/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/input-map/${encodeURIComponent(siteUuid)}`;
1412
+ const path17 = url.pathname.replace(/\/$/, "");
1413
+ url.pathname = path17.endsWith("/manifest") ? `${path17.slice(0, -"/manifest".length)}/input-map/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/input-map/${encodeURIComponent(siteUuid)}`;
1414
1414
  url.search = "";
1415
1415
  url.hash = "";
1416
1416
  return url.toString();
@@ -1827,9 +1827,11 @@ function renderDemoTestCommands(url, scenario) {
1827
1827
 
1828
1828
  // src/config.ts
1829
1829
  import { readFile as readFile9, writeFile, chmod } from "fs/promises";
1830
- import path7 from "path";
1830
+ import path8 from "path";
1831
1831
 
1832
1832
  // src/environment.ts
1833
+ import { readFileSync } from "fs";
1834
+ import path6 from "path";
1833
1835
  var set = (value) => value !== void 0 && value !== "";
1834
1836
  var DISCRIMINATORS = [
1835
1837
  {
@@ -1861,13 +1863,42 @@ var DISCRIMINATORS = [
1861
1863
  }
1862
1864
  }
1863
1865
  ];
1864
- function inferEnvironment(env = process.env) {
1866
+ var HOSTED_BUILDER_PACKAGES = [
1867
+ // `lovable-tagger` covers projects from before ~Aug 2026; current templates ship the
1868
+ // `@lovable.dev/*` scoped tooling instead.
1869
+ { builder: "lovable", exact: ["lovable-tagger"], prefixes: ["@lovable.dev/"] },
1870
+ { builder: "replit", exact: [], prefixes: ["@replit/"] }
1871
+ ];
1872
+ function detectHostedBuilder(cwd) {
1873
+ let declared;
1874
+ try {
1875
+ const pkg = JSON.parse(readFileSync(path6.join(cwd, "package.json"), "utf8"));
1876
+ declared = { ...pkg.dependencies ?? {}, ...pkg.devDependencies ?? {} };
1877
+ } catch {
1878
+ return null;
1879
+ }
1880
+ const names = Object.keys(declared);
1881
+ for (const rule of HOSTED_BUILDER_PACKAGES) {
1882
+ const hit = names.some(
1883
+ (name) => rule.exact.includes(name) || rule.prefixes.some((prefix) => name.startsWith(prefix))
1884
+ );
1885
+ if (hit) return rule.builder;
1886
+ }
1887
+ return null;
1888
+ }
1889
+ function inferEnvironment(env = process.env, builder = null) {
1865
1890
  for (const discriminator of DISCRIMINATORS) {
1866
1891
  const verdict = discriminator.read(env);
1867
1892
  if (verdict !== null) {
1868
1893
  return { environment: verdict.environment, evidence: [`${discriminator.platform}: ${verdict.evidence}`] };
1869
1894
  }
1870
1895
  }
1896
+ if (builder !== null) {
1897
+ return {
1898
+ environment: "production",
1899
+ evidence: [`${builder}: a build in a ${builder} project is its publish step`]
1900
+ };
1901
+ }
1871
1902
  return { environment: "local", evidence: [] };
1872
1903
  }
1873
1904
 
@@ -1940,7 +1971,7 @@ function detectSiteUrl(env = process.env) {
1940
1971
 
1941
1972
  // src/site-name.ts
1942
1973
  import { readFile as readFile8 } from "fs/promises";
1943
- import path6 from "path";
1974
+ import path7 from "path";
1944
1975
  var NAME_MAX_LENGTH = 191;
1945
1976
  var TEMPLATE_PACKAGE_NAMES = /* @__PURE__ */ new Set([
1946
1977
  // Lovable
@@ -2017,13 +2048,13 @@ async function readIfPresent(file) {
2017
2048
  }
2018
2049
  }
2019
2050
  async function detectSiteName(cwd) {
2020
- for (const shell of ["index.html", path6.join("public", "index.html")]) {
2021
- const html = await readIfPresent(path6.join(cwd, shell));
2051
+ for (const shell of ["index.html", path7.join("public", "index.html")]) {
2052
+ const html = await readIfPresent(path7.join(cwd, shell));
2022
2053
  if (html === null) continue;
2023
2054
  const name = nameFromHtmlTitle(html);
2024
2055
  if (name !== null) return { name, source: "index.html" };
2025
2056
  }
2026
- const manifest = await readIfPresent(path6.join(cwd, "package.json"));
2057
+ const manifest = await readIfPresent(path7.join(cwd, "package.json"));
2027
2058
  if (manifest !== null) {
2028
2059
  try {
2029
2060
  const parsed = JSON.parse(manifest);
@@ -2076,7 +2107,7 @@ async function resolveConfig(options) {
2076
2107
  "CONFIG_INVALID"
2077
2108
  );
2078
2109
  }
2079
- const inferred = environmentRaw === void 0 ? inferEnvironment(process.env) : null;
2110
+ const inferred = environmentRaw === void 0 ? inferEnvironment(process.env, detectHostedBuilder(options.cwd)) : null;
2080
2111
  const environment = environmentRaw ?? inferred.environment;
2081
2112
  const environmentEvidence = inferred?.evidence ?? [];
2082
2113
  if (siteUuid !== null && siteUuid.length > 0 && !isUuid(siteUuid)) {
@@ -2110,14 +2141,14 @@ async function resolveConfig(options) {
2110
2141
  };
2111
2142
  }
2112
2143
  async function writeConfigFile(cwd, config) {
2113
- const target = path7.join(cwd, CONFIG_FILENAME);
2144
+ const target = path8.join(cwd, CONFIG_FILENAME);
2114
2145
  const content = JSON.stringify(config, null, 2) + "\n";
2115
2146
  await writeFile(target, content, "utf8");
2116
2147
  return target;
2117
2148
  }
2118
2149
  var SECRET_CONFIG_FILENAME = SECRET_FILENAME;
2119
2150
  async function writeSecretFile(cwd, secrets) {
2120
- const target = path7.join(cwd, SECRET_FILENAME);
2151
+ const target = path8.join(cwd, SECRET_FILENAME);
2121
2152
  await writeFile(target, JSON.stringify(secrets, null, 2) + "\n", { encoding: "utf8", mode: 384 });
2122
2153
  try {
2123
2154
  await chmod(target, 384);
@@ -2127,7 +2158,7 @@ async function writeSecretFile(cwd, secrets) {
2127
2158
  return { path: target, ...ignore };
2128
2159
  }
2129
2160
  async function ensureIgnored(cwd, entry) {
2130
- const target = path7.join(cwd, ".gitignore");
2161
+ const target = path8.join(cwd, ".gitignore");
2131
2162
  let existing = "";
2132
2163
  try {
2133
2164
  existing = await readFile9(target, "utf8");
@@ -2160,7 +2191,7 @@ ${entry}
2160
2191
  }
2161
2192
  async function secretFileIgnored(cwd) {
2162
2193
  try {
2163
- const contents = await readFile9(path7.join(cwd, ".gitignore"), "utf8");
2194
+ const contents = await readFile9(path8.join(cwd, ".gitignore"), "utf8");
2164
2195
  if (ignoresEntry(contents, SECRET_FILENAME)) return { ignored: true };
2165
2196
  return { ignored: false, reason: `.gitignore does not cover ${SECRET_FILENAME}` };
2166
2197
  } catch (err) {
@@ -2211,7 +2242,7 @@ async function readConfigFile(cwd) {
2211
2242
  return readJsonFile(cwd, CONFIG_FILENAME);
2212
2243
  }
2213
2244
  async function readJsonFile(cwd, filename) {
2214
- const target = path7.join(cwd, filename);
2245
+ const target = path8.join(cwd, filename);
2215
2246
  let raw;
2216
2247
  try {
2217
2248
  raw = await readFile9(target, "utf8");
@@ -2268,8 +2299,8 @@ function isEnvironment(value) {
2268
2299
  }
2269
2300
 
2270
2301
  // src/mark-build.ts
2271
- import { existsSync, readFileSync, readdirSync, realpathSync, statSync, writeFileSync } from "fs";
2272
- import path8 from "path";
2302
+ import { existsSync, readFileSync as readFileSync2, readdirSync, realpathSync, statSync, writeFileSync } from "fs";
2303
+ import path9 from "path";
2273
2304
 
2274
2305
  // src/stack.ts
2275
2306
  var STACK_RULES = [
@@ -2362,6 +2393,24 @@ function isEmptyStack(stack) {
2362
2393
 
2363
2394
  // src/mark-build.ts
2364
2395
  var MARKER_ATTR = "data-patchstack-build";
2396
+ function marksPublishedBuild(environment) {
2397
+ return environment === "production";
2398
+ }
2399
+ var STALE_BUILD_WARNING_MS = 60 * 60 * 1e3;
2400
+ function staleBuildAge(files, startedAt) {
2401
+ let newest = null;
2402
+ for (const file of files) {
2403
+ try {
2404
+ const at = statSync(file).mtimeMs;
2405
+ if (newest === null || at > newest) newest = at;
2406
+ } catch {
2407
+ continue;
2408
+ }
2409
+ }
2410
+ if (newest === null) return null;
2411
+ const age = startedAt - newest;
2412
+ return age > STALE_BUILD_WARNING_MS ? age : null;
2413
+ }
2365
2414
  var BUILD_DIR_CANDIDATES = ["dist", "build", "out", ".output/public", "_site"];
2366
2415
  var FRAMEWORK_BUILD_DIRS = {
2367
2416
  eleventy: ["_site"],
@@ -2382,7 +2431,7 @@ function buildDirCandidates(options = {}) {
2382
2431
  }
2383
2432
  function resolveBuildDir(cwd, override, options = {}) {
2384
2433
  if (override !== void 0 && override !== "") {
2385
- const abs = path8.resolve(cwd, override);
2434
+ const abs = path9.resolve(cwd, override);
2386
2435
  return existsSync(abs) && statSync(abs).isDirectory() ? abs : null;
2387
2436
  }
2388
2437
  let boundary;
@@ -2392,7 +2441,7 @@ function resolveBuildDir(cwd, override, options = {}) {
2392
2441
  return null;
2393
2442
  }
2394
2443
  for (const candidate of buildDirCandidates(options)) {
2395
- const abs = path8.resolve(cwd, candidate);
2444
+ const abs = path9.resolve(cwd, candidate);
2396
2445
  if (!existsSync(abs) || !statSync(abs).isDirectory()) continue;
2397
2446
  let real;
2398
2447
  try {
@@ -2400,7 +2449,7 @@ function resolveBuildDir(cwd, override, options = {}) {
2400
2449
  } catch {
2401
2450
  continue;
2402
2451
  }
2403
- if (real === boundary || real.startsWith(boundary + path8.sep)) {
2452
+ if (real === boundary || real.startsWith(boundary + path9.sep)) {
2404
2453
  return abs;
2405
2454
  }
2406
2455
  }
@@ -2410,7 +2459,7 @@ function findHtmlFiles(dir) {
2410
2459
  const out = [];
2411
2460
  const walk2 = (current) => {
2412
2461
  for (const entry of readdirSync(current, { withFileTypes: true })) {
2413
- const full = path8.join(current, entry.name);
2462
+ const full = path9.join(current, entry.name);
2414
2463
  if (entry.isDirectory()) {
2415
2464
  walk2(full);
2416
2465
  } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".html")) {
@@ -2464,12 +2513,16 @@ function productionGate(framework) {
2464
2513
  function hasJsxShell(framework) {
2465
2514
  return framework !== null && JSX_SHELL_FRAMEWORKS.has(framework);
2466
2515
  }
2467
- function buildSourceMarkerSnippet(framework) {
2516
+ function buildSourceMarkerSnippet(framework, checksum = null) {
2468
2517
  const gate = productionGate(framework);
2518
+ const statements = ["window.__PATCHSTACK_PROD__=true;"];
2519
+ if (checksum !== null && checksum !== "") {
2520
+ statements.push(`window.__PATCHSTACK_BUILD__=${JSON.stringify(checksum)};`);
2521
+ }
2469
2522
  return `{${gate} && (
2470
2523
  <script
2471
2524
  ${MARKER_ATTR}="true"
2472
- dangerouslySetInnerHTML={{ __html: 'window.__PATCHSTACK_PROD__=true;' }}
2525
+ dangerouslySetInnerHTML={{ __html: '${statements.join("")}' }}
2473
2526
  />
2474
2527
  )}`;
2475
2528
  }
@@ -2532,7 +2585,7 @@ function findJsxShellAnchor(source, tagName) {
2532
2585
  }
2533
2586
  return null;
2534
2587
  }
2535
- function ensureMarkerInJsxShell(source, framework) {
2588
+ function ensureMarkerInJsxShell(source, framework, checksum = null) {
2536
2589
  if (!hasJsxShell(framework)) {
2537
2590
  return { source, action: "unsupported" };
2538
2591
  }
@@ -2540,7 +2593,7 @@ function ensureMarkerInJsxShell(source, framework) {
2540
2593
  if (stripped.includes(PROD_MARKER_GLOBAL)) {
2541
2594
  return { source: stripped, action: "manual" };
2542
2595
  }
2543
- const block = (indent) => [REGION_OPEN, ...buildSourceMarkerSnippet(framework).split("\n"), REGION_CLOSE].map((line) => `${indent}${line}`).join("\n");
2596
+ const block = (indent) => [REGION_OPEN, ...buildSourceMarkerSnippet(framework, checksum).split("\n"), REGION_CLOSE].map((line) => `${indent}${line}`).join("\n");
2544
2597
  for (const tagName of ["head", "body"]) {
2545
2598
  const anchor = findJsxShellAnchor(stripped, tagName);
2546
2599
  if (anchor === null) continue;
@@ -2555,13 +2608,13 @@ ${block(indent)}${separator}${remainder}`,
2555
2608
  }
2556
2609
  return { source: stripped, action: "no-anchor" };
2557
2610
  }
2558
- function ensureSourceMarker(cwd, shell, framework) {
2611
+ function ensureSourceMarker(cwd, shell, framework, checksum = null) {
2559
2612
  if (shell === null) {
2560
2613
  return { shell: null, source: "", action: "no-anchor" };
2561
2614
  }
2562
- const file = path8.resolve(cwd, shell);
2563
- const before = readFileSync(file, "utf8");
2564
- const result = ensureMarkerInJsxShell(before, framework);
2615
+ const file = path9.resolve(cwd, shell);
2616
+ const before = readFileSync2(file, "utf8");
2617
+ const result = ensureMarkerInJsxShell(before, framework, checksum);
2565
2618
  if (result.source !== before) {
2566
2619
  writeFileSync(file, result.source);
2567
2620
  }
@@ -2569,15 +2622,15 @@ function ensureSourceMarker(cwd, shell, framework) {
2569
2622
  }
2570
2623
 
2571
2624
  // src/guide.ts
2572
- import { existsSync as existsSync13, readFileSync as readFileSync9, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
2573
- import path11 from "path";
2625
+ import { existsSync as existsSync13, readFileSync as readFileSync10, readdirSync as readdirSync5, statSync as statSync4 } from "fs";
2626
+ import path12 from "path";
2574
2627
 
2575
2628
  // src/architecture.ts
2576
- import { existsSync as existsSync2, readFileSync as readFileSync4, statSync as statSync3 } from "fs";
2577
- import path9 from "path";
2629
+ import { existsSync as existsSync2, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
2630
+ import path10 from "path";
2578
2631
 
2579
2632
  // src/map/sources.ts
2580
- import { readFileSync as readFileSync2, readdirSync as readdirSync2, realpathSync as realpathSync2, statSync as statSync2 } from "fs";
2633
+ import { readFileSync as readFileSync3, readdirSync as readdirSync2, realpathSync as realpathSync2, statSync as statSync2 } from "fs";
2581
2634
  import { join as join2, relative, isAbsolute } from "path";
2582
2635
 
2583
2636
  // src/map/ast.ts
@@ -2787,7 +2840,7 @@ function hasEntrySignal(text) {
2787
2840
  }
2788
2841
  function detectFramework(cwd) {
2789
2842
  try {
2790
- const pkg = JSON.parse(readFileSync2(join2(cwd, "package.json"), "utf8"));
2843
+ const pkg = JSON.parse(readFileSync3(join2(cwd, "package.json"), "utf8"));
2791
2844
  const d = { ...pkg.dependencies, ...pkg.devDependencies };
2792
2845
  if (d["@tanstack/react-start"] || d["@tanstack/start"] || d["@tanstack/solid-start"]) return "tanstack-start";
2793
2846
  if (d["next"]) return "next";
@@ -2947,10 +3000,10 @@ function detectDeploymentShapes(cwd, opts = {}) {
2947
3000
  boundary = realpathSync2(boundary);
2948
3001
  } catch {
2949
3002
  }
2950
- const inProject = (path16) => {
3003
+ const inProject = (path17) => {
2951
3004
  if (opts.followOutside) return true;
2952
3005
  try {
2953
- return isInside(realpathSync2(path16), boundary);
3006
+ return isInside(realpathSync2(path17), boundary);
2954
3007
  } catch {
2955
3008
  return false;
2956
3009
  }
@@ -3016,7 +3069,7 @@ function holdsSourceFile(dir) {
3016
3069
  }
3017
3070
 
3018
3071
  // src/map/surface.ts
3019
- import { readFileSync as readFileSync3 } from "fs";
3072
+ import { readFileSync as readFileSync4 } from "fs";
3020
3073
  import { join as join3 } from "path";
3021
3074
  var STATIC_GENERATORS = [
3022
3075
  { dep: "vite", label: "vite" },
@@ -3058,7 +3111,7 @@ var SHAPE_SIGNAL = {
3058
3111
  };
3059
3112
  function readManifest(cwd) {
3060
3113
  try {
3061
- const parsed = JSON.parse(readFileSync3(join3(cwd, "package.json"), "utf8"));
3114
+ const parsed = JSON.parse(readFileSync4(join3(cwd, "package.json"), "utf8"));
3062
3115
  return typeof parsed === "object" && parsed !== null ? parsed : null;
3063
3116
  } catch {
3064
3117
  return null;
@@ -3071,7 +3124,7 @@ function nextExportsStatically(cwd, manifest, ts) {
3071
3124
  for (const file of ["next.config.js", "next.config.mjs", "next.config.ts", "next.config.cjs"]) {
3072
3125
  let text;
3073
3126
  try {
3074
- text = readFileSync3(join3(cwd, file), "utf8");
3127
+ text = readFileSync4(join3(cwd, file), "utf8");
3075
3128
  } catch {
3076
3129
  continue;
3077
3130
  }
@@ -3230,7 +3283,7 @@ var SERVING_CALL = /\b(?:createServer|createSecureServer|Bun\.serve|Deno\.serve|
3230
3283
  var MAX_ENTRY_BYTES = 256 * 1024;
3231
3284
  function readManifest2(cwd) {
3232
3285
  try {
3233
- const parsed = JSON.parse(readFileSync4(path9.join(cwd, "package.json"), "utf8"));
3286
+ const parsed = JSON.parse(readFileSync5(path10.join(cwd, "package.json"), "utf8"));
3234
3287
  return typeof parsed === "object" && parsed !== null ? parsed : {};
3235
3288
  } catch {
3236
3289
  return {};
@@ -3259,16 +3312,16 @@ function staticGenerators(deps) {
3259
3312
  }
3260
3313
  function serverEntries(cwd, manifest) {
3261
3314
  const candidates = new Set(SERVER_ENTRY_CANDIDATES);
3262
- if (typeof manifest.main === "string" && manifest.main !== "" && !path9.isAbsolute(manifest.main)) {
3263
- const main2 = path9.normalize(manifest.main);
3315
+ if (typeof manifest.main === "string" && manifest.main !== "" && !path10.isAbsolute(manifest.main)) {
3316
+ const main2 = path10.normalize(manifest.main);
3264
3317
  if (!main2.startsWith("..")) candidates.add(main2);
3265
3318
  }
3266
3319
  const found = [];
3267
3320
  for (const relative7 of candidates) {
3268
- const file = path9.join(cwd, relative7);
3321
+ const file = path10.join(cwd, relative7);
3269
3322
  try {
3270
3323
  if (!existsSync2(file) || !statSync3(file).isFile()) continue;
3271
- const text = readFileSync4(file, "utf8").slice(0, MAX_ENTRY_BYTES);
3324
+ const text = readFileSync5(file, "utf8").slice(0, MAX_ENTRY_BYTES);
3272
3325
  if (SERVING_CALL.test(text)) found.push(relative7);
3273
3326
  } catch {
3274
3327
  }
@@ -3322,10 +3375,10 @@ function classifyArchitecture(cwd) {
3322
3375
  }
3323
3376
 
3324
3377
  // src/protect/install/util.ts
3325
- import { readFileSync as readFileSync5, existsSync as existsSync3, writeFileSync as writeFileSync2 } from "fs";
3378
+ import { readFileSync as readFileSync6, existsSync as existsSync3, writeFileSync as writeFileSync2 } from "fs";
3326
3379
  import { join as join4, dirname } from "path";
3327
3380
  import { fileURLToPath } from "url";
3328
- var read = (p) => readFileSync5(p, "utf8");
3381
+ var read = (p) => readFileSync6(p, "utf8");
3329
3382
  var log = (msg) => console.log(`patchstack protect: ${msg}`);
3330
3383
  function hasDependency(cwd, name) {
3331
3384
  try {
@@ -3798,7 +3851,7 @@ var nuxtAdapter = {
3798
3851
  };
3799
3852
 
3800
3853
  // src/protect/install/find-app.ts
3801
- import { readFileSync as readFileSync6, existsSync as existsSync7, readdirSync as readdirSync3, lstatSync } from "fs";
3854
+ import { readFileSync as readFileSync7, existsSync as existsSync7, readdirSync as readdirSync3, lstatSync } from "fs";
3802
3855
  import { join as join10, relative as relative2, dirname as dirname3 } from "path";
3803
3856
  var SKIP_DIRS2 = /* @__PURE__ */ new Set([
3804
3857
  "node_modules",
@@ -3844,7 +3897,7 @@ function findAppInstance(cwd, re) {
3844
3897
  else if (/\.(?:ts|js|mjs|cjs)$/.test(name)) {
3845
3898
  let src;
3846
3899
  try {
3847
- src = readFileSync6(p, "utf8");
3900
+ src = readFileSync7(p, "utf8");
3848
3901
  } catch {
3849
3902
  continue;
3850
3903
  }
@@ -3891,7 +3944,7 @@ function declaredEntries(cwd) {
3891
3944
  const paths = /* @__PURE__ */ new Set();
3892
3945
  let pkg;
3893
3946
  try {
3894
- pkg = JSON.parse(readFileSync6(join10(cwd, "package.json"), "utf8"));
3947
+ pkg = JSON.parse(readFileSync7(join10(cwd, "package.json"), "utf8"));
3895
3948
  } catch {
3896
3949
  return paths;
3897
3950
  }
@@ -3921,7 +3974,7 @@ import { writeFileSync as writeFileSync5, existsSync as existsSync10 } from "fs"
3921
3974
  import { join as join13, resolve as resolve2 } from "path";
3922
3975
 
3923
3976
  // src/protect/install/generic.ts
3924
- import { readFileSync as readFileSync7, existsSync as existsSync9, mkdirSync as mkdirSync4, copyFileSync as copyFileSync4, readdirSync as readdirSync4, lstatSync as lstatSync2 } from "fs";
3977
+ import { readFileSync as readFileSync8, existsSync as existsSync9, mkdirSync as mkdirSync4, copyFileSync as copyFileSync4, readdirSync as readdirSync4, lstatSync as lstatSync2 } from "fs";
3925
3978
  import { join as join12, dirname as dirname5 } from "path";
3926
3979
 
3927
3980
  // src/protect/install/source-scope.ts
@@ -4473,7 +4526,7 @@ function guardIsImported(cwd, guardPath) {
4473
4526
  if (st.isDirectory()) walk2(p, depth + 1);
4474
4527
  else if (/\.(?:ts|tsx|js|jsx|mjs|cjs)$/.test(name) && p !== guardPath) {
4475
4528
  try {
4476
- if (importsAndUsesGuard(readFileSync7(p, "utf8"), p, guardDir, packageIsModule)) found = true;
4529
+ if (importsAndUsesGuard(readFileSync8(p, "utf8"), p, guardDir, packageIsModule)) found = true;
4477
4530
  } catch {
4478
4531
  }
4479
4532
  }
@@ -4957,8 +5010,8 @@ function runVerify(cwd) {
4957
5010
  }
4958
5011
 
4959
5012
  // src/widget.ts
4960
- import { existsSync as existsSync12, readFileSync as readFileSync8, writeFileSync as writeFileSync6 } from "fs";
4961
- import path10 from "path";
5013
+ import { existsSync as existsSync12, readFileSync as readFileSync9, writeFileSync as writeFileSync6 } from "fs";
5014
+ import path11 from "path";
4962
5015
  var WIDGET_SCRIPT_URL = "https://cdn.patchstack.com/patchstack-widget.js";
4963
5016
  var WIDGET_MARKER_ATTR = "data-patchstack-connect-widget";
4964
5017
  var WIDGET_NEEDLE = "patchstack-widget";
@@ -4996,19 +5049,22 @@ ${indent}</body>`;
4996
5049
  }
4997
5050
  function findSourceShell(cwd) {
4998
5051
  for (const candidate of SOURCE_SHELL_CANDIDATES) {
4999
- if (existsSync12(path10.join(cwd, candidate))) {
5052
+ if (existsSync12(path11.join(cwd, candidate))) {
5000
5053
  return candidate;
5001
5054
  }
5002
5055
  }
5003
5056
  return null;
5004
5057
  }
5005
- function ensureSourceWidget(cwd, siteUuid) {
5006
- const shell = findSourceShell(cwd);
5058
+ function ensureSourceWidget(cwd, siteUuid, jsxShell = null) {
5059
+ const shell = findSourceShell(cwd) ?? jsxShell;
5007
5060
  if (shell === null) {
5008
5061
  return { shell: null, action: "no-shell" };
5009
5062
  }
5010
- const file = path10.join(cwd, shell);
5011
- const before = readFileSync8(file, "utf8");
5063
+ const file = path11.join(cwd, shell);
5064
+ if (!existsSync12(file)) {
5065
+ return { shell: null, action: "no-shell" };
5066
+ }
5067
+ const before = readFileSync9(file, "utf8");
5012
5068
  const { html, action } = ensureWidgetInHtml(before, siteUuid);
5013
5069
  if (html !== before) {
5014
5070
  writeFileSync6(file, html);
@@ -5090,7 +5146,7 @@ function detectPackageManager(cwd) {
5090
5146
  return declared;
5091
5147
  }
5092
5148
  for (const { filename, pm } of PM_BY_LOCKFILE) {
5093
- if (existsSync13(path11.join(cwd, filename))) {
5149
+ if (existsSync13(path12.join(cwd, filename))) {
5094
5150
  return pm;
5095
5151
  }
5096
5152
  }
@@ -5101,7 +5157,7 @@ function installCommand(pm) {
5101
5157
  }
5102
5158
  function readPackageJson(cwd) {
5103
5159
  try {
5104
- return JSON.parse(readFileSync9(path11.join(cwd, "package.json"), "utf8"));
5160
+ return JSON.parse(readFileSync10(path12.join(cwd, "package.json"), "utf8"));
5105
5161
  } catch {
5106
5162
  return null;
5107
5163
  }
@@ -5109,7 +5165,7 @@ function readPackageJson(cwd) {
5109
5165
  function installedVersion(cwd, declaredRange) {
5110
5166
  try {
5111
5167
  const pkg = JSON.parse(
5112
- readFileSync9(path11.join(cwd, "node_modules", "@patchstack", "connect", "package.json"), "utf8")
5168
+ readFileSync10(path12.join(cwd, "node_modules", "@patchstack", "connect", "package.json"), "utf8")
5113
5169
  );
5114
5170
  if (typeof pkg.version === "string" && pkg.version.length > 0) {
5115
5171
  return pkg.version;
@@ -5137,7 +5193,7 @@ function findWidgetMarker(cwd, siteUuid) {
5137
5193
  if (budget <= 0 || settled()) {
5138
5194
  return;
5139
5195
  }
5140
- const full = path11.join(dir, entry.name);
5196
+ const full = path12.join(dir, entry.name);
5141
5197
  if (entry.isDirectory()) {
5142
5198
  if (SKIPPED_DIRS.has(entry.name) || entry.name.startsWith(".")) {
5143
5199
  continue;
@@ -5145,7 +5201,7 @@ function findWidgetMarker(cwd, siteUuid) {
5145
5201
  walk2(full, depth + 1);
5146
5202
  continue;
5147
5203
  }
5148
- if (!entry.isFile() || !WIDGET_EXTENSIONS.has(path11.extname(entry.name).toLowerCase())) {
5204
+ if (!entry.isFile() || !WIDGET_EXTENSIONS.has(path12.extname(entry.name).toLowerCase())) {
5149
5205
  continue;
5150
5206
  }
5151
5207
  budget -= 1;
@@ -5153,7 +5209,7 @@ function findWidgetMarker(cwd, siteUuid) {
5153
5209
  if (statSync4(full).size > WIDGET_SCAN_MAX_BYTES) {
5154
5210
  continue;
5155
5211
  }
5156
- const content = readFileSync9(full, "utf8");
5212
+ const content = readFileSync10(full, "utf8");
5157
5213
  if (!content.includes(WIDGET_NEEDLE2)) {
5158
5214
  continue;
5159
5215
  }
@@ -5176,7 +5232,7 @@ function findProductionMarker(cwd, widgetFileHint) {
5176
5232
  return false;
5177
5233
  }
5178
5234
  try {
5179
- return readFileSync9(path11.join(cwd, widgetFileHint), "utf8").includes(PROD_MARKER_NEEDLE);
5235
+ return readFileSync10(path12.join(cwd, widgetFileHint), "utf8").includes(PROD_MARKER_NEEDLE);
5180
5236
  } catch {
5181
5237
  return false;
5182
5238
  }
@@ -5187,7 +5243,7 @@ function resolveWidgetFileHint(cwd, framework) {
5187
5243
  ...GENERIC_WIDGET_FILES
5188
5244
  ];
5189
5245
  for (const candidate of candidates) {
5190
- if (existsSync13(path11.join(cwd, candidate))) {
5246
+ if (existsSync13(path12.join(cwd, candidate))) {
5191
5247
  return candidate;
5192
5248
  }
5193
5249
  }
@@ -5295,7 +5351,7 @@ function renderGuideChecklist(state, useColor) {
5295
5351
  const headerParts = [state.framework, state.packageManager].filter(
5296
5352
  (part) => part !== null
5297
5353
  );
5298
- const name = state.projectName ?? path11.basename(process.cwd());
5354
+ const name = state.projectName ?? path12.basename(process.cwd());
5299
5355
  lines.push(paint(ANSI.bold, `Patchstack setup status \u2014 ${name} (${headerParts.join(" \xB7 ")})`));
5300
5356
  if (state.endpointOverride !== null) {
5301
5357
  lines.push(
@@ -5476,12 +5532,12 @@ function renderGuideChecklist(state, useColor) {
5476
5532
  // src/login.ts
5477
5533
  import { mkdtempSync } from "fs";
5478
5534
  import { tmpdir as tmpdir2 } from "os";
5479
- import path13 from "path";
5535
+ import path14 from "path";
5480
5536
 
5481
5537
  // src/device-flow.ts
5482
- import { readFileSync as readFileSync10, rmSync, writeFileSync as writeFileSync7 } from "fs";
5538
+ import { readFileSync as readFileSync11, rmSync, writeFileSync as writeFileSync7 } from "fs";
5483
5539
  import { tmpdir } from "os";
5484
- import path12 from "path";
5540
+ import path13 from "path";
5485
5541
  import { createHash as createHash2 } from "crypto";
5486
5542
  function baseFrom(manifestEndpoint) {
5487
5543
  const url = new URL(manifestEndpoint);
@@ -5493,7 +5549,7 @@ function baseFrom(manifestEndpoint) {
5493
5549
  }
5494
5550
  function pendingPath(siteUuid, intent) {
5495
5551
  const key = createHash2("sha256").update(siteUuid).digest("hex").slice(0, 16);
5496
- return path12.join(tmpdir(), `patchstack-${intent}-${key}.json`);
5552
+ return path13.join(tmpdir(), `patchstack-${intent}-${key}.json`);
5497
5553
  }
5498
5554
  function savePending(siteUuid, intent, pending) {
5499
5555
  writeFileSync7(pendingPath(siteUuid, intent), JSON.stringify(pending), {
@@ -5503,7 +5559,7 @@ function savePending(siteUuid, intent, pending) {
5503
5559
  }
5504
5560
  function readPending(siteUuid, intent) {
5505
5561
  try {
5506
- return JSON.parse(readFileSync10(pendingPath(siteUuid, intent), "utf8"));
5562
+ return JSON.parse(readFileSync11(pendingPath(siteUuid, intent), "utf8"));
5507
5563
  } catch {
5508
5564
  return null;
5509
5565
  }
@@ -5673,7 +5729,7 @@ async function claim2(config, onPrompt, deps = {}) {
5673
5729
  import { relative as relative3 } from "path";
5674
5730
 
5675
5731
  // src/protect/install/runtime/entry.ts
5676
- import { existsSync as existsSync14, readFileSync as readFileSync11, realpathSync as realpathSync3, statSync as statSync5 } from "fs";
5732
+ import { existsSync as existsSync14, readFileSync as readFileSync12, realpathSync as realpathSync3, statSync as statSync5 } from "fs";
5677
5733
  import { join as join15, resolve as resolve3, sep } from "path";
5678
5734
  var CONVENTIONAL = [
5679
5735
  "server.js",
@@ -5700,15 +5756,15 @@ var CONVENTIONAL = [
5700
5756
  var LOADABLE = /\.(?:js|mjs|cjs)$/;
5701
5757
  function readPackage2(cwd) {
5702
5758
  try {
5703
- const parsed = JSON.parse(readFileSync11(join15(cwd, "package.json"), "utf8"));
5759
+ const parsed = JSON.parse(readFileSync12(join15(cwd, "package.json"), "utf8"));
5704
5760
  return parsed !== null && typeof parsed === "object" ? parsed : null;
5705
5761
  } catch {
5706
5762
  return null;
5707
5763
  }
5708
5764
  }
5709
- function isFile(path16) {
5765
+ function isFile(path17) {
5710
5766
  try {
5711
- return statSync5(path16).isFile();
5767
+ return statSync5(path17).isFile();
5712
5768
  } catch {
5713
5769
  return false;
5714
5770
  }
@@ -5717,14 +5773,14 @@ function loadable(cwd, candidate) {
5717
5773
  const written = resolve3(cwd, candidate);
5718
5774
  if (!LOADABLE.test(written) || !isFile(written)) return null;
5719
5775
  let root;
5720
- let path16;
5776
+ let path17;
5721
5777
  try {
5722
5778
  root = realpathSync3(cwd);
5723
- path16 = realpathSync3(written);
5779
+ path17 = realpathSync3(written);
5724
5780
  } catch {
5725
5781
  return null;
5726
5782
  }
5727
- if (path16 !== root && !path16.startsWith(root + sep)) return null;
5783
+ if (path17 !== root && !path17.startsWith(root + sep)) return null;
5728
5784
  return written;
5729
5785
  }
5730
5786
  function passable(token) {
@@ -5804,8 +5860,8 @@ function preloadPath() {
5804
5860
  // beside this file, in the repository
5805
5861
  ];
5806
5862
  for (const candidate of candidates) {
5807
- const path16 = fileURLToPath2(candidate);
5808
- if (existsSync15(path16)) return path16;
5863
+ const path17 = fileURLToPath2(candidate);
5864
+ if (existsSync15(path17)) return path17;
5809
5865
  }
5810
5866
  return null;
5811
5867
  }
@@ -6175,7 +6231,7 @@ async function loadTypeScript(cwd) {
6175
6231
  }
6176
6232
 
6177
6233
  // src/map/extract.ts
6178
- import { readFileSync as readFileSync14, realpathSync as realpathSync5 } from "fs";
6234
+ import { readFileSync as readFileSync15, realpathSync as realpathSync5 } from "fs";
6179
6235
  import { createHash as createHash5 } from "crypto";
6180
6236
  import { relative as relative5 } from "path";
6181
6237
 
@@ -6589,7 +6645,7 @@ function dedupeSinks(sinks, owner) {
6589
6645
  }
6590
6646
 
6591
6647
  // src/map/module-graph.ts
6592
- import { readFileSync as readFileSync12, realpathSync as realpathSync4, statSync as statSync6 } from "fs";
6648
+ import { readFileSync as readFileSync13, realpathSync as realpathSync4, statSync as statSync6 } from "fs";
6593
6649
  import { dirname as dirname7, join as join17, relative as relative4, resolve as resolvePath } from "path";
6594
6650
  var RESOLVE_EXTS = [".ts", ".tsx", ".mts", ".cts", ".js", ".jsx", ".mjs", ".cjs"];
6595
6651
  function createModuleGraph(ts, opts) {
@@ -6598,7 +6654,7 @@ function createModuleGraph(ts, opts) {
6598
6654
  if (cache.has(file)) return cache.get(file) ?? null;
6599
6655
  let entry = null;
6600
6656
  try {
6601
- const text = readFileSync12(file, "utf8");
6657
+ const text = readFileSync13(file, "utf8");
6602
6658
  const sf = ts.createSourceFile(file, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, file));
6603
6659
  const bindings = buildModuleBindings(sf, ts);
6604
6660
  entry = { fnSinks: collectLocalSinks(sf, ts, bindings), calleesOf: collectCallees(sf, ts), bindings };
@@ -6697,8 +6753,8 @@ function collectCallees(sf, ts) {
6697
6753
  }
6698
6754
 
6699
6755
  // src/map/coordinates.ts
6700
- function runtimeCoordinate(source, path16) {
6701
- if (/\[\d*\]/.test(path16)) {
6756
+ function runtimeCoordinate(source, path17) {
6757
+ if (/\[\d*\]/.test(path17)) {
6702
6758
  return { runtimeParameter: null, runtimeParameterReason: "array traversal: needs an array_key_value rule, not a dotted parameter" };
6703
6759
  }
6704
6760
  switch (source) {
@@ -6707,15 +6763,15 @@ function runtimeCoordinate(source, path16) {
6707
6763
  case "multipart":
6708
6764
  case "body":
6709
6765
  case "server-fn-data":
6710
- return { runtimeParameter: `post.${path16}` };
6766
+ return { runtimeParameter: `post.${path17}` };
6711
6767
  case "query":
6712
- return { runtimeParameter: `get.${path16}` };
6768
+ return { runtimeParameter: `get.${path17}` };
6713
6769
  case "cookie":
6714
- return { runtimeParameter: `cookie.${path16}` };
6770
+ return { runtimeParameter: `cookie.${path17}` };
6715
6771
  case "file":
6716
- return { runtimeParameter: `files.${path16}` };
6772
+ return { runtimeParameter: `files.${path17}` };
6717
6773
  case "header":
6718
- return { runtimeParameter: `server.HTTP_${path16.toUpperCase().replace(/-/g, "_")}` };
6774
+ return { runtimeParameter: `server.HTTP_${path17.toUpperCase().replace(/-/g, "_")}` };
6719
6775
  case "route-param":
6720
6776
  return { runtimeParameter: null, runtimeParameterReason: "route parameters are not exposed by the runtime resolver" };
6721
6777
  default:
@@ -6747,8 +6803,8 @@ function addressSpaceOf(source) {
6747
6803
  function isProvenFlow(confidence) {
6748
6804
  return confidence === "exact-local" || confidence === "transformed-local";
6749
6805
  }
6750
- function inputIdOf(source, path16) {
6751
- return `${addressSpaceOf(source)}:${path16}`;
6806
+ function inputIdOf(source, path17) {
6807
+ return `${addressSpaceOf(source)}:${path17}`;
6752
6808
  }
6753
6809
  function withCoordinates(fields, source) {
6754
6810
  return fields.map((f) => {
@@ -6958,8 +7014,8 @@ function linkFlows(bodyNode, params, inputs, sinks, ts) {
6958
7014
  if (!bodyNode || sinks.length === 0 || inputs.length === 0) return { flows: [], limitations: [] };
6959
7015
  const CONTAINER_KEYS = /* @__PURE__ */ new Set([...REQ_SOURCES, "data", "input", "payload"]);
6960
7016
  const rootPath = /* @__PURE__ */ new Map();
6961
- const addRoot = (name, path16, space) => {
6962
- if (!rootPath.has(name)) rootPath.set(name, { path: path16, space });
7017
+ const addRoot = (name, path17, space) => {
7018
+ if (!rootPath.has(name)) rootPath.set(name, { path: path17, space });
6963
7019
  };
6964
7020
  for (const p of params ?? []) {
6965
7021
  if (!p?.name) continue;
@@ -7104,8 +7160,8 @@ function dedupeLimitations(list) {
7104
7160
  function join22(base, seg) {
7105
7161
  return base ? `${base}.${seg}` : seg;
7106
7162
  }
7107
- function normalizePath(path16) {
7108
- return path16.replace(/\[\d*\]/g, "").split(".").filter(Boolean).join(".");
7163
+ function normalizePath(path17) {
7164
+ return path17.replace(/\[\d*\]/g, "").split(".").filter(Boolean).join(".");
7109
7165
  }
7110
7166
  function fluentChainCalls(call, ts) {
7111
7167
  let root = call;
@@ -7351,7 +7407,7 @@ function handlerEntry(name, kindLabel, params, body, ts, localSinks, bindings, c
7351
7407
  }
7352
7408
 
7353
7409
  // src/map/imports.ts
7354
- import { readFileSync as readFileSync13 } from "fs";
7410
+ import { readFileSync as readFileSync14 } from "fs";
7355
7411
  import { join as join18 } from "path";
7356
7412
  var MAX_SITES = 5;
7357
7413
  var NPM_NAME = /^(?:@[^/@\s~][^/@\s]*\/)?[^/@\s.~][^/@\s]*$/;
@@ -7363,7 +7419,7 @@ function readPathAliases(cwd) {
7363
7419
  for (const name of ["tsconfig.json", "jsconfig.json"]) {
7364
7420
  let paths;
7365
7421
  try {
7366
- const raw = readFileSync13(join18(cwd, name), "utf8");
7422
+ const raw = readFileSync14(join18(cwd, name), "utf8");
7367
7423
  const parsed = JSON.parse(stripJsonComments(raw));
7368
7424
  paths = parsed.compilerOptions?.paths;
7369
7425
  } catch {
@@ -7742,7 +7798,7 @@ async function extractInputMap(cwd, ts, options = {}) {
7742
7798
  const hopTargets = /* @__PURE__ */ new Set();
7743
7799
  for (const file of files) {
7744
7800
  try {
7745
- const text = readFileSync14(file, "utf8");
7801
+ const text = readFileSync15(file, "utf8");
7746
7802
  const relFile = relative5(cwd, file);
7747
7803
  sourceBytes += text.length;
7748
7804
  if (!hasEntrySignal(text)) {
@@ -7797,7 +7853,7 @@ async function extractInputMap(cwd, ts, options = {}) {
7797
7853
  for (const target of hopTargets) {
7798
7854
  if (entryFiles.has(target)) continue;
7799
7855
  try {
7800
- const text = readFileSync14(target, "utf8");
7856
+ const text = readFileSync15(target, "utf8");
7801
7857
  const sf = ts.createSourceFile(target, text, ts.ScriptTarget.Latest, true, guessScriptKind(ts, target));
7802
7858
  const bindings = buildModuleBindings(sf, ts);
7803
7859
  const called = collectInvocations(sf, ts, bindings, { file: target, owner: relative5(cwd, target), graph });
@@ -7946,7 +8002,7 @@ function getStringFlag(flags, name) {
7946
8002
  }
7947
8003
 
7948
8004
  // src/build-stamp.ts
7949
- import { chmodSync, lstatSync as lstatSync3, readFileSync as readFileSync15, readdirSync as readdirSync6, realpathSync as realpathSync6, renameSync, unlinkSync, writeFileSync as writeFileSync8 } from "fs";
8005
+ import { chmodSync, lstatSync as lstatSync3, readFileSync as readFileSync16, readdirSync as readdirSync6, realpathSync as realpathSync6, renameSync, unlinkSync, writeFileSync as writeFileSync8 } from "fs";
7950
8006
  import { join as join19, relative as relative6, sep as sep2 } from "path";
7951
8007
  var RULES_FILE_NAMES = /* @__PURE__ */ new Set(["rules.json", "patchstack.rules.json"]);
7952
8008
  var SKIP_DIRECTORIES = /* @__PURE__ */ new Set(["node_modules", "dist", "build", "out", "coverage", "tmp", "vendor"]);
@@ -7965,11 +8021,11 @@ function guardImportsRulesFile(dir, rulesName) {
7965
8021
  }
7966
8022
  for (const name of siblings) {
7967
8023
  if (!GUARD_SOURCE.test(name)) continue;
7968
- const path16 = join19(dir, name);
8024
+ const path17 = join19(dir, name);
7969
8025
  try {
7970
- const entry = lstatSync3(path16);
8026
+ const entry = lstatSync3(path17);
7971
8027
  if (!entry.isFile() || entry.isSymbolicLink() || entry.size > MAX_GUARD_SOURCE_BYTES) continue;
7972
- const source = stripComments(readFileSync15(path16, "utf8"));
8028
+ const source = stripComments(readFileSync16(path17, "utf8"));
7973
8029
  if (source.includes("@patchstack/connect/protect") && importsRules.test(source)) return true;
7974
8030
  } catch {
7975
8031
  }
@@ -7990,9 +8046,9 @@ function findRulesFile(cwd) {
7990
8046
  } catch {
7991
8047
  return { kind: "none" };
7992
8048
  }
7993
- const contained = (path16) => {
8049
+ const contained = (path17) => {
7994
8050
  try {
7995
- const real = realpathSync6(path16);
8051
+ const real = realpathSync6(path17);
7996
8052
  return real === root || real.startsWith(root + sep2);
7997
8053
  } catch {
7998
8054
  return false;
@@ -8008,25 +8064,25 @@ function findRulesFile(cwd) {
8008
8064
  }
8009
8065
  for (const name of entries) {
8010
8066
  if (name.startsWith(".")) continue;
8011
- const path16 = join19(dir, name);
8067
+ const path17 = join19(dir, name);
8012
8068
  let entry;
8013
8069
  try {
8014
- entry = lstatSync3(path16);
8070
+ entry = lstatSync3(path17);
8015
8071
  } catch {
8016
8072
  continue;
8017
8073
  }
8018
8074
  if (entry.isSymbolicLink()) continue;
8019
8075
  if (entry.isDirectory()) {
8020
- if (!SKIP_DIRECTORIES.has(name)) walk2(path16, depth + 1);
8076
+ if (!SKIP_DIRECTORIES.has(name)) walk2(path17, depth + 1);
8021
8077
  continue;
8022
8078
  }
8023
8079
  if (!entry.isFile() || !RULES_FILE_NAMES.has(name)) continue;
8024
- if (!contained(path16)) continue;
8080
+ if (!contained(path17)) continue;
8025
8081
  if (!guardImportsRulesFile(dir, name)) continue;
8026
8082
  try {
8027
- if (isRuleBundle(JSON.parse(readFileSync15(path16, "utf8")))) found.push(path16);
8083
+ if (isRuleBundle(JSON.parse(readFileSync16(path17, "utf8")))) found.push(path17);
8028
8084
  } catch {
8029
- unreadable.push(path16);
8085
+ unreadable.push(path17);
8030
8086
  }
8031
8087
  }
8032
8088
  };
@@ -8057,7 +8113,7 @@ function applyBuildStamp(cwd, id) {
8057
8113
  const file = relative6(cwd, location.path);
8058
8114
  let text;
8059
8115
  try {
8060
- text = readFileSync15(location.path, "utf8");
8116
+ text = readFileSync16(location.path, "utf8");
8061
8117
  } catch (err) {
8062
8118
  return { kind: "skipped", reason: `${file} could not be read (${err.message})` };
8063
8119
  }
@@ -8275,8 +8331,8 @@ async function runMap(flags) {
8275
8331
  }
8276
8332
 
8277
8333
  // src/setup.ts
8278
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "fs";
8279
- import path14 from "path";
8334
+ import { readFileSync as readFileSync17, writeFileSync as writeFileSync10 } from "fs";
8335
+ import path15 from "path";
8280
8336
  var SCAN_COMMAND = "patchstack-connect scan";
8281
8337
  var MARK_BUILD_COMMAND = "patchstack-connect mark-build";
8282
8338
  function setupProtection(cwd) {
@@ -8301,8 +8357,8 @@ function prependHook(existing, command) {
8301
8357
  return [command, ...remaining].join(" && ");
8302
8358
  }
8303
8359
  function wireBuildScripts(cwd, packageManager) {
8304
- const target = path14.join(cwd, "package.json");
8305
- const raw = readFileSync16(target, "utf8");
8360
+ const target = path15.join(cwd, "package.json");
8361
+ const raw = readFileSync17(target, "utf8");
8306
8362
  const pkg = JSON.parse(raw);
8307
8363
  const scripts = pkg.scripts ?? {};
8308
8364
  const build = scripts.build;
@@ -8411,6 +8467,10 @@ Usage:
8411
8467
  patchstack-connect mark-build [options] Stamp built HTML with a production flag +
8412
8468
  build fingerprint, and ensure the widget
8413
8469
  tag in built pages (run as a postbuild step).
8470
+ The flag marks a page as the LIVE site, so it
8471
+ is stamped only on a production build; a local
8472
+ or preview build gets the widget tag and no
8473
+ marker (and any stale marker is removed).
8414
8474
  Looks in the framework's own output dir, an
8415
8475
  --output the build script names, then dist/,
8416
8476
  build/, out/, .output/public/, _site/ \u2014 inside
@@ -8496,8 +8556,15 @@ Options (for scan, setup, status, and uninstall):
8496
8556
  too. Never written to a file, never printed back
8497
8557
 
8498
8558
  Options (for mark-build):
8499
- --dir <path> Build output directory (default: auto-detect
8500
- dist/ build/ out/ .output/public)
8559
+ --dir <path> Build output directory (default: auto-detect the
8560
+ framework's own output dir, an --output the build
8561
+ script names, then dist/ build/ out/ .output/public/
8562
+ _site/ \u2014 inside the project only)
8563
+ --production Stamp the live-site marker on a build this command
8564
+ cannot tell is a deployment \u2014 publishing a static
8565
+ build by hand from a laptop, say. PATCHSTACK_ENVIRONMENT=production
8566
+ does the same. Never needed on a hosting platform,
8567
+ whose own build is recognised
8501
8568
 
8502
8569
  Options (for demo and demo-guide):
8503
8570
  --url <url> Test endpoint printed at the end
@@ -8867,8 +8934,9 @@ async function runScan(args, options = {}) {
8867
8934
  const cleared = applyBuildStamp(process.cwd(), null);
8868
8935
  if (cleared.kind === "cleared") console.log(`Removed the previous map binding from ${cleared.file}.`);
8869
8936
  }
8937
+ const shellFramework = detectStack(payload.packages).framework;
8870
8938
  if (config.widget) {
8871
- reportSourceMarker(detectStack(payload.packages).framework);
8939
+ reportSourceMarker(shellFramework, computeManifestChecksum(payload.packages));
8872
8940
  }
8873
8941
  const provisioning = config.siteUuid === null;
8874
8942
  if (provisioning) {
@@ -8914,7 +8982,7 @@ async function runScan(args, options = {}) {
8914
8982
  const connected = response.claim?.state === "claimed" || response.claim?.state === "owned-by-you";
8915
8983
  const effectiveUuid = config.siteUuid ?? response.uuid ?? null;
8916
8984
  if (config.widget && effectiveUuid !== null && effectiveUuid.length > 0) {
8917
- reportSourceWidget(effectiveUuid);
8985
+ reportSourceWidget(effectiveUuid, shellFramework);
8918
8986
  }
8919
8987
  const linkUuid = response.uuid ?? config.siteUuid;
8920
8988
  if (!connected && (provisioning || claimLines.length > 0) && linkUuid !== null && linkUuid !== void 0 && linkUuid.length > 0) {
@@ -8945,9 +9013,11 @@ async function runScan(args, options = {}) {
8945
9013
  }
8946
9014
  return 0;
8947
9015
  }
8948
- function reportSourceWidget(siteUuid) {
9016
+ function reportSourceWidget(siteUuid, framework) {
8949
9017
  try {
8950
- const result = ensureSourceWidget(process.cwd(), siteUuid);
9018
+ const hint = hasJsxShell(framework) ? resolveWidgetFileHint(process.cwd(), framework) : null;
9019
+ const jsxShell = hint !== null && !hint.toLowerCase().endsWith(".html") ? hint : null;
9020
+ const result = ensureSourceWidget(process.cwd(), siteUuid, jsxShell);
8951
9021
  switch (result.action) {
8952
9022
  case "added":
8953
9023
  console.log(`Widget: added the "Report a vulnerability" tag to ${result.shell}. Reload your preview to see it.`);
@@ -8966,7 +9036,7 @@ function reportSourceWidget(siteUuid) {
8966
9036
  console.log(` ${buildWidgetTag(siteUuid)}`);
8967
9037
  break;
8968
9038
  case "no-shell":
8969
- console.log("Widget: no plain HTML shell found (index.html / public/index.html / src/app.html).");
9039
+ console.log("Widget: no root shell found to edit (index.html / public/index.html / src/app.html, or a JSX root).");
8970
9040
  console.log("Add this tag to your root layout before </body> (run `guide` for framework-specific placement):");
8971
9041
  console.log(` ${buildWidgetTag(siteUuid)}`);
8972
9042
  break;
@@ -8978,13 +9048,13 @@ function reportSourceWidget(siteUuid) {
8978
9048
  console.warn(`Widget: skipped (${err.message}).`);
8979
9049
  }
8980
9050
  }
8981
- function reportSourceMarker(framework) {
9051
+ function reportSourceMarker(framework, checksum = null) {
8982
9052
  try {
8983
9053
  const shell = resolveWidgetFileHint(process.cwd(), framework);
8984
9054
  if (shell === null || shell.toLowerCase().endsWith(".html")) {
8985
9055
  return;
8986
9056
  }
8987
- const result = ensureSourceMarker(process.cwd(), shell, framework);
9057
+ const result = ensureSourceMarker(process.cwd(), shell, framework, checksum);
8988
9058
  switch (result.action) {
8989
9059
  case "added":
8990
9060
  console.log(`Production marker: added to ${shell} (guarded by ${productionGate(framework)}).`);
@@ -9171,7 +9241,7 @@ async function runGuide(args) {
9171
9241
  console.log("\u2014\u2014\u2014\u2014 Full reference guide (ships as AGENT-INSTALL.md) \u2014\u2014\u2014\u2014");
9172
9242
  console.log("");
9173
9243
  const guidePath = new URL("../AGENT-INSTALL.md", import.meta.url);
9174
- console.log(readFileSync17(guidePath, "utf8"));
9244
+ console.log(readFileSync18(guidePath, "utf8"));
9175
9245
  return 0;
9176
9246
  }
9177
9247
  async function runSetup(args) {
@@ -9346,7 +9416,7 @@ function describeStack(stack) {
9346
9416
  }
9347
9417
  function readBuildScript(cwd) {
9348
9418
  try {
9349
- const pkg = JSON.parse(readFileSync17(path15.join(cwd, "package.json"), "utf8"));
9419
+ const pkg = JSON.parse(readFileSync18(path16.join(cwd, "package.json"), "utf8"));
9350
9420
  return pkg.scripts?.build;
9351
9421
  } catch {
9352
9422
  return void 0;
@@ -9354,6 +9424,7 @@ function readBuildScript(cwd) {
9354
9424
  }
9355
9425
  async function runMarkBuild(args) {
9356
9426
  const cwd = process.cwd();
9427
+ const startedAt = Date.now();
9357
9428
  let checksum = null;
9358
9429
  let stack = null;
9359
9430
  try {
@@ -9370,6 +9441,8 @@ async function runMarkBuild(args) {
9370
9441
  );
9371
9442
  }
9372
9443
  let widgetUuid = null;
9444
+ let environment = "local";
9445
+ let environmentEvidence = [];
9373
9446
  try {
9374
9447
  const config = await resolveConfig({
9375
9448
  cwd,
@@ -9378,11 +9451,15 @@ async function runMarkBuild(args) {
9378
9451
  if (config.widget) {
9379
9452
  widgetUuid = config.siteUuid;
9380
9453
  }
9454
+ environment = config.environment;
9455
+ environmentEvidence = config.environmentEvidence ?? [];
9381
9456
  } catch (err) {
9382
9457
  console.warn(
9383
9458
  `mark-build: could not resolve the site UUID (${err.message}). Skipping the widget pass.`
9384
9459
  );
9385
9460
  }
9461
+ const forcedProduction = args.flags.get("production") === true;
9462
+ const published = forcedProduction || marksPublishedBuild(environment);
9386
9463
  const buildDirOptions = { framework: stack?.framework ?? null, buildScript: readBuildScript(cwd) };
9387
9464
  const dir = resolveBuildDir(cwd, getStringFlag(args.flags, "dir"), buildDirOptions);
9388
9465
  if (dir === null) {
@@ -9406,11 +9483,20 @@ async function runMarkBuild(args) {
9406
9483
  );
9407
9484
  return 0;
9408
9485
  }
9409
- const snippet = buildInjectionSnippet(checksum, stack);
9486
+ const staleBy = staleBuildAge(files, startedAt);
9487
+ if (staleBy !== null) {
9488
+ console.warn(
9489
+ `mark-build: nothing in ${dir} has been written in the last ${Math.round(staleBy / 6e4)} minute(s), so this looks like output from an earlier build.`
9490
+ );
9491
+ console.warn(
9492
+ "mark-build: run it as a postbuild hook (or straight after the build) so what is stamped is what was just produced."
9493
+ );
9494
+ }
9495
+ const snippet = published ? buildInjectionSnippet(checksum, stack) : "";
9410
9496
  let marked = 0;
9411
9497
  let widgetTouched = 0;
9412
9498
  for (const file of files) {
9413
- const before = readFileSync17(file, "utf8");
9499
+ const before = readFileSync18(file, "utf8");
9414
9500
  let after = injectMarker(before, snippet);
9415
9501
  if (widgetUuid !== null) {
9416
9502
  const ensured = ensureWidgetInHtml(after, widgetUuid);
@@ -9425,8 +9511,21 @@ async function runMarkBuild(args) {
9425
9511
  }
9426
9512
  }
9427
9513
  const stackSummary = stack !== null ? describeStack(stack) : null;
9514
+ if (published) {
9515
+ console.log(
9516
+ `mark-build: marked ${marked} HTML file(s) in ${dir}${checksum !== null ? ` (build ${checksum})` : ""}${widgetTouched > 0 ? `, widget tag ensured in ${widgetTouched}` : ""}${stackSummary !== null ? ` [${stackSummary}]` : ""}.`
9517
+ );
9518
+ return 0;
9519
+ }
9520
+ const because = environmentEvidence.length > 0 ? ` (${environmentEvidence.join("; ")})` : "";
9521
+ console.log(
9522
+ `mark-build: ${environment} build${because} \u2014 production marker withheld from ${files.length} HTML file(s) in ${dir}${widgetTouched > 0 ? `, widget tag ensured in ${widgetTouched}` : ""}${stackSummary !== null ? ` [${stackSummary}]` : ""}.`
9523
+ );
9524
+ console.log(
9525
+ "mark-build: the marker tells Patchstack a page is the live site, so only the build that deploys carries it. Your hosting platform's build stamps it automatically."
9526
+ );
9428
9527
  console.log(
9429
- `mark-build: marked ${marked} HTML file(s) in ${dir}${checksum !== null ? ` (build ${checksum})` : ""}${widgetTouched > 0 ? `, widget tag ensured in ${widgetTouched}` : ""}${stackSummary !== null ? ` [${stackSummary}]` : ""}.`
9528
+ "mark-build: publishing this directory by hand? Re-run with --production (or set PATCHSTACK_ENVIRONMENT=production) so the deployed pages carry it."
9430
9529
  );
9431
9530
  return 0;
9432
9531
  }