@patchstack/connect 0.4.2 → 0.5.0

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.
Files changed (34) hide show
  1. package/AGENT-INSTALL.md +21 -4
  2. package/README.md +10 -2
  3. package/dist/cli.js +277 -56
  4. package/dist/cli.js.map +1 -1
  5. package/dist/index.cjs +221 -18
  6. package/dist/index.cjs.map +1 -1
  7. package/dist/index.d.cts +37 -0
  8. package/dist/index.d.ts +37 -0
  9. package/dist/index.js +218 -15
  10. package/dist/index.js.map +1 -1
  11. package/dist/protect/templates/astro-middleware.ts +22 -1
  12. package/dist/protect/templates/express-guard.cjs +54 -4
  13. package/dist/protect/templates/express-guard.js +54 -4
  14. package/dist/protect/templates/express-guard.ts +43 -5
  15. package/dist/protect/templates/fastify-plugin.cjs +41 -3
  16. package/dist/protect/templates/fastify-plugin.js +41 -3
  17. package/dist/protect/templates/fastify-plugin.ts +30 -2
  18. package/dist/protect/templates/generic-guard.cjs +58 -5
  19. package/dist/protect/templates/generic-guard.js +58 -5
  20. package/dist/protect/templates/generic-guard.ts +47 -4
  21. package/dist/protect/templates/guard.ts +42 -8
  22. package/dist/protect/templates/next-middleware.ts +22 -1
  23. package/dist/protect/templates/nuxt-middleware.ts +22 -1
  24. package/dist/protect/templates/sveltekit-hooks.ts +22 -1
  25. package/dist/protect.cjs +341 -94
  26. package/dist/protect.cjs.map +1 -1
  27. package/dist/protect.edge.js +56 -24
  28. package/dist/protect.edge.js.map +1 -1
  29. package/dist/protect.js +57 -25
  30. package/dist/protect.js.map +1 -1
  31. package/dist/{refresh-manifest-JNCTIAM5.js → refresh-manifest-LDS35UKV.js} +234 -33
  32. package/dist/refresh-manifest-LDS35UKV.js.map +1 -0
  33. package/package.json +3 -3
  34. package/dist/refresh-manifest-JNCTIAM5.js.map +0 -1
