@reddoorla/maintenance 0.69.0 → 0.72.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/README.md +15 -14
- package/dist/airtable-U2GTDTK5.js +9 -0
- package/dist/{announce-72R3LRNT.js → announce-HJCGEVJA.js} +6 -6
- package/dist/blux-FKZYMKLF.js +2205 -0
- package/dist/blux-FKZYMKLF.js.map +1 -0
- package/dist/{bump-deps-QNM3H4GI.js → bump-deps-GZEJAUKI.js} +3 -2
- package/dist/bump-deps-GZEJAUKI.js.map +1 -0
- package/dist/chunk-7MJBTV2R.js +145 -0
- package/dist/chunk-7MJBTV2R.js.map +1 -0
- package/dist/{chunk-2DCA7FHZ.js → chunk-7P7YETHG.js} +34 -12
- package/dist/chunk-7P7YETHG.js.map +1 -0
- package/dist/{chunk-42KKVLYN.js → chunk-APTAITXZ.js} +10 -2
- package/dist/chunk-APTAITXZ.js.map +1 -0
- package/dist/{chunk-6G7BC5SN.js → chunk-BHE5CUWI.js} +392 -7
- package/dist/chunk-BHE5CUWI.js.map +1 -0
- package/dist/{chunk-XWPZ46IN.js → chunk-BJCA7O4A.js} +2 -2
- package/dist/{chunk-C3PDS623.js → chunk-BWKK2ZXS.js} +67 -8
- package/dist/chunk-BWKK2ZXS.js.map +1 -0
- package/dist/{chunk-45AUGDFR.js → chunk-D4FVAIEJ.js} +6 -20
- package/dist/chunk-D4FVAIEJ.js.map +1 -0
- package/dist/{chunk-YQMYQAJX.js → chunk-GI4M2AMO.js} +2 -2
- package/dist/{chunk-OTBA7DON.js → chunk-GYNRZCM6.js} +7 -2
- package/dist/chunk-GYNRZCM6.js.map +1 -0
- package/dist/{chunk-6MT5W6UF.js → chunk-HR5SONJC.js} +6 -3
- package/dist/chunk-HR5SONJC.js.map +1 -0
- package/dist/{chunk-6JFOAPW4.js → chunk-JCITWZQR.js} +51 -19
- package/dist/chunk-JCITWZQR.js.map +1 -0
- package/dist/{chunk-GKSXZIMX.js → chunk-JGLAWXUC.js} +77 -3
- package/dist/chunk-JGLAWXUC.js.map +1 -0
- package/dist/{chunk-E2GKKUEX.js → chunk-KLB2OLPW.js} +26 -6
- package/dist/chunk-KLB2OLPW.js.map +1 -0
- package/dist/{chunk-RINXPU3H.js → chunk-KLVMJYZZ.js} +2 -2
- package/dist/chunk-KRDLHAKK.js +17 -0
- package/dist/chunk-KRDLHAKK.js.map +1 -0
- package/dist/{chunk-AESJBEM7.js → chunk-KRHPS3R4.js} +3 -3
- package/dist/{chunk-LYCKN7ZR.js → chunk-LJPCMXR7.js} +8 -5
- package/dist/chunk-LJPCMXR7.js.map +1 -0
- package/dist/{chunk-O6G4HHMF.js → chunk-NRISG4XD.js} +308 -104
- package/dist/chunk-NRISG4XD.js.map +1 -0
- package/dist/chunk-PKVZMFEB.js +315 -0
- package/dist/chunk-PKVZMFEB.js.map +1 -0
- package/dist/chunk-SL24DCGC.js +41 -0
- package/dist/chunk-SL24DCGC.js.map +1 -0
- package/dist/{chunk-A5KYRGQA.js → chunk-SQJ53Z7R.js} +4 -4
- package/dist/cli/bin.js +63 -19
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.d.ts +1 -1
- package/dist/cli/commands/audit.js +11 -9
- package/dist/cli/commands/audit.js.map +1 -1
- package/dist/configs/playwright-a11y.d.ts +1 -1
- package/dist/configs/playwright-a11y.js +1 -3
- package/dist/{convert-to-pnpm-XH7NYRVK.js → convert-to-pnpm-SVFDLXZS.js} +3 -2
- package/dist/convert-to-pnpm-SVFDLXZS.js.map +1 -0
- package/dist/{digest-Q2TAQHXF.js → digest-VX7Q4MNF.js} +8 -8
- package/dist/{ensure-site-2PF522VL.js → ensure-site-O7G5ZJS7.js} +2 -2
- package/dist/forms/index.d.ts +5 -2
- package/dist/forms/index.js +22 -9
- package/dist/forms/index.js.map +1 -1
- package/dist/{github-signals-CLQC2IPY.js → github-signals-QVW2LB7N.js} +5 -5
- package/dist/health-endpoint-IYR5VBUT.js +54 -0
- package/dist/health-endpoint-IYR5VBUT.js.map +1 -0
- package/dist/index.d.ts +136 -20
- package/dist/index.js +207 -64
- package/dist/index.js.map +1 -1
- package/dist/{init-UA6D7GKW.js → init-CU6PVZRG.js} +9 -5
- package/dist/init-CU6PVZRG.js.map +1 -0
- package/dist/{launch-AENAS2O2.js → launch-JWXCTJRG.js} +9 -9
- package/dist/{lighthouse-airtable-UBSXKHAQ.js → lighthouse-airtable-J5TLASJC.js} +3 -3
- package/dist/{onboard-X6KJ5Z2V.js → onboard-6UQE5QIO.js} +2 -2
- package/dist/{orchestrate-GADNFGDH.js → orchestrate-ZDH24Z6Y.js} +5 -5
- package/dist/{preflight-D5RQGX4V.js → preflight-BNTMWTPG.js} +7 -7
- package/dist/probe-DJAMLVTT.js +46 -0
- package/dist/probe-DJAMLVTT.js.map +1 -0
- package/dist/recipes/sync-configs.d.ts +1 -1
- package/dist/{renovate-dispatch-LG4RJGDY.js → renovate-dispatch-C3ALLACX.js} +2 -2
- package/dist/{report-3FJ224H5.js → report-YLFSGNWR.js} +12 -12
- package/dist/run-migration-LJNNZWIV.js +205 -0
- package/dist/run-migration-LJNNZWIV.js.map +1 -0
- package/dist/{self-updating-JTTFOJ7Q.js → self-updating-64FNRQB5.js} +2 -2
- package/dist/{selftest-55OJIWEB.js → selftest-7DPNGL6R.js} +12 -9
- package/dist/selftest-7DPNGL6R.js.map +1 -0
- package/dist/smoke-suite-TWILWDSF.js +54 -0
- package/dist/smoke-suite-TWILWDSF.js.map +1 -0
- package/dist/{svelte-codemods-KJLNH6FQ.js → svelte-codemods-RVDTTXFD.js} +3 -2
- package/dist/svelte-codemods-RVDTTXFD.js.map +1 -0
- package/dist/{sync-configs-TIBO6DUJ.js → sync-configs-BUUD6L5Q.js} +3 -2
- package/dist/sync-configs-BUUD6L5Q.js.map +1 -0
- package/dist/{types-Dg4mHvyf.d.ts → types-0jreMCF5.d.ts} +2 -2
- package/dist/{upgrade-TPTE26FG.js → upgrade-HHKWYJRN.js} +3 -2
- package/dist/upgrade-HHKWYJRN.js.map +1 -0
- package/dist/{websites-6YTT5ZTO.js → websites-EH2ULELZ.js} +8 -2
- package/dist/{write-audits-to-airtable-IWOO4FFH.js → write-audits-to-airtable-LZDYTNX5.js} +4 -4
- package/package.json +4 -1
- package/dist/airtable-WGQQ3KAD.js +0 -9
- package/dist/bump-deps-QNM3H4GI.js.map +0 -1
- package/dist/chunk-2DCA7FHZ.js.map +0 -1
- package/dist/chunk-42KKVLYN.js.map +0 -1
- package/dist/chunk-45AUGDFR.js.map +0 -1
- package/dist/chunk-6G7BC5SN.js.map +0 -1
- package/dist/chunk-6JFOAPW4.js.map +0 -1
- package/dist/chunk-6MT5W6UF.js.map +0 -1
- package/dist/chunk-C3PDS623.js.map +0 -1
- package/dist/chunk-E2GKKUEX.js.map +0 -1
- package/dist/chunk-GKSXZIMX.js.map +0 -1
- package/dist/chunk-LYCKN7ZR.js.map +0 -1
- package/dist/chunk-O6G4HHMF.js.map +0 -1
- package/dist/chunk-OTBA7DON.js.map +0 -1
- package/dist/convert-to-pnpm-XH7NYRVK.js.map +0 -1
- package/dist/init-UA6D7GKW.js.map +0 -1
- package/dist/selftest-55OJIWEB.js.map +0 -1
- package/dist/svelte-codemods-KJLNH6FQ.js.map +0 -1
- package/dist/sync-configs-TIBO6DUJ.js.map +0 -1
- package/dist/upgrade-TPTE26FG.js.map +0 -1
- /package/dist/{airtable-WGQQ3KAD.js.map → airtable-U2GTDTK5.js.map} +0 -0
- /package/dist/{announce-72R3LRNT.js.map → announce-HJCGEVJA.js.map} +0 -0
- /package/dist/{chunk-XWPZ46IN.js.map → chunk-BJCA7O4A.js.map} +0 -0
- /package/dist/{chunk-YQMYQAJX.js.map → chunk-GI4M2AMO.js.map} +0 -0
- /package/dist/{chunk-RINXPU3H.js.map → chunk-KLVMJYZZ.js.map} +0 -0
- /package/dist/{chunk-AESJBEM7.js.map → chunk-KRHPS3R4.js.map} +0 -0
- /package/dist/{chunk-A5KYRGQA.js.map → chunk-SQJ53Z7R.js.map} +0 -0
- /package/dist/{digest-Q2TAQHXF.js.map → digest-VX7Q4MNF.js.map} +0 -0
- /package/dist/{ensure-site-2PF522VL.js.map → ensure-site-O7G5ZJS7.js.map} +0 -0
- /package/dist/{github-signals-CLQC2IPY.js.map → github-signals-QVW2LB7N.js.map} +0 -0
- /package/dist/{launch-AENAS2O2.js.map → launch-JWXCTJRG.js.map} +0 -0
- /package/dist/{lighthouse-airtable-UBSXKHAQ.js.map → lighthouse-airtable-J5TLASJC.js.map} +0 -0
- /package/dist/{onboard-X6KJ5Z2V.js.map → onboard-6UQE5QIO.js.map} +0 -0
- /package/dist/{orchestrate-GADNFGDH.js.map → orchestrate-ZDH24Z6Y.js.map} +0 -0
- /package/dist/{preflight-D5RQGX4V.js.map → preflight-BNTMWTPG.js.map} +0 -0
- /package/dist/{renovate-dispatch-LG4RJGDY.js.map → renovate-dispatch-C3ALLACX.js.map} +0 -0
- /package/dist/{report-3FJ224H5.js.map → report-YLFSGNWR.js.map} +0 -0
- /package/dist/{self-updating-JTTFOJ7Q.js.map → self-updating-64FNRQB5.js.map} +0 -0
- /package/dist/{websites-6YTT5ZTO.js.map → websites-EH2ULELZ.js.map} +0 -0
- /package/dist/{write-audits-to-airtable-IWOO4FFH.js.map → write-audits-to-airtable-LZDYTNX5.js.map} +0 -0
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
import {
|
|
20
20
|
a11yRoutes,
|
|
21
21
|
smokeRoutes
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-LJPCMXR7.js";
|
|
23
23
|
|
|
24
24
|
// src/audits/deps.ts
|
|
25
25
|
import { readFile } from "fs/promises";
|
|
@@ -496,7 +496,10 @@ async function dependabotAudit(deps, repo, label) {
|
|
|
496
496
|
let alerts;
|
|
497
497
|
try {
|
|
498
498
|
alerts = await deps.listAlerts(repo);
|
|
499
|
-
} catch {
|
|
499
|
+
} catch (e) {
|
|
500
|
+
console.error(
|
|
501
|
+
`[security] ${label}: dependabot API failed \u2014 degraded to pnpm audit: ${e instanceof Error ? e.message : String(e)}`
|
|
502
|
+
);
|
|
500
503
|
return null;
|
|
501
504
|
}
|
|
502
505
|
const counts = { low: 0, moderate: 0, high: 0, critical: 0 };
|
|
@@ -1187,11 +1190,29 @@ function summarizeBrowser(routes, links, familyCounts) {
|
|
|
1187
1190
|
const mobileOk = routes.length > 0 && routes.every((r) => r.mobile.length > 0 && r.mobile.every((m) => m.ok));
|
|
1188
1191
|
const brokenLinks = links.filter((l) => isBroken(l.status)).length;
|
|
1189
1192
|
const linksOk = links.length > 0 && brokenLinks === 0;
|
|
1193
|
+
const reachableOk = routes.length > 0 && routes.every((r) => r.status !== null && r.status >= 200 && r.status < 400);
|
|
1194
|
+
const trimmedTitles = routes.map((r) => (r.title ?? "").trim());
|
|
1195
|
+
const eachTitleMetaValid = routes.length > 0 && routes.every((r, i) => {
|
|
1196
|
+
const t = trimmedTitles[i];
|
|
1197
|
+
const desc = (r.metaDescription ?? "").trim();
|
|
1198
|
+
return t.length > 0 && t.length <= 70 && desc.length > 0;
|
|
1199
|
+
});
|
|
1200
|
+
const noDuplicateTitles = new Set(trimmedTitles).size === trimmedTitles.length;
|
|
1201
|
+
const titleMetaOk = eachTitleMetaValid && noDuplicateTitles;
|
|
1190
1202
|
const engines = [...new Set(desktopChecks.map((d) => d.engine))];
|
|
1191
1203
|
const devices = [...new Set(mobileChecks.map((m) => m.device))];
|
|
1192
1204
|
const families = Object.entries(familyCounts).map(([f, n]) => f === "/" ? "/" : `${f} \xD7${n}`).join(", ");
|
|
1193
1205
|
const note = `${routes.length} routes (${families}); desktop ${engines.join("/") || "\u2014"}; mobile ${devices.join("/") || "\u2014"}; ${links.length} links, ${brokenLinks} broken`;
|
|
1194
|
-
return {
|
|
1206
|
+
return {
|
|
1207
|
+
desktopOk,
|
|
1208
|
+
mobileOk,
|
|
1209
|
+
linksOk,
|
|
1210
|
+
reachableOk,
|
|
1211
|
+
titleMetaOk,
|
|
1212
|
+
brokenLinks,
|
|
1213
|
+
routesChecked: routes.length,
|
|
1214
|
+
note
|
|
1215
|
+
};
|
|
1195
1216
|
}
|
|
1196
1217
|
async function browserAudit(ctx) {
|
|
1197
1218
|
const { site } = ctx;
|
|
@@ -1263,6 +1284,9 @@ async function defaultBrowserRunner() {
|
|
|
1263
1284
|
for (const url of urls) {
|
|
1264
1285
|
const desktop = [];
|
|
1265
1286
|
const linkSet = /* @__PURE__ */ new Set();
|
|
1287
|
+
let status = null;
|
|
1288
|
+
let title = null;
|
|
1289
|
+
let metaDescription = null;
|
|
1266
1290
|
for (let i = 0; i < desktopEngines.length; i++) {
|
|
1267
1291
|
const engine = desktopEngines[i].engine;
|
|
1268
1292
|
const browser = browsers[i];
|
|
@@ -1279,6 +1303,11 @@ async function defaultBrowserRunner() {
|
|
|
1279
1303
|
const hasMain = await page.locator("main, [role=main]").first().isVisible().catch(() => false);
|
|
1280
1304
|
ok = !!resp && resp.ok() && errors.length === 0 && hasMain;
|
|
1281
1305
|
if (engine === "chromium") {
|
|
1306
|
+
status = resp ? resp.status() : null;
|
|
1307
|
+
title = (await page.title().catch(() => "")).trim() || null;
|
|
1308
|
+
metaDescription = (await page.evaluate(
|
|
1309
|
+
`document.querySelector('meta[name="description"]')?.getAttribute('content') || ''`
|
|
1310
|
+
).catch(() => "") || "").trim() || null;
|
|
1282
1311
|
const hrefs = await page.evaluate("Array.from(document.querySelectorAll('a[href]')).map((a) => a.href)").catch(() => []);
|
|
1283
1312
|
const origin = new URL(url).origin;
|
|
1284
1313
|
for (const h of hrefs) {
|
|
@@ -1320,7 +1349,15 @@ async function defaultBrowserRunner() {
|
|
|
1320
1349
|
}
|
|
1321
1350
|
mobile.push({ device, ok });
|
|
1322
1351
|
}
|
|
1323
|
-
results.push({
|
|
1352
|
+
results.push({
|
|
1353
|
+
url,
|
|
1354
|
+
desktop,
|
|
1355
|
+
mobile,
|
|
1356
|
+
links: [...linkSet],
|
|
1357
|
+
status,
|
|
1358
|
+
title,
|
|
1359
|
+
metaDescription
|
|
1360
|
+
});
|
|
1324
1361
|
}
|
|
1325
1362
|
} finally {
|
|
1326
1363
|
await Promise.all([...browsers, ...mobileBrowsers].map((b) => b.close().catch(() => {
|
|
@@ -1357,6 +1394,7 @@ async function defaultBrowserRunner() {
|
|
|
1357
1394
|
}
|
|
1358
1395
|
|
|
1359
1396
|
// src/audits/netlify-deploy.ts
|
|
1397
|
+
var NETLIFY_FETCH_TIMEOUT_MS = 15e3;
|
|
1360
1398
|
var READY_STATES = /* @__PURE__ */ new Set(["ready"]);
|
|
1361
1399
|
var FAILED_STATES = /* @__PURE__ */ new Set(["error", "failed", "rejected"]);
|
|
1362
1400
|
function isReadyState(state) {
|
|
@@ -1381,7 +1419,8 @@ function defaultNetlifyDeployDeps(token, now, fetchImpl = fetch) {
|
|
|
1381
1419
|
let res;
|
|
1382
1420
|
try {
|
|
1383
1421
|
res = await fetchImpl(url, {
|
|
1384
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
1422
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
1423
|
+
signal: AbortSignal.timeout(NETLIFY_FETCH_TIMEOUT_MS)
|
|
1385
1424
|
});
|
|
1386
1425
|
} catch {
|
|
1387
1426
|
return { ok: false };
|
|
@@ -1456,6 +1495,349 @@ async function netlifyDeployAudit(ctx) {
|
|
|
1456
1495
|
};
|
|
1457
1496
|
}
|
|
1458
1497
|
|
|
1498
|
+
// src/audits/function-health.ts
|
|
1499
|
+
var HEALTH_TIMEOUT_MS = 1e4;
|
|
1500
|
+
var ERRORING_BODY = { ok: false, prismic: null, forms: null };
|
|
1501
|
+
function parseHealthBody(raw) {
|
|
1502
|
+
if (!raw || typeof raw !== "object") return { present: false };
|
|
1503
|
+
const o = raw;
|
|
1504
|
+
if (typeof o["ok"] !== "boolean") return { present: false };
|
|
1505
|
+
const prismic = o["prismic"] === "ok" || o["prismic"] === "error" || o["prismic"] === "skipped" ? o["prismic"] : null;
|
|
1506
|
+
const f = o["forms"];
|
|
1507
|
+
const forms = f && typeof f === "object" ? {
|
|
1508
|
+
ingestUrl: f["ingestUrl"] === true,
|
|
1509
|
+
ingestToken: f["ingestToken"] === true,
|
|
1510
|
+
turnstile: f["turnstile"] === true
|
|
1511
|
+
} : null;
|
|
1512
|
+
return { present: true, body: { ok: o["ok"], prismic, forms } };
|
|
1513
|
+
}
|
|
1514
|
+
function defaultFunctionHealthDeps(now, fetchImpl = fetch) {
|
|
1515
|
+
return {
|
|
1516
|
+
now,
|
|
1517
|
+
fetchHealth: async (deployedUrl) => {
|
|
1518
|
+
let url;
|
|
1519
|
+
try {
|
|
1520
|
+
url = new URL("/health", deployedUrl).toString();
|
|
1521
|
+
} catch {
|
|
1522
|
+
return { present: false };
|
|
1523
|
+
}
|
|
1524
|
+
let res;
|
|
1525
|
+
try {
|
|
1526
|
+
res = await fetchImpl(url, { signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS) });
|
|
1527
|
+
} catch {
|
|
1528
|
+
return { present: false };
|
|
1529
|
+
}
|
|
1530
|
+
if (res.status === 404) return { present: false };
|
|
1531
|
+
if (!res.ok) return { present: true, body: ERRORING_BODY };
|
|
1532
|
+
let body;
|
|
1533
|
+
try {
|
|
1534
|
+
body = await res.json();
|
|
1535
|
+
} catch {
|
|
1536
|
+
return { present: true, body: ERRORING_BODY };
|
|
1537
|
+
}
|
|
1538
|
+
return parseHealthBody(body);
|
|
1539
|
+
}
|
|
1540
|
+
};
|
|
1541
|
+
}
|
|
1542
|
+
async function functionHealthAudit(ctx) {
|
|
1543
|
+
const { site } = ctx;
|
|
1544
|
+
const label = siteLabel(site);
|
|
1545
|
+
if (!site.deployedUrl) {
|
|
1546
|
+
return { audit: "function-health", site: label, status: "skip", summary: "no deployed URL" };
|
|
1547
|
+
}
|
|
1548
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
1549
|
+
const deps = ctx.functionHealthDeps ?? defaultFunctionHealthDeps(now);
|
|
1550
|
+
const fetched = await deps.fetchHealth(site.deployedUrl).catch(() => ({ present: false }));
|
|
1551
|
+
if (!fetched.present) {
|
|
1552
|
+
return {
|
|
1553
|
+
audit: "function-health",
|
|
1554
|
+
site: label,
|
|
1555
|
+
status: "skip",
|
|
1556
|
+
summary: "health endpoint unreachable / not adopted"
|
|
1557
|
+
};
|
|
1558
|
+
}
|
|
1559
|
+
const checkedAt = now.toISOString();
|
|
1560
|
+
const status = fetched.body.ok ? "pass" : "fail";
|
|
1561
|
+
const summary = `health ${fetched.body.ok ? "ok" : "not ok"} (prismic ${fetched.body.prismic ?? "?"})`;
|
|
1562
|
+
return {
|
|
1563
|
+
audit: "function-health",
|
|
1564
|
+
site: label,
|
|
1565
|
+
status,
|
|
1566
|
+
summary,
|
|
1567
|
+
details: {
|
|
1568
|
+
ok: fetched.body.ok,
|
|
1569
|
+
prismic: fetched.body.prismic,
|
|
1570
|
+
forms: fetched.body.forms,
|
|
1571
|
+
checkedAt
|
|
1572
|
+
}
|
|
1573
|
+
};
|
|
1574
|
+
}
|
|
1575
|
+
|
|
1576
|
+
// src/audits/smoke.ts
|
|
1577
|
+
import { access, readFile as readFile6 } from "fs/promises";
|
|
1578
|
+
import { join as join7 } from "path";
|
|
1579
|
+
var ANSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
1580
|
+
function summarizeSmokeFailure(stdout, stderr) {
|
|
1581
|
+
const lines = stdout.replace(ANSI, "").split("\n").map((l) => l.trim());
|
|
1582
|
+
const failing = [];
|
|
1583
|
+
for (let i = 0; i < lines.length; i++) {
|
|
1584
|
+
const line = lines[i];
|
|
1585
|
+
if (line === void 0) continue;
|
|
1586
|
+
if (/^\d+\)\s/.test(line)) {
|
|
1587
|
+
failing.push(line);
|
|
1588
|
+
let taken = 0;
|
|
1589
|
+
for (let j = i + 1; j < lines.length && taken < 3; j++) {
|
|
1590
|
+
const next = lines[j];
|
|
1591
|
+
if (!next) continue;
|
|
1592
|
+
if (/^\d+\)\s/.test(next)) break;
|
|
1593
|
+
failing.push(next);
|
|
1594
|
+
taken++;
|
|
1595
|
+
}
|
|
1596
|
+
break;
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
const tally = lines.find((l) => l !== void 0 && /\b\d+\s+failed\b/.test(l));
|
|
1600
|
+
const distilled = [tally, ...failing].filter(Boolean).join(" | ");
|
|
1601
|
+
if (distilled) return distilled.slice(0, 300);
|
|
1602
|
+
const err = stderr.replace(ANSI, "").trim();
|
|
1603
|
+
return err ? err.slice(0, 200) : "no reporter output";
|
|
1604
|
+
}
|
|
1605
|
+
async function hasTestSmokeScript(sitePath) {
|
|
1606
|
+
try {
|
|
1607
|
+
const raw = await readFile6(join7(sitePath, "package.json"), "utf-8");
|
|
1608
|
+
const pkg = JSON.parse(raw);
|
|
1609
|
+
return typeof pkg.scripts?.["test:smoke"] === "string";
|
|
1610
|
+
} catch {
|
|
1611
|
+
return false;
|
|
1612
|
+
}
|
|
1613
|
+
}
|
|
1614
|
+
async function hasNodeModules(sitePath) {
|
|
1615
|
+
try {
|
|
1616
|
+
await access(join7(sitePath, "node_modules"));
|
|
1617
|
+
return true;
|
|
1618
|
+
} catch {
|
|
1619
|
+
return false;
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
async function smokeAudit(ctx) {
|
|
1623
|
+
const spawn = ctx.spawn ?? defaultSpawn;
|
|
1624
|
+
const site = ctx.site;
|
|
1625
|
+
const label = siteLabel(site);
|
|
1626
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
1627
|
+
const checkedAt = now.toISOString();
|
|
1628
|
+
if (!await hasTestSmokeScript(site.path)) {
|
|
1629
|
+
return {
|
|
1630
|
+
audit: "smoke",
|
|
1631
|
+
site: label,
|
|
1632
|
+
status: "skip",
|
|
1633
|
+
summary: "no test:smoke script"
|
|
1634
|
+
};
|
|
1635
|
+
}
|
|
1636
|
+
if (!await hasNodeModules(site.path)) {
|
|
1637
|
+
let install;
|
|
1638
|
+
try {
|
|
1639
|
+
install = await spawn("pnpm", ["install", "--frozen-lockfile"], {
|
|
1640
|
+
cwd: site.path,
|
|
1641
|
+
timeoutMs: 5 * 6e4
|
|
1642
|
+
});
|
|
1643
|
+
} catch (err) {
|
|
1644
|
+
const e = err;
|
|
1645
|
+
if (e.code === "ENOENT" || /ENOENT/.test(String(err))) {
|
|
1646
|
+
return { audit: "smoke", site: label, status: "skip", summary: "pnpm not available" };
|
|
1647
|
+
}
|
|
1648
|
+
throw err;
|
|
1649
|
+
}
|
|
1650
|
+
if (install.code !== 0) {
|
|
1651
|
+
const retry = await spawn("pnpm", ["install"], { cwd: site.path, timeoutMs: 5 * 6e4 });
|
|
1652
|
+
if (retry.code !== 0) {
|
|
1653
|
+
return {
|
|
1654
|
+
audit: "smoke",
|
|
1655
|
+
site: label,
|
|
1656
|
+
status: "skip",
|
|
1657
|
+
summary: `smoke: pnpm install failed (exit ${retry.code}) \u2014 deps unavailable`
|
|
1658
|
+
};
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
}
|
|
1662
|
+
const port = await findFreePort();
|
|
1663
|
+
let raw;
|
|
1664
|
+
try {
|
|
1665
|
+
raw = await spawn("pnpm", ["test:smoke"], {
|
|
1666
|
+
cwd: site.path,
|
|
1667
|
+
env: { ...process.env, REDDOOR_SMOKE_PORT: String(port) },
|
|
1668
|
+
// Playwright on a cold tree installs chromium, boots the site's dev server,
|
|
1669
|
+
// and runs the smoke specs — the shared 30s default starves it (mirrors a11y).
|
|
1670
|
+
timeoutMs: 5 * 6e4
|
|
1671
|
+
});
|
|
1672
|
+
} catch (err) {
|
|
1673
|
+
const e = err;
|
|
1674
|
+
if (e.code === "ENOENT" || /ENOENT/.test(String(err))) {
|
|
1675
|
+
return { audit: "smoke", site: label, status: "skip", summary: "pnpm not available" };
|
|
1676
|
+
}
|
|
1677
|
+
throw err;
|
|
1678
|
+
}
|
|
1679
|
+
if (raw.code === 0) {
|
|
1680
|
+
return {
|
|
1681
|
+
audit: "smoke",
|
|
1682
|
+
site: label,
|
|
1683
|
+
status: "pass",
|
|
1684
|
+
summary: "smoke: suite green",
|
|
1685
|
+
details: { ok: "pass", checkedAt }
|
|
1686
|
+
};
|
|
1687
|
+
}
|
|
1688
|
+
return {
|
|
1689
|
+
audit: "smoke",
|
|
1690
|
+
site: label,
|
|
1691
|
+
status: "fail",
|
|
1692
|
+
summary: `smoke: suite failed (exit ${raw.code}) \u2014 ${summarizeSmokeFailure(raw.stdout, raw.stderr)}`,
|
|
1693
|
+
details: { ok: "fail", checkedAt }
|
|
1694
|
+
};
|
|
1695
|
+
}
|
|
1696
|
+
|
|
1697
|
+
// src/audits/form-e2e.ts
|
|
1698
|
+
var CF_TEST_SITEKEY = "1x00000000000000000000AA";
|
|
1699
|
+
var CONTACT_PATH = "/contact";
|
|
1700
|
+
var LIVE_ENV_VAR = "REDDOOR_FORM_E2E_LIVE";
|
|
1701
|
+
function liveRunnerEnabled(env = process.env) {
|
|
1702
|
+
return env[LIVE_ENV_VAR] === "1" || env[LIVE_ENV_VAR] === "true";
|
|
1703
|
+
}
|
|
1704
|
+
async function formE2eAudit(ctx) {
|
|
1705
|
+
const { site } = ctx;
|
|
1706
|
+
const label = siteLabel(site);
|
|
1707
|
+
if (!site.deployedUrl) {
|
|
1708
|
+
return { audit: "form-e2e", site: label, status: "skip", summary: "no deployed URL" };
|
|
1709
|
+
}
|
|
1710
|
+
if (!ctx.formRunner && !liveRunnerEnabled()) {
|
|
1711
|
+
return {
|
|
1712
|
+
audit: "form-e2e",
|
|
1713
|
+
site: label,
|
|
1714
|
+
status: "skip",
|
|
1715
|
+
summary: `live form-e2e disabled (central testMode ingest suppression not yet wired \u2014 set ${LIVE_ENV_VAR}=1 once it is)`
|
|
1716
|
+
};
|
|
1717
|
+
}
|
|
1718
|
+
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
1719
|
+
const checkedAt = now.toISOString();
|
|
1720
|
+
const runner = ctx.formRunner ?? await defaultFormRunner();
|
|
1721
|
+
try {
|
|
1722
|
+
const outcome = await runner.submit({
|
|
1723
|
+
baseUrl: site.deployedUrl,
|
|
1724
|
+
testMode: true,
|
|
1725
|
+
testSitekey: CF_TEST_SITEKEY
|
|
1726
|
+
});
|
|
1727
|
+
if ("testModeUndeclared" in outcome) {
|
|
1728
|
+
return {
|
|
1729
|
+
audit: "form-e2e",
|
|
1730
|
+
site: label,
|
|
1731
|
+
status: "skip",
|
|
1732
|
+
summary: "site /health does not declare forms.testMode \u2014 probe refused (testMode forwarding not yet rolled out here)"
|
|
1733
|
+
};
|
|
1734
|
+
}
|
|
1735
|
+
if (!outcome.formPresent) {
|
|
1736
|
+
return {
|
|
1737
|
+
audit: "form-e2e",
|
|
1738
|
+
site: label,
|
|
1739
|
+
status: "skip",
|
|
1740
|
+
summary: "no contact form (n/a)",
|
|
1741
|
+
details: { ok: null, formPresent: false, checkedAt }
|
|
1742
|
+
};
|
|
1743
|
+
}
|
|
1744
|
+
const ok = outcome.success ? "pass" : "fail";
|
|
1745
|
+
return {
|
|
1746
|
+
audit: "form-e2e",
|
|
1747
|
+
site: label,
|
|
1748
|
+
status: outcome.success ? "pass" : "warn",
|
|
1749
|
+
summary: outcome.success ? "form-e2e: synthetic submission succeeded" : `form-e2e: synthetic submission failed${outcome.detail ? ` \u2014 ${outcome.detail}` : ""}`,
|
|
1750
|
+
details: { ok, formPresent: true, checkedAt }
|
|
1751
|
+
};
|
|
1752
|
+
} finally {
|
|
1753
|
+
await runner.close?.();
|
|
1754
|
+
}
|
|
1755
|
+
}
|
|
1756
|
+
var FILL_SETTLE_MS = 1200;
|
|
1757
|
+
var PAGE_TIMEOUT_MS2 = 3e4;
|
|
1758
|
+
var HEALTH_TIMEOUT_MS2 = 1e4;
|
|
1759
|
+
async function declaresTestModeForwarding(baseUrl) {
|
|
1760
|
+
try {
|
|
1761
|
+
const res = await fetch(new URL("/health", baseUrl), {
|
|
1762
|
+
signal: AbortSignal.timeout(HEALTH_TIMEOUT_MS2)
|
|
1763
|
+
});
|
|
1764
|
+
if (!res.ok) return false;
|
|
1765
|
+
const body = await res.json();
|
|
1766
|
+
if (typeof body !== "object" || body === null) return false;
|
|
1767
|
+
const forms = body.forms;
|
|
1768
|
+
if (typeof forms !== "object" || forms === null) return false;
|
|
1769
|
+
return forms.testMode === true;
|
|
1770
|
+
} catch {
|
|
1771
|
+
return false;
|
|
1772
|
+
}
|
|
1773
|
+
}
|
|
1774
|
+
async function defaultFormRunner() {
|
|
1775
|
+
const { chromium } = await import("@playwright/test");
|
|
1776
|
+
return {
|
|
1777
|
+
async submit({ baseUrl, testSitekey }) {
|
|
1778
|
+
if (!await declaresTestModeForwarding(baseUrl)) {
|
|
1779
|
+
return { testModeUndeclared: true };
|
|
1780
|
+
}
|
|
1781
|
+
const browser = await chromium.launch();
|
|
1782
|
+
try {
|
|
1783
|
+
const ctx = await browser.newContext();
|
|
1784
|
+
const page = await ctx.newPage();
|
|
1785
|
+
const url = new URL(CONTACT_PATH, baseUrl).toString();
|
|
1786
|
+
const resp = await page.goto(url, { waitUntil: "domcontentloaded", timeout: PAGE_TIMEOUT_MS2 }).catch(() => null);
|
|
1787
|
+
const form = page.locator("form").first();
|
|
1788
|
+
const hasForm = !!resp && resp.ok() && await form.count().catch(() => 0) > 0 && await page.locator('input[name="email"], input[type="email"]').count().catch(() => 0) > 0;
|
|
1789
|
+
if (!hasForm) return { formPresent: false };
|
|
1790
|
+
await page.fill('[name="name"]', "Reddoor Monitor").catch(() => {
|
|
1791
|
+
});
|
|
1792
|
+
await page.fill('[name="email"]', "monitor+e2e@reddoorla.com").catch(() => {
|
|
1793
|
+
});
|
|
1794
|
+
await page.fill('[name="phone"]', "5555550123").catch(() => {
|
|
1795
|
+
});
|
|
1796
|
+
await page.fill('[name="message"]', "Synthetic end-to-end health check \u2014 please ignore.").catch(() => {
|
|
1797
|
+
});
|
|
1798
|
+
await page.evaluate(`
|
|
1799
|
+
(function () {
|
|
1800
|
+
const f = document.querySelector("form");
|
|
1801
|
+
if (!f) return;
|
|
1802
|
+
const add = (name, value) => {
|
|
1803
|
+
let el = f.querySelector('input[name="' + name + '"]');
|
|
1804
|
+
if (!el) {
|
|
1805
|
+
el = document.createElement("input");
|
|
1806
|
+
el.type = "hidden";
|
|
1807
|
+
el.name = name;
|
|
1808
|
+
f.appendChild(el);
|
|
1809
|
+
}
|
|
1810
|
+
el.value = value;
|
|
1811
|
+
};
|
|
1812
|
+
add("testMode", "true");
|
|
1813
|
+
add("cf-turnstile-response", ${JSON.stringify(`testmode-${testSitekey}`)});
|
|
1814
|
+
})();
|
|
1815
|
+
`);
|
|
1816
|
+
await page.waitForTimeout(FILL_SETTLE_MS);
|
|
1817
|
+
const postResponse = page.waitForResponse((r) => r.request().method() === "POST", {
|
|
1818
|
+
timeout: PAGE_TIMEOUT_MS2
|
|
1819
|
+
}).catch(() => null);
|
|
1820
|
+
await page.locator('button[type="submit"], input[type="submit"]').first().click({ timeout: PAGE_TIMEOUT_MS2 });
|
|
1821
|
+
const ok = await page.locator('[role="status"]').first().waitFor({ state: "visible", timeout: PAGE_TIMEOUT_MS2 }).then(() => true).catch(() => false);
|
|
1822
|
+
if (ok) return { formPresent: true, success: true };
|
|
1823
|
+
const actionResp = await postResponse;
|
|
1824
|
+
const alertText = await page.locator('[role="alert"]').first().textContent({ timeout: 1e3 }).catch(() => null);
|
|
1825
|
+
const respBit = actionResp ? `POST ${actionResp.status()}${actionResp.status() >= 400 ? ` ${(await actionResp.text().catch(() => "")).slice(0, 80)}` : ""}` : "no POST observed";
|
|
1826
|
+
return {
|
|
1827
|
+
formPresent: true,
|
|
1828
|
+
success: false,
|
|
1829
|
+
detail: `no success banner after submit \u2014 ${respBit}${alertText ? `; alert: ${alertText.trim().slice(0, 80)}` : ""}`
|
|
1830
|
+
};
|
|
1831
|
+
} catch (err) {
|
|
1832
|
+
return { formPresent: true, success: false, detail: String(err).slice(0, 120) };
|
|
1833
|
+
} finally {
|
|
1834
|
+
await browser.close().catch(() => {
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
};
|
|
1839
|
+
}
|
|
1840
|
+
|
|
1459
1841
|
// src/audits/index.ts
|
|
1460
1842
|
var REGISTRY = {
|
|
1461
1843
|
deps: depsAudit,
|
|
@@ -1465,7 +1847,10 @@ var REGISTRY = {
|
|
|
1465
1847
|
a11y: a11yAudit,
|
|
1466
1848
|
domain: domainAudit,
|
|
1467
1849
|
browser: browserAudit,
|
|
1468
|
-
"netlify-deploy": netlifyDeployAudit
|
|
1850
|
+
"netlify-deploy": netlifyDeployAudit,
|
|
1851
|
+
"function-health": functionHealthAudit,
|
|
1852
|
+
smoke: smokeAudit,
|
|
1853
|
+
"form-e2e": formE2eAudit
|
|
1469
1854
|
};
|
|
1470
1855
|
var ALL_AUDIT_NAMES = Object.keys(REGISTRY);
|
|
1471
1856
|
var DEFAULT_AUDIT_TIMEOUT_MS = 3e4;
|
|
@@ -1510,4 +1895,4 @@ export {
|
|
|
1510
1895
|
runAudits,
|
|
1511
1896
|
runAuditsAcross
|
|
1512
1897
|
};
|
|
1513
|
-
//# sourceMappingURL=chunk-
|
|
1898
|
+
//# sourceMappingURL=chunk-BHE5CUWI.js.map
|