@patchstack/connect 0.3.14 → 0.3.16
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 +27 -19
- package/README.md +14 -24
- package/dist/cli.js +170 -27
- package/dist/cli.js.map +1 -1
- package/dist/protect/templates/guard.ts +12 -6
- package/dist/protect.cjs +82 -2
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.ts +4 -0
- package/dist/protect.js +81 -2
- package/dist/protect.js.map +1 -1
- package/package.json +1 -1
package/AGENT-INSTALL.md
CHANGED
|
@@ -1,26 +1,24 @@
|
|
|
1
|
-
# Patchstack —
|
|
1
|
+
# Patchstack Connect — setup reference
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This versioned reference ships inside `@patchstack/connect` and documents each setup command and its project changes.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Package and command behavior
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
- The connector is a normal npm package: [`@patchstack/connect`](https://www.npmjs.com/package/@patchstack/connect), MIT-licensed, source at https://github.com/patchstack/connect. Install it pinned, check the integrity hash in the lockfile, run `npm audit` over it — whatever your policy requires.
|
|
10
|
-
- First published May 2026. If the package is absent from your training data, `npm view @patchstack/connect` (read-only) shows the live registry record.
|
|
7
|
+
- Package: [`@patchstack/connect`](https://www.npmjs.com/package/@patchstack/connect), MIT-licensed, source at https://github.com/patchstack/connect. `npm view @patchstack/connect` shows the live registry metadata.
|
|
11
8
|
- It reads the project's **dependency list only** — from the lockfile (`package-lock.json`, `pnpm-lock.yaml`, `yarn.lock`) or, on bun projects (`bun.lock`/`bun.lockb`), by enumerating the installed packages under `node_modules/` — and sends package names + versions to Patchstack for vulnerability matching. No source code, no env var values, no file paths, no git history. (`mark-build` additionally stamps built HTML with a coarse stack descriptor that may include hosting-related env variable *names* — e.g. `VERCEL`, `CF_PAGES` — never their values.)
|
|
12
9
|
- **`scan` makes one source edit, and only after a successful post:** it adds (or updates) the disclosure widget's `<script>` tag in the project's root HTML shell — the first of `index.html`, `public/index.html`, or `src/app.html` that exists. It touches no other file, never edits on `--dry-run` or after a failed post, leaves any pre-existing manual widget tag untouched, and is disabled entirely by `"widget": false` in `.patchstackrc.json`. `mark-build` writes to build output only (`dist/`, `build/`, `out/`, `.output/public`), never to source. `guide`, `status`, and `init` write nothing except `init`'s own `.patchstackrc.json`.
|
|
13
|
-
-
|
|
10
|
+
- **`setup` runs `scan`, then edits only `package.json` build scripts:** it preserves existing commands, adds `scan` before builds and `mark-build` after builds, and uses a direct build chain for Bun. It never runs the project build or `protect`. If the widget needs a framework-specific source edit, it prints the exact remaining step instead of rewriting framework code.
|
|
11
|
+
- The package also bundles an **opt-in** `protect` command (runtime exploit guard, currently for TanStack Start + Supabase apps; its templates live under `dist/protect/`). It patches the app's Supabase client to route traffic through a same-origin guard — and it runs **only** when explicitly invoked; `scan`, `setup`, `guide`, `status`, and `mark-build` never invoke it.
|
|
14
12
|
- Patchstack is not WordPress-only. This connector monitors any JS/Node project — Vite, Next.js, plain vanilla JS, anything with a lockfile.
|
|
15
13
|
|
|
16
14
|
## Before you start — never install twice
|
|
17
15
|
|
|
18
|
-
- `npx @patchstack/connect guide` prints a live checklist
|
|
19
|
-
- If `.patchstackrc.json` contains a `siteUuid` key, the project is already provisioned. Reuse that UUID; run `npx @patchstack/connect status` to re-print it and the
|
|
16
|
+
- `npx @patchstack/connect guide` prints a read-only live checklist showing which steps are already done in the current project (install, provisioning, build hooks, widget).
|
|
17
|
+
- If `.patchstackrc.json` contains a `siteUuid` key, the project is already provisioned. Reuse that UUID; run `npx @patchstack/connect status` to re-print it and the dashboard URL. **Do not delete the file and provision a second site.** (A `.patchstackrc.json` with other keys — e.g. an `endpoint` override — but no `siteUuid` is *not* provisioned yet; scan normally.)
|
|
20
18
|
- If `@patchstack/connect` is already in `devDependencies`, skip the install command.
|
|
21
19
|
- If the widget script tag (`cdn.patchstack.com/patchstack-widget.js`) is already in the layout, don't add a second one — `scan` also respects an existing tag: it updates its own managed tag in place and leaves a manual one untouched.
|
|
22
20
|
|
|
23
|
-
##
|
|
21
|
+
## Automated setup
|
|
24
22
|
|
|
25
23
|
1. **Install** (skip if already present), matching the project's package manager:
|
|
26
24
|
|
|
@@ -31,15 +29,25 @@ You are reading this either from `node_modules/@patchstack/connect/AGENT-INSTALL
|
|
|
31
29
|
bun add -d @patchstack/connect # bun.lock / bun-managed platforms
|
|
32
30
|
```
|
|
33
31
|
|
|
34
|
-
2. **
|
|
32
|
+
2. **Run bounded setup:**
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
npx @patchstack/connect setup
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
This provisions or reuses the site, manages the widget, wires the build scripts, prints a dashboard link, and finishes with the same status shown by `guide`. Re-running it reuses existing configuration, widget tags, and build commands rather than duplicating them.
|
|
39
|
+
|
|
40
|
+
## Manual setup
|
|
41
|
+
|
|
42
|
+
1. **First scan** — provisions a Patchstack site automatically, writes the UUID to `.patchstackrc.json`, and installs the disclosure widget's `<script>` tag into the root HTML shell (`index.html`, `public/index.html`, or `src/app.html`) when one exists. No signup, dashboard step, or UUID is needed up front:
|
|
35
43
|
|
|
36
44
|
```
|
|
37
45
|
npx @patchstack/connect scan
|
|
38
46
|
```
|
|
39
47
|
|
|
40
|
-
It prints a
|
|
48
|
+
It prints a dashboard link but never opens it. Open that link in a browser to view reports. It also prints what it did about the widget — if it added the tag, reload the preview and confirm the "Report a vulnerability" button appears.
|
|
41
49
|
|
|
42
|
-
|
|
50
|
+
2. **Wire builds** in `package.json`:
|
|
43
51
|
|
|
44
52
|
```jsonc
|
|
45
53
|
{
|
|
@@ -54,7 +62,7 @@ You are reading this either from `node_modules/@patchstack/connect/AGENT-INSTALL
|
|
|
54
62
|
|
|
55
63
|
**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"`.
|
|
56
64
|
|
|
57
|
-
|
|
65
|
+
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`:
|
|
58
66
|
|
|
59
67
|
```html
|
|
60
68
|
<script src="https://cdn.patchstack.com/patchstack-widget.js" data-site-uuid="<SITE_UUID>" defer></script>
|
|
@@ -62,20 +70,20 @@ You are reading this either from `node_modules/@patchstack/connect/AGENT-INSTALL
|
|
|
62
70
|
|
|
63
71
|
Framework-specific placement patterns: https://cdn.patchstack.com/llm.html. The site UUID is public by design — it ships in client-side HTML and is not a secret. If the project must not carry the widget, persist `"widget": false` in `.patchstackrc.json`; otherwise the next scan re-adds it.
|
|
64
72
|
|
|
65
|
-
|
|
73
|
+
4. **Commit** `.patchstackrc.json`, the updated `package.json`, and the layout/HTML file carrying the widget tag, so every developer and CI run reports to the same site.
|
|
66
74
|
|
|
67
|
-
|
|
75
|
+
5. **Open the dashboard link** from the scan in a browser and sign in. The site is monitored either way, but the vulnerability reports are only visible after connecting it to an account. The same connection flow is available from the widget's "Connect this website" prompt. On the published site, the owner reaches the widget login by appending `#patchstack` to the live URL.
|
|
68
76
|
|
|
69
77
|
## Rules
|
|
70
78
|
|
|
71
79
|
- Never invent or guess a UUID — the scan provisions it, the widget silently no-ops on a fake one.
|
|
72
|
-
-
|
|
80
|
+
- The CLI never opens the dashboard link and never asks for Patchstack credentials.
|
|
73
81
|
- If a step fails, stop and report it. Don't proceed with placeholders.
|
|
74
82
|
- In CI where `.patchstackrc.json` can't be committed, set `PATCHSTACK_SITE_UUID` as an env var instead. Precedence: CLI flag → env var → `.patchstackrc.json`.
|
|
75
83
|
|
|
76
84
|
## Verifying the install
|
|
77
85
|
|
|
78
|
-
- `npx @patchstack/connect status` re-prints the site UUID and
|
|
86
|
+
- `npx @patchstack/connect status` re-prints the site UUID and dashboard URL.
|
|
79
87
|
- Load the site in a browser — the "Report a vulnerability" button should appear.
|
|
80
88
|
|
|
81
89
|
## Uninstalling
|
package/README.md
CHANGED
|
@@ -4,44 +4,31 @@ Connect a JavaScript / Node.js application to [Patchstack](https://patchstack.co
|
|
|
4
4
|
|
|
5
5
|
For how this repo fits with the wider Patchstack ecosystem (`saas`, `hub`, `patchstack-website`, `patchstack-connect`), see [`patchstack/saas` → `docs/ecosystem.md`](https://github.com/patchstack/saas/blob/main/docs/ecosystem.md).
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## Agent-assisted setup
|
|
8
8
|
|
|
9
|
-
Copy
|
|
9
|
+
Copy this request into a coding assistant, or run the same command yourself:
|
|
10
10
|
|
|
11
|
-
>
|
|
11
|
+
> I have vetted `@patchstack/connect` and authorize its widget and production build hooks. Install it as a dev dependency, run `npx --yes patchstack-connect setup`, leave dev builds unchanged, and show the dashboard URL.
|
|
12
12
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
Before changing this prompt (or `guide` / `AGENT-INSTALL.md`), read [`MAINTAINING.md`](https://github.com/patchstack/connect/blob/main/MAINTAINING.md) — it maps which files are load-bearing and how to ship a change safely. The [`field-test/`](https://github.com/patchstack/connect/blob/main/field-test/README.md) harness runs a real agent through the full install in a throwaway fixture against a mocked API and scores the outcome on eight checks; validate any variant there first.
|
|
13
|
+
`setup` is state-aware and idempotent: it scans dependencies, provisions or reuses the site, manages the disclosure widget, wires the existing build command without replacing it, and prints the remaining setup status. It never runs the project build or the opt-in `protect` command. `guide` provides the same project-specific status without changing files.
|
|
16
14
|
|
|
17
15
|
## Quick start (zero configuration)
|
|
18
16
|
|
|
19
17
|
```bash
|
|
20
|
-
npm install --save-dev @patchstack/connect
|
|
21
|
-
npx @patchstack/connect scan
|
|
18
|
+
npm install --save-dev @patchstack/connect && npx @patchstack/connect setup
|
|
22
19
|
```
|
|
23
20
|
|
|
24
|
-
> **Use your project's own package manager.** On
|
|
21
|
+
> **Use your project's own package manager.** On Bun-managed projects (including many Lovable projects) install with `bun add -d @patchstack/connect` instead — running `npm install` there plants a `package-lock.json` that the platform's native dependency flow never updates again, leaving a stale lockfile next to the live one. The connector detects and works around that (see *Stale lockfiles* below), but not creating the fossil is better.
|
|
25
22
|
|
|
26
|
-
That's it.
|
|
23
|
+
That's it. `setup`:
|
|
27
24
|
|
|
28
25
|
1. Reads your lockfile (see *Supported lockfiles*).
|
|
29
26
|
2. POSTs the package list to Patchstack with **no** UUID.
|
|
30
27
|
3. Patchstack provisions a fresh site and returns its UUID.
|
|
31
28
|
4. The connector writes the UUID to `.patchstackrc.json` so the next `scan` targets the same site.
|
|
32
29
|
5. The connector installs the disclosure widget's `<script>` tag into your root HTML shell (see *The disclosure widget* below) so the "Report a vulnerability" button shows up on the next preview reload.
|
|
33
|
-
6.
|
|
34
|
-
|
|
35
|
-
Then wire it into builds:
|
|
36
|
-
|
|
37
|
-
```jsonc
|
|
38
|
-
// package.json
|
|
39
|
-
{
|
|
40
|
-
"scripts": {
|
|
41
|
-
"prebuild": "patchstack-connect scan"
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
```
|
|
30
|
+
6. Wires `scan` before builds and `mark-build` after builds, preserving existing commands and using direct build chaining for Bun.
|
|
31
|
+
7. Prints a dashboard link — open it in a browser to attach the new site to your Patchstack account. You can re-display it any time with `npx @patchstack/connect status`.
|
|
45
32
|
|
|
46
33
|
## Quick start (existing site)
|
|
47
34
|
|
|
@@ -50,7 +37,7 @@ If you already created an "Application" site in the Patchstack dashboard, pre-se
|
|
|
50
37
|
```bash
|
|
51
38
|
npm install --save-dev @patchstack/connect
|
|
52
39
|
npx @patchstack/connect init <your-site-uuid>
|
|
53
|
-
npx @patchstack/connect
|
|
40
|
+
npx @patchstack/connect setup
|
|
54
41
|
```
|
|
55
42
|
|
|
56
43
|
## CLI
|
|
@@ -62,6 +49,9 @@ patchstack-connect scan [options] Scan the lockfile and POST to
|
|
|
62
49
|
successful post, adds/updates the disclosure
|
|
63
50
|
widget tag in the root HTML shell (opt out
|
|
64
51
|
with "widget": false in .patchstackrc.json)
|
|
52
|
+
patchstack-connect setup [options] Run scan, manage the widget, and idempotently
|
|
53
|
+
wire package.json build scripts. Never runs
|
|
54
|
+
the project build or protect
|
|
65
55
|
patchstack-connect init <site-uuid> Optional: pre-seed .patchstackrc.json with
|
|
66
56
|
an existing site UUID
|
|
67
57
|
patchstack-connect status [options] Show current configuration
|
|
@@ -75,7 +65,7 @@ patchstack-connect protect Opt-in: install the always-on
|
|
|
75
65
|
guard (currently TanStack Start + Supabase; it
|
|
76
66
|
patches the app's Supabase client to route
|
|
77
67
|
traffic through a same-origin guard). Never
|
|
78
|
-
run by scan/guide/mark-build.
|
|
68
|
+
run by scan/setup/guide/mark-build.
|
|
79
69
|
patchstack-connect help Print help
|
|
80
70
|
|
|
81
71
|
Options (for scan and status):
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/cli.ts
|
|
4
|
-
import { readFileSync as
|
|
4
|
+
import { readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
5
5
|
|
|
6
6
|
// src/parsers/index.ts
|
|
7
7
|
import { access } from "fs/promises";
|
|
@@ -1324,11 +1324,11 @@ var INSTALL_COMMANDS = {
|
|
|
1324
1324
|
bun: "bun add -d @patchstack/connect"
|
|
1325
1325
|
};
|
|
1326
1326
|
var PM_BY_LOCKFILE = [
|
|
1327
|
-
{ filename: "package-lock.json", pm: "npm" },
|
|
1328
1327
|
{ filename: "bun.lock", pm: "bun" },
|
|
1329
1328
|
{ filename: "bun.lockb", pm: "bun" },
|
|
1330
1329
|
{ filename: "pnpm-lock.yaml", pm: "pnpm" },
|
|
1331
|
-
{ filename: "yarn.lock", pm: "yarn" }
|
|
1330
|
+
{ filename: "yarn.lock", pm: "yarn" },
|
|
1331
|
+
{ filename: "package-lock.json", pm: "npm" }
|
|
1332
1332
|
];
|
|
1333
1333
|
var WIDGET_FILE_CANDIDATES = {
|
|
1334
1334
|
next: [
|
|
@@ -1383,6 +1383,10 @@ var WIDGET_SCAN_MAX_FILES = 4e3;
|
|
|
1383
1383
|
var WIDGET_SCAN_MAX_DEPTH = 6;
|
|
1384
1384
|
var WIDGET_SCAN_MAX_BYTES = 512 * 1024;
|
|
1385
1385
|
function detectPackageManager(cwd) {
|
|
1386
|
+
const declared = readPackageJson(cwd)?.packageManager?.split("@")[0];
|
|
1387
|
+
if (declared === "npm" || declared === "pnpm" || declared === "yarn" || declared === "bun") {
|
|
1388
|
+
return declared;
|
|
1389
|
+
}
|
|
1386
1390
|
for (const { filename, pm } of PM_BY_LOCKFILE) {
|
|
1387
1391
|
if (existsSync3(path9.join(cwd, filename))) {
|
|
1388
1392
|
return pm;
|
|
@@ -1587,8 +1591,8 @@ function renderGuideChecklist(state, useColor) {
|
|
|
1587
1591
|
} else {
|
|
1588
1592
|
lines.push(todo("Provision the site \u2014 run the first scan"));
|
|
1589
1593
|
lines.push(detail("Run \u2192 npx @patchstack/connect scan"));
|
|
1590
|
-
lines.push(detail("
|
|
1591
|
-
lines.push(detail("and prints a
|
|
1594
|
+
lines.push(detail("Reads the lockfile, registers the project, writes .patchstackrc.json,"));
|
|
1595
|
+
lines.push(detail("and prints a dashboard link. The CLI prints the link but never opens it."));
|
|
1592
1596
|
}
|
|
1593
1597
|
if (state.prebuildWired && state.postbuildWired) {
|
|
1594
1598
|
lines.push(done("Build hooks wired (scan before builds, mark-build after)"));
|
|
@@ -1626,14 +1630,14 @@ function renderGuideChecklist(state, useColor) {
|
|
|
1626
1630
|
}
|
|
1627
1631
|
lines.push("");
|
|
1628
1632
|
if (state.claimUrl !== null) {
|
|
1629
|
-
lines.push(` ${paint(ANSI.cyan, "\u279C")} ${paint(ANSI.bold, "
|
|
1633
|
+
lines.push(` ${paint(ANSI.cyan, "\u279C")} ${paint(ANSI.bold, "Dashboard link (open to view reports):")}`);
|
|
1630
1634
|
lines.push(` ${paint(ANSI.cyan, state.claimUrl)}`);
|
|
1631
|
-
lines.push(detail("Open in a browser.
|
|
1635
|
+
lines.push(detail("Open this link in a browser. The CLI never opens it."));
|
|
1632
1636
|
if (state.endpointOverride !== null) {
|
|
1633
1637
|
lines.push(detail("(this URL inherits the endpoint override above)"));
|
|
1634
1638
|
}
|
|
1635
1639
|
} else {
|
|
1636
|
-
lines.push(detail("The
|
|
1640
|
+
lines.push(detail("The dashboard link appears after the first scan (re-print any time with `status`)."));
|
|
1637
1641
|
}
|
|
1638
1642
|
const remaining = countRemainingSteps(state);
|
|
1639
1643
|
lines.push("");
|
|
@@ -1647,7 +1651,7 @@ function renderGuideChecklist(state, useColor) {
|
|
|
1647
1651
|
)
|
|
1648
1652
|
);
|
|
1649
1653
|
if (state.claimUrl !== null) {
|
|
1650
|
-
lines.push(detail("The only manual action left is
|
|
1654
|
+
lines.push(detail("The only manual action left is opening the dashboard link above (if not already connected)."));
|
|
1651
1655
|
}
|
|
1652
1656
|
} else {
|
|
1653
1657
|
lines.push(
|
|
@@ -1723,6 +1727,25 @@ function scaffold(cwd) {
|
|
|
1723
1727
|
copyFileSync(join(TEMPLATES, "rules.json"), join(dst, "rules.json"));
|
|
1724
1728
|
log("scaffolded guard.ts + rules.json");
|
|
1725
1729
|
}
|
|
1730
|
+
function bakeSiteUuid(cwd) {
|
|
1731
|
+
const rc = join(cwd, ".patchstackrc.json");
|
|
1732
|
+
if (!existsSync4(rc)) return log("no .patchstackrc.json \u2014 guard uses PATCHSTACK_SITE_UUID env or the bundled fallback");
|
|
1733
|
+
let uuid;
|
|
1734
|
+
try {
|
|
1735
|
+
uuid = JSON.parse(read(rc)).siteUuid;
|
|
1736
|
+
} catch {
|
|
1737
|
+
return log(".patchstackrc.json unreadable \u2014 skipping site-UUID bake");
|
|
1738
|
+
}
|
|
1739
|
+
if (!uuid || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(uuid)) {
|
|
1740
|
+
return log(".patchstackrc.json siteUuid missing or malformed \u2014 guard uses PATCHSTACK_SITE_UUID env or the bundled fallback");
|
|
1741
|
+
}
|
|
1742
|
+
const p = join(cwd, "src/integrations/patchstack/guard.ts");
|
|
1743
|
+
if (!existsSync4(p)) return;
|
|
1744
|
+
const s = read(p);
|
|
1745
|
+
if (!s.includes("__PATCHSTACK_SITE_UUID__")) return log("guard.ts site UUID already baked");
|
|
1746
|
+
writeFileSync2(p, s.replace("__PATCHSTACK_SITE_UUID__", uuid));
|
|
1747
|
+
log("baked site UUID into guard.ts \u2014 live rules from the Patchstack API");
|
|
1748
|
+
}
|
|
1726
1749
|
function patchClient(cwd) {
|
|
1727
1750
|
const p = join(cwd, "src/integrations/supabase/client.ts");
|
|
1728
1751
|
let s = read(p);
|
|
@@ -1777,11 +1800,84 @@ function runProtect(cwd) {
|
|
|
1777
1800
|
return;
|
|
1778
1801
|
}
|
|
1779
1802
|
scaffold(cwd);
|
|
1803
|
+
bakeSiteUuid(cwd);
|
|
1780
1804
|
patchClient(cwd);
|
|
1781
1805
|
patchStart(cwd);
|
|
1782
1806
|
log("done \u2014 guard wired and always-on (blocks by default). Set PATCHSTACK_MODE=dry-run for log-only.");
|
|
1783
1807
|
}
|
|
1784
1808
|
|
|
1809
|
+
// src/setup.ts
|
|
1810
|
+
import { readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
1811
|
+
import path10 from "path";
|
|
1812
|
+
var SCAN_COMMAND = "patchstack-connect scan";
|
|
1813
|
+
var MARK_BUILD_COMMAND = "patchstack-connect mark-build";
|
|
1814
|
+
function appendHook(existing, command) {
|
|
1815
|
+
if (existing === void 0 || existing.trim().length === 0) {
|
|
1816
|
+
return command;
|
|
1817
|
+
}
|
|
1818
|
+
if (existing.includes(command)) {
|
|
1819
|
+
return existing;
|
|
1820
|
+
}
|
|
1821
|
+
return `${existing} && ${command}`;
|
|
1822
|
+
}
|
|
1823
|
+
function wireBuildScripts(cwd, packageManager) {
|
|
1824
|
+
const target = path10.join(cwd, "package.json");
|
|
1825
|
+
const raw = readFileSync4(target, "utf8");
|
|
1826
|
+
const pkg = JSON.parse(raw);
|
|
1827
|
+
const scripts = pkg.scripts ?? {};
|
|
1828
|
+
const build = scripts.build;
|
|
1829
|
+
if (build === void 0 || build.trim().length === 0) {
|
|
1830
|
+
return {
|
|
1831
|
+
changed: false,
|
|
1832
|
+
strategy: "skipped",
|
|
1833
|
+
detail: "package.json has no build script; no build integration was added."
|
|
1834
|
+
};
|
|
1835
|
+
}
|
|
1836
|
+
if (packageManager === "bun") {
|
|
1837
|
+
let nextBuild = build;
|
|
1838
|
+
if (!nextBuild.includes(SCAN_COMMAND)) {
|
|
1839
|
+
nextBuild = `${SCAN_COMMAND} && ${nextBuild}`;
|
|
1840
|
+
}
|
|
1841
|
+
if (!nextBuild.includes(MARK_BUILD_COMMAND)) {
|
|
1842
|
+
nextBuild = `${nextBuild} && ${MARK_BUILD_COMMAND}`;
|
|
1843
|
+
}
|
|
1844
|
+
if (nextBuild === build) {
|
|
1845
|
+
return {
|
|
1846
|
+
changed: false,
|
|
1847
|
+
strategy: "build-chain",
|
|
1848
|
+
detail: "build script already runs scan and mark-build."
|
|
1849
|
+
};
|
|
1850
|
+
}
|
|
1851
|
+
scripts.build = nextBuild;
|
|
1852
|
+
} else {
|
|
1853
|
+
const prebuild = appendHook(scripts.prebuild, SCAN_COMMAND);
|
|
1854
|
+
const postbuild = appendHook(scripts.postbuild, MARK_BUILD_COMMAND);
|
|
1855
|
+
if (prebuild === scripts.prebuild && postbuild === scripts.postbuild) {
|
|
1856
|
+
return {
|
|
1857
|
+
changed: false,
|
|
1858
|
+
strategy: "lifecycle-hooks",
|
|
1859
|
+
detail: "prebuild and postbuild hooks are already wired."
|
|
1860
|
+
};
|
|
1861
|
+
}
|
|
1862
|
+
scripts.prebuild = prebuild;
|
|
1863
|
+
scripts.postbuild = postbuild;
|
|
1864
|
+
}
|
|
1865
|
+
pkg.scripts = scripts;
|
|
1866
|
+
const indentMatch = raw.match(/^[\t ]+(?=")/m)?.[0];
|
|
1867
|
+
const indent = indentMatch?.includes(" ") ? " " : indentMatch?.length ?? 2;
|
|
1868
|
+
const trailingNewline = raw.endsWith("\n") ? "\n" : "";
|
|
1869
|
+
writeFileSync3(target, `${JSON.stringify(pkg, null, indent)}${trailingNewline}`, "utf8");
|
|
1870
|
+
return packageManager === "bun" ? {
|
|
1871
|
+
changed: true,
|
|
1872
|
+
strategy: "build-chain",
|
|
1873
|
+
detail: "chained scan and mark-build around the existing build command."
|
|
1874
|
+
} : {
|
|
1875
|
+
changed: true,
|
|
1876
|
+
strategy: "lifecycle-hooks",
|
|
1877
|
+
detail: "added scan to prebuild and mark-build to postbuild."
|
|
1878
|
+
};
|
|
1879
|
+
}
|
|
1880
|
+
|
|
1785
1881
|
// src/cli.ts
|
|
1786
1882
|
var HELP = `@patchstack/connect \u2014 scan your lockfile and report packages to Patchstack.
|
|
1787
1883
|
|
|
@@ -1794,6 +1890,9 @@ Usage:
|
|
|
1794
1890
|
HTML shell (index.html, public/index.html,
|
|
1795
1891
|
or src/app.html) \u2014 opt out with
|
|
1796
1892
|
"widget": false in .patchstackrc.json
|
|
1893
|
+
patchstack-connect setup [options] Finish the bounded project setup: run scan,
|
|
1894
|
+
manage the widget, and wire package.json build
|
|
1895
|
+
scripts. Never runs a build or protect
|
|
1797
1896
|
patchstack-connect init <site-uuid> Optional: pre-seed .patchstackrc.json
|
|
1798
1897
|
with an existing site UUID
|
|
1799
1898
|
patchstack-connect status [options] Show current configuration
|
|
@@ -1809,7 +1908,7 @@ Usage:
|
|
|
1809
1908
|
guide even when setup is complete
|
|
1810
1909
|
patchstack-connect help Print this message
|
|
1811
1910
|
|
|
1812
|
-
Options (for scan and status):
|
|
1911
|
+
Options (for scan, setup, and status):
|
|
1813
1912
|
--site-uuid <uuid> Override the configured site UUID
|
|
1814
1913
|
--endpoint <url> Override the API endpoint
|
|
1815
1914
|
--dry-run (scan only) Show the payload without posting
|
|
@@ -1827,6 +1926,7 @@ Environment:
|
|
|
1827
1926
|
Precedence: CLI flag > environment variable > .patchstackrc.json.
|
|
1828
1927
|
|
|
1829
1928
|
Examples:
|
|
1929
|
+
npx @patchstack/connect setup
|
|
1830
1930
|
npx @patchstack/connect scan
|
|
1831
1931
|
npx @patchstack/connect scan --dry-run
|
|
1832
1932
|
npx @patchstack/connect init 550e8400-e29b-41d4-a716-446655440000
|
|
@@ -1884,7 +1984,7 @@ async function runInit(args) {
|
|
|
1884
1984
|
console.log("Next: run `npx @patchstack/connect scan` to send your first manifest.");
|
|
1885
1985
|
return 0;
|
|
1886
1986
|
}
|
|
1887
|
-
async function runScan(args) {
|
|
1987
|
+
async function runScan(args, options = {}) {
|
|
1888
1988
|
const dryRun = args.flags.get("dry-run") === true;
|
|
1889
1989
|
const config = await resolveConfig({
|
|
1890
1990
|
cwd: process.cwd(),
|
|
@@ -1951,24 +2051,26 @@ async function runScan(args) {
|
|
|
1951
2051
|
}
|
|
1952
2052
|
if (provisioning && response.uuid !== void 0 && response.uuid.length > 0) {
|
|
1953
2053
|
console.log("");
|
|
1954
|
-
console.log("
|
|
2054
|
+
console.log("Open this dashboard link to view vulnerability reports:");
|
|
1955
2055
|
console.log(` ${buildClaimUrl(config.endpoint, response.uuid)}`);
|
|
1956
2056
|
if (config.endpoint !== DEFAULT_ENDPOINT) {
|
|
1957
2057
|
console.log(" (this URL inherits the endpoint override above)");
|
|
1958
2058
|
}
|
|
1959
2059
|
}
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
1969
|
-
|
|
2060
|
+
if (options.showRemainingSetup !== false) {
|
|
2061
|
+
try {
|
|
2062
|
+
const state = await collectGuideState(process.cwd());
|
|
2063
|
+
const remaining = countRemainingSteps(state);
|
|
2064
|
+
if (remaining > 0) {
|
|
2065
|
+
const hooksMissing = !(state.prebuildWired && state.postbuildWired);
|
|
2066
|
+
console.log("");
|
|
2067
|
+
console.log(
|
|
2068
|
+
`Setup not complete \u2014 ${remaining} step(s) remaining${hooksMissing ? ", including the package.json build hooks (which scan can't wire)" : ""}.`
|
|
2069
|
+
);
|
|
2070
|
+
console.log("Run `npx @patchstack/connect guide` for the exact steps to finish for this project.");
|
|
2071
|
+
}
|
|
2072
|
+
} catch {
|
|
1970
2073
|
}
|
|
1971
|
-
} catch {
|
|
1972
2074
|
}
|
|
1973
2075
|
return 0;
|
|
1974
2076
|
}
|
|
@@ -2033,7 +2135,46 @@ async function runGuide(args) {
|
|
|
2033
2135
|
console.log("\u2014\u2014\u2014\u2014 Full reference guide (ships as AGENT-INSTALL.md) \u2014\u2014\u2014\u2014");
|
|
2034
2136
|
console.log("");
|
|
2035
2137
|
const guidePath = new URL("../AGENT-INSTALL.md", import.meta.url);
|
|
2036
|
-
console.log(
|
|
2138
|
+
console.log(readFileSync5(guidePath, "utf8"));
|
|
2139
|
+
return 0;
|
|
2140
|
+
}
|
|
2141
|
+
async function runSetup(args) {
|
|
2142
|
+
if (args.flags.get("dry-run") === true) {
|
|
2143
|
+
console.error("Error: setup does not support --dry-run. Use `scan --dry-run` to preview the manifest.");
|
|
2144
|
+
return 1;
|
|
2145
|
+
}
|
|
2146
|
+
const before = await collectGuideState(process.cwd());
|
|
2147
|
+
if (!before.hasPackageJson) {
|
|
2148
|
+
console.error("Error: no package.json found. Run setup from the project root.");
|
|
2149
|
+
return 1;
|
|
2150
|
+
}
|
|
2151
|
+
if (before.installed === null) {
|
|
2152
|
+
console.error(
|
|
2153
|
+
`Error: @patchstack/connect is not declared in package.json.
|
|
2154
|
+
Run: ${installCommand(before.packageManager)}`
|
|
2155
|
+
);
|
|
2156
|
+
return 1;
|
|
2157
|
+
}
|
|
2158
|
+
console.log("Patchstack setup \u2014 applying bounded project changes");
|
|
2159
|
+
console.log(" 1. Scan dependencies, provision/reuse the site, and manage the source widget");
|
|
2160
|
+
const scanCode = await runScan(args, { showRemainingSetup: false });
|
|
2161
|
+
if (scanCode !== 0) {
|
|
2162
|
+
return scanCode;
|
|
2163
|
+
}
|
|
2164
|
+
console.log("");
|
|
2165
|
+
console.log(" 2. Wire scan and mark-build into package.json");
|
|
2166
|
+
const wired = wireBuildScripts(process.cwd(), before.packageManager);
|
|
2167
|
+
console.log(`Build integration: ${wired.detail}`);
|
|
2168
|
+
console.log("");
|
|
2169
|
+
console.log(" 3. Verify setup status");
|
|
2170
|
+
const after = await collectGuideState(process.cwd());
|
|
2171
|
+
const useColor = process.stdout.isTTY === true && process.env.NO_COLOR === void 0;
|
|
2172
|
+
console.log(renderGuideChecklist(after, useColor));
|
|
2173
|
+
const remaining = countRemainingSteps(after);
|
|
2174
|
+
if (remaining > 0) {
|
|
2175
|
+
console.log("");
|
|
2176
|
+
console.log(`Setup applied its bounded changes; ${remaining} manual step(s) remain above.`);
|
|
2177
|
+
}
|
|
2037
2178
|
return 0;
|
|
2038
2179
|
}
|
|
2039
2180
|
async function runStatus(args) {
|
|
@@ -2049,7 +2190,7 @@ async function runStatus(args) {
|
|
|
2049
2190
|
console.log(`Timeout: ${config.timeoutMs}ms`);
|
|
2050
2191
|
console.log(`Environment: ${config.environment}`);
|
|
2051
2192
|
if (config.siteUuid !== null) {
|
|
2052
|
-
console.log(`
|
|
2193
|
+
console.log(`Dashboard URL: ${buildClaimUrl(config.endpoint, config.siteUuid)}`);
|
|
2053
2194
|
}
|
|
2054
2195
|
return 0;
|
|
2055
2196
|
}
|
|
@@ -2109,7 +2250,7 @@ async function runMarkBuild(args) {
|
|
|
2109
2250
|
let marked = 0;
|
|
2110
2251
|
let widgetTouched = 0;
|
|
2111
2252
|
for (const file of files) {
|
|
2112
|
-
const before =
|
|
2253
|
+
const before = readFileSync5(file, "utf8");
|
|
2113
2254
|
let after = injectMarker(before, snippet);
|
|
2114
2255
|
if (widgetUuid !== null) {
|
|
2115
2256
|
const ensured = ensureWidgetInHtml(after, widgetUuid);
|
|
@@ -2119,7 +2260,7 @@ async function runMarkBuild(args) {
|
|
|
2119
2260
|
after = ensured.html;
|
|
2120
2261
|
}
|
|
2121
2262
|
if (after !== before) {
|
|
2122
|
-
|
|
2263
|
+
writeFileSync4(file, after);
|
|
2123
2264
|
marked += 1;
|
|
2124
2265
|
}
|
|
2125
2266
|
}
|
|
@@ -2148,6 +2289,8 @@ async function main() {
|
|
|
2148
2289
|
return runProtectCommand(args);
|
|
2149
2290
|
case "guide":
|
|
2150
2291
|
return runGuide(args);
|
|
2292
|
+
case "setup":
|
|
2293
|
+
return runSetup(args);
|
|
2151
2294
|
default:
|
|
2152
2295
|
console.error(`Unknown command: ${args.command}
|
|
2153
2296
|
`);
|