@patchstack/connect 0.5.15 → 0.5.17
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 +42 -14
- package/README.md +5 -5
- package/dist/cli.js +695 -183
- package/dist/cli.js.map +1 -1
- package/dist/protect/templates/demo-rules.json +2 -2
- package/dist/protect.cjs +1388 -748
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.cts +110 -16
- package/dist/protect.d.ts +110 -16
- package/dist/protect.edge.js +1376 -736
- package/dist/protect.edge.js.map +3 -3
- package/dist/protect.js +1376 -736
- package/dist/protect.js.map +1 -1
- package/package.json +1 -1
package/AGENT-INSTALL.md
CHANGED
|
@@ -85,7 +85,7 @@ Only `map` analyses your source, and only `map --upload` sends anything derived
|
|
|
85
85
|
|
|
86
86
|
## Talk to the person while you work
|
|
87
87
|
|
|
88
|
-
The person who asked for this usually did not write the project and has never installed a package. Installs and builds here can run for several minutes, and a silent wall of build output is the worst part of their first experience of Patchstack. Narrate it.
|
|
88
|
+
The person who asked for this usually did not write the project and has never installed a package. Installs and builds here can run for several minutes, and a silent wall of build output is the worst part of their first experience of Patchstack. Narrate it, one or two sentences at a time.
|
|
89
89
|
|
|
90
90
|
- **Say what you are about to do, before the slow command.** One sentence, no command names: "I'm adding Patchstack to your site — this takes a few minutes because it has to reinstall everything the site is built from."
|
|
91
91
|
- **Say what the wait is.** "It's downloading the pieces your site is built out of. Nothing is broken; this is just slow."
|
|
@@ -97,6 +97,17 @@ The person who asked for this usually did not write the project and has never in
|
|
|
97
97
|
|
|
98
98
|
This is a request, not a mechanism: nothing in the install depends on it. Do it anyway — the wait is the only moment where the person is paying full attention.
|
|
99
99
|
|
|
100
|
+
### The message you end on
|
|
101
|
+
|
|
102
|
+
When setup is finished, the person reads one short message, not a transcript of the install. Keep it to about ten lines, in this order:
|
|
103
|
+
|
|
104
|
+
1. **One sentence on the result.** "Patchstack is now watching your site." If something did not finish, say what, in one line.
|
|
105
|
+
2. **The four steps**, copied from the end of the `setup` output with their `✔` and `✘`.
|
|
106
|
+
3. **The next step**, from the `Next:` line, in plain words, with its link or command.
|
|
107
|
+
4. **Refresh and deploy.** Refresh the preview to see the Patchstack Connector, and deploy when ready.
|
|
108
|
+
|
|
109
|
+
Leave out the files you changed, the commands you ran, settings, and anything that worked as expected. If the person asks for the detail, give it then. `--verbose` prints it.
|
|
110
|
+
|
|
100
111
|
## Automated setup
|
|
101
112
|
|
|
102
113
|
1. **Install** (skip if already present), matching the project's package manager:
|
|
@@ -135,7 +146,7 @@ This is a request, not a mechanism: nothing in the install depends on it. Do it
|
|
|
135
146
|
|
|
136
147
|
**A `✘` line under `Missing` is yours to finish, not a result to report.** `setup` applies what it can apply safely and prints the exact edit (or the command that prints it) for anything it would have had to overwrite user code to do. They are: moving `@patchstack/connect` out of `devDependencies`, the widget tag in a root layout `setup` could not edit, the production marker on a server-rendered root, and wiring a generic guard into the server entry. The last three are steps 3 and 4 of "Manual setup" below; after the guard one, `npx @patchstack/connect protect --check` must exit 0.
|
|
137
148
|
|
|
138
|
-
**Read the report.** `setup` and `scan` start with `Done` (what this run did) and `Missing` (what is still missing, each with the one thing to do); `guide` shows `Missing` only. All three end on the same four steps — `Install the Patchstack connector`, `Connect project to Patchstack account`, `Sync and monitor in local environment`, `Deploy project to protect live app` — each marked `✔` (done) or `✘` (not yet), followed by the one next step. A part the project cannot carry (no build script, no request path for runtime protection) is simply not listed. The CLI does not store whether the site has an owner, so `Connect` stays `✘` until a scan hears otherwise from Patchstack; do not report the site as connected from that line alone, and do not report it as unconnected once the person has signed in.
|
|
149
|
+
**Read the report.** `setup` and `scan` start with `Done` (what this run did) and `Missing` (what is still missing, each with the one thing to do); `guide` shows `Missing` only. All three end on the same four steps — `Install the Patchstack connector`, `Connect project to Patchstack account`, `Sync and monitor in local environment`, `Deploy project to protect live app` — each marked `✔` (done) or `✘` (not yet), followed by the one next step. A part the project cannot carry (no build script, no request path for runtime protection) is simply not listed. The CLI does not store whether the site has an owner, so `Connect` stays `✘` until a scan hears otherwise from Patchstack; do not report the site as connected from that line alone, and do not report it as unconnected once the person has signed in. In the message you end on, name only the parts that are not active — dependency scans, the Patchstack Connector, the build hooks, runtime protection — and why, one line each; the four steps already say what is done.
|
|
139
150
|
|
|
140
151
|
**The widget is part of the install.** It is on by default; add it without asking the person whether to. Honour `"widget": false` in `.patchstackrc.json` only when the person set it themselves. If it is there and they did not ask for it, remove it, run `setup` again, and tell them the widget is back on.
|
|
141
152
|
|
|
@@ -229,7 +240,9 @@ Handle it in this order:
|
|
|
229
240
|
|
|
230
241
|
**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"`.
|
|
231
242
|
|
|
232
|
-
|
|
243
|
+
**Checking a build yourself:** run it through the package manager (`npm run build`), never the framework's own CLI (`astro build`, `vite build`, `next build`). Calling the CLI directly skips the `prebuild`/`postbuild` hooks, so the build is not scanned, not marked and not reported, and it tells you nothing about what the deployed build will carry.
|
|
244
|
+
|
|
245
|
+
3. **Verify the Patchstack Connector** — a floating control whose form follows the site's claim state: while the site is unclaimed it is a one-time "Connect this website" panel. It is part of the install and on by default; do not ask whether to add it. `scan` installs it automatically into a plain HTML shell, **a JSX root** (Next, Remix, React Router, TanStack Start, Gatsby) **or an Astro layout**, 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` — 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 or Astro layout, 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:
|
|
233
246
|
|
|
234
247
|
```html
|
|
235
248
|
<script src="https://cdn.patchstack.com/patchstack-widget.js" data-site-uuid="<SITE_UUID>" defer></script>
|
|
@@ -293,7 +306,7 @@ It is server-only. Never put it in the widget tag, client bundles, or public env
|
|
|
293
306
|
and `mark-build` only read and write files; `scan` and `mark-build` also report the dependency
|
|
294
307
|
manifest they read.
|
|
295
308
|
|
|
296
|
-
5. **Commit** `.patchstackrc.json`, the updated `package.json`, the guard/framework source changes, and the layout/HTML file carrying the widget tag (and the production marker, when `scan` wrote one into a JSX root), so every developer and CI run reports to the same site.
|
|
309
|
+
5. **Commit** `.patchstackrc.json`, the updated `package.json`, the guard/framework source changes, and the layout/HTML file carrying the widget tag (and the production marker, when `scan` wrote one into a JSX root or Astro layout), so every developer and CI run reports to the same site.
|
|
297
310
|
|
|
298
311
|
**Do not commit `.patchstackrc.local.json`.** That file holds the API key issued at provision; the scan writes it and adds it to `.gitignore`, and tells you if it could not. `.patchstackrc.json` holds only the site UUID and settings, and the UUID is public by design — it ships in the widget tag in served HTML.
|
|
299
312
|
|
|
@@ -459,8 +472,9 @@ requests are aborted, it starts nothing further, and it discards what it was hol
|
|
|
459
472
|
request to stop, not a guarantee — a transport that ignores it is detached rather than completed, so
|
|
460
473
|
"resolved" means the reporter is finished with it, and a runtime that kills the process still wins
|
|
461
474
|
regardless. Every detection event ends up delivered, refused or dropped and is reported in the health
|
|
462
|
-
counts
|
|
463
|
-
|
|
475
|
+
counts. The block log keeps the same kind of local counts: `protection.blockLogHealth()` returns how many
|
|
476
|
+
block records were accepted, delivered, failed or dropped, and how many are still queued. Those counts
|
|
477
|
+
carry no request data and stay in your process.
|
|
464
478
|
|
|
465
479
|
The client address is reported with its **provenance**, because an address is only as trustworthy as
|
|
466
480
|
whatever supplied it. `client_ip_source` is one of `runtime` (the address the transport observed),
|
|
@@ -468,7 +482,8 @@ whatever supplied it. `client_ip_source` is one of `runtime` (the address the tr
|
|
|
468
482
|
`unavailable`. When it is `unavailable` the `client_ip` field is **omitted entirely** rather than sent
|
|
469
483
|
empty, so a missing address cannot read as a failed lookup of a real one. A forwarded header is never
|
|
470
484
|
trusted implicitly: with no `trustedProxy` policy the address is whatever the transport observed, and in a
|
|
471
|
-
runtime that exposes no transport peer there is no address to report at all
|
|
485
|
+
runtime that exposes no transport peer there is no address to report at all unless your code supplies one
|
|
486
|
+
with `peerAddress` (below).
|
|
472
487
|
|
|
473
488
|
### Behaviour change: how the client address is determined
|
|
474
489
|
|
|
@@ -483,11 +498,17 @@ Two consequences if you are upgrading:
|
|
|
483
498
|
peer. **If your app runs behind a proxy or load balancer, addresses will now show as the proxy's**
|
|
484
499
|
until you declare your proxies with `trustedProxy` (below) — which affects attribution in reports and
|
|
485
500
|
any rule matching on `server.ip` or `REMOTE_ADDR`.
|
|
486
|
-
- **Fetch runtimes report no address
|
|
487
|
-
guard (Workers, Deno, Bun, edge) has nothing to observe, and no
|
|
488
|
-
|
|
501
|
+
- **Fetch runtimes report no address unless you supply the peer.** A WHATWG `Request` exposes no
|
|
502
|
+
transport peer, so a Fetch guard (Workers, Deno, Bun, edge) has nothing to observe on its own, and no
|
|
503
|
+
forwarded header is accepted in its place: `client_ip_source` is `unavailable` and no address is sent.
|
|
489
504
|
Earlier versions reported the forwarded header here, so an address-scoped rule that appeared to work on
|
|
490
|
-
such a runtime was matching a client-supplied value.
|
|
505
|
+
such a runtime was matching a client-supplied value. Where your runtime does know the peer, pass
|
|
506
|
+
`peerAddress: (request, ...handlerArgs) => string` — for example `(req, info) => info.remoteAddr.hostname`
|
|
507
|
+
on Deno, or `(req, server) => server.requestIP(req)?.address` on Bun. It receives the request and the
|
|
508
|
+
arguments your handler was called with (`fetchGuard()(request, ...args)` and
|
|
509
|
+
`screenResponse(response, request, ...args)` pass them on), and that address then counts as the
|
|
510
|
+
transport peer, including for `trustedProxy`. With `trustedProxy` set and no peer supplied, the guard
|
|
511
|
+
warns once, because the policy can never apply.
|
|
491
512
|
|
|
492
513
|
`trustedProxy` is the only way to make a forwarded header count. It takes the proxies you actually run —
|
|
493
514
|
`{ peers: ['10.0.0.0/8'] }`, or `{ hops: 1 }` to trust that many hops in from the peer, plus optional
|
|
@@ -576,8 +597,15 @@ carries no detections and would otherwise read as one. Those counts stay in your
|
|
|
576
597
|
sent to report them.
|
|
577
598
|
|
|
578
599
|
`protection.stop()` stops everything the guard has running in the background — the rule-refresh loop, the
|
|
579
|
-
block-log reporter, the detection reporter — and flushes what is buffered. `
|
|
580
|
-
|
|
600
|
+
block-log reporter, the detection reporter — and flushes what is buffered. With `egress: true` it also
|
|
601
|
+
removes this guard's outbound-request screening; once no guard in the process is screening, `fetch` and
|
|
602
|
+
`node:http`/`node:https` are restored. Call it on shutdown; it is safe to call twice.
|
|
603
|
+
`protection.stopRefresh()` stops only the rule refresh: the reporters and outbound-request screening keep
|
|
604
|
+
running.
|
|
605
|
+
|
|
606
|
+
When more than one guard in a process has `egress: true`, an outbound call is checked by each of them and
|
|
607
|
+
refused if any one refuses it. A host listed in one guard's `allowHosts` is still refused when another guard
|
|
608
|
+
refuses it.
|
|
581
609
|
|
|
582
610
|
Two more endpoints the package can call, for completeness:
|
|
583
611
|
|
|
@@ -720,7 +748,7 @@ Remove only the pieces that are actually present — check for each first. If no
|
|
|
720
748
|
5. **Signal Patchstack that the package is being removed**: run `npx @patchstack/connect uninstall` (while the package is still installed and `.patchstackrc.json` still exists). If the site was never claimed, this deletes its anonymous record on Patchstack; if the site is claimed, it is only flagged — the record stays until its owner removes it in the dashboard. A failed signal must not stop the uninstall; continue with the remaining steps.
|
|
721
749
|
6. **Uninstall the package** with the manager matching the lockfile: `npm uninstall` / `pnpm remove` / `yarn remove` / `bun remove` `@patchstack/connect`. Don't hand-edit `node_modules` or the lockfile.
|
|
722
750
|
7. **Delete `.patchstackrc.json` and `.patchstackrc.local.json`** (the second holds the API key and is git-ignored, so it is present locally even when the repo shows nothing), remove the `.gitignore` entry setup added for it, and remove `PATCHSTACK_SITE_UUID`, `PATCHSTACK_API_KEY` (and public-prefixed variants like `NEXT_PUBLIC_PATCHSTACK_SITE_UUID`) from env files and CI variables.
|
|
723
|
-
8. **Commit** the changes. Reporting stops immediately. On HTML shells the `window.__PATCHSTACK_PROD__` flag that `mark-build` stamped on production builds lives only in build output — the next build simply won't contain it (rebuild if build output is committed). On JSX roots `scan` wrote the same marker into source; remove that managed `#region patchstack` block (or the hand-pasted equivalent) with the widget tag in step 2.
|
|
751
|
+
8. **Commit** the changes. Reporting stops immediately. On HTML shells the `window.__PATCHSTACK_PROD__` flag that `mark-build` stamped on production builds lives only in build output — the next build simply won't contain it (rebuild if build output is committed). On JSX roots and Astro layouts `scan` wrote the same marker into source; remove that managed `#region patchstack` block (or the hand-pasted equivalent) with the widget tag in step 2.
|
|
724
752
|
|
|
725
753
|
The `uninstall` signal is the only account-side effect local removal can have: it deletes an *unclaimed* (anonymous) record and merely flags a *claimed* one. A claimed site keeps using a site slot until its owner removes it in the dashboard at https://app.patchstack.com — end your report by telling the user this, alongside the site UUID from step 1. Never attempt to authenticate or remove a claimed site on the user's behalf.
|
|
726
754
|
|
package/README.md
CHANGED
|
@@ -123,8 +123,8 @@ patchstack-connect scan [options] Scan the lockfile and POST to
|
|
|
123
123
|
one and Connect persists it. After a
|
|
124
124
|
successful post, adds/updates the Patchstack
|
|
125
125
|
Connector tag in the root HTML shell. Also adds the
|
|
126
|
-
production marker to a JSX root
|
|
127
|
-
post (opt out of both with "widget": false in
|
|
126
|
+
production marker to a JSX root or Astro layout,
|
|
127
|
+
before the post (opt out of both with "widget": false in
|
|
128
128
|
.patchstackrc.json)
|
|
129
129
|
patchstack-connect setup [options] Run scan, manage the widget, and idempotently
|
|
130
130
|
install + verify runtime protection and wire
|
|
@@ -376,14 +376,14 @@ The Patchstack Connector is a floating control whose form follows the site's cla
|
|
|
376
376
|
<script src="https://cdn.patchstack.com/patchstack-widget.js" data-site-uuid="<SITE_UUID>" defer data-patchstack-connect-widget="true"></script>
|
|
377
377
|
```
|
|
378
378
|
|
|
379
|
-
- **`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.
|
|
379
|
+
- **`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`, …) or an Astro layout (`src/layouts/Layout.astro`, `Base.astro`, or the first layout that closes `</body>`), 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.
|
|
380
380
|
- **`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.
|
|
381
381
|
|
|
382
|
-
Re-runs update the tag in place (the `data-patchstack-connect-widget` attribute marks it as managed by Connect); 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
|
|
382
|
+
Re-runs update the tag in place (the `data-patchstack-connect-widget` attribute marks it as managed by Connect); 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 that Connect does not edit (Nuxt, …) get the exact snippet and target file printed instead — `guide` shows framework-specific placement.
|
|
383
383
|
|
|
384
384
|
- **`mark-build`** ensures the same tag in built HTML output, covering builds whose source shell Connect couldn't edit, and stamps `window.__PATCHSTACK_PROD__` so the widget hides the claim/login UI on the published site (owners reach it by appending `#patchstack` to the live URL). It then reports what it did — `stamped`, `withheld`, `no-pages` for a server-rendered build, or `no-output` — alongside the same manifest `scan` sent before the bundler ran, so the dashboard can say why a published app is or is not reporting its build. That report is the second half of one build, not a second build: Patchstack keeps one copy of the manifest and reads the two together. It is sent only for a site that is already registered, and never carries the site's address or name, which `mark-build` does not resolve. The marker says the page is the live site, so **only a production build carries it**: the environment is read the same way `scan` reads it (the build platform's own tier or branch name, then the hosted builder the project belongs to), and a local or preview build gets the widget tag, no marker, and any marker an earlier build left behind removed. Publishing a static build by hand from your machine is the case that needs `--production` (or `PATCHSTACK_ENVIRONMENT=production`), because nothing in that environment can say the build is a deployment.
|
|
385
385
|
|
|
386
|
-
- **Opting out:** persist `"widget": false` in `.patchstackrc.json` to disable both the widget tag and the production marker (dependency scanning only). Without it, the next successful scan re-adds the managed tag, and the next scan re-adds the marker on a JSX root.
|
|
386
|
+
- **Opting out:** persist `"widget": false` in `.patchstackrc.json` to disable both the widget tag and the production marker (dependency scanning only). Without it, the next successful scan re-adds the managed tag, and the next scan re-adds the marker on a JSX root or Astro layout.
|
|
387
387
|
|
|
388
388
|
## Programmatic API
|
|
389
389
|
|