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