@reddoorla/maintenance 0.72.0 → 0.75.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.
Files changed (106) hide show
  1. package/dist/airtable-6L3JD7FY.js +9 -0
  2. package/dist/{announce-HJCGEVJA.js → announce-V6EYPPUR.js} +4 -4
  3. package/dist/{blux-FKZYMKLF.js → blux-C24OVDDW.js} +1044 -69
  4. package/dist/blux-C24OVDDW.js.map +1 -0
  5. package/dist/{bump-deps-GZEJAUKI.js → bump-deps-OQR3IXLN.js} +2 -2
  6. package/dist/chunk-2OP4JKIQ.js +19 -0
  7. package/dist/chunk-2OP4JKIQ.js.map +1 -0
  8. package/dist/{chunk-BJCA7O4A.js → chunk-3PU2QWOJ.js} +2 -2
  9. package/dist/{chunk-GI4M2AMO.js → chunk-4BHNP6Y2.js} +2 -2
  10. package/dist/{chunk-KRHPS3R4.js → chunk-6OYICQSJ.js} +47 -3
  11. package/dist/chunk-6OYICQSJ.js.map +1 -0
  12. package/dist/{chunk-BWKK2ZXS.js → chunk-GXVX4HUH.js} +32 -2
  13. package/dist/chunk-GXVX4HUH.js.map +1 -0
  14. package/dist/{chunk-KLB2OLPW.js → chunk-HBFMTM2E.js} +3 -3
  15. package/dist/{chunk-SQJ53Z7R.js → chunk-I55YWV6T.js} +3 -3
  16. package/dist/{chunk-7P7YETHG.js → chunk-IDBMYDVH.js} +2 -2
  17. package/dist/{chunk-JGLAWXUC.js → chunk-JR67FZVC.js} +7 -3
  18. package/dist/chunk-JR67FZVC.js.map +1 -0
  19. package/dist/{chunk-APTAITXZ.js → chunk-KKL3RYFQ.js} +2 -2
  20. package/dist/{chunk-NRISG4XD.js → chunk-NUIBQPB3.js} +42 -11
  21. package/dist/chunk-NUIBQPB3.js.map +1 -0
  22. package/dist/{chunk-BHE5CUWI.js → chunk-PERP7LSS.js} +269 -18
  23. package/dist/chunk-PERP7LSS.js.map +1 -0
  24. package/dist/chunk-RHPDQW6D.js +29 -0
  25. package/dist/chunk-RHPDQW6D.js.map +1 -0
  26. package/dist/{chunk-HR5SONJC.js → chunk-SK44IFZN.js} +2 -2
  27. package/dist/cli/bin.js +31 -19
  28. package/dist/cli/bin.js.map +1 -1
  29. package/dist/cli/commands/audit.js +6 -6
  30. package/dist/{convert-to-pnpm-SVFDLXZS.js → convert-to-pnpm-OVMASATZ.js} +2 -2
  31. package/dist/{digest-VX7Q4MNF.js → digest-LEMZS5ZK.js} +99 -7
  32. package/dist/digest-LEMZS5ZK.js.map +1 -0
  33. package/dist/{ensure-site-O7G5ZJS7.js → ensure-site-3SOAWTZF.js} +2 -2
  34. package/dist/forms/index.js +1 -1
  35. package/dist/{github-signals-QVW2LB7N.js → github-signals-TF3VSNIN.js} +4 -4
  36. package/dist/{health-endpoint-IYR5VBUT.js → health-endpoint-JZGYCMTV.js} +2 -2
  37. package/dist/index.d.ts +104 -19
  38. package/dist/index.js +122 -30
  39. package/dist/index.js.map +1 -1
  40. package/dist/{init-CU6PVZRG.js → init-7PTP4O2K.js} +4 -4
  41. package/dist/{launch-JWXCTJRG.js → launch-W33FWWOZ.js} +6 -6
  42. package/dist/{lighthouse-airtable-J5TLASJC.js → lighthouse-airtable-ZXNMDEPE.js} +3 -3
  43. package/dist/{onboard-6UQE5QIO.js → onboard-PX36YDZA.js} +2 -2
  44. package/dist/{orchestrate-ZDH24Z6Y.js → orchestrate-NYRYEKQM.js} +3 -3
  45. package/dist/{preflight-BNTMWTPG.js → preflight-JCQFKB6M.js} +4 -4
  46. package/dist/{renovate-dispatch-C3ALLACX.js → renovate-dispatch-6II2JVOX.js} +29 -27
  47. package/dist/renovate-dispatch-6II2JVOX.js.map +1 -0
  48. package/dist/{report-YLFSGNWR.js → report-POHQEV6F.js} +21 -5
  49. package/dist/report-POHQEV6F.js.map +1 -0
  50. package/dist/screenouts-7P5GOZMV.js +57 -0
  51. package/dist/screenouts-7P5GOZMV.js.map +1 -0
  52. package/dist/{self-updating-64FNRQB5.js → self-updating-2YSVNMPP.js} +2 -2
  53. package/dist/{selftest-7DPNGL6R.js → selftest-OO2WDSDM.js} +5 -5
  54. package/dist/{smoke-suite-TWILWDSF.js → smoke-suite-CSP23NZM.js} +2 -2
  55. package/dist/spam-classifier-VG4ICIWS.js +217 -0
  56. package/dist/spam-classifier-VG4ICIWS.js.map +1 -0
  57. package/dist/submissions-BNRGY3JQ.js +122 -0
  58. package/dist/submissions-BNRGY3JQ.js.map +1 -0
  59. package/dist/submissions-MDKWJYMM.js +281 -0
  60. package/dist/submissions-MDKWJYMM.js.map +1 -0
  61. package/dist/{svelte-codemods-RVDTTXFD.js → svelte-codemods-V54KOBWU.js} +2 -2
  62. package/dist/{sync-configs-BUUD6L5Q.js → sync-configs-UBCJRASZ.js} +2 -2
  63. package/dist/{upgrade-HHKWYJRN.js → upgrade-WR6ICPAG.js} +2 -2
  64. package/dist/{websites-EH2ULELZ.js → websites-U6642XSE.js} +10 -2
  65. package/dist/{write-audits-to-airtable-LZDYTNX5.js → write-audits-to-airtable-22T6D733.js} +4 -4
  66. package/package.json +1 -1
  67. package/dist/airtable-U2GTDTK5.js +0 -9
  68. package/dist/blux-FKZYMKLF.js.map +0 -1
  69. package/dist/chunk-BHE5CUWI.js.map +0 -1
  70. package/dist/chunk-BWKK2ZXS.js.map +0 -1
  71. package/dist/chunk-JGLAWXUC.js.map +0 -1
  72. package/dist/chunk-KRHPS3R4.js.map +0 -1
  73. package/dist/chunk-NRISG4XD.js.map +0 -1
  74. package/dist/chunk-QTHKOIFJ.js +0 -13
  75. package/dist/chunk-QTHKOIFJ.js.map +0 -1
  76. package/dist/digest-VX7Q4MNF.js.map +0 -1
  77. package/dist/renovate-dispatch-C3ALLACX.js.map +0 -1
  78. package/dist/report-YLFSGNWR.js.map +0 -1
  79. /package/dist/{airtable-U2GTDTK5.js.map → airtable-6L3JD7FY.js.map} +0 -0
  80. /package/dist/{announce-HJCGEVJA.js.map → announce-V6EYPPUR.js.map} +0 -0
  81. /package/dist/{bump-deps-GZEJAUKI.js.map → bump-deps-OQR3IXLN.js.map} +0 -0
  82. /package/dist/{chunk-BJCA7O4A.js.map → chunk-3PU2QWOJ.js.map} +0 -0
  83. /package/dist/{chunk-GI4M2AMO.js.map → chunk-4BHNP6Y2.js.map} +0 -0
  84. /package/dist/{chunk-KLB2OLPW.js.map → chunk-HBFMTM2E.js.map} +0 -0
  85. /package/dist/{chunk-SQJ53Z7R.js.map → chunk-I55YWV6T.js.map} +0 -0
  86. /package/dist/{chunk-7P7YETHG.js.map → chunk-IDBMYDVH.js.map} +0 -0
  87. /package/dist/{chunk-APTAITXZ.js.map → chunk-KKL3RYFQ.js.map} +0 -0
  88. /package/dist/{chunk-HR5SONJC.js.map → chunk-SK44IFZN.js.map} +0 -0
  89. /package/dist/{convert-to-pnpm-SVFDLXZS.js.map → convert-to-pnpm-OVMASATZ.js.map} +0 -0
  90. /package/dist/{ensure-site-O7G5ZJS7.js.map → ensure-site-3SOAWTZF.js.map} +0 -0
  91. /package/dist/{github-signals-QVW2LB7N.js.map → github-signals-TF3VSNIN.js.map} +0 -0
  92. /package/dist/{health-endpoint-IYR5VBUT.js.map → health-endpoint-JZGYCMTV.js.map} +0 -0
  93. /package/dist/{init-CU6PVZRG.js.map → init-7PTP4O2K.js.map} +0 -0
  94. /package/dist/{launch-JWXCTJRG.js.map → launch-W33FWWOZ.js.map} +0 -0
  95. /package/dist/{lighthouse-airtable-J5TLASJC.js.map → lighthouse-airtable-ZXNMDEPE.js.map} +0 -0
  96. /package/dist/{onboard-6UQE5QIO.js.map → onboard-PX36YDZA.js.map} +0 -0
  97. /package/dist/{orchestrate-ZDH24Z6Y.js.map → orchestrate-NYRYEKQM.js.map} +0 -0
  98. /package/dist/{preflight-BNTMWTPG.js.map → preflight-JCQFKB6M.js.map} +0 -0
  99. /package/dist/{self-updating-64FNRQB5.js.map → self-updating-2YSVNMPP.js.map} +0 -0
  100. /package/dist/{selftest-7DPNGL6R.js.map → selftest-OO2WDSDM.js.map} +0 -0
  101. /package/dist/{smoke-suite-TWILWDSF.js.map → smoke-suite-CSP23NZM.js.map} +0 -0
  102. /package/dist/{svelte-codemods-RVDTTXFD.js.map → svelte-codemods-V54KOBWU.js.map} +0 -0
  103. /package/dist/{sync-configs-BUUD6L5Q.js.map → sync-configs-UBCJRASZ.js.map} +0 -0
  104. /package/dist/{upgrade-HHKWYJRN.js.map → upgrade-WR6ICPAG.js.map} +0 -0
  105. /package/dist/{websites-EH2ULELZ.js.map → websites-U6642XSE.js.map} +0 -0
  106. /package/dist/{write-audits-to-airtable-LZDYTNX5.js.map → write-audits-to-airtable-22T6D733.js.map} +0 -0
