@patchstack/connect 0.4.2 → 0.5.1
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 +24 -4
- package/README.md +27 -3
- package/dist/cli.js +319 -61
- package/dist/cli.js.map +1 -1
- package/dist/index.cjs +230 -20
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +65 -0
- package/dist/index.d.ts +65 -0
- package/dist/index.js +227 -17
- 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 +351 -97
- 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-2MMPQN2A.js} +243 -35
- package/dist/refresh-manifest-2MMPQN2A.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();
|
|
@@ -1291,6 +1291,25 @@ async function pulseFetch(config, url, init, fetchImpl = fetch) {
|
|
|
1291
1291
|
// src/client.ts
|
|
1292
1292
|
var DEFAULT_ENDPOINT = "https://api.patchstack.com/monitor/pulse/manifest";
|
|
1293
1293
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
1294
|
+
var CLAIM_TOKEN_HEADER = "X-Patchstack-Claim-Token";
|
|
1295
|
+
function claimTokenHeader(config) {
|
|
1296
|
+
return typeof config.claimToken === "string" && config.claimToken !== "" ? { [CLAIM_TOKEN_HEADER]: config.claimToken } : {};
|
|
1297
|
+
}
|
|
1298
|
+
function claimOutcomeLines(claim2, config) {
|
|
1299
|
+
if (typeof config.claimToken !== "string" || config.claimToken === "") return [];
|
|
1300
|
+
if (claim2?.state === "claimed" || claim2?.state === "owned-by-you") {
|
|
1301
|
+
const dashboard = typeof claim2.dashboard_url === "string" && claim2.dashboard_url !== "" ? [`Dashboard: ${claim2.dashboard_url}`] : [];
|
|
1302
|
+
return [
|
|
1303
|
+
claim2.state === "claimed" ? "Connected to your Patchstack account." : "This site is already connected to your Patchstack account.",
|
|
1304
|
+
...dashboard
|
|
1305
|
+
];
|
|
1306
|
+
}
|
|
1307
|
+
const why = claim2 === void 0 ? "Patchstack did not act on the claim token" : claim2.state === "owned-by-other" ? "this site belongs to a different Patchstack account" : claim2.reason === "expired" ? "the claim token has expired" : "Patchstack did not recognise the claim token";
|
|
1308
|
+
return [
|
|
1309
|
+
`Not connected to your account: ${why}.`,
|
|
1310
|
+
"Open the dashboard link below to connect it, or copy a fresh prompt from the dashboard."
|
|
1311
|
+
];
|
|
1312
|
+
}
|
|
1294
1313
|
function buildEndpointUrl(base, siteUuid) {
|
|
1295
1314
|
const trimmed = base.replace(/\/$/, "");
|
|
1296
1315
|
return siteUuid !== void 0 && siteUuid !== null && siteUuid.length > 0 ? `${trimmed}/${encodeURIComponent(siteUuid)}` : trimmed;
|
|
@@ -1310,8 +1329,8 @@ function authFailureMessage(status, config) {
|
|
|
1310
1329
|
}
|
|
1311
1330
|
function buildRulesUrl(manifestEndpoint, siteUuid) {
|
|
1312
1331
|
const url = new URL(manifestEndpoint);
|
|
1313
|
-
const
|
|
1314
|
-
url.pathname =
|
|
1332
|
+
const path13 = url.pathname.replace(/\/$/, "");
|
|
1333
|
+
url.pathname = path13.endsWith("/manifest") ? `${path13.slice(0, -"/manifest".length)}/rules/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/rules/${encodeURIComponent(siteUuid)}`;
|
|
1315
1334
|
url.search = "";
|
|
1316
1335
|
url.hash = "";
|
|
1317
1336
|
return url.toString();
|
|
@@ -1326,16 +1345,16 @@ function buildSettingsUrl(endpoint, siteUuid) {
|
|
|
1326
1345
|
}
|
|
1327
1346
|
function buildPackageRemovedUrl(manifestEndpoint, siteUuid) {
|
|
1328
1347
|
const url = new URL(manifestEndpoint);
|
|
1329
|
-
const
|
|
1330
|
-
url.pathname =
|
|
1348
|
+
const path13 = url.pathname.replace(/\/$/, "");
|
|
1349
|
+
url.pathname = path13.endsWith("/manifest") ? `${path13.slice(0, -"/manifest".length)}/package-removed/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/package-removed/${encodeURIComponent(siteUuid)}`;
|
|
1331
1350
|
url.search = "";
|
|
1332
1351
|
url.hash = "";
|
|
1333
1352
|
return url.toString();
|
|
1334
1353
|
}
|
|
1335
1354
|
function buildInputMapUrl(manifestEndpoint, siteUuid) {
|
|
1336
1355
|
const url = new URL(manifestEndpoint);
|
|
1337
|
-
const
|
|
1338
|
-
url.pathname =
|
|
1356
|
+
const path13 = url.pathname.replace(/\/$/, "");
|
|
1357
|
+
url.pathname = path13.endsWith("/manifest") ? `${path13.slice(0, -"/manifest".length)}/input-map/${encodeURIComponent(siteUuid)}` : `/monitor/pulse/input-map/${encodeURIComponent(siteUuid)}`;
|
|
1339
1358
|
url.search = "";
|
|
1340
1359
|
url.hash = "";
|
|
1341
1360
|
return url.toString();
|
|
@@ -1436,6 +1455,14 @@ async function fetchSiteStatus(config) {
|
|
|
1436
1455
|
return "unknown";
|
|
1437
1456
|
}
|
|
1438
1457
|
}
|
|
1458
|
+
function buildManifestBody(config, payload) {
|
|
1459
|
+
return {
|
|
1460
|
+
...payload,
|
|
1461
|
+
environment: config.environment,
|
|
1462
|
+
...typeof config.siteUrl === "string" && config.siteUrl !== "" ? { url: config.siteUrl } : {},
|
|
1463
|
+
...typeof config.siteName === "string" && config.siteName !== "" ? { name: config.siteName } : {}
|
|
1464
|
+
};
|
|
1465
|
+
}
|
|
1439
1466
|
async function postManifest(config, payload) {
|
|
1440
1467
|
const url = buildEndpointUrl(config.endpoint, config.siteUuid);
|
|
1441
1468
|
const timeoutMs = config.timeoutMs;
|
|
@@ -1446,9 +1473,10 @@ async function postManifest(config, payload) {
|
|
|
1446
1473
|
headers: {
|
|
1447
1474
|
"Content-Type": "application/json",
|
|
1448
1475
|
Accept: "application/json",
|
|
1449
|
-
"User-Agent": "@patchstack/connect"
|
|
1476
|
+
"User-Agent": "@patchstack/connect",
|
|
1477
|
+
...claimTokenHeader(config)
|
|
1450
1478
|
},
|
|
1451
|
-
body: JSON.stringify(
|
|
1479
|
+
body: JSON.stringify(buildManifestBody(config, payload)),
|
|
1452
1480
|
signal: AbortSignal.timeout(timeoutMs)
|
|
1453
1481
|
});
|
|
1454
1482
|
} catch (cause) {
|
|
@@ -1711,11 +1739,201 @@ function renderDemoTestCommands(url, scenario) {
|
|
|
1711
1739
|
}
|
|
1712
1740
|
|
|
1713
1741
|
// src/config.ts
|
|
1714
|
-
import { readFile as
|
|
1742
|
+
import { readFile as readFile9, writeFile, chmod } from "fs/promises";
|
|
1743
|
+
import path7 from "path";
|
|
1744
|
+
|
|
1745
|
+
// src/site-url.ts
|
|
1746
|
+
var RESERVED_SUFFIXES = [
|
|
1747
|
+
".local",
|
|
1748
|
+
".localhost",
|
|
1749
|
+
".localdomain",
|
|
1750
|
+
".internal",
|
|
1751
|
+
".intranet",
|
|
1752
|
+
".invalid",
|
|
1753
|
+
".test",
|
|
1754
|
+
".example",
|
|
1755
|
+
".home.arpa",
|
|
1756
|
+
".in-addr.arpa",
|
|
1757
|
+
".ip6.arpa"
|
|
1758
|
+
];
|
|
1759
|
+
function isUnpublishableHost(hostname) {
|
|
1760
|
+
const host = hostname.toLowerCase().replace(/\.+$/, "");
|
|
1761
|
+
if (host === "") return true;
|
|
1762
|
+
if (host.startsWith("[")) return true;
|
|
1763
|
+
if (/^\d{1,3}(\.\d{1,3}){3}$/.test(host)) return true;
|
|
1764
|
+
if (!host.includes(".")) return true;
|
|
1765
|
+
return RESERVED_SUFFIXES.some((suffix) => host.endsWith(suffix));
|
|
1766
|
+
}
|
|
1767
|
+
function normaliseSiteUrl(value) {
|
|
1768
|
+
const trimmed = (value ?? "").trim();
|
|
1769
|
+
if (trimmed === "") return null;
|
|
1770
|
+
const withScheme = /^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`;
|
|
1771
|
+
let parsed;
|
|
1772
|
+
try {
|
|
1773
|
+
parsed = new URL(withScheme);
|
|
1774
|
+
} catch {
|
|
1775
|
+
return null;
|
|
1776
|
+
}
|
|
1777
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") return null;
|
|
1778
|
+
if (parsed.hostname === "" || isUnpublishableHost(parsed.hostname)) return null;
|
|
1779
|
+
const origin = `${parsed.protocol}//${parsed.host}`;
|
|
1780
|
+
return origin.length <= 191 ? origin : null;
|
|
1781
|
+
}
|
|
1782
|
+
var PRODUCTION_URL_SOURCES = [
|
|
1783
|
+
{
|
|
1784
|
+
// VERCEL_URL is per-deployment and changes every build, so it is deliberately not read here.
|
|
1785
|
+
platform: "vercel",
|
|
1786
|
+
read: (env) => env.VERCEL_ENV === "production" ? env.VERCEL_PROJECT_PRODUCTION_URL : void 0
|
|
1787
|
+
},
|
|
1788
|
+
{
|
|
1789
|
+
// `URL` is generic enough to belong to anything, so it counts only alongside Netlify's own markers.
|
|
1790
|
+
platform: "netlify",
|
|
1791
|
+
read: (env) => env.NETLIFY === "true" && env.CONTEXT === "production" ? env.URL : void 0
|
|
1792
|
+
},
|
|
1793
|
+
{
|
|
1794
|
+
platform: "render",
|
|
1795
|
+
read: (env) => env.RENDER === "true" && env.IS_PULL_REQUEST !== "true" ? env.RENDER_EXTERNAL_URL : void 0
|
|
1796
|
+
},
|
|
1797
|
+
{
|
|
1798
|
+
platform: "railway",
|
|
1799
|
+
read: (env) => env.RAILWAY_ENVIRONMENT_NAME === "production" ? env.RAILWAY_PUBLIC_DOMAIN : void 0
|
|
1800
|
+
}
|
|
1801
|
+
];
|
|
1802
|
+
function detectSiteUrl(env = process.env) {
|
|
1803
|
+
for (const source of PRODUCTION_URL_SOURCES) {
|
|
1804
|
+
const url = normaliseSiteUrl(source.read(env));
|
|
1805
|
+
if (url !== null) {
|
|
1806
|
+
return { url, platform: source.platform };
|
|
1807
|
+
}
|
|
1808
|
+
}
|
|
1809
|
+
return null;
|
|
1810
|
+
}
|
|
1811
|
+
|
|
1812
|
+
// src/site-name.ts
|
|
1813
|
+
import { readFile as readFile8 } from "fs/promises";
|
|
1715
1814
|
import path6 from "path";
|
|
1815
|
+
var NAME_MAX_LENGTH = 191;
|
|
1816
|
+
var TEMPLATE_PACKAGE_NAMES = /* @__PURE__ */ new Set([
|
|
1817
|
+
// Lovable
|
|
1818
|
+
"vite_react_shadcn_ts",
|
|
1819
|
+
// Bolt
|
|
1820
|
+
"vite-react-typescript-starter",
|
|
1821
|
+
// Scaffolders and the names people accept from them
|
|
1822
|
+
"vite-project",
|
|
1823
|
+
"my-app",
|
|
1824
|
+
"my-project",
|
|
1825
|
+
"my-v0-project",
|
|
1826
|
+
"app",
|
|
1827
|
+
"frontend",
|
|
1828
|
+
"web",
|
|
1829
|
+
"client",
|
|
1830
|
+
"project",
|
|
1831
|
+
"template",
|
|
1832
|
+
"example",
|
|
1833
|
+
"react-app",
|
|
1834
|
+
"next-app",
|
|
1835
|
+
"nextjs",
|
|
1836
|
+
"starter",
|
|
1837
|
+
"test"
|
|
1838
|
+
]);
|
|
1839
|
+
var TEMPLATE_TITLES = /* @__PURE__ */ new Set([
|
|
1840
|
+
"vite + react",
|
|
1841
|
+
"vite + react + ts",
|
|
1842
|
+
"vite + vue",
|
|
1843
|
+
"vite + vue + ts",
|
|
1844
|
+
"vite app",
|
|
1845
|
+
"react app",
|
|
1846
|
+
"create next app",
|
|
1847
|
+
"document",
|
|
1848
|
+
"untitled",
|
|
1849
|
+
"index",
|
|
1850
|
+
"home",
|
|
1851
|
+
"app"
|
|
1852
|
+
]);
|
|
1853
|
+
var NAMED_ENTITIES = {
|
|
1854
|
+
amp: "&",
|
|
1855
|
+
lt: "<",
|
|
1856
|
+
gt: ">",
|
|
1857
|
+
quot: '"',
|
|
1858
|
+
apos: "'",
|
|
1859
|
+
nbsp: " "
|
|
1860
|
+
};
|
|
1861
|
+
function normaliseSiteName(value) {
|
|
1862
|
+
const collapsed = (value ?? "").replace(/\s+/g, " ").trim();
|
|
1863
|
+
if (collapsed === "") return null;
|
|
1864
|
+
return collapsed.slice(0, NAME_MAX_LENGTH);
|
|
1865
|
+
}
|
|
1866
|
+
function decodeEntities(text) {
|
|
1867
|
+
return text.replace(/&([a-z]+);/gi, (whole, name) => NAMED_ENTITIES[name.toLowerCase()] ?? whole);
|
|
1868
|
+
}
|
|
1869
|
+
function nameFromHtmlTitle(html) {
|
|
1870
|
+
const match = /<title\b[^>]*>([\s\S]*?)<\/title>/i.exec(html);
|
|
1871
|
+
if (match === null) return null;
|
|
1872
|
+
const name = normaliseSiteName(decodeEntities(match[1] ?? ""));
|
|
1873
|
+
if (name === null || TEMPLATE_TITLES.has(name.toLowerCase())) return null;
|
|
1874
|
+
return name;
|
|
1875
|
+
}
|
|
1876
|
+
function nameFromPackageName(raw) {
|
|
1877
|
+
if (typeof raw !== "string") return null;
|
|
1878
|
+
const unscoped = raw.trim().replace(/^@[^/]+\//, "");
|
|
1879
|
+
if (unscoped === "" || TEMPLATE_PACKAGE_NAMES.has(unscoped.toLowerCase())) return null;
|
|
1880
|
+
const words = unscoped.split(/[-_.\s]+/).filter((word) => word !== "").map((word) => word === word.toUpperCase() ? word : word.charAt(0).toUpperCase() + word.slice(1));
|
|
1881
|
+
return normaliseSiteName(words.join(" "));
|
|
1882
|
+
}
|
|
1883
|
+
async function readIfPresent(file) {
|
|
1884
|
+
try {
|
|
1885
|
+
return await readFile8(file, "utf8");
|
|
1886
|
+
} catch {
|
|
1887
|
+
return null;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
async function detectSiteName(cwd) {
|
|
1891
|
+
for (const shell of ["index.html", path6.join("public", "index.html")]) {
|
|
1892
|
+
const html = await readIfPresent(path6.join(cwd, shell));
|
|
1893
|
+
if (html === null) continue;
|
|
1894
|
+
const name = nameFromHtmlTitle(html);
|
|
1895
|
+
if (name !== null) return { name, source: "index.html" };
|
|
1896
|
+
}
|
|
1897
|
+
const manifest = await readIfPresent(path6.join(cwd, "package.json"));
|
|
1898
|
+
if (manifest !== null) {
|
|
1899
|
+
try {
|
|
1900
|
+
const parsed = JSON.parse(manifest);
|
|
1901
|
+
const name = nameFromPackageName(
|
|
1902
|
+
typeof parsed === "object" && parsed !== null ? parsed.name : void 0
|
|
1903
|
+
);
|
|
1904
|
+
if (name !== null) return { name, source: "package.json" };
|
|
1905
|
+
} catch {
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
return null;
|
|
1909
|
+
}
|
|
1910
|
+
|
|
1911
|
+
// src/config.ts
|
|
1716
1912
|
var CONFIG_FILENAME = ".patchstackrc.json";
|
|
1717
1913
|
var SECRET_FILENAME = ".patchstackrc.local.json";
|
|
1718
1914
|
var DEFAULT_ENVIRONMENT = "production";
|
|
1915
|
+
async function resolveSiteIdentity(cwd, fromEnv, fromFile) {
|
|
1916
|
+
const configuredUrl = stated(fromEnv.url) ?? stated(fromFile.url);
|
|
1917
|
+
const configuredName = stated(fromEnv.name) ?? stated(fromFile.name);
|
|
1918
|
+
let url;
|
|
1919
|
+
if (configuredUrl === null) {
|
|
1920
|
+
url = detectSiteUrl(process.env)?.url ?? null;
|
|
1921
|
+
} else {
|
|
1922
|
+
url = normaliseSiteUrl(configuredUrl);
|
|
1923
|
+
if (url === null) {
|
|
1924
|
+
throw new PatchstackError(
|
|
1925
|
+
`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.`,
|
|
1926
|
+
"CONFIG_INVALID"
|
|
1927
|
+
);
|
|
1928
|
+
}
|
|
1929
|
+
}
|
|
1930
|
+
const name = configuredName === null ? (await detectSiteName(cwd))?.name ?? null : normaliseSiteName(configuredName);
|
|
1931
|
+
return { url, name };
|
|
1932
|
+
}
|
|
1933
|
+
function stated(value) {
|
|
1934
|
+
const trimmed = (value ?? "").trim();
|
|
1935
|
+
return trimmed === "" ? null : trimmed;
|
|
1936
|
+
}
|
|
1719
1937
|
async function resolveConfig(options) {
|
|
1720
1938
|
const fromFile = await readConfigFile(options.cwd);
|
|
1721
1939
|
const fromSecretFile = await readSecretFile(options.cwd);
|
|
@@ -1745,25 +1963,30 @@ async function resolveConfig(options) {
|
|
|
1745
1963
|
}
|
|
1746
1964
|
const apiKeyRaw = fromEnv.apiKey ?? fromSecretFile.apiKey ?? fromFile.apiKey ?? null;
|
|
1747
1965
|
const pulseAuthRaw = fromEnv.pulseAuth ?? fromSecretFile.pulseAuth ?? fromFile.pulseAuth ?? apiKeyRaw;
|
|
1966
|
+
const identity = options.detectSiteIdentity === true ? await resolveSiteIdentity(options.cwd, fromEnv, fromFile) : { url: null, name: null };
|
|
1967
|
+
const claimToken = stated(options.cliClaimToken) ?? stated(process.env.PATCHSTACK_CLAIM_TOKEN);
|
|
1748
1968
|
return {
|
|
1749
1969
|
siteUuid: siteUuid === null || siteUuid.length === 0 ? null : siteUuid,
|
|
1750
1970
|
apiKey: apiKeyRaw === null || apiKeyRaw.length === 0 ? null : apiKeyRaw,
|
|
1751
1971
|
pulseAuth: pulseAuthRaw === null || pulseAuthRaw.length === 0 ? null : pulseAuthRaw,
|
|
1972
|
+
siteUrl: identity.url,
|
|
1973
|
+
siteName: identity.name,
|
|
1752
1974
|
endpoint,
|
|
1753
1975
|
timeoutMs,
|
|
1754
1976
|
environment,
|
|
1755
|
-
widget: fromFile.widget !== false
|
|
1977
|
+
widget: fromFile.widget !== false,
|
|
1978
|
+
claimToken
|
|
1756
1979
|
};
|
|
1757
1980
|
}
|
|
1758
1981
|
async function writeConfigFile(cwd, config) {
|
|
1759
|
-
const target =
|
|
1982
|
+
const target = path7.join(cwd, CONFIG_FILENAME);
|
|
1760
1983
|
const content = JSON.stringify(config, null, 2) + "\n";
|
|
1761
1984
|
await writeFile(target, content, "utf8");
|
|
1762
1985
|
return target;
|
|
1763
1986
|
}
|
|
1764
1987
|
var SECRET_CONFIG_FILENAME = SECRET_FILENAME;
|
|
1765
1988
|
async function writeSecretFile(cwd, secrets) {
|
|
1766
|
-
const target =
|
|
1989
|
+
const target = path7.join(cwd, SECRET_FILENAME);
|
|
1767
1990
|
await writeFile(target, JSON.stringify(secrets, null, 2) + "\n", { encoding: "utf8", mode: 384 });
|
|
1768
1991
|
try {
|
|
1769
1992
|
await chmod(target, 384);
|
|
@@ -1773,10 +1996,10 @@ async function writeSecretFile(cwd, secrets) {
|
|
|
1773
1996
|
return { path: target, ...ignore };
|
|
1774
1997
|
}
|
|
1775
1998
|
async function ensureIgnored(cwd, entry) {
|
|
1776
|
-
const target =
|
|
1999
|
+
const target = path7.join(cwd, ".gitignore");
|
|
1777
2000
|
let existing = "";
|
|
1778
2001
|
try {
|
|
1779
|
-
existing = await
|
|
2002
|
+
existing = await readFile9(target, "utf8");
|
|
1780
2003
|
} catch (err) {
|
|
1781
2004
|
const code = err.code;
|
|
1782
2005
|
if (code !== "ENOENT") {
|
|
@@ -1796,7 +2019,7 @@ ${entry}
|
|
|
1796
2019
|
return { ignored: false, reason: `.gitignore could not be written (${code ?? "unknown error"})` };
|
|
1797
2020
|
}
|
|
1798
2021
|
try {
|
|
1799
|
-
const written = await
|
|
2022
|
+
const written = await readFile9(target, "utf8");
|
|
1800
2023
|
if (ignoresEntry(written, entry)) return { ignored: true };
|
|
1801
2024
|
return { ignored: false, reason: ".gitignore does not cover the file after writing it" };
|
|
1802
2025
|
} catch (err) {
|
|
@@ -1806,7 +2029,7 @@ ${entry}
|
|
|
1806
2029
|
}
|
|
1807
2030
|
async function secretFileIgnored(cwd) {
|
|
1808
2031
|
try {
|
|
1809
|
-
const contents = await
|
|
2032
|
+
const contents = await readFile9(path7.join(cwd, ".gitignore"), "utf8");
|
|
1810
2033
|
if (ignoresEntry(contents, SECRET_FILENAME)) return { ignored: true };
|
|
1811
2034
|
return { ignored: false, reason: `.gitignore does not cover ${SECRET_FILENAME}` };
|
|
1812
2035
|
} catch (err) {
|
|
@@ -1853,10 +2076,10 @@ async function readConfigFile(cwd) {
|
|
|
1853
2076
|
return readJsonFile(cwd, CONFIG_FILENAME);
|
|
1854
2077
|
}
|
|
1855
2078
|
async function readJsonFile(cwd, filename) {
|
|
1856
|
-
const target =
|
|
2079
|
+
const target = path7.join(cwd, filename);
|
|
1857
2080
|
let raw;
|
|
1858
2081
|
try {
|
|
1859
|
-
raw = await
|
|
2082
|
+
raw = await readFile9(target, "utf8");
|
|
1860
2083
|
} catch (err) {
|
|
1861
2084
|
if (err.code === "ENOENT") {
|
|
1862
2085
|
return {};
|
|
@@ -1893,6 +2116,8 @@ function readEnv() {
|
|
|
1893
2116
|
const environmentRaw = process.env.PATCHSTACK_ENVIRONMENT;
|
|
1894
2117
|
return {
|
|
1895
2118
|
siteUuid: process.env.PATCHSTACK_SITE_UUID ?? void 0,
|
|
2119
|
+
url: process.env.PATCHSTACK_SITE_URL ?? void 0,
|
|
2120
|
+
name: process.env.PATCHSTACK_SITE_NAME ?? void 0,
|
|
1896
2121
|
apiKey: process.env.PATCHSTACK_API_KEY ?? void 0,
|
|
1897
2122
|
pulseAuth: process.env.PATCHSTACK_PULSE_AUTH ?? void 0,
|
|
1898
2123
|
endpoint: process.env.PATCHSTACK_ENDPOINT ?? void 0,
|
|
@@ -1909,7 +2134,7 @@ function isEnvironment(value) {
|
|
|
1909
2134
|
|
|
1910
2135
|
// src/mark-build.ts
|
|
1911
2136
|
import { existsSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
|
|
1912
|
-
import
|
|
2137
|
+
import path8 from "path";
|
|
1913
2138
|
|
|
1914
2139
|
// src/stack.ts
|
|
1915
2140
|
var STACK_RULES = [
|
|
@@ -2002,11 +2227,11 @@ var MARKER_ATTR = "data-patchstack-build";
|
|
|
2002
2227
|
var BUILD_DIR_CANDIDATES = ["dist", "build", "out", ".output/public"];
|
|
2003
2228
|
function resolveBuildDir(cwd, override) {
|
|
2004
2229
|
if (override !== void 0 && override !== "") {
|
|
2005
|
-
const abs =
|
|
2230
|
+
const abs = path8.resolve(cwd, override);
|
|
2006
2231
|
return existsSync(abs) && statSync(abs).isDirectory() ? abs : null;
|
|
2007
2232
|
}
|
|
2008
2233
|
for (const candidate of BUILD_DIR_CANDIDATES) {
|
|
2009
|
-
const abs =
|
|
2234
|
+
const abs = path8.resolve(cwd, candidate);
|
|
2010
2235
|
if (existsSync(abs) && statSync(abs).isDirectory()) {
|
|
2011
2236
|
return abs;
|
|
2012
2237
|
}
|
|
@@ -2017,7 +2242,7 @@ function findHtmlFiles(dir) {
|
|
|
2017
2242
|
const out = [];
|
|
2018
2243
|
const walk2 = (current) => {
|
|
2019
2244
|
for (const entry of readdirSync(current, { withFileTypes: true })) {
|
|
2020
|
-
const full =
|
|
2245
|
+
const full = path8.join(current, entry.name);
|
|
2021
2246
|
if (entry.isDirectory()) {
|
|
2022
2247
|
walk2(full);
|
|
2023
2248
|
} else if (entry.isFile() && entry.name.toLowerCase().endsWith(".html")) {
|
|
@@ -2166,7 +2391,7 @@ function ensureSourceMarker(cwd, shell, framework) {
|
|
|
2166
2391
|
if (shell === null) {
|
|
2167
2392
|
return { shell: null, source: "", action: "no-anchor" };
|
|
2168
2393
|
}
|
|
2169
|
-
const file =
|
|
2394
|
+
const file = path8.resolve(cwd, shell);
|
|
2170
2395
|
const before = readFileSync(file, "utf8");
|
|
2171
2396
|
const result = ensureMarkerInJsxShell(before, framework);
|
|
2172
2397
|
if (result.source !== before) {
|
|
@@ -2177,7 +2402,7 @@ function ensureSourceMarker(cwd, shell, framework) {
|
|
|
2177
2402
|
|
|
2178
2403
|
// src/guide.ts
|
|
2179
2404
|
import { existsSync as existsSync12, readFileSync as readFileSync6, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
|
|
2180
|
-
import
|
|
2405
|
+
import path10 from "path";
|
|
2181
2406
|
|
|
2182
2407
|
// src/protect/install/util.ts
|
|
2183
2408
|
import { readFileSync as readFileSync2, existsSync as existsSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
@@ -3674,7 +3899,7 @@ function runVerify(cwd) {
|
|
|
3674
3899
|
|
|
3675
3900
|
// src/widget.ts
|
|
3676
3901
|
import { existsSync as existsSync11, readFileSync as readFileSync5, writeFileSync as writeFileSync6 } from "fs";
|
|
3677
|
-
import
|
|
3902
|
+
import path9 from "path";
|
|
3678
3903
|
var WIDGET_SCRIPT_URL = "https://cdn.patchstack.com/patchstack-widget.js";
|
|
3679
3904
|
var WIDGET_MARKER_ATTR = "data-patchstack-connect-widget";
|
|
3680
3905
|
var WIDGET_NEEDLE = "patchstack-widget";
|
|
@@ -3712,7 +3937,7 @@ ${indent}</body>`;
|
|
|
3712
3937
|
}
|
|
3713
3938
|
function findSourceShell(cwd) {
|
|
3714
3939
|
for (const candidate of SOURCE_SHELL_CANDIDATES) {
|
|
3715
|
-
if (existsSync11(
|
|
3940
|
+
if (existsSync11(path9.join(cwd, candidate))) {
|
|
3716
3941
|
return candidate;
|
|
3717
3942
|
}
|
|
3718
3943
|
}
|
|
@@ -3723,7 +3948,7 @@ function ensureSourceWidget(cwd, siteUuid) {
|
|
|
3723
3948
|
if (shell === null) {
|
|
3724
3949
|
return { shell: null, action: "no-shell" };
|
|
3725
3950
|
}
|
|
3726
|
-
const file =
|
|
3951
|
+
const file = path9.join(cwd, shell);
|
|
3727
3952
|
const before = readFileSync5(file, "utf8");
|
|
3728
3953
|
const { html, action } = ensureWidgetInHtml(before, siteUuid);
|
|
3729
3954
|
if (html !== before) {
|
|
@@ -3806,7 +4031,7 @@ function detectPackageManager(cwd) {
|
|
|
3806
4031
|
return declared;
|
|
3807
4032
|
}
|
|
3808
4033
|
for (const { filename, pm } of PM_BY_LOCKFILE) {
|
|
3809
|
-
if (existsSync12(
|
|
4034
|
+
if (existsSync12(path10.join(cwd, filename))) {
|
|
3810
4035
|
return pm;
|
|
3811
4036
|
}
|
|
3812
4037
|
}
|
|
@@ -3817,7 +4042,7 @@ function installCommand(pm) {
|
|
|
3817
4042
|
}
|
|
3818
4043
|
function readPackageJson(cwd) {
|
|
3819
4044
|
try {
|
|
3820
|
-
return JSON.parse(readFileSync6(
|
|
4045
|
+
return JSON.parse(readFileSync6(path10.join(cwd, "package.json"), "utf8"));
|
|
3821
4046
|
} catch {
|
|
3822
4047
|
return null;
|
|
3823
4048
|
}
|
|
@@ -3825,7 +4050,7 @@ function readPackageJson(cwd) {
|
|
|
3825
4050
|
function installedVersion(cwd, declaredRange) {
|
|
3826
4051
|
try {
|
|
3827
4052
|
const pkg = JSON.parse(
|
|
3828
|
-
readFileSync6(
|
|
4053
|
+
readFileSync6(path10.join(cwd, "node_modules", "@patchstack", "connect", "package.json"), "utf8")
|
|
3829
4054
|
);
|
|
3830
4055
|
if (typeof pkg.version === "string" && pkg.version.length > 0) {
|
|
3831
4056
|
return pkg.version;
|
|
@@ -3853,7 +4078,7 @@ function findWidgetMarker(cwd, siteUuid) {
|
|
|
3853
4078
|
if (budget <= 0 || settled()) {
|
|
3854
4079
|
return;
|
|
3855
4080
|
}
|
|
3856
|
-
const full =
|
|
4081
|
+
const full = path10.join(dir, entry.name);
|
|
3857
4082
|
if (entry.isDirectory()) {
|
|
3858
4083
|
if (SKIPPED_DIRS.has(entry.name) || entry.name.startsWith(".")) {
|
|
3859
4084
|
continue;
|
|
@@ -3861,7 +4086,7 @@ function findWidgetMarker(cwd, siteUuid) {
|
|
|
3861
4086
|
walk2(full, depth + 1);
|
|
3862
4087
|
continue;
|
|
3863
4088
|
}
|
|
3864
|
-
if (!entry.isFile() || !WIDGET_EXTENSIONS.has(
|
|
4089
|
+
if (!entry.isFile() || !WIDGET_EXTENSIONS.has(path10.extname(entry.name).toLowerCase())) {
|
|
3865
4090
|
continue;
|
|
3866
4091
|
}
|
|
3867
4092
|
budget -= 1;
|
|
@@ -3892,7 +4117,7 @@ function findProductionMarker(cwd, widgetFileHint) {
|
|
|
3892
4117
|
return false;
|
|
3893
4118
|
}
|
|
3894
4119
|
try {
|
|
3895
|
-
return readFileSync6(
|
|
4120
|
+
return readFileSync6(path10.join(cwd, widgetFileHint), "utf8").includes(PROD_MARKER_NEEDLE);
|
|
3896
4121
|
} catch {
|
|
3897
4122
|
return false;
|
|
3898
4123
|
}
|
|
@@ -3903,7 +4128,7 @@ function resolveWidgetFileHint(cwd, framework) {
|
|
|
3903
4128
|
...GENERIC_WIDGET_FILES
|
|
3904
4129
|
];
|
|
3905
4130
|
for (const candidate of candidates) {
|
|
3906
|
-
if (existsSync12(
|
|
4131
|
+
if (existsSync12(path10.join(cwd, candidate))) {
|
|
3907
4132
|
return candidate;
|
|
3908
4133
|
}
|
|
3909
4134
|
}
|
|
@@ -4010,7 +4235,7 @@ function renderGuideChecklist(state, useColor) {
|
|
|
4010
4235
|
const headerParts = [state.framework, state.packageManager].filter(
|
|
4011
4236
|
(part) => part !== null
|
|
4012
4237
|
);
|
|
4013
|
-
const name = state.projectName ??
|
|
4238
|
+
const name = state.projectName ?? path10.basename(process.cwd());
|
|
4014
4239
|
lines.push(paint(ANSI.bold, `Patchstack setup status \u2014 ${name} (${headerParts.join(" \xB7 ")})`));
|
|
4015
4240
|
if (state.endpointOverride !== null) {
|
|
4016
4241
|
lines.push(
|
|
@@ -4025,6 +4250,15 @@ function renderGuideChecklist(state, useColor) {
|
|
|
4025
4250
|
lines.push(detail("Run the guide from the project root."));
|
|
4026
4251
|
return lines.join("\n");
|
|
4027
4252
|
}
|
|
4253
|
+
if (countRemainingSteps(state) > 0) {
|
|
4254
|
+
lines.push(` ${paint(ANSI.cyan, "\u279C")} ${paint(ANSI.bold, "Tell the user what you are doing as you go.")}`);
|
|
4255
|
+
lines.push(" Installs here take minutes and they are watching output they cannot read.");
|
|
4256
|
+
lines.push(" Say it in plain words: their site is built out of hundreds of ready-made");
|
|
4257
|
+
lines.push(" pieces of code, Patchstack keeps track of which ones it uses and warns them");
|
|
4258
|
+
lines.push(" when one turns out to be unsafe, and nothing their visitors see changes.");
|
|
4259
|
+
lines.push(" Avoid lockfile / dependency / manifest / npm / UUID when speaking to them.");
|
|
4260
|
+
lines.push("");
|
|
4261
|
+
}
|
|
4028
4262
|
if (state.installed?.section === "dependencies") {
|
|
4029
4263
|
lines.push(done(`@patchstack/connect installed (${state.installed.version}, ${state.installed.section})`));
|
|
4030
4264
|
} else if (state.installed !== null) {
|
|
@@ -4174,7 +4408,7 @@ function renderGuideChecklist(state, useColor) {
|
|
|
4174
4408
|
// src/login.ts
|
|
4175
4409
|
import { mkdtempSync, readFileSync as readFileSync7, rmSync, writeFileSync as writeFileSync7 } from "fs";
|
|
4176
4410
|
import { tmpdir } from "os";
|
|
4177
|
-
import
|
|
4411
|
+
import path11 from "path";
|
|
4178
4412
|
import { createHash as createHash2 } from "crypto";
|
|
4179
4413
|
function baseFrom(manifestEndpoint) {
|
|
4180
4414
|
const url = new URL(manifestEndpoint);
|
|
@@ -4186,7 +4420,7 @@ function baseFrom(manifestEndpoint) {
|
|
|
4186
4420
|
}
|
|
4187
4421
|
function pendingPath(siteUuid) {
|
|
4188
4422
|
const key = createHash2("sha256").update(siteUuid).digest("hex").slice(0, 16);
|
|
4189
|
-
return
|
|
4423
|
+
return path11.join(tmpdir(), `patchstack-login-${key}.json`);
|
|
4190
4424
|
}
|
|
4191
4425
|
function savePendingLogin(siteUuid, pending) {
|
|
4192
4426
|
writeFileSync7(pendingPath(siteUuid), JSON.stringify(pending), { encoding: "utf8", mode: 384 });
|
|
@@ -4821,10 +5055,10 @@ function detectDeploymentShapes(cwd, opts = {}) {
|
|
|
4821
5055
|
boundary = realpathSync(boundary);
|
|
4822
5056
|
} catch {
|
|
4823
5057
|
}
|
|
4824
|
-
const inProject = (
|
|
5058
|
+
const inProject = (path13) => {
|
|
4825
5059
|
if (opts.followOutside) return true;
|
|
4826
5060
|
try {
|
|
4827
|
-
return isInside(realpathSync(
|
|
5061
|
+
return isInside(realpathSync(path13), boundary);
|
|
4828
5062
|
} catch {
|
|
4829
5063
|
return false;
|
|
4830
5064
|
}
|
|
@@ -5461,8 +5695,8 @@ function collectCallees(sf, ts) {
|
|
|
5461
5695
|
}
|
|
5462
5696
|
|
|
5463
5697
|
// src/map/coordinates.ts
|
|
5464
|
-
function runtimeCoordinate(source,
|
|
5465
|
-
if (/\[\d*\]/.test(
|
|
5698
|
+
function runtimeCoordinate(source, path13) {
|
|
5699
|
+
if (/\[\d*\]/.test(path13)) {
|
|
5466
5700
|
return { runtimeParameter: null, runtimeParameterReason: "array traversal: needs an array_key_value rule, not a dotted parameter" };
|
|
5467
5701
|
}
|
|
5468
5702
|
switch (source) {
|
|
@@ -5471,15 +5705,15 @@ function runtimeCoordinate(source, path12) {
|
|
|
5471
5705
|
case "multipart":
|
|
5472
5706
|
case "body":
|
|
5473
5707
|
case "server-fn-data":
|
|
5474
|
-
return { runtimeParameter: `post.${
|
|
5708
|
+
return { runtimeParameter: `post.${path13}` };
|
|
5475
5709
|
case "query":
|
|
5476
|
-
return { runtimeParameter: `get.${
|
|
5710
|
+
return { runtimeParameter: `get.${path13}` };
|
|
5477
5711
|
case "cookie":
|
|
5478
|
-
return { runtimeParameter: `cookie.${
|
|
5712
|
+
return { runtimeParameter: `cookie.${path13}` };
|
|
5479
5713
|
case "file":
|
|
5480
|
-
return { runtimeParameter: `files.${
|
|
5714
|
+
return { runtimeParameter: `files.${path13}` };
|
|
5481
5715
|
case "header":
|
|
5482
|
-
return { runtimeParameter: `server.HTTP_${
|
|
5716
|
+
return { runtimeParameter: `server.HTTP_${path13.toUpperCase().replace(/-/g, "_")}` };
|
|
5483
5717
|
case "route-param":
|
|
5484
5718
|
return { runtimeParameter: null, runtimeParameterReason: "route parameters are not exposed by the runtime resolver" };
|
|
5485
5719
|
default:
|
|
@@ -5511,8 +5745,8 @@ function addressSpaceOf(source) {
|
|
|
5511
5745
|
function isProvenFlow(confidence) {
|
|
5512
5746
|
return confidence === "exact-local" || confidence === "transformed-local";
|
|
5513
5747
|
}
|
|
5514
|
-
function inputIdOf(source,
|
|
5515
|
-
return `${addressSpaceOf(source)}:${
|
|
5748
|
+
function inputIdOf(source, path13) {
|
|
5749
|
+
return `${addressSpaceOf(source)}:${path13}`;
|
|
5516
5750
|
}
|
|
5517
5751
|
function withCoordinates(fields, source) {
|
|
5518
5752
|
return fields.map((f) => {
|
|
@@ -5722,8 +5956,8 @@ function linkFlows(bodyNode, params, inputs, sinks, ts) {
|
|
|
5722
5956
|
if (!bodyNode || sinks.length === 0 || inputs.length === 0) return { flows: [], limitations: [] };
|
|
5723
5957
|
const CONTAINER_KEYS = /* @__PURE__ */ new Set([...REQ_SOURCES, "data", "input", "payload"]);
|
|
5724
5958
|
const rootPath = /* @__PURE__ */ new Map();
|
|
5725
|
-
const addRoot = (name,
|
|
5726
|
-
if (!rootPath.has(name)) rootPath.set(name, { path:
|
|
5959
|
+
const addRoot = (name, path13, space) => {
|
|
5960
|
+
if (!rootPath.has(name)) rootPath.set(name, { path: path13, space });
|
|
5727
5961
|
};
|
|
5728
5962
|
for (const p of params ?? []) {
|
|
5729
5963
|
if (!p?.name) continue;
|
|
@@ -5868,8 +6102,8 @@ function dedupeLimitations(list) {
|
|
|
5868
6102
|
function join22(base, seg) {
|
|
5869
6103
|
return base ? `${base}.${seg}` : seg;
|
|
5870
6104
|
}
|
|
5871
|
-
function normalizePath(
|
|
5872
|
-
return
|
|
6105
|
+
function normalizePath(path13) {
|
|
6106
|
+
return path13.replace(/\[\d*\]/g, "").split(".").filter(Boolean).join(".");
|
|
5873
6107
|
}
|
|
5874
6108
|
function fluentChainCalls(call, ts) {
|
|
5875
6109
|
let root = call;
|
|
@@ -6785,7 +7019,7 @@ async function runMap(flags) {
|
|
|
6785
7019
|
|
|
6786
7020
|
// src/setup.ts
|
|
6787
7021
|
import { readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
|
|
6788
|
-
import
|
|
7022
|
+
import path12 from "path";
|
|
6789
7023
|
var SCAN_COMMAND = "patchstack-connect scan";
|
|
6790
7024
|
var MARK_BUILD_COMMAND = "patchstack-connect mark-build";
|
|
6791
7025
|
function setupProtection(cwd) {
|
|
@@ -6803,7 +7037,7 @@ function appendHook(existing, command) {
|
|
|
6803
7037
|
return `${existing} && ${command}`;
|
|
6804
7038
|
}
|
|
6805
7039
|
function wireBuildScripts(cwd, packageManager) {
|
|
6806
|
-
const target =
|
|
7040
|
+
const target = path12.join(cwd, "package.json");
|
|
6807
7041
|
const raw = readFileSync13(target, "utf8");
|
|
6808
7042
|
const pkg = JSON.parse(raw);
|
|
6809
7043
|
const scripts = pkg.scripts ?? {};
|
|
@@ -7002,6 +7236,10 @@ Options (for scan, setup, status, and uninstall):
|
|
|
7002
7236
|
same under a workspace that pins its own copy), so an
|
|
7003
7237
|
advisory can be matched to the copy your code actually
|
|
7004
7238
|
loads. Off by default; never source file paths
|
|
7239
|
+
--claim-token <token> (scan, setup) Connect the site to the Patchstack account
|
|
7240
|
+
that issued the token \u2014 it comes from the dashboard's
|
|
7241
|
+
"Connect website" prompt. PATCHSTACK_CLAIM_TOKEN works
|
|
7242
|
+
too. Never written to a file, never printed back
|
|
7005
7243
|
|
|
7006
7244
|
Options (for mark-build):
|
|
7007
7245
|
--dir <path> Build output directory (default: auto-detect
|
|
@@ -7035,7 +7273,7 @@ Examples:
|
|
|
7035
7273
|
npx @patchstack/connect demo node-serialize
|
|
7036
7274
|
npx @patchstack/connect demo-guide node-serialize
|
|
7037
7275
|
`;
|
|
7038
|
-
var VALUE_FLAGS = /* @__PURE__ */ new Set(["site-uuid", "endpoint", "dir", "url", "out"]);
|
|
7276
|
+
var VALUE_FLAGS = /* @__PURE__ */ new Set(["site-uuid", "endpoint", "dir", "url", "out", "claim-token"]);
|
|
7039
7277
|
function parseArgs(argv) {
|
|
7040
7278
|
const args = argv.slice(2);
|
|
7041
7279
|
const positional = [];
|
|
@@ -7168,7 +7406,10 @@ async function runScan(args, options = {}) {
|
|
|
7168
7406
|
const config = await resolveConfig({
|
|
7169
7407
|
cwd: process.cwd(),
|
|
7170
7408
|
cliSiteUuid: getStringFlag(args.flags, "site-uuid"),
|
|
7171
|
-
cliEndpoint: getStringFlag(args.flags, "endpoint")
|
|
7409
|
+
cliEndpoint: getStringFlag(args.flags, "endpoint"),
|
|
7410
|
+
cliClaimToken: getStringFlag(args.flags, "claim-token"),
|
|
7411
|
+
// The one command that reports them, so the one command that resolves them.
|
|
7412
|
+
detectSiteIdentity: true
|
|
7172
7413
|
});
|
|
7173
7414
|
const manifest = await scanLockfile(process.cwd());
|
|
7174
7415
|
for (const warning of manifest.warnings ?? []) {
|
|
@@ -7200,6 +7441,16 @@ async function runScan(args, options = {}) {
|
|
|
7200
7441
|
`${stats.duplicateNames.length} package(s) appear at multiple versions: ${sample}${more}`
|
|
7201
7442
|
);
|
|
7202
7443
|
}
|
|
7444
|
+
const body = buildManifestBody(config, payload);
|
|
7445
|
+
if (typeof body.url === "string") {
|
|
7446
|
+
console.log(`Reporting this app's address as ${body.url}.`);
|
|
7447
|
+
}
|
|
7448
|
+
if (typeof body.name === "string") {
|
|
7449
|
+
console.log(`Reporting this app's name as "${body.name}".`);
|
|
7450
|
+
}
|
|
7451
|
+
if (typeof config.claimToken === "string" && config.claimToken !== "") {
|
|
7452
|
+
console.log("A claim token is set: the site will be connected to the Patchstack account that issued it.");
|
|
7453
|
+
}
|
|
7203
7454
|
if (dryRun) {
|
|
7204
7455
|
console.log("");
|
|
7205
7456
|
if (config.siteUuid === null) {
|
|
@@ -7208,7 +7459,7 @@ async function runScan(args, options = {}) {
|
|
|
7208
7459
|
console.log(`--dry-run: not posting to Patchstack (site UUID ${config.siteUuid}).`);
|
|
7209
7460
|
}
|
|
7210
7461
|
console.log("Payload preview:");
|
|
7211
|
-
const preview = JSON.stringify(
|
|
7462
|
+
const preview = JSON.stringify(body, null, 2).split("\n");
|
|
7212
7463
|
console.log(preview.slice(0, Math.min(preview.length, 30)).join("\n"));
|
|
7213
7464
|
if (preview.length > 30) {
|
|
7214
7465
|
console.log(` ... (${preview.length - 30} more lines)`);
|
|
@@ -7254,14 +7505,21 @@ async function runScan(args, options = {}) {
|
|
|
7254
7505
|
} else {
|
|
7255
7506
|
console.log(`Server response: ${response.message ?? JSON.stringify(response)}`);
|
|
7256
7507
|
}
|
|
7508
|
+
const claimLines = claimOutcomeLines(response.claim, config);
|
|
7509
|
+
if (claimLines.length > 0) {
|
|
7510
|
+
console.log("");
|
|
7511
|
+
for (const line of claimLines) console.log(line);
|
|
7512
|
+
}
|
|
7513
|
+
const connected = response.claim?.state === "claimed" || response.claim?.state === "owned-by-you";
|
|
7257
7514
|
const effectiveUuid = config.siteUuid ?? response.uuid ?? null;
|
|
7258
7515
|
if (config.widget && effectiveUuid !== null && effectiveUuid.length > 0) {
|
|
7259
7516
|
reportSourceWidget(effectiveUuid);
|
|
7260
7517
|
}
|
|
7261
|
-
|
|
7518
|
+
const linkUuid = response.uuid ?? config.siteUuid;
|
|
7519
|
+
if (!connected && (provisioning || claimLines.length > 0) && linkUuid !== null && linkUuid !== void 0 && linkUuid.length > 0) {
|
|
7262
7520
|
console.log("");
|
|
7263
7521
|
console.log("Open this dashboard link to view vulnerability reports:");
|
|
7264
|
-
console.log(` ${buildClaimUrl(config.endpoint,
|
|
7522
|
+
console.log(` ${buildClaimUrl(config.endpoint, linkUuid)}`);
|
|
7265
7523
|
if (config.endpoint !== DEFAULT_ENDPOINT) {
|
|
7266
7524
|
console.log(" (this URL inherits the endpoint override above)");
|
|
7267
7525
|
}
|