package/dist/cli.js CHANGED
@@ -1210,8 +1210,8 @@ function computeManifestChecksum(packages) {
1210
1210
  var TOKEN_SKEW_MS = 6e4;
1211
1211
  function buildTokenUrl(manifestEndpoint) {
1212
1212
  const url = new URL(manifestEndpoint);
1213
- const path12 = url.pathname.replace(/\/$/, "");
1214
- url.pathname = path12.endsWith("/manifest") ? `${path12.slice(0, -"/manifest".length)}/token` : "/monitor/pulse/token";
1213
+ const path13 = url.pathname.replace(/\/$/, "");
1214
+ url.pathname = path13.endsWith("/manifest") ? `${path13.slice(0, -"/manifest".length)}/token` : "/monitor/pulse/token";
1215
1215
  url.search = "";
1216
1216
  url.hash = "";
1217
1217
  return url.toString();
@@ -1310,8 +1310,8 @@ function authFailureMessage(status, config) {
1310
1310
  }
1311
1311
  function buildRulesUrl(manifestEndpoint, siteUuid) {
1312
1312
  const url = new URL(manifestEndpoint);
1313
- const path12 = url.pathname.replace(/\/$/, "");
1314
- url.pathname = path12.endsWith("/manifest") ? `${path12.slice(0, -"/manifest".length)}/rules/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/rules/${encodeURIComponent(siteUuid)}`;
1313
+ const path13 = url.pathname.replace(/\/$/, "");
1314
+ url.pathname = path13.endsWith("/manifest") ? `${path13.slice(0, -"/manifest".length)}/rules/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/rules/${encodeURIComponent(siteUuid)}`;
1315
1315
  url.search = "";
1316
1316
  url.hash = "";
1317
1317
  return url.toString();
@@ -1326,16 +1326,16 @@ function buildSettingsUrl(endpoint, siteUuid) {
1326
1326
  }
1327
1327
  function buildPackageRemovedUrl(manifestEndpoint, siteUuid) {
1328
1328
  const url = new URL(manifestEndpoint);
1329
- const path12 = url.pathname.replace(/\/$/, "");
1330
- url.pathname = path12.endsWith("/manifest") ? `${path12.slice(0, -"/manifest".length)}/package-removed/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/package-removed/${encodeURIComponent(siteUuid)}`;
1329
+ const path13 = url.pathname.replace(/\/$/, "");
1330
+ url.pathname = path13.endsWith("/manifest") ? `${path13.slice(0, -"/manifest".length)}/package-removed/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/package-removed/${encodeURIComponent(siteUuid)}`;
1331
1331
  url.search = "";
1332
1332
  url.hash = "";
1333
1333
  return url.toString();
1334
1334
  }
1335
1335
  function buildInputMapUrl(manifestEndpoint, siteUuid) {
1336
1336
  const url = new URL(manifestEndpoint);
1337
- const path12 = url.pathname.replace(/\/$/, "");
1338
- url.pathname = path12.endsWith("/manifest") ? `${path12.slice(0, -"/manifest".length)}/input-map/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/input-map/${encodeURIComponent(siteUuid)}`;
1337
+ const path13 = url.pathname.replace(/\/$/, "");
1338
+ url.pathname = path13.endsWith("/manifest") ? `${path13.slice(0, -"/manifest".length)}/input-map/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/input-map/${encodeURIComponent(siteUuid)}`;
1339
1339
  url.search = "";
1340
1340
  url.hash = "";
1341
1341
  return url.toString();
@@ -1436,6 +1436,14 @@ async function fetchSiteStatus(config) {
1436
1436
  return "unknown";
1437
1437
  }
1438
1438
  }
1439
+ function buildManifestBody(config, payload) {
1440
+ return {
1441
+ ...payload,
1442
+ environment: config.environment,
1443
+ ...typeof config.siteUrl === "string" && config.siteUrl !== "" ? { url: config.siteUrl } : {},
1444
+ ...typeof config.siteName === "string" && config.siteName !== "" ? { name: config.siteName } : {}
1445
+ };
1446
+ }
1439
1447
  async function postManifest(config, payload) {
1440
1448
  const url = buildEndpointUrl(config.endpoint, config.siteUuid);
1441
1449
  const timeoutMs = config.timeoutMs;
@@ -1448,7 +1456,7 @@ async function postManifest(config, payload) {
1448
1456
  Accept: "application/json",
1449
1457
  "User-Agent": "@patchstack/connect"
1450
1458
  },
1451
- body: JSON.stringify({ ...payload, environment: config.environment }),
1459
+ body: JSON.stringify(buildManifestBody(config, payload)),
1452
1460
  signal: AbortSignal.timeout(timeoutMs)
1453
1461
  });
1454
1462
  } catch (cause) {
@@ -1711,11 +1719,201 @@ function renderDemoTestCommands(url, scenario) {
1711
1719
  }
1712
1720
 
1713
1721
  // src/config.ts
1714
- import { readFile as readFile8, writeFile, chmod } from "fs/promises";
1722
+ import { readFile as readFile9, writeFile, chmod } from "fs/promises";
1723
+ import path7 from "path";
1724
+
1725
+ // src/site-url.ts
1726
+ var RESERVED_SUFFIXES = [
1727
+ ".local",
1728
+ ".localhost",
1729
+ ".localdomain",
1730
+ ".internal",
1731
+ ".intranet",
1732
+ ".invalid",
1733
+ ".test",
1734
+ ".example",
1735
+ ".home.arpa",
1736
+ ".in-addr.arpa",
1737
+ ".ip6.arpa"
1738
+ ];
1739
+ function isUnpublishableHost(hostname) {
1740
+ const host = hostname.toLowerCase().replace(/\.+$/, "");
1741
+ if (host === "") return true;
1742
+ if (host.startsWith("[")) return true;
1743
+ if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return true;
1744
+ if (!host.includes(".")) return true;
1745
+ return RESERVED_SUFFIXES.some((suffix) => host.endsWith(suffix));
1746
+ }
1747
+ function normaliseSiteUrl(value) {
1748
+ const trimmed = (value ?? "").trim();
1749
+ if (trimmed === "") return null;
1750
+ const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
1751
+ let parsed;
1752
+ try {
1753
+ parsed = new URL(withScheme);
1754
+ } catch {
1755
+ return null;
1756
+ }
1757
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
1758
+ if (parsed.hostname === "" || isUnpublishableHost(parsed.hostname)) return null;
1759
+ const origin = `${parsed.protocol}//${parsed.host}`;
1760
+ return origin.length <= 191 ? origin : null;
1761
+ }
1762
+ var PRODUCTION_URL_SOURCES = [
1763
+ {
1764
+ // VERCEL_URL is per-deployment and changes every build, so it is deliberately not read here.
1765
+ platform: "vercel",
1766
+ read: (env) => env.VERCEL_ENV === "production" ? env.VERCEL_PROJECT_PRODUCTION_URL : void 0
1767
+ },
1768
+ {
1769
+ // `URL` is generic enough to belong to anything, so it counts only alongside Netlify's own markers.
1770
+ platform: "netlify",
1771
+ read: (env) => env.NETLIFY === "true" && env.CONTEXT === "production" ? env.URL : void 0
1772
+ },
1773
+ {
1774
+ platform: "render",
1775
+ read: (env) => env.RENDER === "true" && env.IS_PULL_REQUEST !== "true" ? env.RENDER_EXTERNAL_URL : void 0
1776
+ },
1777
+ {
1778
+ platform: "railway",
1779
+ read: (env) => env.RAILWAY_ENVIRONMENT_NAME === "production" ? env.RAILWAY_PUBLIC_DOMAIN : void 0
1780
+ }
1781
+ ];
1782
+ function detectSiteUrl(env = process.env) {
1783
+ for (const source of PRODUCTION_URL_SOURCES) {
1784
+ const url = normaliseSiteUrl(source.read(env));
1785
+ if (url !== null) {
1786
+ return { url, platform: source.platform };
1787
+ }
1788
+ }
1789
+ return null;
1790
+ }
1791
+
1792
+ // src/site-name.ts
1793
+ import { readFile as readFile8 } from "fs/promises";
1715
1794
  import path6 from "path";
1795
+ var NAME_MAX_LENGTH = 191;
1796
+ var TEMPLATE_PACKAGE_NAMES = /* @__PURE__ */ new Set([
1797
+ // Lovable
1798
+ "vite_react_shadcn_ts",
1799
+ // Bolt
1800
+ "vite-react-typescript-starter",
1801
+ // Scaffolders and the names people accept from them
1802
+ "vite-project",
1803
+ "my-app",
1804
+ "my-project",
1805
+ "my-v0-project",
1806
+ "app",
1807
+ "frontend",
1808
+ "web",
1809
+ "client",
1810
+ "project",
1811
+ "template",
1812
+ "example",
1813
+ "react-app",
1814
+ "next-app",
1815
+ "nextjs",
1816
+ "starter",
1817
+ "test"
1818
+ ]);
1819
+ var TEMPLATE_TITLES = /* @__PURE__ */ new Set([
1820
+ "vite + react",
1821
+ "vite + react + ts",
1822
+ "vite + vue",
1823
+ "vite + vue + ts",
1824
+ "vite app",
1825
+ "react app",
1826
+ "create next app",
1827
+ "document",
1828
+ "untitled",
1829
+ "index",
1830
+ "home",
1831
+ "app"
1832
+ ]);
1833
+ var NAMED_ENTITIES = {
1834
+ amp: "&",
1835
+ lt: "<",
1836
+ gt: ">",
1837
+ quot: '"',
1838
+ apos: "'",
1839
+ nbsp: " "
1840
+ };
1841
+ function normaliseSiteName(value) {
1842
+ const collapsed = (value ?? "").replace(/\s+/g, " ").trim();
1843
+ if (collapsed === "") return null;
1844
+ return collapsed.slice(0, NAME_MAX_LENGTH);
1845
+ }
1846
+ function decodeEntities(text) {
1847
+ return text.replace(/&([a-z]+);/gi, (whole, name) => NAMED_ENTITIES[name.toLowerCase()] ?? whole);
1848
+ }
1849
+ function nameFromHtmlTitle(html) {
1850
+ const match = /<title\b[^>]*>([\s\S]*?)<\/title>/i.exec(html);
1851
+ if (match === null) return null;
1852
+ const name = normaliseSiteName(decodeEntities(match[1] ?? ""));
1853
+ if (name === null || TEMPLATE_TITLES.has(name.toLowerCase())) return null;
1854
+ return name;
1855
+ }
1856
+ function nameFromPackageName(raw) {
1857
+ if (typeof raw !== "string") return null;
1858
+ const unscoped = raw.trim().replace(/^@[^/]+\//, "");
1859
+ if (unscoped === "" || TEMPLATE_PACKAGE_NAMES.has(unscoped.toLowerCase())) return null;
1860
+ const words = unscoped.split(/[-_.\s]+/).filter((word) => word !== "").map((word) => word === word.toUpperCase() ? word : word.charAt(0).toUpperCase() + word.slice(1));
1861
+ return normaliseSiteName(words.join(" "));
1862
+ }
1863
+ async function readIfPresent(file) {
1864
+ try {
1865
+ return await readFile8(file, "utf8");
1866
+ } catch {
1867
+ return null;
1868
+ }
1869
+ }
1870
+ async function detectSiteName(cwd) {
1871
+ for (const shell of ["index.html", path6.join("public", "index.html")]) {
1872
+ const html = await readIfPresent(path6.join(cwd, shell));
1873
+ if (html === null) continue;
1874
+ const name = nameFromHtmlTitle(html);
1875
+ if (name !== null) return { name, source: "index.html" };
1876
+ }
1877
+ const manifest = await readIfPresent(path6.join(cwd, "package.json"));
1878
+ if (manifest !== null) {
1879
+ try {
1880
+ const parsed = JSON.parse(manifest);
1881
+ const name = nameFromPackageName(
1882
+ typeof parsed === "object" && parsed !== null ? parsed.name : void 0
1883
+ );
1884
+ if (name !== null) return { name, source: "package.json" };
1885
+ } catch {
1886
+ }
1887
+ }
1888
+ return null;
1889
+ }
1890
+
1891
+ // src/config.ts
1716
1892
  var CONFIG_FILENAME = ".patchstackrc.json";
1717
1893
  var SECRET_FILENAME = ".patchstackrc.local.json";
1718
1894
  var DEFAULT_ENVIRONMENT = "production";
1895
+ async function resolveSiteIdentity(cwd, fromEnv, fromFile) {
1896
+ const configuredUrl = stated(fromEnv.url) ?? stated(fromFile.url);
1897
+ const configuredName = stated(fromEnv.name) ?? stated(fromFile.name);
1898
+ let url;
1899
+ if (configuredUrl === null) {
1900
+ url = detectSiteUrl(process.env)?.url ?? null;
1901
+ } else {
1902
+ url = normaliseSiteUrl(configuredUrl);
1903
+ if (url === null) {
1904
+ throw new PatchstackError(
1905
+ `Site URL "${configuredUrl}" is not an address the public can reach this site at. Set "url" in .patchstackrc.json (or PATCHSTACK_SITE_URL) to the http(s) address visitors use \u2014 a hostname, not an IP address \u2014 or remove it to let the build environment answer.`,
1906
+ "CONFIG_INVALID"
1907
+ );
1908
+ }
1909
+ }
1910
+ const name = configuredName === null ? (await detectSiteName(cwd))?.name ?? null : normaliseSiteName(configuredName);
1911
+ return { url, name };
1912
+ }
1913
+ function stated(value) {
1914
+ const trimmed = (value ?? "").trim();
1915
+ return trimmed === "" ? null : trimmed;
1916
+ }
1719
1917
  async function resolveConfig(options) {
1720
1918
  const fromFile = await readConfigFile(options.cwd);
1721
1919
  const fromSecretFile = await readSecretFile(options.cwd);
@@ -1745,10 +1943,13 @@ async function resolveConfig(options) {
1745
1943
  }
1746
1944
  const apiKeyRaw = fromEnv.apiKey ?? fromSecretFile.apiKey ?? fromFile.apiKey ?? null;
1747
1945
  const pulseAuthRaw = fromEnv.pulseAuth ?? fromSecretFile.pulseAuth ?? fromFile.pulseAuth ?? apiKeyRaw;
1946
+ const identity = options.detectSiteIdentity === true ? await resolveSiteIdentity(options.cwd, fromEnv, fromFile) : { url: null, name: null };
1748
1947
  return {
1749
1948
  siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,
1750
1949
  apiKey: apiKeyRaw === null || apiKeyRaw.length === 0 ? null : apiKeyRaw,
1751
1950
  pulseAuth: pulseAuthRaw === null || pulseAuthRaw.length === 0 ? null : pulseAuthRaw,
1951
+ siteUrl: identity.url,
1952
+ siteName: identity.name,
1752
1953
  endpoint,
1753
1954
  timeoutMs,
1754
1955
  environment,
@@ -1756,14 +1957,14 @@ async function resolveConfig(options) {
1756
1957
  };
1757
1958
  }
1758
1959
  async function writeConfigFile(cwd, config) {
1759
- const target = path6.join(cwd, CONFIG_FILENAME);
1960
+ const target = path7.join(cwd, CONFIG_FILENAME);
1760
1961
  const content = JSON.stringify(config, null, 2) + "\n";
1761
1962
  await writeFile(target, content, "utf8");
1762
1963
  return target;
1763
1964
  }
1764
1965
  var SECRET_CONFIG_FILENAME = SECRET_FILENAME;
1765
1966
  async function writeSecretFile(cwd, secrets) {
1766
- const target = path6.join(cwd, SECRET_FILENAME);
1967
+ const target = path7.join(cwd, SECRET_FILENAME);
1767
1968
  await writeFile(target, JSON.stringify(secrets, null, 2) + "\n", { encoding: "utf8", mode: 384 });
1768
1969
  try {
1769
1970
  await chmod(target, 384);
@@ -1773,10 +1974,10 @@ async function writeSecretFile(cwd, secrets) {
1773
1974
  return { path: target, ...ignore };
1774
1975
  }
1775
1976
  async function ensureIgnored(cwd, entry) {
1776
- const target = path6.join(cwd, ".gitignore");
1977
+ const target = path7.join(cwd, ".gitignore");
1777
1978
  let existing = "";
1778
1979
  try {
1779
- existing = await readFile8(target, "utf8");
1980
+ existing = await readFile9(target, "utf8");
1780
1981
  } catch (err) {
1781
1982
  const code = err.code;
1782
1983
  if (code !== "ENOENT") {
@@ -1796,7 +1997,7 @@ ${entry}
1796
1997
  return { ignored: false, reason: `.gitignore could not be written (${code ?? "unknown error"})` };
1797
1998
  }
1798
1999
  try {
1799
- const written = await readFile8(target, "utf8");
2000
+ const written = await readFile9(target, "utf8");
1800
2001
  if (ignoresEntry(written, entry)) return { ignored: true };
1801
2002
  return { ignored: false, reason: ".gitignore does not cover the file after writing it" };
1802
2003
  } catch (err) {
@@ -1806,7 +2007,7 @@ ${entry}
1806
2007
  }
1807
2008
  async function secretFileIgnored(cwd) {
1808
2009
  try {
1809
- const contents = await readFile8(path6.join(cwd, ".gitignore"), "utf8");
2010
+ const contents = await readFile9(path7.join(cwd, ".gitignore"), "utf8");
1810
2011
  if (ignoresEntry(contents, SECRET_FILENAME)) return { ignored: true };
1811
2012
  return { ignored: false, reason: `.gitignore does not cover ${SECRET_FILENAME}` };
1812
2013
  } catch (err) {
@@ -1853,10 +2054,10 @@ async function readConfigFile(cwd) {
1853
2054
  return readJsonFile(cwd, CONFIG_FILENAME);
1854
2055
  }
1855
2056
  async function readJsonFile(cwd, filename) {
1856
- const target = path6.join(cwd, filename);
2057
+ const target = path7.join(cwd, filename);
1857
2058
  let raw;
1858
2059
  try {
1859
- raw = await readFile8(target, "utf8");
2060
+ raw = await readFile9(target, "utf8");
1860
2061
  } catch (err) {
1861
2062
  if (err.code === "ENOENT") {
1862
2063
  return {};
@@ -1893,6 +2094,8 @@ function readEnv() {
1893
2094
  const environmentRaw = process.env.PATCHSTACK_ENVIRONMENT;
1894
2095
  return {
1895
2096
  siteUuid: process.env.PATCHSTACK_SITE_UUID ?? void 0,
2097
+ url: process.env.PATCHSTACK_SITE_URL ?? void 0,
2098
+ name: process.env.PATCHSTACK_SITE_NAME ?? void 0,
1896
2099
  apiKey: process.env.PATCHSTACK_API_KEY ?? void 0,
1897
2100
  pulseAuth: process.env.PATCHSTACK_PULSE_AUTH ?? void 0,
1898
2101
  endpoint: process.env.PATCHSTACK_ENDPOINT ?? void 0,
@@ -1909,7 +2112,7 @@ function isEnvironment(value) {
1909
2112
 
1910
2113
  // src/mark-build.ts
1911
2114
  import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
1912
- import path7 from "path";
2115
+ import path8 from "path";
1913
2116
 
1914
2117
  // src/stack.ts
1915
2118
  var STACK_RULES = [
@@ -2002,11 +2205,11 @@ var MARKER_ATTR = "data-patchstack-build";
2002
2205
  var BUILD_DIR_CANDIDATES = ["dist", "build", "out", ".output/public"];
2003
2206
  function resolveBuildDir(cwd, override) {
2004
2207
  if (override !== void 0 && override !== "") {
2005
- const abs = path7.resolve(cwd, override);
2208
+ const abs = path8.resolve(cwd, override);
2006
2209
  return existsSync(abs) && statSync(abs).isDirectory() ? abs : null;
2007
2210
  }
2008
2211
  for (const candidate of BUILD_DIR_CANDIDATES) {
2009
- const abs = path7.resolve(cwd, candidate);
2212
+ const abs = path8.resolve(cwd, candidate);
2010
2213
  if (existsSync(abs) && statSync(abs).isDirectory()) {
2011
2214
  return abs;
2012
2215
  }
@@ -2017,7 +2220,7 @@ function findHtmlFiles(dir) {
2017
2220
  const out = [];
2018
2221
  const walk2 = (current) => {
2019
2222
  for (const entry of readdirSync(current, { withFileTypes: true })) {
2020
- const full = path7.join(current, entry.name);
2223
+ const full = path8.join(current, entry.name);
2021
2224
  if (entry.isDirectory()) {
2022
2225
  walk2(full);
2023
2226
  } else if (entry.isFile() && entry.name.toLowerCase().endsWith(".html")) {
@@ -2166,7 +2369,7 @@ function ensureSourceMarker(cwd, shell, framework) {
2166
2369
  if (shell === null) {
2167
2370
  return { shell: null, source: "", action: "no-anchor" };
2168
2371
  }
2169
- const file = path7.resolve(cwd, shell);
2372
+ const file = path8.resolve(cwd, shell);
2170
2373
  const before = readFileSync(file, "utf8");
2171
2374
  const result = ensureMarkerInJsxShell(before, framework);
2172
2375
  if (result.source !== before) {
@@ -2177,7 +2380,7 @@ function ensureSourceMarker(cwd, shell, framework) {
2177
2380
 
2178
2381
  // src/guide.ts
2179
2382
  import { existsSync as existsSync12, readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
2180
- import path9 from "path";
2383
+ import path10 from "path";
2181
2384
 
2182
2385
  // src/protect/install/util.ts
2183
2386
  import { readFileSync as readFileSync2, existsSync as existsSync2, writeFileSync as writeFileSync2 } from "fs";
@@ -3674,7 +3877,7 @@ function runVerify(cwd) {
3674
3877
 
3675
3878
  // src/widget.ts
3676
3879
  import { existsSync as existsSync11, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
3677
- import path8 from "path";
3880
+ import path9 from "path";
3678
3881
  var WIDGET_SCRIPT_URL = "https://cdn.patchstack.com/patchstack-widget.js";
3679
3882
  var WIDGET_MARKER_ATTR = "data-patchstack-connect-widget";
3680
3883
  var WIDGET_NEEDLE = "patchstack-widget";
@@ -3712,7 +3915,7 @@ ${indent}</body>`;
3712
3915
  }
3713
3916
  function findSourceShell(cwd) {
3714
3917
  for (const candidate of SOURCE_SHELL_CANDIDATES) {
3715
- if (existsSync11(path8.join(cwd, candidate))) {
3918
+ if (existsSync11(path9.join(cwd, candidate))) {
3716
3919
  return candidate;
3717
3920
  }
3718
3921
  }
@@ -3723,7 +3926,7 @@ function ensureSourceWidget(cwd, siteUuid) {
3723
3926
  if (shell === null) {
3724
3927
  return { shell: null, action: "no-shell" };
3725
3928
  }
3726
- const file = path8.join(cwd, shell);
3929
+ const file = path9.join(cwd, shell);
3727
3930
  const before = readFileSync5(file, "utf8");
3728
3931
  const { html, action } = ensureWidgetInHtml(before, siteUuid);
3729
3932
  if (html !== before) {
@@ -3806,7 +4009,7 @@ function detectPackageManager(cwd) {
3806
4009
  return declared;
3807
4010
  }
3808
4011
  for (const { filename, pm } of PM_BY_LOCKFILE) {
3809
- if (existsSync12(path9.join(cwd, filename))) {
4012
+ if (existsSync12(path10.join(cwd, filename))) {
3810
4013
  return pm;
3811
4014
  }
3812
4015
  }
@@ -3817,7 +4020,7 @@ function installCommand(pm) {
3817
4020
  }
3818
4021
  function readPackageJson(cwd) {
3819
4022
  try {
3820
- return JSON.parse(readFileSync6(path9.join(cwd, "package.json"), "utf8"));
4023
+ return JSON.parse(readFileSync6(path10.join(cwd, "package.json"), "utf8"));
3821
4024
  } catch {
3822
4025
  return null;
3823
4026
  }
@@ -3825,7 +4028,7 @@ function readPackageJson(cwd) {
3825
4028
  function installedVersion(cwd, declaredRange) {
3826
4029
  try {
3827
4030
  const pkg = JSON.parse(
3828
- readFileSync6(path9.join(cwd, "node_modules", "@patchstack", "connect", "package.json"), "utf8")
4031
+ readFileSync6(path10.join(cwd, "node_modules", "@patchstack", "connect", "package.json"), "utf8")
3829
4032
  );
3830
4033
  if (typeof pkg.version === "string" && pkg.version.length > 0) {
3831
4034
  return pkg.version;
@@ -3853,7 +4056,7 @@ function findWidgetMarker(cwd, siteUuid) {
3853
4056
  if (budget <= 0 || settled()) {
3854
4057
  return;
3855
4058
  }
3856
- const full = path9.join(dir, entry.name);
4059
+ const full = path10.join(dir, entry.name);
3857
4060
  if (entry.isDirectory()) {
3858
4061
  if (SKIPPED_DIRS.has(entry.name) || entry.name.startsWith(".")) {
3859
4062
  continue;
@@ -3861,7 +4064,7 @@ function findWidgetMarker(cwd, siteUuid) {
3861
4064
  walk2(full, depth + 1);
3862
4065
  continue;
3863
4066
  }
3864
- if (!entry.isFile() || !WIDGET_EXTENSIONS.has(path9.extname(entry.name).toLowerCase())) {
4067
+ if (!entry.isFile() || !WIDGET_EXTENSIONS.has(path10.extname(entry.name).toLowerCase())) {
3865
4068
  continue;
3866
4069
  }
3867
4070
  budget -= 1;
@@ -3892,7 +4095,7 @@ function findProductionMarker(cwd, widgetFileHint) {
3892
4095
  return false;
3893
4096
  }
3894
4097
  try {
3895
- return readFileSync6(path9.join(cwd, widgetFileHint), "utf8").includes(PROD_MARKER_NEEDLE);
4098
+ return readFileSync6(path10.join(cwd, widgetFileHint), "utf8").includes(PROD_MARKER_NEEDLE);
3896
4099
  } catch {
3897
4100
  return false;
3898
4101
  }
@@ -3903,7 +4106,7 @@ function resolveWidgetFileHint(cwd, framework) {
3903
4106
  ...GENERIC_WIDGET_FILES
3904
4107
  ];
3905
4108
  for (const candidate of candidates) {
3906
- if (existsSync12(path9.join(cwd, candidate))) {
4109
+ if (existsSync12(path10.join(cwd, candidate))) {
3907
4110
  return candidate;
3908
4111
  }
3909
4112
  }
@@ -4010,7 +4213,7 @@ function renderGuideChecklist(state, useColor) {
4010
4213
  const headerParts = [state.framework, state.packageManager].filter(
4011
4214
  (part) => part !== null
4012
4215
  );
4013
- const name = state.projectName ?? path9.basename(process.cwd());
4216
+ const name = state.projectName ?? path10.basename(process.cwd());
4014
4217
  lines.push(paint(ANSI.bold, `Patchstack setup status \u2014 ${name} (${headerParts.join(" \xB7 ")})`));
4015
4218
  if (state.endpointOverride !== null) {
4016
4219
  lines.push(
@@ -4025,6 +4228,15 @@ function renderGuideChecklist(state, useColor) {
4025
4228
  lines.push(detail("Run the guide from the project root."));
4026
4229
  return lines.join("\n");
4027
4230
  }
4231
+ if (countRemainingSteps(state) > 0) {
4232
+ lines.push(` ${paint(ANSI.cyan, "\u279C")} ${paint(ANSI.bold, "Tell the user what you are doing as you go.")}`);
4233
+ lines.push(" Installs here take minutes and they are watching output they cannot read.");
4234
+ lines.push(" Say it in plain words: their site is built out of hundreds of ready-made");
4235
+ lines.push(" pieces of code, Patchstack keeps track of which ones it uses and warns them");
4236
+ lines.push(" when one turns out to be unsafe, and nothing their visitors see changes.");
4237
+ lines.push(" Avoid lockfile / dependency / manifest / npm / UUID when speaking to them.");
4238
+ lines.push("");
4239
+ }
4028
4240
  if (state.installed?.section === "dependencies") {
4029
4241
  lines.push(done(`@patchstack/connect installed (${state.installed.version}, ${state.installed.section})`));
4030
4242
  } else if (state.installed !== null) {
@@ -4174,7 +4386,7 @@ function renderGuideChecklist(state, useColor) {
4174
4386
  // src/login.ts
4175
4387
  import { mkdtempSync, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync7 } from "fs";
4176
4388
  import { tmpdir } from "os";
4177
- import path10 from "path";
4389
+ import path11 from "path";
4178
4390
  import { createHash as createHash2 } from "crypto";
4179
4391
  function baseFrom(manifestEndpoint) {
4180
4392
  const url = new URL(manifestEndpoint);
@@ -4186,7 +4398,7 @@ function baseFrom(manifestEndpoint) {
4186
4398
  }
4187
4399
  function pendingPath(siteUuid) {
4188
4400
  const key = createHash2("sha256").update(siteUuid).digest("hex").slice(0, 16);
4189
- return path10.join(tmpdir(), `patchstack-login-${key}.json`);
4401
+ return path11.join(tmpdir(), `patchstack-login-${key}.json`);
4190
4402
  }
4191
4403
  function savePendingLogin(siteUuid, pending) {
4192
4404
  writeFileSync7(pendingPath(siteUuid), JSON.stringify(pending), { encoding: "utf8", mode: 384 });
@@ -4821,10 +5033,10 @@ function detectDeploymentShapes(cwd, opts = {}) {
4821
5033
  boundary = realpathSync(boundary);
4822
5034
  } catch {
4823
5035
  }
4824
- const inProject = (path12) => {
5036
+ const inProject = (path13) => {
4825
5037
  if (opts.followOutside) return true;
4826
5038
  try {
4827
- return isInside(realpathSync(path12), boundary);
5039
+ return isInside(realpathSync(path13), boundary);
4828
5040
  } catch {
4829
5041
  return false;
4830
5042
  }
@@ -5461,8 +5673,8 @@ function collectCallees(sf, ts) {
5461
5673
  }
5462
5674
 
5463
5675
  // src/map/coordinates.ts
5464
- function runtimeCoordinate(source, path12) {
5465
- if (/\[\d*\]/.test(path12)) {
5676
+ function runtimeCoordinate(source, path13) {
5677
+ if (/\[\d*\]/.test(path13)) {
5466
5678
  return { runtimeParameter: null, runtimeParameterReason: "array traversal: needs an array_key_value rule, not a dotted parameter" };
5467
5679
  }
5468
5680
  switch (source) {
@@ -5471,15 +5683,15 @@ function runtimeCoordinate(source, path12) {
5471
5683
  case "multipart":
5472
5684
  case "body":
5473
5685
  case "server-fn-data":
5474
- return { runtimeParameter: `post.${path12}` };
5686
+ return { runtimeParameter: `post.${path13}` };
5475
5687
  case "query":
5476
- return { runtimeParameter: `get.${path12}` };
5688
+ return { runtimeParameter: `get.${path13}` };
5477
5689
  case "cookie":
5478
- return { runtimeParameter: `cookie.${path12}` };
5690
+ return { runtimeParameter: `cookie.${path13}` };
5479
5691
  case "file":
5480
- return { runtimeParameter: `files.${path12}` };
5692
+ return { runtimeParameter: `files.${path13}` };
5481
5693
  case "header":
5482
- return { runtimeParameter: `server.HTTP_${path12.toUpperCase().replace(/-/g, "_")}` };
5694
+ return { runtimeParameter: `server.HTTP_${path13.toUpperCase().replace(/-/g, "_")}` };
5483
5695
  case "route-param":
5484
5696
  return { runtimeParameter: null, runtimeParameterReason: "route parameters are not exposed by the runtime resolver" };
5485
5697
  default:
@@ -5511,8 +5723,8 @@ function addressSpaceOf(source) {
5511
5723
  function isProvenFlow(confidence) {
5512
5724
  return confidence === "exact-local" || confidence === "transformed-local";
5513
5725
  }
5514
- function inputIdOf(source, path12) {
5515
- return `${addressSpaceOf(source)}:${path12}`;
5726
+ function inputIdOf(source, path13) {
5727
+ return `${addressSpaceOf(source)}:${path13}`;
5516
5728
  }
5517
5729
  function withCoordinates(fields, source) {
5518
5730
  return fields.map((f) => {
@@ -5722,8 +5934,8 @@ function linkFlows(bodyNode, params, inputs, sinks, ts) {
5722
5934
  if (!bodyNode || sinks.length === 0 || inputs.length === 0) return { flows: [], limitations: [] };
5723
5935
  const CONTAINER_KEYS = /* @__PURE__ */ new Set([...REQ_SOURCES, "data", "input", "payload"]);
5724
5936
  const rootPath = /* @__PURE__ */ new Map();
5725
- const addRoot = (name, path12, space) => {
5726
- if (!rootPath.has(name)) rootPath.set(name, { path: path12, space });
5937
+ const addRoot = (name, path13, space) => {
5938
+ if (!rootPath.has(name)) rootPath.set(name, { path: path13, space });
5727
5939
  };
5728
5940
  for (const p of params ?? []) {
5729
5941
  if (!p?.name) continue;
@@ -5868,8 +6080,8 @@ function dedupeLimitations(list) {
5868
6080
  function join22(base, seg) {
5869
6081
  return base ? `${base}.${seg}` : seg;
5870
6082
  }
5871
- function normalizePath(path12) {
5872
- return path12.replace(/\[\d*\]/g, "").split(".").filter(Boolean).join(".");
6083
+ function normalizePath(path13) {
6084
+ return path13.replace(/\[\d*\]/g, "").split(".").filter(Boolean).join(".");
5873
6085
  }
5874
6086
  function fluentChainCalls(call, ts) {
5875
6087
  let root = call;
@@ -6785,7 +6997,7 @@ async function runMap(flags) {
6785
6997
 
6786
6998
  // src/setup.ts
6787
6999
  import { readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
6788
- import path11 from "path";
7000
+ import path12 from "path";
6789
7001
  var SCAN_COMMAND = "patchstack-connect scan";
6790
7002
  var MARK_BUILD_COMMAND = "patchstack-connect mark-build";
6791
7003
  function setupProtection(cwd) {
@@ -6803,7 +7015,7 @@ function appendHook(existing, command) {
6803
7015
  return `${existing} && ${command}`;
6804
7016
  }
6805
7017
  function wireBuildScripts(cwd, packageManager) {
6806
- const target = path11.join(cwd, "package.json");
7018
+ const target = path12.join(cwd, "package.json");
6807
7019
  const raw = readFileSync13(target, "utf8");
6808
7020
  const pkg = JSON.parse(raw);
6809
7021
  const scripts = pkg.scripts ?? {};
@@ -7168,7 +7380,9 @@ async function runScan(args, options = {}) {
7168
7380
  const config = await resolveConfig({
7169
7381
  cwd: process.cwd(),
7170
7382
  cliSiteUuid: getStringFlag(args.flags, "site-uuid"),
7171
- cliEndpoint: getStringFlag(args.flags, "endpoint")
7383
+ cliEndpoint: getStringFlag(args.flags, "endpoint"),
7384
+ // The one command that reports them, so the one command that resolves them.
7385
+ detectSiteIdentity: true
7172
7386
  });
7173
7387
  const manifest = await scanLockfile(process.cwd());
7174
7388
  for (const warning of manifest.warnings ?? []) {
@@ -7200,6 +7414,13 @@ async function runScan(args, options = {}) {
7200
7414
  `${stats.duplicateNames.length} package(s) appear at multiple versions: ${sample}${more}`
7201
7415
  );
7202
7416
  }
7417
+ const body = buildManifestBody(config, payload);
7418
+ if (typeof body.url === "string") {
7419
+ console.log(`Reporting this app's address as ${body.url}.`);
7420
+ }
7421
+ if (typeof body.name === "string") {
7422
+ console.log(`Reporting this app's name as "${body.name}".`);
7423
+ }
7203
7424
  if (dryRun) {
7204
7425
  console.log("");
7205
7426
  if (config.siteUuid === null) {
@@ -7208,7 +7429,7 @@ async function runScan(args, options = {}) {
7208
7429
  console.log(`--dry-run: not posting to Patchstack (site UUID ${config.siteUuid}).`);
7209
7430
  }
7210
7431
  console.log("Payload preview:");
7211
- const preview = JSON.stringify(payload, null, 2).split("\n");
7432
+ const preview = JSON.stringify(body, null, 2).split("\n");
7212
7433
  console.log(preview.slice(0, Math.min(preview.length, 30)).join("\n"));
7213
7434
  if (preview.length > 30) {
7214
7435
  console.log(` ... (${preview.length - 30} more lines)`);