@patchstack/connect 0.3.15 → 0.3.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 +27 -19
- package/README.md +14 -24
- package/dist/cli.js +817 -110
- package/dist/cli.js.map +1 -1
- package/dist/protect/templates/astro-middleware.ts +35 -0
- package/dist/protect/templates/demo-rules.json +238 -0
- package/dist/protect/templates/fastify-plugin.ts +48 -0
- package/dist/protect/templates/generic-guard.ts +51 -0
- package/dist/protect/templates/guard.ts +17 -3
- package/dist/protect/templates/next-middleware.ts +37 -0
- package/dist/protect/templates/sveltekit-hooks.ts +35 -0
- package/dist/protect.cjs +451 -78
- package/dist/protect.cjs.map +1 -1
- package/dist/protect.d.ts +9 -1
- package/dist/protect.js +451 -78
- package/dist/protect.js.map +1 -1
- package/package.json +3 -2
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 readFileSync7, writeFileSync as writeFileSync7 } 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(
|
|
@@ -1657,16 +1661,65 @@ function renderGuideChecklist(state, useColor) {
|
|
|
1657
1661
|
return lines.join("\n");
|
|
1658
1662
|
}
|
|
1659
1663
|
|
|
1660
|
-
// src/protect/install.ts
|
|
1661
|
-
import { readFileSync as readFileSync3,
|
|
1664
|
+
// src/protect/install/util.ts
|
|
1665
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4, writeFileSync as writeFileSync2 } from "fs";
|
|
1662
1666
|
import { join, dirname } from "path";
|
|
1663
1667
|
import { fileURLToPath } from "url";
|
|
1664
|
-
var HERE = dirname(fileURLToPath(import.meta.url));
|
|
1665
|
-
var TEMPLATES = [join(HERE, "protect", "templates"), join(HERE, "templates")].find((p) => existsSync4(p)) ?? join(HERE, "protect", "templates");
|
|
1666
|
-
var APP = process.cwd();
|
|
1667
|
-
var PS_DIR = join(APP, "src/integrations/patchstack");
|
|
1668
1668
|
var read = (p) => readFileSync3(p, "utf8");
|
|
1669
1669
|
var log = (msg) => console.log(`patchstack protect: ${msg}`);
|
|
1670
|
+
function hasDependency(cwd, name) {
|
|
1671
|
+
try {
|
|
1672
|
+
const pkg = JSON.parse(read(join(cwd, "package.json")));
|
|
1673
|
+
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }[name]);
|
|
1674
|
+
} catch {
|
|
1675
|
+
return false;
|
|
1676
|
+
}
|
|
1677
|
+
}
|
|
1678
|
+
var SITE_UUID_PLACEHOLDER = "__PATCHSTACK_SITE_UUID__";
|
|
1679
|
+
var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
1680
|
+
function bakeSiteUuid(cwd, guardRelPath) {
|
|
1681
|
+
const rc = join(cwd, ".patchstackrc.json");
|
|
1682
|
+
if (!existsSync4(rc)) {
|
|
1683
|
+
log("no .patchstackrc.json \u2014 guard uses PATCHSTACK_SITE_UUID env or the bundled fallback");
|
|
1684
|
+
return false;
|
|
1685
|
+
}
|
|
1686
|
+
let uuid;
|
|
1687
|
+
try {
|
|
1688
|
+
uuid = JSON.parse(read(rc)).siteUuid;
|
|
1689
|
+
} catch {
|
|
1690
|
+
log(".patchstackrc.json unreadable \u2014 skipping site-UUID bake");
|
|
1691
|
+
return false;
|
|
1692
|
+
}
|
|
1693
|
+
if (typeof uuid !== "string" || !UUID_RE.test(uuid)) {
|
|
1694
|
+
log(".patchstackrc.json siteUuid missing or malformed \u2014 guard uses PATCHSTACK_SITE_UUID env or the bundled fallback");
|
|
1695
|
+
return false;
|
|
1696
|
+
}
|
|
1697
|
+
const guardPath = join(cwd, guardRelPath);
|
|
1698
|
+
if (!existsSync4(guardPath)) return false;
|
|
1699
|
+
const source = read(guardPath);
|
|
1700
|
+
if (!source.includes(SITE_UUID_PLACEHOLDER)) {
|
|
1701
|
+
log(`${guardRelPath} site UUID already baked`);
|
|
1702
|
+
return false;
|
|
1703
|
+
}
|
|
1704
|
+
writeFileSync2(guardPath, source.replace(SITE_UUID_PLACEHOLDER, uuid));
|
|
1705
|
+
log(`baked site UUID into ${guardRelPath} \u2014 live rules from the Patchstack API`);
|
|
1706
|
+
return true;
|
|
1707
|
+
}
|
|
1708
|
+
var HERE = dirname(fileURLToPath(import.meta.url));
|
|
1709
|
+
function templatesDir() {
|
|
1710
|
+
const builtLayout = join(HERE, "protect", "templates");
|
|
1711
|
+
const candidates = [
|
|
1712
|
+
builtLayout,
|
|
1713
|
+
join(HERE, "..", "templates"),
|
|
1714
|
+
// source: src/protect/install/ → src/protect/templates
|
|
1715
|
+
join(HERE, "templates")
|
|
1716
|
+
];
|
|
1717
|
+
return candidates.find((p) => existsSync4(p)) ?? builtLayout;
|
|
1718
|
+
}
|
|
1719
|
+
|
|
1720
|
+
// src/protect/install/adapters/tanstack-supabase.ts
|
|
1721
|
+
import { writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync, copyFileSync } from "fs";
|
|
1722
|
+
import { join as join2 } from "path";
|
|
1670
1723
|
var CLIENT_TUNNEL = [
|
|
1671
1724
|
"",
|
|
1672
1725
|
" // PATCHSTACK: in the browser, tunnel Supabase traffic through the app's own server guard",
|
|
@@ -1680,101 +1733,127 @@ var CLIENT_TUNNEL = [
|
|
|
1680
1733
|
" }",
|
|
1681
1734
|
""
|
|
1682
1735
|
].join("\n");
|
|
1683
|
-
var
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
"",
|
|
1689
|
-
"// Patchstack guard (browser tunnel): intercept tunneled Supabase traffic before anything else,",
|
|
1690
|
-
"// then screen the outgoing response (SSR HTML / data) for leaked secrets & PII.",
|
|
1736
|
+
var GUARD_IMPORT = 'import { GUARD_PATH, handleGuardRequest, inspectServerFn, screenResponse, guardRequest } from "@/integrations/patchstack/guard";';
|
|
1737
|
+
var GUARD_IMPORT_RE = /import \{[^}]*\} from "@\/integrations\/patchstack\/guard";/;
|
|
1738
|
+
var START_IMPORTS = ['import { getRequest } from "@tanstack/react-start/server";', GUARD_IMPORT].join("\n");
|
|
1739
|
+
var REQUEST_MIDDLEWARE_BLOCK = [
|
|
1740
|
+
"// #region patchstack-guard (managed by patchstack-connect protect \u2014 do not edit)",
|
|
1741
|
+
"// Browser\u2192Supabase tunnel + response screening; optional route WAF via PATCHSTACK_ROUTE_WAF=1.",
|
|
1691
1742
|
"const patchstackGuard = createMiddleware().server(async ({ next }) => {",
|
|
1692
1743
|
" const request = getRequest();",
|
|
1693
1744
|
" if (request) {",
|
|
1694
1745
|
" const { pathname } = new URL(request.url);",
|
|
1695
1746
|
" if (pathname === GUARD_PATH) return handleGuardRequest(request);",
|
|
1747
|
+
' if (process.env.PATCHSTACK_ROUTE_WAF === "1") {',
|
|
1748
|
+
" const blocked = await guardRequest(request);",
|
|
1749
|
+
" if (blocked) return blocked;",
|
|
1750
|
+
" }",
|
|
1696
1751
|
" }",
|
|
1697
1752
|
" return screenResponse(await next());",
|
|
1698
1753
|
"});",
|
|
1699
|
-
""
|
|
1754
|
+
"// #endregion patchstack-guard"
|
|
1700
1755
|
].join("\n");
|
|
1701
|
-
var
|
|
1702
|
-
"",
|
|
1703
|
-
"//
|
|
1704
|
-
"// covering apps that mutate via TanStack server functions (which bypass the browser tunnel).",
|
|
1756
|
+
var FUNCTION_MIDDLEWARE_BLOCK = [
|
|
1757
|
+
"// #region patchstack-function-guard (managed by patchstack-connect protect \u2014 do not edit)",
|
|
1758
|
+
"// Inspect server-function args before they reach the database.",
|
|
1705
1759
|
'const patchstackFunctionGuard = createMiddleware({ type: "function" }).server(async ({ next, data }) => {',
|
|
1706
1760
|
" const blocked = await inspectServerFn(data);",
|
|
1707
1761
|
" if (blocked) throw new Error(blocked.message);",
|
|
1708
1762
|
" return next();",
|
|
1709
1763
|
"});",
|
|
1710
|
-
""
|
|
1764
|
+
"// #endregion patchstack-function-guard"
|
|
1711
1765
|
].join("\n");
|
|
1712
|
-
function
|
|
1713
|
-
const
|
|
1714
|
-
|
|
1715
|
-
const
|
|
1716
|
-
const
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1766
|
+
function reconcileBlock(s, region, block, legacyConst, insertBefore) {
|
|
1767
|
+
const lines = s.split("\n");
|
|
1768
|
+
const startMarker = `// #region ${region} `;
|
|
1769
|
+
const endMarker = `// #endregion ${region}`;
|
|
1770
|
+
const si = lines.findIndex((l) => l.includes(startMarker));
|
|
1771
|
+
if (si !== -1) {
|
|
1772
|
+
const ei = lines.findIndex((l, i) => i > si && l.trim() === endMarker);
|
|
1773
|
+
if (ei !== -1) {
|
|
1774
|
+
lines.splice(si, ei - si + 1, ...block.split("\n"));
|
|
1775
|
+
return lines.join("\n");
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
const ci = lines.findIndex((l) => l.includes(legacyConst));
|
|
1779
|
+
if (ci !== -1) {
|
|
1780
|
+
const close = lines.findIndex((l, i) => i >= ci && l.trim() === "});");
|
|
1781
|
+
if (close !== -1) {
|
|
1782
|
+
let start = ci;
|
|
1783
|
+
while (start > 0 && (lines[start - 1] ?? "").trim().startsWith("//")) start--;
|
|
1784
|
+
lines.splice(start, close - start + 1, ...block.split("\n"));
|
|
1785
|
+
return lines.join("\n");
|
|
1786
|
+
}
|
|
1787
|
+
}
|
|
1788
|
+
return s.replace(insertBefore, block + "\n\n" + insertBefore);
|
|
1789
|
+
}
|
|
1790
|
+
function detect(cwd) {
|
|
1791
|
+
const pkgPath = join2(cwd, "package.json");
|
|
1792
|
+
if (!existsSync5(pkgPath)) return false;
|
|
1793
|
+
let pkg;
|
|
1730
1794
|
try {
|
|
1731
|
-
|
|
1795
|
+
pkg = JSON.parse(read(pkgPath));
|
|
1732
1796
|
} catch {
|
|
1733
|
-
return
|
|
1797
|
+
return false;
|
|
1734
1798
|
}
|
|
1735
|
-
|
|
1736
|
-
|
|
1799
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1800
|
+
return Boolean(deps["@tanstack/react-start"]) && existsSync5(join2(cwd, "src/start.ts")) && existsSync5(join2(cwd, "src/integrations/supabase/client.ts"));
|
|
1801
|
+
}
|
|
1802
|
+
var GUARD_FILE = "src/integrations/patchstack/guard.ts";
|
|
1803
|
+
function scaffold(cwd, opts) {
|
|
1804
|
+
const templates = templatesDir();
|
|
1805
|
+
const dst = join2(cwd, "src/integrations/patchstack");
|
|
1806
|
+
mkdirSync(dst, { recursive: true });
|
|
1807
|
+
copyFileSync(join2(templates, "guard.ts"), join2(dst, "guard.ts"));
|
|
1808
|
+
const changed = [GUARD_FILE];
|
|
1809
|
+
const rulesDst = join2(dst, "rules.json");
|
|
1810
|
+
if (opts.demo) {
|
|
1811
|
+
copyFileSync(join2(templates, "demo-rules.json"), rulesDst);
|
|
1812
|
+
changed.push("src/integrations/patchstack/rules.json");
|
|
1813
|
+
log("scaffolded guard.ts + rules.json (demo sample rule set)");
|
|
1814
|
+
} else if (!existsSync5(rulesDst)) {
|
|
1815
|
+
copyFileSync(join2(templates, "rules.json"), rulesDst);
|
|
1816
|
+
changed.push("src/integrations/patchstack/rules.json");
|
|
1817
|
+
log("scaffolded guard.ts + rules.json (starter rules)");
|
|
1818
|
+
} else {
|
|
1819
|
+
log("scaffolded guard.ts (kept existing rules.json)");
|
|
1737
1820
|
}
|
|
1738
|
-
|
|
1739
|
-
if (!existsSync4(p)) return;
|
|
1740
|
-
const s = read(p);
|
|
1741
|
-
if (!s.includes("__PATCHSTACK_SITE_UUID__")) return log("guard.ts site UUID already baked");
|
|
1742
|
-
writeFileSync2(p, s.replace("__PATCHSTACK_SITE_UUID__", uuid));
|
|
1743
|
-
log("baked site UUID into guard.ts \u2014 live rules from the Patchstack API");
|
|
1821
|
+
return changed;
|
|
1744
1822
|
}
|
|
1745
1823
|
function patchClient(cwd) {
|
|
1746
|
-
const p =
|
|
1747
|
-
|
|
1748
|
-
if (s.includes("x-ps-target"))
|
|
1824
|
+
const p = join2(cwd, "src/integrations/supabase/client.ts");
|
|
1825
|
+
const s = read(p);
|
|
1826
|
+
if (s.includes("x-ps-target")) {
|
|
1827
|
+
log("client.ts already wired");
|
|
1828
|
+
return false;
|
|
1829
|
+
}
|
|
1749
1830
|
const anchor = "headers.set('apikey', supabaseKey);";
|
|
1750
|
-
if (!s.includes(anchor))
|
|
1751
|
-
|
|
1831
|
+
if (!s.includes(anchor)) {
|
|
1832
|
+
log("client.ts anchor not found \u2014 skipping (template changed?)");
|
|
1833
|
+
return false;
|
|
1834
|
+
}
|
|
1835
|
+
writeFileSync3(p, s.replace(anchor, anchor + "\n" + CLIENT_TUNNEL));
|
|
1752
1836
|
log("patched client.ts (tunnel Supabase through the guard)");
|
|
1837
|
+
return true;
|
|
1753
1838
|
}
|
|
1754
1839
|
function patchStart(cwd) {
|
|
1755
|
-
const p =
|
|
1840
|
+
const p = join2(cwd, "src/start.ts");
|
|
1756
1841
|
let s = read(p);
|
|
1757
1842
|
const importAnchor = 'import { createStart, createMiddleware } from "@tanstack/react-start";';
|
|
1758
1843
|
const exportAnchor = "export const startInstance";
|
|
1759
1844
|
const rmAnchor = "requestMiddleware: [";
|
|
1760
1845
|
if (!s.includes(importAnchor) || !s.includes(exportAnchor)) {
|
|
1761
|
-
|
|
1846
|
+
log("start.ts anchors not found \u2014 skipping (template changed?)");
|
|
1847
|
+
return false;
|
|
1762
1848
|
}
|
|
1763
1849
|
const original = s;
|
|
1764
|
-
if (
|
|
1850
|
+
if (GUARD_IMPORT_RE.test(s)) {
|
|
1851
|
+
s = s.replace(GUARD_IMPORT_RE, GUARD_IMPORT);
|
|
1852
|
+
} else {
|
|
1765
1853
|
s = s.replace(importAnchor, importAnchor + "\n" + START_IMPORTS);
|
|
1766
|
-
} else if (!s.includes("inspectServerFn")) {
|
|
1767
|
-
s = s.replace(
|
|
1768
|
-
'import { GUARD_PATH, handleGuardRequest } from "@/integrations/patchstack/guard";',
|
|
1769
|
-
'import { GUARD_PATH, handleGuardRequest, inspectServerFn } from "@/integrations/patchstack/guard";'
|
|
1770
|
-
);
|
|
1771
|
-
}
|
|
1772
|
-
if (!s.includes("const patchstackGuard =")) {
|
|
1773
|
-
s = s.replace(exportAnchor, REQUEST_MIDDLEWARE_DEF + "\n" + exportAnchor);
|
|
1774
|
-
}
|
|
1775
|
-
if (!s.includes("const patchstackFunctionGuard =")) {
|
|
1776
|
-
s = s.replace(exportAnchor, FUNCTION_MIDDLEWARE_DEF + "\n" + exportAnchor);
|
|
1777
1854
|
}
|
|
1855
|
+
s = reconcileBlock(s, "patchstack-guard", REQUEST_MIDDLEWARE_BLOCK, "const patchstackGuard =", exportAnchor);
|
|
1856
|
+
s = reconcileBlock(s, "patchstack-function-guard", FUNCTION_MIDDLEWARE_BLOCK, "const patchstackFunctionGuard =", exportAnchor);
|
|
1778
1857
|
if (s.includes(rmAnchor) && !s.includes("requestMiddleware: [patchstackGuard")) {
|
|
1779
1858
|
s = s.replace(rmAnchor, rmAnchor + "patchstackGuard, ");
|
|
1780
1859
|
}
|
|
@@ -1786,20 +1865,584 @@ function patchStart(cwd) {
|
|
|
1786
1865
|
s = s.replace(rmAnchor, "functionMiddleware: [patchstackFunctionGuard],\n " + rmAnchor);
|
|
1787
1866
|
}
|
|
1788
1867
|
}
|
|
1789
|
-
if (s === original)
|
|
1790
|
-
|
|
1868
|
+
if (s === original) {
|
|
1869
|
+
log("start.ts already wired");
|
|
1870
|
+
return false;
|
|
1871
|
+
}
|
|
1872
|
+
writeFileSync3(p, s);
|
|
1791
1873
|
log("patched start.ts (guard registered as request + function middleware)");
|
|
1874
|
+
return true;
|
|
1875
|
+
}
|
|
1876
|
+
function wire(cwd, opts) {
|
|
1877
|
+
const changed = scaffold(cwd, opts);
|
|
1878
|
+
if (!opts.demo && bakeSiteUuid(cwd, GUARD_FILE)) changed.push(GUARD_FILE);
|
|
1879
|
+
if (patchClient(cwd)) changed.push("src/integrations/supabase/client.ts");
|
|
1880
|
+
if (patchStart(cwd)) changed.push("src/start.ts");
|
|
1881
|
+
log(
|
|
1882
|
+
opts.demo ? "done \u2014 guard wired with the demo sample rules (blocks by default). Set PATCHSTACK_MODE=dry-run for log-only." : "done \u2014 guard wired and always-on (blocks by default). Set PATCHSTACK_MODE=dry-run for log-only."
|
|
1883
|
+
);
|
|
1884
|
+
return { ok: true, changed: [...new Set(changed)] };
|
|
1885
|
+
}
|
|
1886
|
+
function verify(cwd) {
|
|
1887
|
+
const guardPath = join2(cwd, GUARD_FILE);
|
|
1888
|
+
const clientPath = join2(cwd, "src/integrations/supabase/client.ts");
|
|
1889
|
+
const startPath = join2(cwd, "src/start.ts");
|
|
1890
|
+
const guard = existsSync5(guardPath) ? read(guardPath) : "";
|
|
1891
|
+
const client = existsSync5(clientPath) ? read(clientPath) : "";
|
|
1892
|
+
const start = existsSync5(startPath) ? read(startPath) : "";
|
|
1893
|
+
const checks = [
|
|
1894
|
+
{ label: "guard.ts scaffolded", ok: guard.length > 0, hint: "run `patchstack-connect protect`" },
|
|
1895
|
+
{ label: "Supabase client tunnels through the guard", ok: client.includes("x-ps-target"), hint: "run `patchstack-connect protect` to re-patch src/integrations/supabase/client.ts" },
|
|
1896
|
+
{ label: "request middleware defined + registered", ok: start.includes("const patchstackGuard =") && start.includes("requestMiddleware: [patchstackGuard"), hint: "run `patchstack-connect protect` to re-patch src/start.ts" },
|
|
1897
|
+
{ label: "server-function middleware defined + registered", ok: start.includes("const patchstackFunctionGuard =") && start.includes("functionMiddleware: [patchstackFunctionGuard"), hint: "run `patchstack-connect protect` to re-patch src/start.ts" }
|
|
1898
|
+
];
|
|
1899
|
+
return { wired: checks.every((c) => c.ok), checks };
|
|
1900
|
+
}
|
|
1901
|
+
var tanstackSupabaseAdapter = {
|
|
1902
|
+
name: "tanstack-supabase",
|
|
1903
|
+
label: "TanStack Start + Supabase",
|
|
1904
|
+
detect,
|
|
1905
|
+
wire,
|
|
1906
|
+
verify
|
|
1907
|
+
};
|
|
1908
|
+
|
|
1909
|
+
// src/protect/install/adapters/next.ts
|
|
1910
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync2, copyFileSync as copyFileSync2 } from "fs";
|
|
1911
|
+
import { join as join3 } from "path";
|
|
1912
|
+
function hasNextDep(cwd) {
|
|
1913
|
+
try {
|
|
1914
|
+
const pkg = JSON.parse(read(join3(cwd, "package.json")));
|
|
1915
|
+
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }.next);
|
|
1916
|
+
} catch {
|
|
1917
|
+
return false;
|
|
1918
|
+
}
|
|
1792
1919
|
}
|
|
1793
|
-
function
|
|
1794
|
-
|
|
1795
|
-
|
|
1796
|
-
return;
|
|
1920
|
+
function middlewareInfo(cwd) {
|
|
1921
|
+
const candidates = ["middleware.ts", "middleware.js", "src/middleware.ts", "src/middleware.js"];
|
|
1922
|
+
for (const rel of candidates) {
|
|
1923
|
+
if (existsSync6(join3(cwd, rel))) return { relDir: rel.includes("/") ? "src" : ".", relFile: rel, exists: true };
|
|
1924
|
+
}
|
|
1925
|
+
const useSrc = existsSync6(join3(cwd, "src"));
|
|
1926
|
+
return { relDir: useSrc ? "src" : ".", relFile: useSrc ? "src/middleware.ts" : "middleware.ts", exists: false };
|
|
1927
|
+
}
|
|
1928
|
+
function detect2(cwd) {
|
|
1929
|
+
return hasNextDep(cwd);
|
|
1930
|
+
}
|
|
1931
|
+
function rulesFile(relDir) {
|
|
1932
|
+
return relDir === "." ? "patchstack.rules.json" : `${relDir}/patchstack.rules.json`;
|
|
1933
|
+
}
|
|
1934
|
+
function wire2(cwd, opts) {
|
|
1935
|
+
const templates = templatesDir();
|
|
1936
|
+
const mw = middlewareInfo(cwd);
|
|
1937
|
+
const dir = join3(cwd, mw.relDir === "." ? "" : mw.relDir);
|
|
1938
|
+
mkdirSync2(dir, { recursive: true });
|
|
1939
|
+
const rulesDst = join3(dir, "patchstack.rules.json");
|
|
1940
|
+
const changed = [];
|
|
1941
|
+
if (opts.demo || !existsSync6(rulesDst)) {
|
|
1942
|
+
copyFileSync2(join3(templates, opts.demo ? "demo-rules.json" : "rules.json"), rulesDst);
|
|
1943
|
+
changed.push(rulesFile(mw.relDir));
|
|
1944
|
+
}
|
|
1945
|
+
const mwPath = join3(cwd, mw.relFile);
|
|
1946
|
+
const existing = mw.exists ? read(mwPath) : "";
|
|
1947
|
+
if (mw.exists && !existing.includes("patchstack-next")) {
|
|
1948
|
+
log(
|
|
1949
|
+
`existing ${mw.relFile} left untouched \u2014 scaffolded ${rulesFile(mw.relDir)}; add the guard to your middleware: import { createProtection } from "@patchstack/connect/protect", run fetchGuard() on the request, and return the block Response. Then: npx patchstack-connect protect --check`
|
|
1950
|
+
);
|
|
1951
|
+
return { ok: true, changed };
|
|
1952
|
+
}
|
|
1953
|
+
copyFileSync2(join3(templates, "next-middleware.ts"), mwPath);
|
|
1954
|
+
if (!opts.demo) bakeSiteUuid(cwd, mw.relFile);
|
|
1955
|
+
changed.push(mw.relFile);
|
|
1956
|
+
log(mw.exists ? `refreshed ${mw.relFile} (Patchstack middleware)` : `scaffolded ${mw.relFile} (Patchstack middleware)`);
|
|
1957
|
+
return { ok: true, changed: [...new Set(changed)] };
|
|
1958
|
+
}
|
|
1959
|
+
function verify2(cwd) {
|
|
1960
|
+
const mw = middlewareInfo(cwd);
|
|
1961
|
+
const present = mw.exists && read(join3(cwd, mw.relFile)).includes("patchstack-next");
|
|
1962
|
+
const rulesPresent = existsSync6(join3(cwd, rulesFile(mw.relDir)));
|
|
1963
|
+
return {
|
|
1964
|
+
wired: present && rulesPresent,
|
|
1965
|
+
checks: [
|
|
1966
|
+
{ label: "Patchstack middleware present", ok: present, hint: `run \`patchstack-connect protect\` (writes ${mw.relFile})` },
|
|
1967
|
+
{ label: "rules co-located with the middleware", ok: rulesPresent, hint: "run `patchstack-connect protect`" }
|
|
1968
|
+
]
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1971
|
+
var nextAdapter = {
|
|
1972
|
+
name: "nextjs",
|
|
1973
|
+
label: "Next.js",
|
|
1974
|
+
detect: detect2,
|
|
1975
|
+
wire: wire2,
|
|
1976
|
+
verify: verify2
|
|
1977
|
+
};
|
|
1978
|
+
|
|
1979
|
+
// src/protect/install/adapters/sveltekit.ts
|
|
1980
|
+
import { join as join5 } from "path";
|
|
1981
|
+
|
|
1982
|
+
// src/protect/install/seam.ts
|
|
1983
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync3, copyFileSync as copyFileSync3 } from "fs";
|
|
1984
|
+
import { join as join4, dirname as dirname2 } from "path";
|
|
1985
|
+
function rulesRel(seamRel) {
|
|
1986
|
+
const d = dirname2(seamRel);
|
|
1987
|
+
return (d === "." ? "patchstack.rules.json" : `${d}/patchstack.rules.json`).replace(/\\/g, "/");
|
|
1988
|
+
}
|
|
1989
|
+
function wireSeam(cwd, opts, spec) {
|
|
1990
|
+
const templates = templatesDir();
|
|
1991
|
+
const existing = spec.candidates.find((c) => existsSync7(join4(cwd, c)));
|
|
1992
|
+
const seamRel = existing ?? spec.target;
|
|
1993
|
+
mkdirSync3(dirname2(join4(cwd, seamRel)), { recursive: true });
|
|
1994
|
+
const rulesDst = join4(cwd, rulesRel(seamRel));
|
|
1995
|
+
const changed = [];
|
|
1996
|
+
if (opts.demo || !existsSync7(rulesDst)) {
|
|
1997
|
+
copyFileSync3(join4(templates, opts.demo ? "demo-rules.json" : "rules.json"), rulesDst);
|
|
1998
|
+
changed.push(rulesRel(seamRel));
|
|
1999
|
+
}
|
|
2000
|
+
const current = existing ? read(join4(cwd, existing)) : "";
|
|
2001
|
+
if (existing && !current.includes(spec.marker)) {
|
|
2002
|
+
log(`existing ${existing} left untouched \u2014 scaffolded ${rulesRel(seamRel)}; ${spec.planHint}`);
|
|
2003
|
+
return { ok: true, changed };
|
|
2004
|
+
}
|
|
2005
|
+
copyFileSync3(join4(templates, spec.templateName), join4(cwd, seamRel));
|
|
2006
|
+
if (!opts.demo) bakeSiteUuid(cwd, seamRel);
|
|
2007
|
+
changed.push(seamRel);
|
|
2008
|
+
log(existing ? `refreshed ${seamRel}` : `scaffolded ${seamRel}`);
|
|
2009
|
+
return { ok: true, changed: [...new Set(changed)] };
|
|
2010
|
+
}
|
|
2011
|
+
function verifySeam(cwd, spec) {
|
|
2012
|
+
const existing = spec.candidates.find((c) => existsSync7(join4(cwd, c)));
|
|
2013
|
+
const seamRel = existing ?? spec.target;
|
|
2014
|
+
const present = existing ? read(join4(cwd, existing)).includes(spec.marker) : false;
|
|
2015
|
+
const rulesPresent = existsSync7(join4(cwd, rulesRel(seamRel)));
|
|
2016
|
+
return {
|
|
2017
|
+
wired: present && rulesPresent,
|
|
2018
|
+
checks: [
|
|
2019
|
+
{ label: `${spec.seamLabel} present`, ok: present, hint: `run \`patchstack-connect protect\` (writes ${spec.target})` },
|
|
2020
|
+
{ label: "rules co-located with the guard", ok: rulesPresent, hint: "run `patchstack-connect protect`" }
|
|
2021
|
+
]
|
|
2022
|
+
};
|
|
2023
|
+
}
|
|
2024
|
+
|
|
2025
|
+
// src/protect/install/adapters/sveltekit.ts
|
|
2026
|
+
var SPEC = {
|
|
2027
|
+
templateName: "sveltekit-hooks.ts",
|
|
2028
|
+
candidates: ["src/hooks.server.ts", "src/hooks.server.js"],
|
|
2029
|
+
target: "src/hooks.server.ts",
|
|
2030
|
+
marker: "patchstack-sveltekit",
|
|
2031
|
+
planHint: 'add the guard to your `handle` (compose with `sequence()` from "@sveltejs/kit/hooks"), then: npx patchstack-connect protect --check',
|
|
2032
|
+
seamLabel: "SvelteKit server hook"
|
|
2033
|
+
};
|
|
2034
|
+
function detect3(cwd) {
|
|
2035
|
+
try {
|
|
2036
|
+
const pkg = JSON.parse(read(join5(cwd, "package.json")));
|
|
2037
|
+
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }["@sveltejs/kit"]);
|
|
2038
|
+
} catch {
|
|
2039
|
+
return false;
|
|
2040
|
+
}
|
|
2041
|
+
}
|
|
2042
|
+
function wire3(cwd, opts) {
|
|
2043
|
+
return wireSeam(cwd, opts, SPEC);
|
|
2044
|
+
}
|
|
2045
|
+
function verify3(cwd) {
|
|
2046
|
+
return verifySeam(cwd, SPEC);
|
|
2047
|
+
}
|
|
2048
|
+
var sveltekitAdapter = {
|
|
2049
|
+
name: "sveltekit",
|
|
2050
|
+
label: "SvelteKit",
|
|
2051
|
+
detect: detect3,
|
|
2052
|
+
wire: wire3,
|
|
2053
|
+
verify: verify3
|
|
2054
|
+
};
|
|
2055
|
+
|
|
2056
|
+
// src/protect/install/adapters/astro.ts
|
|
2057
|
+
import { join as join6 } from "path";
|
|
2058
|
+
var SPEC2 = {
|
|
2059
|
+
templateName: "astro-middleware.ts",
|
|
2060
|
+
candidates: ["src/middleware.ts", "src/middleware.js", "src/middleware/index.ts", "src/middleware/index.js"],
|
|
2061
|
+
target: "src/middleware.ts",
|
|
2062
|
+
marker: "patchstack-astro",
|
|
2063
|
+
planHint: 'add the guard to your `onRequest` (compose with `sequence()` from "astro:middleware"), then: npx patchstack-connect protect --check',
|
|
2064
|
+
seamLabel: "Astro middleware"
|
|
2065
|
+
};
|
|
2066
|
+
function detect4(cwd) {
|
|
2067
|
+
try {
|
|
2068
|
+
const pkg = JSON.parse(read(join6(cwd, "package.json")));
|
|
2069
|
+
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }.astro);
|
|
2070
|
+
} catch {
|
|
2071
|
+
return false;
|
|
1797
2072
|
}
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
2073
|
+
}
|
|
2074
|
+
function wire4(cwd, opts) {
|
|
2075
|
+
return wireSeam(cwd, opts, SPEC2);
|
|
2076
|
+
}
|
|
2077
|
+
function verify4(cwd) {
|
|
2078
|
+
return verifySeam(cwd, SPEC2);
|
|
2079
|
+
}
|
|
2080
|
+
var astroAdapter = {
|
|
2081
|
+
name: "astro",
|
|
2082
|
+
label: "Astro",
|
|
2083
|
+
detect: detect4,
|
|
2084
|
+
wire: wire4,
|
|
2085
|
+
verify: verify4
|
|
2086
|
+
};
|
|
2087
|
+
|
|
2088
|
+
// src/protect/install/find-app.ts
|
|
2089
|
+
import { readFileSync as readFileSync4, existsSync as existsSync8, readdirSync as readdirSync3, statSync as statSync3 } from "fs";
|
|
2090
|
+
import { join as join7, relative, dirname as dirname3 } from "path";
|
|
2091
|
+
function findAppInstance(cwd, re) {
|
|
2092
|
+
const root = existsSync8(join7(cwd, "src")) ? join7(cwd, "src") : cwd;
|
|
2093
|
+
let hit = null;
|
|
2094
|
+
const walk2 = (dir) => {
|
|
2095
|
+
if (hit) return;
|
|
2096
|
+
let entries;
|
|
2097
|
+
try {
|
|
2098
|
+
entries = readdirSync3(dir);
|
|
2099
|
+
} catch {
|
|
2100
|
+
return;
|
|
2101
|
+
}
|
|
2102
|
+
for (const name of entries) {
|
|
2103
|
+
if (hit) return;
|
|
2104
|
+
if (name === "node_modules" || name === "patchstack" || name.startsWith(".")) continue;
|
|
2105
|
+
const p = join7(dir, name);
|
|
2106
|
+
let st;
|
|
2107
|
+
try {
|
|
2108
|
+
st = statSync3(p);
|
|
2109
|
+
} catch {
|
|
2110
|
+
continue;
|
|
2111
|
+
}
|
|
2112
|
+
if (st.isDirectory()) walk2(p);
|
|
2113
|
+
else if (/\.(?:ts|js|mjs|cjs)$/.test(name)) {
|
|
2114
|
+
let src;
|
|
2115
|
+
try {
|
|
2116
|
+
src = readFileSync4(p, "utf8");
|
|
2117
|
+
} catch {
|
|
2118
|
+
continue;
|
|
2119
|
+
}
|
|
2120
|
+
const m = re.exec(src);
|
|
2121
|
+
if (m) hit = { relPath: relative(cwd, p).replace(/\\/g, "/"), appVar: m[1] };
|
|
2122
|
+
}
|
|
2123
|
+
}
|
|
2124
|
+
};
|
|
2125
|
+
walk2(root);
|
|
2126
|
+
return hit;
|
|
2127
|
+
}
|
|
2128
|
+
function importSpecifier(fromRel, toRel) {
|
|
2129
|
+
let spec = relative(dirname3(fromRel), toRel).replace(/\\/g, "/").replace(/\.(?:ts|js)$/, "");
|
|
2130
|
+
if (!spec.startsWith(".")) spec = `./${spec}`;
|
|
2131
|
+
return spec;
|
|
2132
|
+
}
|
|
2133
|
+
|
|
2134
|
+
// src/protect/install/register.ts
|
|
2135
|
+
import { writeFileSync as writeFileSync5, existsSync as existsSync10 } from "fs";
|
|
2136
|
+
import { join as join9 } from "path";
|
|
2137
|
+
|
|
2138
|
+
// src/protect/install/generic.ts
|
|
2139
|
+
import { readFileSync as readFileSync5, existsSync as existsSync9, mkdirSync as mkdirSync4, copyFileSync as copyFileSync4, readdirSync as readdirSync4, statSync as statSync4 } from "fs";
|
|
2140
|
+
import { join as join8 } from "path";
|
|
2141
|
+
var GUARD_MARKER = "patchstack/guard";
|
|
2142
|
+
function genericDir(cwd) {
|
|
2143
|
+
return existsSync9(join8(cwd, "src")) ? "src/patchstack" : "patchstack";
|
|
2144
|
+
}
|
|
2145
|
+
function scaffoldGeneric(cwd, opts, guardTemplate = "generic-guard.ts") {
|
|
2146
|
+
const templates = templatesDir();
|
|
2147
|
+
const dir = genericDir(cwd);
|
|
2148
|
+
const dst = join8(cwd, dir);
|
|
2149
|
+
mkdirSync4(dst, { recursive: true });
|
|
2150
|
+
copyFileSync4(join8(templates, guardTemplate), join8(dst, "guard.ts"));
|
|
2151
|
+
const changed = [`${dir}/guard.ts`];
|
|
2152
|
+
if (!opts.demo) bakeSiteUuid(cwd, `${dir}/guard.ts`);
|
|
2153
|
+
const rulesDst = join8(dst, "rules.json");
|
|
2154
|
+
if (opts.demo || !existsSync9(rulesDst)) {
|
|
2155
|
+
copyFileSync4(join8(templates, opts.demo ? "demo-rules.json" : "rules.json"), rulesDst);
|
|
2156
|
+
changed.push(`${dir}/rules.json`);
|
|
2157
|
+
}
|
|
2158
|
+
return { changed, dir };
|
|
2159
|
+
}
|
|
2160
|
+
function candidateEntries(cwd) {
|
|
2161
|
+
const names = [
|
|
2162
|
+
"src/server.ts",
|
|
2163
|
+
"src/server.js",
|
|
2164
|
+
"src/index.ts",
|
|
2165
|
+
"src/index.js",
|
|
2166
|
+
"src/app.ts",
|
|
2167
|
+
"src/app.js",
|
|
2168
|
+
"src/main.ts",
|
|
2169
|
+
"server.ts",
|
|
2170
|
+
"server.js",
|
|
2171
|
+
"index.ts",
|
|
2172
|
+
"index.js",
|
|
2173
|
+
"app.ts",
|
|
2174
|
+
"app.js"
|
|
2175
|
+
];
|
|
2176
|
+
const hits = names.filter((n) => existsSync9(join8(cwd, n)));
|
|
2177
|
+
try {
|
|
2178
|
+
const pkg = JSON.parse(read(join8(cwd, "package.json")));
|
|
2179
|
+
if (typeof pkg.main === "string" && existsSync9(join8(cwd, pkg.main))) hits.push(pkg.main);
|
|
2180
|
+
} catch {
|
|
2181
|
+
}
|
|
2182
|
+
return [...new Set(hits)];
|
|
2183
|
+
}
|
|
2184
|
+
function usesExpress(cwd) {
|
|
2185
|
+
try {
|
|
2186
|
+
const pkg = JSON.parse(read(join8(cwd, "package.json")));
|
|
2187
|
+
return Boolean({ ...pkg.dependencies, ...pkg.devDependencies }.express);
|
|
2188
|
+
} catch {
|
|
2189
|
+
return false;
|
|
2190
|
+
}
|
|
2191
|
+
}
|
|
2192
|
+
function wiringPlan(cwd, dir) {
|
|
2193
|
+
const entries = candidateEntries(cwd);
|
|
2194
|
+
const express = usesExpress(cwd);
|
|
2195
|
+
const lines = [
|
|
2196
|
+
`no built-in adapter matched this stack \u2014 scaffolded a generic guard at ${dir}/guard.ts + ${dir}/rules.json.`,
|
|
2197
|
+
"Finish by wiring it into your server (pick the one that fits):",
|
|
2198
|
+
` \u2022 Web-Fetch entry: export default { fetch: protectFetch(yourHandler) } // import from "${dir}/guard"`,
|
|
2199
|
+
` \u2022 Node / Express: app.use(patchstackMiddleware) // add before your routes`,
|
|
2200
|
+
entries.length ? `Likely server ${entries.length === 1 ? "entry" : "entries"}: ${entries.join(", ")}${express ? " (Express detected)" : ""}.` : "Could not locate a server entry \u2014 wire it wherever requests enter your app.",
|
|
2201
|
+
"Then confirm it is hooked up: npx patchstack-connect protect --check"
|
|
2202
|
+
];
|
|
2203
|
+
return lines.join("\n");
|
|
2204
|
+
}
|
|
2205
|
+
function genericVerify(cwd) {
|
|
2206
|
+
const dir = genericDir(cwd);
|
|
2207
|
+
const scaffolded = existsSync9(join8(cwd, dir, "guard.ts"));
|
|
2208
|
+
const imported = scaffolded && guardIsImported(cwd, join8(cwd, dir, "guard.ts"));
|
|
2209
|
+
return {
|
|
2210
|
+
wired: scaffolded && imported,
|
|
2211
|
+
checks: [
|
|
2212
|
+
{ label: "generic guard scaffolded", ok: scaffolded, hint: "run `patchstack-connect protect`" },
|
|
2213
|
+
{
|
|
2214
|
+
label: "guard imported into a server entry",
|
|
2215
|
+
ok: imported,
|
|
2216
|
+
hint: `import { protectFetch } (or patchstackMiddleware) from "${dir}/guard" and wire it into your request path`
|
|
2217
|
+
}
|
|
2218
|
+
]
|
|
2219
|
+
};
|
|
2220
|
+
}
|
|
2221
|
+
function guardIsImported(cwd, guardPath) {
|
|
2222
|
+
const root = existsSync9(join8(cwd, "src")) ? join8(cwd, "src") : cwd;
|
|
2223
|
+
let found = false;
|
|
2224
|
+
const walk2 = (d) => {
|
|
2225
|
+
if (found) return;
|
|
2226
|
+
let entries;
|
|
2227
|
+
try {
|
|
2228
|
+
entries = readdirSync4(d);
|
|
2229
|
+
} catch {
|
|
2230
|
+
return;
|
|
2231
|
+
}
|
|
2232
|
+
for (const name of entries) {
|
|
2233
|
+
if (found) return;
|
|
2234
|
+
if (name === "node_modules" || name.startsWith(".")) continue;
|
|
2235
|
+
const p = join8(d, name);
|
|
2236
|
+
let st;
|
|
2237
|
+
try {
|
|
2238
|
+
st = statSync4(p);
|
|
2239
|
+
} catch {
|
|
2240
|
+
continue;
|
|
2241
|
+
}
|
|
2242
|
+
if (st.isDirectory()) walk2(p);
|
|
2243
|
+
else if (/\.(?:ts|tsx|js|jsx|mjs|cjs)$/.test(name) && p !== guardPath) {
|
|
2244
|
+
try {
|
|
2245
|
+
if (readFileSync5(p, "utf8").includes(GUARD_MARKER)) found = true;
|
|
2246
|
+
} catch {
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
}
|
|
2250
|
+
};
|
|
2251
|
+
walk2(root);
|
|
2252
|
+
return found;
|
|
2253
|
+
}
|
|
2254
|
+
|
|
2255
|
+
// src/protect/install/register.ts
|
|
2256
|
+
var REGION = "// #region patchstack (managed by patchstack-connect protect \u2014 do not edit)";
|
|
2257
|
+
function wireRegister(cwd, opts, spec) {
|
|
2258
|
+
const { changed, dir } = scaffoldGeneric(cwd, opts, spec.guardTemplate);
|
|
2259
|
+
const entry = findAppInstance(cwd, spec.appRe);
|
|
2260
|
+
if (!entry) {
|
|
2261
|
+
log(`${spec.label} not located \u2014 scaffolded guard; ${spec.manualHint}`);
|
|
2262
|
+
return { ok: true, changed };
|
|
2263
|
+
}
|
|
2264
|
+
const p = join9(cwd, entry.relPath);
|
|
2265
|
+
const s = read(p);
|
|
2266
|
+
if (s.includes(spec.importName)) {
|
|
2267
|
+
log(`${entry.relPath} already wired`);
|
|
2268
|
+
return { ok: true, changed };
|
|
2269
|
+
}
|
|
2270
|
+
const importLine = `import { ${spec.importName} } from "${importSpecifier(entry.relPath, `${dir}/guard`)}";`;
|
|
2271
|
+
const lines = s.split("\n");
|
|
2272
|
+
let lastImport = -1;
|
|
2273
|
+
for (let i = 0; i < lines.length; i++) if (/^\s*import\b/.test(lines[i] ?? "")) lastImport = i;
|
|
2274
|
+
lines.splice(lastImport + 1, 0, importLine);
|
|
2275
|
+
const appIdx = lines.findIndex((l) => spec.appRe.test(l));
|
|
2276
|
+
if (appIdx !== -1) {
|
|
2277
|
+
lines.splice(appIdx + 1, 0, REGION, spec.call(entry.appVar), "// #endregion patchstack");
|
|
2278
|
+
}
|
|
2279
|
+
writeFileSync5(p, lines.join("\n"));
|
|
2280
|
+
changed.push(entry.relPath);
|
|
2281
|
+
log(`patched ${entry.relPath} (${spec.call(entry.appVar).trim()})`);
|
|
2282
|
+
return { ok: true, changed: [...new Set(changed)] };
|
|
2283
|
+
}
|
|
2284
|
+
function verifyRegister(cwd, spec) {
|
|
2285
|
+
const dir = existsSync10(join9(cwd, "src")) ? "src/patchstack" : "patchstack";
|
|
2286
|
+
const scaffolded = existsSync10(join9(cwd, dir, "guard.ts"));
|
|
2287
|
+
const entry = findAppInstance(cwd, spec.appRe);
|
|
2288
|
+
const wired = entry ? read(join9(cwd, entry.relPath)).includes(spec.importName) : false;
|
|
2289
|
+
return {
|
|
2290
|
+
wired: scaffolded && wired,
|
|
2291
|
+
checks: [
|
|
2292
|
+
{ label: `${spec.label} guard scaffolded`, ok: scaffolded, hint: "run `patchstack-connect protect`" },
|
|
2293
|
+
{ label: `guard registered on the ${spec.label}`, ok: wired, hint: spec.manualHint }
|
|
2294
|
+
]
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
|
|
2298
|
+
// src/protect/install/adapters/nestjs.ts
|
|
2299
|
+
var SPEC3 = {
|
|
2300
|
+
appRe: /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*await\s+NestFactory\.create/,
|
|
2301
|
+
guardTemplate: "generic-guard.ts",
|
|
2302
|
+
importName: "patchstackMiddleware",
|
|
2303
|
+
call: (v) => `${v}.use(patchstackMiddleware);`,
|
|
2304
|
+
label: "NestJS app",
|
|
2305
|
+
manualHint: "add `app.use(patchstackMiddleware)` right after your NestFactory.create(...) call"
|
|
2306
|
+
};
|
|
2307
|
+
var nestjsAdapter = {
|
|
2308
|
+
name: "nestjs",
|
|
2309
|
+
label: "NestJS",
|
|
2310
|
+
detect: (cwd) => hasDependency(cwd, "@nestjs/core") && findAppInstance(cwd, SPEC3.appRe) !== null,
|
|
2311
|
+
wire: (cwd, opts) => wireRegister(cwd, opts, SPEC3),
|
|
2312
|
+
verify: (cwd) => verifyRegister(cwd, SPEC3)
|
|
2313
|
+
};
|
|
2314
|
+
|
|
2315
|
+
// src/protect/install/adapters/fastify.ts
|
|
2316
|
+
var SPEC4 = {
|
|
2317
|
+
appRe: /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*(?:fastify|Fastify)\(/,
|
|
2318
|
+
guardTemplate: "fastify-plugin.ts",
|
|
2319
|
+
importName: "patchstackFastify",
|
|
2320
|
+
call: (v) => `${v}.register(patchstackFastify);`,
|
|
2321
|
+
label: "Fastify app",
|
|
2322
|
+
manualHint: "add `app.register(patchstackFastify)` right after you create your fastify() app"
|
|
2323
|
+
};
|
|
2324
|
+
var fastifyAdapter = {
|
|
2325
|
+
name: "fastify",
|
|
2326
|
+
label: "Fastify (Node)",
|
|
2327
|
+
detect: (cwd) => hasDependency(cwd, "fastify") && findAppInstance(cwd, SPEC4.appRe) !== null,
|
|
2328
|
+
wire: (cwd, opts) => wireRegister(cwd, opts, SPEC4),
|
|
2329
|
+
verify: (cwd) => verifyRegister(cwd, SPEC4)
|
|
2330
|
+
};
|
|
2331
|
+
|
|
2332
|
+
// src/protect/install/adapters/express.ts
|
|
2333
|
+
var SPEC5 = {
|
|
2334
|
+
appRe: /(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*express\(\)/,
|
|
2335
|
+
guardTemplate: "generic-guard.ts",
|
|
2336
|
+
importName: "patchstackMiddleware",
|
|
2337
|
+
call: (v) => `${v}.use(patchstackMiddleware);`,
|
|
2338
|
+
label: "Express app",
|
|
2339
|
+
manualHint: "add `app.use(patchstackMiddleware)` right after you create your express() app"
|
|
2340
|
+
};
|
|
2341
|
+
var expressAdapter = {
|
|
2342
|
+
name: "express",
|
|
2343
|
+
label: "Express (Node)",
|
|
2344
|
+
detect: (cwd) => hasDependency(cwd, "express") && findAppInstance(cwd, SPEC5.appRe) !== null,
|
|
2345
|
+
wire: (cwd, opts) => wireRegister(cwd, opts, SPEC5),
|
|
2346
|
+
verify: (cwd) => verifyRegister(cwd, SPEC5)
|
|
2347
|
+
};
|
|
2348
|
+
|
|
2349
|
+
// src/protect/install/index.ts
|
|
2350
|
+
var ADAPTERS = [
|
|
2351
|
+
tanstackSupabaseAdapter,
|
|
2352
|
+
nextAdapter,
|
|
2353
|
+
sveltekitAdapter,
|
|
2354
|
+
astroAdapter,
|
|
2355
|
+
nestjsAdapter,
|
|
2356
|
+
fastifyAdapter,
|
|
2357
|
+
expressAdapter
|
|
2358
|
+
];
|
|
2359
|
+
function runProtect(cwd, opts = {}) {
|
|
2360
|
+
const adapter = ADAPTERS.find((a) => a.detect(cwd));
|
|
2361
|
+
if (adapter) {
|
|
2362
|
+
const result = adapter.wire(cwd, opts);
|
|
2363
|
+
return { status: "wired", adapter: adapter.name, changed: result.changed };
|
|
2364
|
+
}
|
|
2365
|
+
const { changed, dir } = scaffoldGeneric(cwd, opts);
|
|
2366
|
+
const plan = wiringPlan(cwd, dir);
|
|
2367
|
+
log(plan);
|
|
2368
|
+
return { status: "scaffolded", adapter: "generic", changed, plan };
|
|
2369
|
+
}
|
|
2370
|
+
function runVerify(cwd) {
|
|
2371
|
+
const adapter = ADAPTERS.find((a) => a.detect(cwd));
|
|
2372
|
+
if (adapter) return { stack: adapter.label, ...adapter.verify(cwd) };
|
|
2373
|
+
return { stack: "generic", ...genericVerify(cwd) };
|
|
2374
|
+
}
|
|
2375
|
+
|
|
2376
|
+
// src/setup.ts
|
|
2377
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync6 } from "fs";
|
|
2378
|
+
import path10 from "path";
|
|
2379
|
+
var SCAN_COMMAND = "patchstack-connect scan";
|
|
2380
|
+
var MARK_BUILD_COMMAND = "patchstack-connect mark-build";
|
|
2381
|
+
function appendHook(existing, command) {
|
|
2382
|
+
if (existing === void 0 || existing.trim().length === 0) {
|
|
2383
|
+
return command;
|
|
2384
|
+
}
|
|
2385
|
+
if (existing.includes(command)) {
|
|
2386
|
+
return existing;
|
|
2387
|
+
}
|
|
2388
|
+
return `${existing} && ${command}`;
|
|
2389
|
+
}
|
|
2390
|
+
function wireBuildScripts(cwd, packageManager) {
|
|
2391
|
+
const target = path10.join(cwd, "package.json");
|
|
2392
|
+
const raw = readFileSync6(target, "utf8");
|
|
2393
|
+
const pkg = JSON.parse(raw);
|
|
2394
|
+
const scripts = pkg.scripts ?? {};
|
|
2395
|
+
const build = scripts.build;
|
|
2396
|
+
if (build === void 0 || build.trim().length === 0) {
|
|
2397
|
+
return {
|
|
2398
|
+
changed: false,
|
|
2399
|
+
strategy: "skipped",
|
|
2400
|
+
detail: "package.json has no build script; no build integration was added."
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
if (packageManager === "bun") {
|
|
2404
|
+
let nextBuild = build;
|
|
2405
|
+
if (!nextBuild.includes(SCAN_COMMAND)) {
|
|
2406
|
+
nextBuild = `${SCAN_COMMAND} && ${nextBuild}`;
|
|
2407
|
+
}
|
|
2408
|
+
if (!nextBuild.includes(MARK_BUILD_COMMAND)) {
|
|
2409
|
+
nextBuild = `${nextBuild} && ${MARK_BUILD_COMMAND}`;
|
|
2410
|
+
}
|
|
2411
|
+
if (nextBuild === build) {
|
|
2412
|
+
return {
|
|
2413
|
+
changed: false,
|
|
2414
|
+
strategy: "build-chain",
|
|
2415
|
+
detail: "build script already runs scan and mark-build."
|
|
2416
|
+
};
|
|
2417
|
+
}
|
|
2418
|
+
scripts.build = nextBuild;
|
|
2419
|
+
} else {
|
|
2420
|
+
const prebuild = appendHook(scripts.prebuild, SCAN_COMMAND);
|
|
2421
|
+
const postbuild = appendHook(scripts.postbuild, MARK_BUILD_COMMAND);
|
|
2422
|
+
if (prebuild === scripts.prebuild && postbuild === scripts.postbuild) {
|
|
2423
|
+
return {
|
|
2424
|
+
changed: false,
|
|
2425
|
+
strategy: "lifecycle-hooks",
|
|
2426
|
+
detail: "prebuild and postbuild hooks are already wired."
|
|
2427
|
+
};
|
|
2428
|
+
}
|
|
2429
|
+
scripts.prebuild = prebuild;
|
|
2430
|
+
scripts.postbuild = postbuild;
|
|
2431
|
+
}
|
|
2432
|
+
pkg.scripts = scripts;
|
|
2433
|
+
const indentMatch = raw.match(/^[\t ]+(?=")/m)?.[0];
|
|
2434
|
+
const indent = indentMatch?.includes(" ") ? " " : indentMatch?.length ?? 2;
|
|
2435
|
+
const trailingNewline = raw.endsWith("\n") ? "\n" : "";
|
|
2436
|
+
writeFileSync6(target, `${JSON.stringify(pkg, null, indent)}${trailingNewline}`, "utf8");
|
|
2437
|
+
return packageManager === "bun" ? {
|
|
2438
|
+
changed: true,
|
|
2439
|
+
strategy: "build-chain",
|
|
2440
|
+
detail: "chained scan and mark-build around the existing build command."
|
|
2441
|
+
} : {
|
|
2442
|
+
changed: true,
|
|
2443
|
+
strategy: "lifecycle-hooks",
|
|
2444
|
+
detail: "added scan to prebuild and mark-build to postbuild."
|
|
2445
|
+
};
|
|
1803
2446
|
}
|
|
1804
2447
|
|
|
1805
2448
|
// src/cli.ts
|
|
@@ -1814,22 +2457,30 @@ Usage:
|
|
|
1814
2457
|
HTML shell (index.html, public/index.html,
|
|
1815
2458
|
or src/app.html) \u2014 opt out with
|
|
1816
2459
|
"widget": false in .patchstackrc.json
|
|
2460
|
+
patchstack-connect setup [options] Finish the bounded project setup: run scan,
|
|
2461
|
+
manage the widget, and wire package.json build
|
|
2462
|
+
scripts. Never runs a build or protect
|
|
1817
2463
|
patchstack-connect init <site-uuid> Optional: pre-seed .patchstackrc.json
|
|
1818
2464
|
with an existing site UUID
|
|
1819
2465
|
patchstack-connect status [options] Show current configuration
|
|
1820
2466
|
patchstack-connect mark-build [options] Stamp built HTML with a production flag +
|
|
1821
2467
|
build fingerprint, and ensure the widget
|
|
1822
2468
|
tag in built pages (run as a postbuild step)
|
|
1823
|
-
patchstack-connect protect
|
|
1824
|
-
guard)
|
|
1825
|
-
|
|
2469
|
+
patchstack-connect protect [--demo|--check] Install always-on runtime protection (the
|
|
2470
|
+
guard). Auto-wires known stacks (TanStack
|
|
2471
|
+
Start + Supabase); for others it scaffolds a
|
|
2472
|
+
generic guard + prints a wiring plan.
|
|
2473
|
+
--demo seeds a broad sample rule set (for
|
|
2474
|
+
demonstrations, not production).
|
|
2475
|
+
--check verifies the guard is wired (exit 1
|
|
2476
|
+
if not) \u2014 for the wire-then-verify loop.
|
|
1826
2477
|
patchstack-connect guide [--full] Show this project's setup status (what's done,
|
|
1827
2478
|
what's missing, with tailored commands), then
|
|
1828
2479
|
print the full setup guide. --full prints the
|
|
1829
2480
|
guide even when setup is complete
|
|
1830
2481
|
patchstack-connect help Print this message
|
|
1831
2482
|
|
|
1832
|
-
Options (for scan and status):
|
|
2483
|
+
Options (for scan, setup, and status):
|
|
1833
2484
|
--site-uuid <uuid> Override the configured site UUID
|
|
1834
2485
|
--endpoint <url> Override the API endpoint
|
|
1835
2486
|
--dry-run (scan only) Show the payload without posting
|
|
@@ -1843,10 +2494,13 @@ Environment:
|
|
|
1843
2494
|
PATCHSTACK_ENDPOINT API endpoint (default: https://api.patchstack.com/monitor/pulse/manifest)
|
|
1844
2495
|
PATCHSTACK_TIMEOUT_MS Request timeout in ms (default: 30000)
|
|
1845
2496
|
PATCHSTACK_ENVIRONMENT Manifest environment: production | sandbox (default: production)
|
|
2497
|
+
PATCHSTACK_MODE (protect) Runtime guard mode: block (default) | dry-run
|
|
2498
|
+
PATCHSTACK_ROUTE_WAF (protect) Set to 1 to also screen every request at the route level (opt-in)
|
|
1846
2499
|
|
|
1847
2500
|
Precedence: CLI flag > environment variable > .patchstackrc.json.
|
|
1848
2501
|
|
|
1849
2502
|
Examples:
|
|
2503
|
+
npx @patchstack/connect setup
|
|
1850
2504
|
npx @patchstack/connect scan
|
|
1851
2505
|
npx @patchstack/connect scan --dry-run
|
|
1852
2506
|
npx @patchstack/connect init 550e8400-e29b-41d4-a716-446655440000
|
|
@@ -1904,7 +2558,7 @@ async function runInit(args) {
|
|
|
1904
2558
|
console.log("Next: run `npx @patchstack/connect scan` to send your first manifest.");
|
|
1905
2559
|
return 0;
|
|
1906
2560
|
}
|
|
1907
|
-
async function runScan(args) {
|
|
2561
|
+
async function runScan(args, options = {}) {
|
|
1908
2562
|
const dryRun = args.flags.get("dry-run") === true;
|
|
1909
2563
|
const config = await resolveConfig({
|
|
1910
2564
|
cwd: process.cwd(),
|
|
@@ -1971,24 +2625,26 @@ async function runScan(args) {
|
|
|
1971
2625
|
}
|
|
1972
2626
|
if (provisioning && response.uuid !== void 0 && response.uuid.length > 0) {
|
|
1973
2627
|
console.log("");
|
|
1974
|
-
console.log("
|
|
2628
|
+
console.log("Open this dashboard link to view vulnerability reports:");
|
|
1975
2629
|
console.log(` ${buildClaimUrl(config.endpoint, response.uuid)}`);
|
|
1976
2630
|
if (config.endpoint !== DEFAULT_ENDPOINT) {
|
|
1977
2631
|
console.log(" (this URL inherits the endpoint override above)");
|
|
1978
2632
|
}
|
|
1979
2633
|
}
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
2634
|
+
if (options.showRemainingSetup !== false) {
|
|
2635
|
+
try {
|
|
2636
|
+
const state = await collectGuideState(process.cwd());
|
|
2637
|
+
const remaining = countRemainingSteps(state);
|
|
2638
|
+
if (remaining > 0) {
|
|
2639
|
+
const hooksMissing = !(state.prebuildWired && state.postbuildWired);
|
|
2640
|
+
console.log("");
|
|
2641
|
+
console.log(
|
|
2642
|
+
`Setup not complete \u2014 ${remaining} step(s) remaining${hooksMissing ? ", including the package.json build hooks (which scan can't wire)" : ""}.`
|
|
2643
|
+
);
|
|
2644
|
+
console.log("Run `npx @patchstack/connect guide` for the exact steps to finish for this project.");
|
|
2645
|
+
}
|
|
2646
|
+
} catch {
|
|
1990
2647
|
}
|
|
1991
|
-
} catch {
|
|
1992
2648
|
}
|
|
1993
2649
|
return 0;
|
|
1994
2650
|
}
|
|
@@ -2025,9 +2681,19 @@ function reportSourceWidget(siteUuid) {
|
|
|
2025
2681
|
console.warn(`Widget: skipped (${err.message}).`);
|
|
2026
2682
|
}
|
|
2027
2683
|
}
|
|
2028
|
-
async function runProtectCommand(
|
|
2684
|
+
async function runProtectCommand(args) {
|
|
2685
|
+
if (args.flags.get("check") === true) {
|
|
2686
|
+
const report = runVerify(process.cwd());
|
|
2687
|
+
console.log(`patchstack protect --check (${report.stack}):`);
|
|
2688
|
+
for (const c of report.checks) {
|
|
2689
|
+
console.log(` ${c.ok ? "\u2713" : "\u2717"} ${c.label}${!c.ok && c.hint ? ` \u2014 ${c.hint}` : ""}`);
|
|
2690
|
+
}
|
|
2691
|
+
console.log(report.wired ? "guard is wired \u2713" : "guard is NOT fully wired \u2717");
|
|
2692
|
+
return report.wired ? 0 : 1;
|
|
2693
|
+
}
|
|
2694
|
+
const demo = args.flags.get("demo") === true;
|
|
2029
2695
|
try {
|
|
2030
|
-
runProtect(process.cwd());
|
|
2696
|
+
runProtect(process.cwd(), { demo });
|
|
2031
2697
|
} catch (err) {
|
|
2032
2698
|
console.warn(`patchstack protect: skipped (${err.message}).`);
|
|
2033
2699
|
}
|
|
@@ -2053,7 +2719,46 @@ async function runGuide(args) {
|
|
|
2053
2719
|
console.log("\u2014\u2014\u2014\u2014 Full reference guide (ships as AGENT-INSTALL.md) \u2014\u2014\u2014\u2014");
|
|
2054
2720
|
console.log("");
|
|
2055
2721
|
const guidePath = new URL("../AGENT-INSTALL.md", import.meta.url);
|
|
2056
|
-
console.log(
|
|
2722
|
+
console.log(readFileSync7(guidePath, "utf8"));
|
|
2723
|
+
return 0;
|
|
2724
|
+
}
|
|
2725
|
+
async function runSetup(args) {
|
|
2726
|
+
if (args.flags.get("dry-run") === true) {
|
|
2727
|
+
console.error("Error: setup does not support --dry-run. Use `scan --dry-run` to preview the manifest.");
|
|
2728
|
+
return 1;
|
|
2729
|
+
}
|
|
2730
|
+
const before = await collectGuideState(process.cwd());
|
|
2731
|
+
if (!before.hasPackageJson) {
|
|
2732
|
+
console.error("Error: no package.json found. Run setup from the project root.");
|
|
2733
|
+
return 1;
|
|
2734
|
+
}
|
|
2735
|
+
if (before.installed === null) {
|
|
2736
|
+
console.error(
|
|
2737
|
+
`Error: @patchstack/connect is not declared in package.json.
|
|
2738
|
+
Run: ${installCommand(before.packageManager)}`
|
|
2739
|
+
);
|
|
2740
|
+
return 1;
|
|
2741
|
+
}
|
|
2742
|
+
console.log("Patchstack setup \u2014 applying bounded project changes");
|
|
2743
|
+
console.log(" 1. Scan dependencies, provision/reuse the site, and manage the source widget");
|
|
2744
|
+
const scanCode = await runScan(args, { showRemainingSetup: false });
|
|
2745
|
+
if (scanCode !== 0) {
|
|
2746
|
+
return scanCode;
|
|
2747
|
+
}
|
|
2748
|
+
console.log("");
|
|
2749
|
+
console.log(" 2. Wire scan and mark-build into package.json");
|
|
2750
|
+
const wired = wireBuildScripts(process.cwd(), before.packageManager);
|
|
2751
|
+
console.log(`Build integration: ${wired.detail}`);
|
|
2752
|
+
console.log("");
|
|
2753
|
+
console.log(" 3. Verify setup status");
|
|
2754
|
+
const after = await collectGuideState(process.cwd());
|
|
2755
|
+
const useColor = process.stdout.isTTY === true && process.env.NO_COLOR === void 0;
|
|
2756
|
+
console.log(renderGuideChecklist(after, useColor));
|
|
2757
|
+
const remaining = countRemainingSteps(after);
|
|
2758
|
+
if (remaining > 0) {
|
|
2759
|
+
console.log("");
|
|
2760
|
+
console.log(`Setup applied its bounded changes; ${remaining} manual step(s) remain above.`);
|
|
2761
|
+
}
|
|
2057
2762
|
return 0;
|
|
2058
2763
|
}
|
|
2059
2764
|
async function runStatus(args) {
|
|
@@ -2069,7 +2774,7 @@ async function runStatus(args) {
|
|
|
2069
2774
|
console.log(`Timeout: ${config.timeoutMs}ms`);
|
|
2070
2775
|
console.log(`Environment: ${config.environment}`);
|
|
2071
2776
|
if (config.siteUuid !== null) {
|
|
2072
|
-
console.log(`
|
|
2777
|
+
console.log(`Dashboard URL: ${buildClaimUrl(config.endpoint, config.siteUuid)}`);
|
|
2073
2778
|
}
|
|
2074
2779
|
return 0;
|
|
2075
2780
|
}
|
|
@@ -2129,7 +2834,7 @@ async function runMarkBuild(args) {
|
|
|
2129
2834
|
let marked = 0;
|
|
2130
2835
|
let widgetTouched = 0;
|
|
2131
2836
|
for (const file of files) {
|
|
2132
|
-
const before =
|
|
2837
|
+
const before = readFileSync7(file, "utf8");
|
|
2133
2838
|
let after = injectMarker(before, snippet);
|
|
2134
2839
|
if (widgetUuid !== null) {
|
|
2135
2840
|
const ensured = ensureWidgetInHtml(after, widgetUuid);
|
|
@@ -2139,7 +2844,7 @@ async function runMarkBuild(args) {
|
|
|
2139
2844
|
after = ensured.html;
|
|
2140
2845
|
}
|
|
2141
2846
|
if (after !== before) {
|
|
2142
|
-
|
|
2847
|
+
writeFileSync7(file, after);
|
|
2143
2848
|
marked += 1;
|
|
2144
2849
|
}
|
|
2145
2850
|
}
|
|
@@ -2168,6 +2873,8 @@ async function main() {
|
|
|
2168
2873
|
return runProtectCommand(args);
|
|
2169
2874
|
case "guide":
|
|
2170
2875
|
return runGuide(args);
|
|
2876
|
+
case "setup":
|
|
2877
|
+
return runSetup(args);
|
|
2171
2878
|
default:
|
|
2172
2879
|
console.error(`Unknown command: ${args.command}
|
|
2173
2880
|
`);
|