@@ -1119,6 +1119,67 @@ function parseHtmlLinks(html, baseUrl) {
1119
1119
  function family(pathname) {
1120
1120
  return pathname.split("/").filter(Boolean)[0] ?? "";
1121
1121
  }
1122
+ var ASSET_EXTENSIONS = /* @__PURE__ */ new Set([
1123
+ "pdf",
1124
+ "doc",
1125
+ "docx",
1126
+ "xls",
1127
+ "xlsx",
1128
+ "ppt",
1129
+ "pptx",
1130
+ "csv",
1131
+ "txt",
1132
+ "rtf",
1133
+ "jpg",
1134
+ "jpeg",
1135
+ "png",
1136
+ "gif",
1137
+ "webp",
1138
+ "avif",
1139
+ "svg",
1140
+ "ico",
1141
+ "bmp",
1142
+ "tif",
1143
+ "tiff",
1144
+ "css",
1145
+ "js",
1146
+ "mjs",
1147
+ "map",
1148
+ "json",
1149
+ "xml",
1150
+ "webmanifest",
1151
+ "rss",
1152
+ "atom",
1153
+ "zip",
1154
+ "gz",
1155
+ "tar",
1156
+ "rar",
1157
+ "7z",
1158
+ "dmg",
1159
+ "exe",
1160
+ "mp3",
1161
+ "mp4",
1162
+ "webm",
1163
+ "mov",
1164
+ "avi",
1165
+ "wav",
1166
+ "ogg",
1167
+ "m4a",
1168
+ "m4v",
1169
+ "woff",
1170
+ "woff2",
1171
+ "ttf",
1172
+ "otf",
1173
+ "eot"
1174
+ ]);
1175
+ function isPageRoutePath(pathname) {
1176
+ const last = pathname.split("/").filter(Boolean).pop() ?? "";
1177
+ const dot = last.lastIndexOf(".");
1178
+ if (dot <= 0) return true;
1179
+ const ext = last.slice(dot + 1).toLowerCase();
1180
+ if (ext === "html" || ext === "htm") return true;
1181
+ return !ASSET_EXTENSIONS.has(ext);
1182
+ }
1122
1183
  function sampleRoutePaths(urlsOrPaths, cap = DEFAULT_CAP) {
1123
1184
  const seen = /* @__PURE__ */ new Set(["/"]);
1124
1185
  const buckets = /* @__PURE__ */ new Map();
@@ -1129,9 +1190,11 @@ function sampleRoutePaths(urlsOrPaths, cap = DEFAULT_CAP) {
1129
1190
  } catch {
1130
1191
  continue;
1131
1192
  }
1132
- if (pathname === "/") continue;
1193
+ pathname = pathname.replace(/\/+$/, "");
1194
+ if (pathname === "") continue;
1133
1195
  if (seen.has(pathname)) continue;
1134
1196
  seen.add(pathname);
1197
+ if (!isPageRoutePath(pathname)) continue;
1135
1198
  const fam = family(pathname);
1136
1199
  const arr = buckets.get(fam) ?? [];
1137
1200
  arr.push(pathname);
@@ -1183,32 +1246,174 @@ async function discoverRoutes(deployedUrl, deps, cap = DEFAULT_CAP) {
1183
1246
  function isBroken(status) {
1184
1247
  return status === null || status >= 400;
1185
1248
  }
1249
+ function isOkStatus(status) {
1250
+ return status !== null && status >= 200 && status < 400;
1251
+ }
1252
+ function decodeEntities(s) {
1253
+ return s.replace(/&amp;/gi, "&").replace(/&lt;/gi, "<").replace(/&gt;/gi, ">").replace(/&quot;/gi, '"').replace(/&#0?39;|&apos;/gi, "'").replace(/&nbsp;/gi, " ");
1254
+ }
1255
+ function extractTitleMeta(html) {
1256
+ const t = /<title[^>]*>([\s\S]*?)<\/title>/i.exec(html);
1257
+ const title = t?.[1] ? decodeEntities(t[1]).trim() || null : null;
1258
+ let metaDescription = null;
1259
+ const metaTags = html.match(/<meta\b[^>]*>/gi) ?? [];
1260
+ for (const tag of metaTags) {
1261
+ if (!/\bname\s*=\s*["']description["']/i.test(tag)) continue;
1262
+ const content = /\bcontent\s*=\s*("([^"]*)"|'([^']*)')/i.exec(tag);
1263
+ const raw = content?.[2] ?? content?.[3] ?? "";
1264
+ metaDescription = decodeEntities(raw).trim() || null;
1265
+ break;
1266
+ }
1267
+ return { title, metaDescription };
1268
+ }
1269
+ var REVERIFY_RETRY_DELAYS_MS = [15e3, 45e3];
1270
+ var defaultSleep = (ms) => new Promise((r) => setTimeout(r, ms));
1271
+ function isRetryableStatus(status) {
1272
+ return status === null || status === 403 || status === 408 || status === 429 || status >= 500;
1273
+ }
1274
+ async function fetchStatusesWithCooldown(urls, verify, sleep) {
1275
+ const byUrl = /* @__PURE__ */ new Map();
1276
+ for (const url of new Set(urls)) byUrl.set(url, await verify.fetchPage(url));
1277
+ for (const delayMs of REVERIFY_RETRY_DELAYS_MS) {
1278
+ const pending = [...byUrl.entries()].filter(([, f]) => !isOkStatus(f.status) && isRetryableStatus(f.status)).map(([u]) => u);
1279
+ if (pending.length === 0) break;
1280
+ await sleep(delayMs);
1281
+ for (const url of pending) byUrl.set(url, await verify.fetchPage(url));
1282
+ }
1283
+ return byUrl;
1284
+ }
1285
+ async function reverifyRoutes(routes, verify, sleep = defaultSleep) {
1286
+ const statusIdx = [];
1287
+ const titleMetaIdx = [];
1288
+ routes.forEach((r, i) => {
1289
+ if (!isOkStatus(r.status)) statusIdx.push(i);
1290
+ else if (!(r.title ?? "").trim() || !(r.metaDescription ?? "").trim()) titleMetaIdx.push(i);
1291
+ });
1292
+ const statusFetches = await fetchStatusesWithCooldown(
1293
+ statusIdx.map((i) => routes[i].url),
1294
+ verify,
1295
+ sleep
1296
+ );
1297
+ const fetchedByIdx = /* @__PURE__ */ new Map();
1298
+ for (const i of titleMetaIdx) fetchedByIdx.set(i, await verify.fetchPage(routes[i].url));
1299
+ const out = [...routes];
1300
+ const reverified = [];
1301
+ for (const i of statusIdx) {
1302
+ const r = routes[i];
1303
+ const fetched = statusFetches.get(r.url);
1304
+ reverified.push({ url: r.url, browserStatus: r.status, fetchedStatus: fetched.status });
1305
+ const ok = isOkStatus(fetched.status);
1306
+ out[i] = {
1307
+ ...r,
1308
+ status: fetched.status,
1309
+ title: ok ? fetched.title : null,
1310
+ metaDescription: ok ? fetched.metaDescription : null
1311
+ };
1312
+ }
1313
+ for (const i of titleMetaIdx) {
1314
+ const r = routes[i];
1315
+ const fetched = fetchedByIdx.get(i);
1316
+ if (!isOkStatus(fetched.status)) continue;
1317
+ out[i] = {
1318
+ ...r,
1319
+ title: (r.title ?? "").trim() ? r.title : fetched.title,
1320
+ metaDescription: (r.metaDescription ?? "").trim() ? r.metaDescription : fetched.metaDescription
1321
+ };
1322
+ }
1323
+ return { routes: out, reverified };
1324
+ }
1325
+ function excuseChallengedEngineChecks(routes) {
1326
+ const excused = [];
1327
+ const excusable = (e) => !e.ok && typeof e.status === "number" && !isOkStatus(e.status) && isRetryableStatus(e.status);
1328
+ const out = routes.map((route) => {
1329
+ if (!isOkStatus(route.status)) return route;
1330
+ const desktop = route.desktop.map((d) => {
1331
+ if (!excusable(d)) return d;
1332
+ excused.push(
1333
+ `${route.url} [${d.engine}]: browser saw ${d.status}, route re-verified reachable (${route.status})`
1334
+ );
1335
+ return { ...d, ok: true };
1336
+ });
1337
+ const mobile = route.mobile.map((m) => {
1338
+ if (!excusable(m)) return m;
1339
+ excused.push(
1340
+ `${route.url} [${m.device}]: browser saw ${m.status}, route re-verified reachable (${route.status})`
1341
+ );
1342
+ return { ...m, ok: true };
1343
+ });
1344
+ return { ...route, desktop, mobile };
1345
+ });
1346
+ return { routes: out, excused };
1347
+ }
1348
+ async function reverifyLinks(links, verify, sleep = defaultSleep) {
1349
+ const suspects = links.filter((l) => isBroken(l.status) && isRetryableStatus(l.status));
1350
+ if (suspects.length === 0) return { links, reverified: [] };
1351
+ const fetched = await fetchStatusesWithCooldown(
1352
+ suspects.map((l) => l.url),
1353
+ verify,
1354
+ sleep
1355
+ );
1356
+ const suspectUrls = new Set(suspects.map((l) => l.url));
1357
+ const reverified = [];
1358
+ const out = links.map((l) => {
1359
+ if (!suspectUrls.has(l.url)) return l;
1360
+ const finalStatus = fetched.get(l.url).status;
1361
+ reverified.push({ url: l.url, firstStatus: l.status, fetchedStatus: finalStatus });
1362
+ return { url: l.url, status: finalStatus };
1363
+ });
1364
+ return { links: out, reverified };
1365
+ }
1366
+ var fmtEntryStatus = (s) => typeof s === "number" && !isOkStatus(s) ? ` \u2192 ${s}` : "";
1186
1367
  function summarizeBrowser(routes, links, familyCounts) {
1187
1368
  const desktopChecks = routes.flatMap((r) => r.desktop);
1188
1369
  const mobileChecks = routes.flatMap((r) => r.mobile);
1189
1370
  const desktopOk = routes.length > 0 && routes.every((r) => r.desktop.length > 0 && r.desktop.every((d) => d.ok));
1190
1371
  const mobileOk = routes.length > 0 && routes.every((r) => r.mobile.length > 0 && r.mobile.every((m) => m.ok));
1191
- const brokenLinks = links.filter((l) => isBroken(l.status)).length;
1372
+ const desktopFailures = routes.flatMap(
1373
+ (r) => r.desktop.length === 0 ? [`${r.url}: no desktop observations`] : r.desktop.filter((d) => !d.ok).map((d) => `${r.url} [${d.engine}]${fmtEntryStatus(d.status)}`)
1374
+ );
1375
+ const mobileFailures = routes.flatMap(
1376
+ (r) => r.mobile.length === 0 ? [`${r.url}: no mobile observations`] : r.mobile.filter((m) => !m.ok).map((m) => `${r.url} [${m.device}]${fmtEntryStatus(m.status)}`)
1377
+ );
1378
+ const brokenLinkUrls = links.filter((l) => isBroken(l.status)).map((l) => `${l.url} \u2192 ${l.status ?? "no response"}`);
1379
+ const brokenLinks = brokenLinkUrls.length;
1192
1380
  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];
1381
+ const unreachableUrls = routes.filter((r) => !isOkStatus(r.status)).map((r) => `${r.url} \u2192 ${r.status ?? "no response"}`);
1382
+ const reachableOk = routes.length > 0 && unreachableUrls.length === 0;
1383
+ const titleMetaProblems = [];
1384
+ for (const r of routes) {
1385
+ const t = (r.title ?? "").trim();
1197
1386
  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;
1387
+ if (t.length === 0) titleMetaProblems.push(`${r.url}: empty title`);
1388
+ else if (t.length > 70) titleMetaProblems.push(`${r.url}: title ${t.length} chars (max 70)`);
1389
+ if (desc.length === 0) titleMetaProblems.push(`${r.url}: missing meta description`);
1390
+ }
1391
+ const byTitle = /* @__PURE__ */ new Map();
1392
+ for (const r of routes) {
1393
+ const t = (r.title ?? "").trim();
1394
+ if (t.length === 0) continue;
1395
+ byTitle.set(t, [...byTitle.get(t) ?? [], r.url]);
1396
+ }
1397
+ for (const [t, urls] of byTitle) {
1398
+ if (urls.length > 1) titleMetaProblems.push(`duplicate title "${t}": ${urls.join(" + ")}`);
1399
+ }
1400
+ const titleMetaOk = routes.length > 0 && titleMetaProblems.length === 0;
1202
1401
  const engines = [...new Set(desktopChecks.map((d) => d.engine))];
1203
1402
  const devices = [...new Set(mobileChecks.map((m) => m.device))];
1204
1403
  const families = Object.entries(familyCounts).map(([f, n]) => f === "/" ? "/" : `${f} \xD7${n}`).join(", ");
1205
- const note = `${routes.length} routes (${families}); desktop ${engines.join("/") || "\u2014"}; mobile ${devices.join("/") || "\u2014"}; ${links.length} links, ${brokenLinks} broken`;
1404
+ const firstOf = (xs, n) => xs.slice(0, n).join("; ") + (xs.length > n ? ` (+${xs.length - n} more)` : "");
1405
+ const note = `${routes.length} routes (${families}); desktop ${engines.join("/") || "\u2014"}; mobile ${devices.join("/") || "\u2014"}; ${links.length} links, ${brokenLinks} broken` + (unreachableUrls.length > 0 ? `; unreachable: ${firstOf(unreachableUrls, 3)}` : "") + (titleMetaProblems.length > 0 ? `; title/meta: ${firstOf(titleMetaProblems, 3)}` : "") + (desktopFailures.length > 0 ? `; desktop failing: ${firstOf(desktopFailures, 3)}` : "") + (mobileFailures.length > 0 ? `; mobile failing: ${firstOf(mobileFailures, 3)}` : "") + (brokenLinkUrls.length > 0 ? `; broken: ${firstOf(brokenLinkUrls, 3)}` : "");
1206
1406
  return {
1207
1407
  desktopOk,
1208
1408
  mobileOk,
1209
1409
  linksOk,
1210
1410
  reachableOk,
1211
1411
  titleMetaOk,
1412
+ unreachableUrls,
1413
+ titleMetaProblems,
1414
+ desktopFailures,
1415
+ mobileFailures,
1416
+ brokenLinkUrls,
1212
1417
  brokenLinks,
1213
1418
  routesChecked: routes.length,
1214
1419
  note
@@ -1222,24 +1427,47 @@ async function browserAudit(ctx) {
1222
1427
  }
1223
1428
  const now = ctx.now ?? /* @__PURE__ */ new Date();
1224
1429
  const discoverDeps = ctx.discoverDeps ?? defaultDiscoverDeps();
1430
+ const verifyDeps = ctx.verifyDeps ?? defaultVerifyDeps();
1225
1431
  const runner = ctx.browserRunner ?? await defaultBrowserRunner();
1226
1432
  try {
1227
1433
  const discovered = await discoverRoutes(site.deployedUrl, discoverDeps);
1228
- const routeResults = await runner.probe(discovered.routes);
1434
+ const probed = await runner.probe(discovered.routes);
1435
+ const { routes: reverifiedRoutes, reverified } = await reverifyRoutes(probed, verifyDeps);
1436
+ const { routes: routeResults, excused } = excuseChallengedEngineChecks(reverifiedRoutes);
1229
1437
  const internalLinks = [...new Set(routeResults.flatMap((r) => r.links))];
1230
- const linkResults = await runner.checkLinks(internalLinks);
1438
+ const rawLinks = await runner.checkLinks(internalLinks);
1439
+ const { links: linkResults, reverified: linkReverified } = await reverifyLinks(
1440
+ rawLinks,
1441
+ verifyDeps
1442
+ );
1231
1443
  const summary = summarizeBrowser(
1232
1444
  routeResults,
1233
1445
  linkResults,
1234
1446
  discovered.familyCounts ?? familyCountsOf(discovered.routes)
1235
1447
  );
1448
+ const cleared = reverified.filter((v) => isOkStatus(v.fetchedStatus));
1449
+ const reverifiedUrls = cleared.map(
1450
+ (v) => `${v.url}: browser saw ${v.browserStatus ?? "no response"}, plain fetch ${v.fetchedStatus}`
1451
+ );
1452
+ const clearedLinks = linkReverified.filter((v) => isOkStatus(v.fetchedStatus));
1453
+ const linkReverifiedUrls = clearedLinks.map(
1454
+ (v) => `${v.url}: first check saw ${v.firstStatus ?? "no response"}, plain re-fetch ${v.fetchedStatus}`
1455
+ );
1456
+ const note = summary.note + (cleared.length > 0 ? `; ${cleared.length} route(s) re-verified reachable by plain fetch after a browser-side challenge/flake` : "") + (excused.length > 0 ? `; ${excused.length} engine/device check(s) excused (challenge on a re-verified-reachable route)` : "") + (clearedLinks.length > 0 ? `; ${clearedLinks.length} link(s) re-verified ok by plain fetch after cooldown` : "");
1236
1457
  const status = summary.desktopOk && summary.mobileOk && summary.linksOk ? "pass" : "warn";
1237
1458
  return {
1238
1459
  audit: "browser",
1239
1460
  site: label,
1240
1461
  status,
1241
- summary: summary.note,
1242
- details: { ...summary, checkedAt: now.toISOString() }
1462
+ summary: note,
1463
+ details: {
1464
+ ...summary,
1465
+ note,
1466
+ reverifiedUrls,
1467
+ excusedEngineChecks: excused,
1468
+ linkReverifiedUrls,
1469
+ checkedAt: now.toISOString()
1470
+ }
1243
1471
  };
1244
1472
  } finally {
1245
1473
  await runner.close?.();
@@ -1262,6 +1490,25 @@ function defaultDiscoverDeps() {
1262
1490
  }
1263
1491
  };
1264
1492
  }
1493
+ function defaultVerifyDeps() {
1494
+ return {
1495
+ fetchPage: async (url) => {
1496
+ try {
1497
+ const res = await fetch(url, {
1498
+ redirect: "follow",
1499
+ signal: AbortSignal.timeout(FETCH_TIMEOUT_MS)
1500
+ });
1501
+ const isHtml = (res.headers.get("content-type") ?? "").includes("html");
1502
+ if (!res.ok || !isHtml) {
1503
+ return { status: res.status, title: null, metaDescription: null };
1504
+ }
1505
+ return { status: res.status, ...extractTitleMeta(await res.text()) };
1506
+ } catch {
1507
+ return { status: null, title: null, metaDescription: null };
1508
+ }
1509
+ }
1510
+ };
1511
+ }
1265
1512
  var DESKTOP_VIEWPORT = { width: 1366, height: 900 };
1266
1513
  var PAGE_TIMEOUT_MS = 3e4;
1267
1514
  async function defaultBrowserRunner() {
@@ -1295,11 +1542,13 @@ async function defaultBrowserRunner() {
1295
1542
  const errors = [];
1296
1543
  page.on("pageerror", (e) => errors.push(String(e)));
1297
1544
  let ok = false;
1545
+ let navStatus = null;
1298
1546
  try {
1299
1547
  const resp = await page.goto(url, {
1300
1548
  waitUntil: "domcontentloaded",
1301
1549
  timeout: PAGE_TIMEOUT_MS
1302
1550
  });
1551
+ navStatus = resp ? resp.status() : null;
1303
1552
  const hasMain = await page.locator("main, [role=main]").first().isVisible().catch(() => false);
1304
1553
  ok = !!resp && resp.ok() && errors.length === 0 && hasMain;
1305
1554
  if (engine === "chromium") {
@@ -1323,7 +1572,7 @@ async function defaultBrowserRunner() {
1323
1572
  await ctx.close().catch(() => {
1324
1573
  });
1325
1574
  }
1326
- desktop.push({ engine, ok });
1575
+ desktop.push({ engine, ok, status: navStatus });
1327
1576
  }
1328
1577
  const mobile = [];
1329
1578
  for (let i = 0; i < mobileTargets.length; i++) {
@@ -1334,11 +1583,13 @@ async function defaultBrowserRunner() {
1334
1583
  const errors = [];
1335
1584
  page.on("pageerror", (e) => errors.push(String(e)));
1336
1585
  let ok = false;
1586
+ let navStatus = null;
1337
1587
  try {
1338
1588
  const resp = await page.goto(url, {
1339
1589
  waitUntil: "domcontentloaded",
1340
1590
  timeout: PAGE_TIMEOUT_MS
1341
1591
  });
1592
+ navStatus = resp ? resp.status() : null;
1342
1593
  const overflow = await page.evaluate("document.documentElement.scrollWidth > window.innerWidth + 2").catch(() => true);
1343
1594
  ok = !!resp && resp.ok() && errors.length === 0 && !overflow;
1344
1595
  } catch {
@@ -1347,7 +1598,7 @@ async function defaultBrowserRunner() {
1347
1598
  await ctx.close().catch(() => {
1348
1599
  });
1349
1600
  }
1350
- mobile.push({ device, ok });
1601
+ mobile.push({ device, ok, status: navStatus });
1351
1602
  }
1352
1603
  results.push({
1353
1604
  url,
@@ -1895,4 +2146,4 @@ export {
1895
2146
  runAudits,
1896
2147
  runAuditsAcross
1897
2148
  };
1898
- //# sourceMappingURL=chunk-BHE5CUWI.js.map
2149
+ //# sourceMappingURL=chunk-PERP7LSS.js.map