@patchstack/connect 0.5.5 → 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/AGENT-INSTALL.md CHANGED
@@ -120,7 +120,7 @@ This is a request, not a mechanism: nothing in the install depends on it. Do it
120
120
 
121
121
  **Bun-managed projects:** `bun run` does not execute npm-style `pre`/`post` scripts, so wire the build script directly instead: `"build": "patchstack-connect scan && <existing build command> && patchstack-connect mark-build"`.
122
122
 
123
- 3. **Verify the disclosure widget** — a floating "Report a vulnerability" button. `scan` installs it automatically into a plain HTML shell, and `mark-build` carries it into built HTML. Only when `scan` reported that it found no editable shell (frameworks whose root layout is code, e.g. Next.js/Nuxt/Astro) add the one-liner it printed to the root layout yourself, just before `</body>` (never a JS entry point), reading `siteUuid` from `.patchstackrc.json`. On those same roots the widget also needs the production marker above the tag — `scan` adds it automatically to a JSX root, and prints it to paste when it finds no anchor. A server-rendered site without the marker serves the build-mode claim flow to its visitors:
123
+ 3. **Verify the disclosure widget** — a floating "Report a vulnerability" button. `scan` installs it automatically into a plain HTML shell **or a JSX root** (Next, Remix, React Router, TanStack Start, Gatsby), and `mark-build` carries it into built HTML. Only when `scan` reported that it found no editable shell at all — a root whose head mechanism is not a plain script tag, e.g. Nuxt's `useHead` or an Astro layout — add the one-liner it printed to the root layout yourself, just before `</body>` (never a JS entry point), reading `siteUuid` from `.patchstackrc.json`. On those same roots the widget also needs the production marker above the tag — `scan` adds it automatically to a JSX root, and prints it to paste when it finds no anchor. A server-rendered site without the marker serves the build-mode claim flow to its visitors:
124
124
 
125
125
  ```html
126
126
  <script src="https://cdn.patchstack.com/patchstack-widget.js" data-site-uuid="<SITE_UUID>" defer></script>
package/README.md CHANGED
@@ -306,6 +306,7 @@ The widget is a floating "Report a vulnerability" button — a disclosure channe
306
306
  <script src="https://cdn.patchstack.com/patchstack-widget.js" data-site-uuid="<SITE_UUID>" defer data-patchstack-connect-widget="true"></script>
307
307
  ```
308
308
 
309
+ - **`scan`** installs the widget tag into a plain HTML shell, and — where there is none — into a JSX root (`src/routes/__root.tsx`, `app/layout.tsx`, …), just before `</body>`. The same tag serves both: JSX reads `defer` as a boolean attribute and passes `data-*` through. A server-rendered app has no HTML shell at all, so without this its published site carries no widget and Patchstack never hears from the live page.
309
310
  - **`scan`** also adds the production marker when the root shell is JSX rather than HTML (`src/routes/__root.tsx`, `app/layout.tsx`, …), above the widget tag and guarded by the framework's production expression. A server-rendered app emits no built HTML for `mark-build` to stamp, so without it the widget reads the published site as build mode and shows the claim flow to visitors instead of the report form.
310
311
 
311
312
  Re-runs update the tag in place (the `data-patchstack-connect-widget` attribute marks it as connector-managed); a pre-existing manual widget tag is left untouched. `--dry-run` never edits anything; a failed post still skips the widget tag (it needs the site UUID) but the production marker may already have been written, since it runs before the post. Projects whose root layout is code rather than HTML (Next.js, Nuxt, Astro, …) get the exact snippet and target file printed instead — `guide` shows framework-specific placement.
package/dist/cli.js CHANGED
@@ -2513,12 +2513,16 @@ function productionGate(framework) {
2513
2513
  function hasJsxShell(framework) {
2514
2514
  return framework !== null && JSX_SHELL_FRAMEWORKS.has(framework);
2515
2515
  }
2516
- function buildSourceMarkerSnippet(framework) {
2516
+ function buildSourceMarkerSnippet(framework, checksum = null) {
2517
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
+ }
2518
2522
  return `{${gate} && (
2519
2523
  <script
2520
2524
  ${MARKER_ATTR}="true"
2521
- dangerouslySetInnerHTML={{ __html: 'window.__PATCHSTACK_PROD__=true;' }}
2525
+ dangerouslySetInnerHTML={{ __html: '${statements.join("")}' }}
2522
2526
  />
2523
2527
  )}`;
2524
2528
  }
@@ -2581,7 +2585,7 @@ function findJsxShellAnchor(source, tagName) {
2581
2585
  }
2582
2586
  return null;
2583
2587
  }
