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