2584
- function ensureMarkerInJsxShell(source, framework) {
2588
+ function ensureMarkerInJsxShell(source, framework, checksum = null) {
2585
2589
  if (!hasJsxShell(framework)) {
2586
2590
  return { source, action: "unsupported" };
2587
2591
  }
@@ -2589,7 +2593,7 @@ function ensureMarkerInJsxShell(source, framework) {
2589
2593
  if (stripped.includes(PROD_MARKER_GLOBAL)) {
2590
2594
  return { source: stripped, action: "manual" };
2591
2595
  }
2592
- 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");
2593
2597
  for (const tagName of ["head", "body"]) {
2594
2598
  const anchor = findJsxShellAnchor(stripped, tagName);
2595
2599
  if (anchor === null) continue;
@@ -2604,13 +2608,13 @@ ${block(indent)}${separator}${remainder}`,
2604
2608
  }
2605
2609
  return { source: stripped, action: "no-anchor" };
2606
2610
  }
2607
- function ensureSourceMarker(cwd, shell, framework) {
2611
+ function ensureSourceMarker(cwd, shell, framework, checksum = null) {
2608
2612
  if (shell === null) {
2609
2613
  return { shell: null, source: "", action: "no-anchor" };
2610
2614
  }
2611
2615
  const file = path9.resolve(cwd, shell);
2612
2616
  const before = readFileSync2(file, "utf8");
2613
- const result = ensureMarkerInJsxShell(before, framework);
2617
+ const result = ensureMarkerInJsxShell(before, framework, checksum);
2614
2618
  if (result.source !== before) {
2615
2619
  writeFileSync(file, result.source);
2616
2620
  }
@@ -5051,12 +5055,15 @@ function findSourceShell(cwd) {
5051
5055
  }
5052
5056
  return null;
5053
5057
  }
5054
- function ensureSourceWidget(cwd, siteUuid) {
5055
- const shell = findSourceShell(cwd);
5058
+ function ensureSourceWidget(cwd, siteUuid, jsxShell = null) {
5059
+ const shell = findSourceShell(cwd) ?? jsxShell;
5056
5060
  if (shell === null) {
5057
5061
  return { shell: null, action: "no-shell" };
5058
5062
  }
5059
5063
  const file = path11.join(cwd, shell);
5064
+ if (!existsSync12(file)) {
5065
+ return { shell: null, action: "no-shell" };
5066
+ }
5060
5067
  const before = readFileSync9(file, "utf8");
5061
5068
  const { html, action } = ensureWidgetInHtml(before, siteUuid);
5062
5069
  if (html !== before) {
@@ -8927,8 +8934,9 @@ async function runScan(args, options = {}) {
8927
8934
  const cleared = applyBuildStamp(process.cwd(), null);
8928
8935
  if (cleared.kind === "cleared") console.log(`Removed the previous map binding from ${cleared.file}.`);
8929
8936
  }
8937
+ const shellFramework = detectStack(payload.packages).framework;
8930
8938
  if (config.widget) {
8931
- reportSourceMarker(detectStack(payload.packages).framework);
8939
+ reportSourceMarker(shellFramework, computeManifestChecksum(payload.packages));
8932
8940
  }
8933
8941
  const provisioning = config.siteUuid === null;
8934
8942
  if (provisioning) {
@@ -8974,7 +8982,7 @@ async function runScan(args, options = {}) {
8974
8982
  const connected = response.claim?.state === "claimed" || response.claim?.state === "owned-by-you";
8975
8983
  const effectiveUuid = config.siteUuid ?? response.uuid ?? null;
8976
8984
  if (config.widget && effectiveUuid !== null && effectiveUuid.length > 0) {
8977
- reportSourceWidget(effectiveUuid);
8985
+ reportSourceWidget(effectiveUuid, shellFramework);
8978
8986
  }
8979
8987
  const linkUuid = response.uuid ?? config.siteUuid;
8980
8988
  if (!connected && (provisioning || claimLines.length > 0) && linkUuid !== null && linkUuid !== void 0 && linkUuid.length > 0) {
@@ -9005,9 +9013,11 @@ async function runScan(args, options = {}) {
9005
9013
  }
9006
9014
  return 0;
9007
9015
  }
9008
- function reportSourceWidget(siteUuid) {
9016
+ function reportSourceWidget(siteUuid, framework) {
9009
9017
  try {
9010
- 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);
9011
9021
  switch (result.action) {
9012
9022
  case "added":
9013
9023
  console.log(`Widget: added the "Report a vulnerability" tag to ${result.shell}. Reload your preview to see it.`);
@@ -9026,7 +9036,7 @@ function reportSourceWidget(siteUuid) {
9026
9036
  console.log(` ${buildWidgetTag(siteUuid)}`);
9027
9037
  break;
9028
9038
  case "no-shell":
9029
- 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).");
9030
9040
  console.log("Add this tag to your root layout before </body> (run `guide` for framework-specific placement):");
9031
9041
  console.log(` ${buildWidgetTag(siteUuid)}`);
9032
9042
  break;
@@ -9038,13 +9048,13 @@ function reportSourceWidget(siteUuid) {
9038
9048
  console.warn(`Widget: skipped (${err.message}).`);
9039
9049
  }
9040
9050
  }
9041
- function reportSourceMarker(framework) {
9051
+ function reportSourceMarker(framework, checksum = null) {
9042
9052
  try {
9043
9053
  const shell = resolveWidgetFileHint(process.cwd(), framework);
9044
9054
  if (shell === null || shell.toLowerCase().endsWith(".html")) {
9045
9055
  return;
9046
9056
  }
9047
- const result = ensureSourceMarker(process.cwd(), shell, framework);
9057
+ const result = ensureSourceMarker(process.cwd(), shell, framework, checksum);
9048
9058
  switch (result.action) {
9049
9059
  case "added":
9050
9060
  console.log(`Production marker: added to ${shell} (guarded by ${productionGate(framework)}).`);