@reddoorla/maintenance 0.71.0 → 0.74.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-6L3JD7FY.js +9 -0
- package/dist/{announce-3JE5FKW7.js → announce-V6EYPPUR.js} +6 -6
- package/dist/{blux-FKZYMKLF.js → blux-OL5HKP26.js} +950 -69
- package/dist/blux-OL5HKP26.js.map +1 -0
- package/dist/{bump-deps-K3JMLMBB.js → bump-deps-OQR3IXLN.js} +2 -2
- package/dist/chunk-2OP4JKIQ.js +19 -0
- package/dist/chunk-2OP4JKIQ.js.map +1 -0
- package/dist/{chunk-5QC5KJHR.js → chunk-3PU2QWOJ.js} +2 -2
- package/dist/{chunk-Y7LZHPDF.js → chunk-4BHNP6Y2.js} +2 -2
- package/dist/{chunk-KOAGMTRI.js → chunk-6OYICQSJ.js} +47 -3
- package/dist/chunk-6OYICQSJ.js.map +1 -0
- package/dist/{chunk-WN4NEHE7.js → chunk-D4FVAIEJ.js} +5 -14
- package/dist/chunk-D4FVAIEJ.js.map +1 -0
- package/dist/{chunk-YWRQEDB3.js → chunk-GXVX4HUH.js} +46 -7
- package/dist/chunk-GXVX4HUH.js.map +1 -0
- package/dist/{chunk-J2SHWT2O.js → chunk-GYNRZCM6.js} +7 -2
- package/dist/{chunk-J2SHWT2O.js.map → chunk-GYNRZCM6.js.map} +1 -1
- package/dist/{chunk-VR246V3G.js → chunk-HBFMTM2E.js} +6 -6
- package/dist/chunk-HBFMTM2E.js.map +1 -0
- package/dist/{chunk-PSJQE6T6.js → chunk-I55YWV6T.js} +4 -4
- package/dist/{chunk-52TDNN7K.js → chunk-IDBMYDVH.js} +4 -4
- package/dist/{chunk-AYM2EFSK.js → chunk-JCITWZQR.js} +14 -14
- package/dist/chunk-JCITWZQR.js.map +1 -0
- package/dist/{chunk-CMQMNFFJ.js → chunk-JR67FZVC.js} +7 -3
- package/dist/chunk-JR67FZVC.js.map +1 -0
- package/dist/{chunk-MPRLMGJU.js → chunk-KKL3RYFQ.js} +2 -2
- package/dist/{chunk-2PWQ6MPG.js → chunk-KLVMJYZZ.js} +2 -2
- package/dist/{chunk-LPMQXS2N.js → chunk-LJPCMXR7.js} +1 -2
- package/dist/chunk-LJPCMXR7.js.map +1 -0
- package/dist/{chunk-6J25IIAI.js → chunk-NUIBQPB3.js} +187 -72
- package/dist/chunk-NUIBQPB3.js.map +1 -0
- package/dist/{chunk-M7HOQ4W7.js → chunk-PERP7LSS.js} +277 -21
- package/dist/chunk-PERP7LSS.js.map +1 -0
- package/dist/chunk-RHPDQW6D.js +29 -0
- package/dist/chunk-RHPDQW6D.js.map +1 -0
- package/dist/{chunk-F6OE6MGQ.js → chunk-SK44IFZN.js} +2 -2
- package/dist/cli/bin.js +31 -19
- package/dist/cli/bin.js.map +1 -1
- package/dist/cli/commands/audit.js +8 -8
- package/dist/configs/playwright-a11y.d.ts +1 -1
- package/dist/configs/playwright-a11y.js +1 -3
- package/dist/{convert-to-pnpm-6F4RAFEJ.js → convert-to-pnpm-OVMASATZ.js} +2 -2
- package/dist/{digest-GZCZYJKC.js → digest-LEMZS5ZK.js} +102 -10
- package/dist/digest-LEMZS5ZK.js.map +1 -0
- package/dist/{ensure-site-H5VMBPLF.js → ensure-site-3SOAWTZF.js} +2 -2
- package/dist/forms/index.d.ts +3 -2
- package/dist/forms/index.js +16 -9
- package/dist/forms/index.js.map +1 -1
- package/dist/{github-signals-SQECQNST.js → github-signals-TF3VSNIN.js} +5 -5
- package/dist/{health-endpoint-QXWM6GW2.js → health-endpoint-JZGYCMTV.js} +2 -2
- package/dist/index.d.ts +108 -22
- package/dist/index.js +127 -35
- package/dist/index.js.map +1 -1
- package/dist/{init-KQOIPLVT.js → init-7PTP4O2K.js} +5 -5
- package/dist/{launch-RX4VI7F2.js → launch-W33FWWOZ.js} +9 -9
- package/dist/{lighthouse-airtable-TTXULEGU.js → lighthouse-airtable-ZXNMDEPE.js} +3 -3
- package/dist/{onboard-TFBL62J3.js → onboard-PX36YDZA.js} +2 -2
- package/dist/{orchestrate-DYA4XAQJ.js → orchestrate-NYRYEKQM.js} +5 -5
- package/dist/{preflight-S3GYME3Q.js → preflight-JCQFKB6M.js} +7 -7
- package/dist/{renovate-dispatch-MZPAWVBL.js → renovate-dispatch-6II2JVOX.js} +29 -27
- package/dist/renovate-dispatch-6II2JVOX.js.map +1 -0
- package/dist/{report-BLZ67QAQ.js → report-POHQEV6F.js} +24 -8
- package/dist/report-POHQEV6F.js.map +1 -0
- package/dist/screenouts-7P5GOZMV.js +57 -0
- package/dist/screenouts-7P5GOZMV.js.map +1 -0
- package/dist/{self-updating-GSH427XP.js → self-updating-2YSVNMPP.js} +2 -2
- package/dist/{selftest-KPTL6KC4.js → selftest-OO2WDSDM.js} +12 -9
- package/dist/selftest-OO2WDSDM.js.map +1 -0
- package/dist/{smoke-suite-GP3W57JP.js → smoke-suite-CSP23NZM.js} +2 -2
- package/dist/spam-classifier-VG4ICIWS.js +217 -0
- package/dist/spam-classifier-VG4ICIWS.js.map +1 -0
- package/dist/submissions-BNRGY3JQ.js +122 -0
- package/dist/submissions-BNRGY3JQ.js.map +1 -0
- package/dist/submissions-MDKWJYMM.js +281 -0
- package/dist/submissions-MDKWJYMM.js.map +1 -0
- package/dist/{svelte-codemods-GYZZX742.js → svelte-codemods-V54KOBWU.js} +2 -2
- package/dist/{sync-configs-CQNOTJ2B.js → sync-configs-UBCJRASZ.js} +2 -2
- package/dist/{upgrade-RTAJZD56.js → upgrade-WR6ICPAG.js} +2 -2
- package/dist/{websites-SVI4KYSH.js → websites-U6642XSE.js} +10 -2
- package/dist/{write-audits-to-airtable-3XFNWMUE.js → write-audits-to-airtable-22T6D733.js} +4 -4
- package/package.json +1 -1
- package/dist/airtable-2PJ4NSAV.js +0 -9
- package/dist/blux-FKZYMKLF.js.map +0 -1
- package/dist/chunk-6J25IIAI.js.map +0 -1
- package/dist/chunk-AYM2EFSK.js.map +0 -1
- package/dist/chunk-CMQMNFFJ.js.map +0 -1
- package/dist/chunk-KOAGMTRI.js.map +0 -1
- package/dist/chunk-LPMQXS2N.js.map +0 -1
- package/dist/chunk-M7HOQ4W7.js.map +0 -1
- package/dist/chunk-QTHKOIFJ.js +0 -13
- package/dist/chunk-QTHKOIFJ.js.map +0 -1
- package/dist/chunk-VR246V3G.js.map +0 -1
- package/dist/chunk-WN4NEHE7.js.map +0 -1
- package/dist/chunk-YWRQEDB3.js.map +0 -1
- package/dist/digest-GZCZYJKC.js.map +0 -1
- package/dist/renovate-dispatch-MZPAWVBL.js.map +0 -1
- package/dist/report-BLZ67QAQ.js.map +0 -1
- package/dist/selftest-KPTL6KC4.js.map +0 -1
- /package/dist/{airtable-2PJ4NSAV.js.map → airtable-6L3JD7FY.js.map} +0 -0
- /package/dist/{announce-3JE5FKW7.js.map → announce-V6EYPPUR.js.map} +0 -0
- /package/dist/{bump-deps-K3JMLMBB.js.map → bump-deps-OQR3IXLN.js.map} +0 -0
- /package/dist/{chunk-5QC5KJHR.js.map → chunk-3PU2QWOJ.js.map} +0 -0
- /package/dist/{chunk-Y7LZHPDF.js.map → chunk-4BHNP6Y2.js.map} +0 -0
- /package/dist/{chunk-PSJQE6T6.js.map → chunk-I55YWV6T.js.map} +0 -0
- /package/dist/{chunk-52TDNN7K.js.map → chunk-IDBMYDVH.js.map} +0 -0
- /package/dist/{chunk-MPRLMGJU.js.map → chunk-KKL3RYFQ.js.map} +0 -0
- /package/dist/{chunk-2PWQ6MPG.js.map → chunk-KLVMJYZZ.js.map} +0 -0
- /package/dist/{chunk-F6OE6MGQ.js.map → chunk-SK44IFZN.js.map} +0 -0
- /package/dist/{convert-to-pnpm-6F4RAFEJ.js.map → convert-to-pnpm-OVMASATZ.js.map} +0 -0
- /package/dist/{ensure-site-H5VMBPLF.js.map → ensure-site-3SOAWTZF.js.map} +0 -0
- /package/dist/{github-signals-SQECQNST.js.map → github-signals-TF3VSNIN.js.map} +0 -0
- /package/dist/{health-endpoint-QXWM6GW2.js.map → health-endpoint-JZGYCMTV.js.map} +0 -0
- /package/dist/{init-KQOIPLVT.js.map → init-7PTP4O2K.js.map} +0 -0
- /package/dist/{launch-RX4VI7F2.js.map → launch-W33FWWOZ.js.map} +0 -0
- /package/dist/{lighthouse-airtable-TTXULEGU.js.map → lighthouse-airtable-ZXNMDEPE.js.map} +0 -0
- /package/dist/{onboard-TFBL62J3.js.map → onboard-PX36YDZA.js.map} +0 -0
- /package/dist/{orchestrate-DYA4XAQJ.js.map → orchestrate-NYRYEKQM.js.map} +0 -0
- /package/dist/{preflight-S3GYME3Q.js.map → preflight-JCQFKB6M.js.map} +0 -0
- /package/dist/{self-updating-GSH427XP.js.map → self-updating-2YSVNMPP.js.map} +0 -0
- /package/dist/{smoke-suite-GP3W57JP.js.map → smoke-suite-CSP23NZM.js.map} +0 -0
- /package/dist/{svelte-codemods-GYZZX742.js.map → svelte-codemods-V54KOBWU.js.map} +0 -0
- /package/dist/{sync-configs-CQNOTJ2B.js.map → sync-configs-UBCJRASZ.js.map} +0 -0
- /package/dist/{upgrade-RTAJZD56.js.map → upgrade-WR6ICPAG.js.map} +0 -0
- /package/dist/{websites-SVI4KYSH.js.map → websites-U6642XSE.js.map} +0 -0
- /package/dist/{write-audits-to-airtable-3XFNWMUE.js.map → write-audits-to-airtable-22T6D733.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 };
|
|
@@ -1116,6 +1119,67 @@ function parseHtmlLinks(html, baseUrl) {
|
|
|
1116
1119
|
function family(pathname) {
|
|
1117
1120
|
return pathname.split("/").filter(Boolean)[0] ?? "";
|
|
1118
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
|
+
}
|
|
1119
1183
|
function sampleRoutePaths(urlsOrPaths, cap = DEFAULT_CAP) {
|
|
1120
1184
|
const seen = /* @__PURE__ */ new Set(["/"]);
|
|
1121
1185
|
const buckets = /* @__PURE__ */ new Map();
|
|
@@ -1126,9 +1190,11 @@ function sampleRoutePaths(urlsOrPaths, cap = DEFAULT_CAP) {
|
|
|
1126
1190
|
} catch {
|
|
1127
1191
|
continue;
|
|
1128
1192
|
}
|
|
1129
|
-
|
|
1193
|
+
pathname = pathname.replace(/\/+$/, "");
|
|
1194
|
+
if (pathname === "") continue;
|
|
1130
1195
|
if (seen.has(pathname)) continue;
|
|
1131
1196
|
seen.add(pathname);
|
|
1197
|
+
if (!isPageRoutePath(pathname)) continue;
|
|
1132
1198
|
const fam = family(pathname);
|
|
1133
1199
|
const arr = buckets.get(fam) ?? [];
|
|
1134
1200
|
arr.push(pathname);
|
|
@@ -1180,32 +1246,174 @@ async function discoverRoutes(deployedUrl, deps, cap = DEFAULT_CAP) {
|
|
|
1180
1246
|
function isBroken(status) {
|
|
1181
1247
|
return status === null || status >= 400;
|
|
1182
1248
|
}
|
|
1249
|
+
function isOkStatus(status) {
|
|
1250
|
+
return status !== null && status >= 200 && status < 400;
|
|
1251
|
+
}
|
|
1252
|
+
function decodeEntities(s) {
|
|
1253
|
+
return s.replace(/&/gi, "&").replace(/</gi, "<").replace(/>/gi, ">").replace(/"/gi, '"').replace(/�?39;|'/gi, "'").replace(/ /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}` : "";
|
|
1183
1367
|
function summarizeBrowser(routes, links, familyCounts) {
|
|
1184
1368
|
const desktopChecks = routes.flatMap((r) => r.desktop);
|
|
1185
1369
|
const mobileChecks = routes.flatMap((r) => r.mobile);
|
|
1186
1370
|
const desktopOk = routes.length > 0 && routes.every((r) => r.desktop.length > 0 && r.desktop.every((d) => d.ok));
|
|
1187
1371
|
const mobileOk = routes.length > 0 && routes.every((r) => r.mobile.length > 0 && r.mobile.every((m) => m.ok));
|
|
1188
|
-
const
|
|
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;
|
|
1189
1380
|
const linksOk = links.length > 0 && brokenLinks === 0;
|
|
1190
|
-
const
|
|
1191
|
-
const
|
|
1192
|
-
const
|
|
1193
|
-
|
|
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();
|
|
1194
1386
|
const desc = (r.metaDescription ?? "").trim();
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
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;
|
|
1199
1401
|
const engines = [...new Set(desktopChecks.map((d) => d.engine))];
|
|
1200
1402
|
const devices = [...new Set(mobileChecks.map((m) => m.device))];
|
|
1201
1403
|
const families = Object.entries(familyCounts).map(([f, n]) => f === "/" ? "/" : `${f} \xD7${n}`).join(", ");
|
|
1202
|
-
const
|
|
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)}` : "");
|
|
1203
1406
|
return {
|
|
1204
1407
|
desktopOk,
|
|
1205
1408
|
mobileOk,
|
|
1206
1409
|
linksOk,
|
|
1207
1410
|
reachableOk,
|
|
1208
1411
|
titleMetaOk,
|
|
1412
|
+
unreachableUrls,
|
|
1413
|
+
titleMetaProblems,
|
|
1414
|
+
desktopFailures,
|
|
1415
|
+
mobileFailures,
|
|
1416
|
+
brokenLinkUrls,
|
|
1209
1417
|
brokenLinks,
|
|
1210
1418
|
routesChecked: routes.length,
|
|
1211
1419
|
note
|
|
@@ -1219,24 +1427,47 @@ async function browserAudit(ctx) {
|
|
|
1219
1427
|
}
|
|
1220
1428
|
const now = ctx.now ?? /* @__PURE__ */ new Date();
|
|
1221
1429
|
const discoverDeps = ctx.discoverDeps ?? defaultDiscoverDeps();
|
|
1430
|
+
const verifyDeps = ctx.verifyDeps ?? defaultVerifyDeps();
|
|
1222
1431
|
const runner = ctx.browserRunner ?? await defaultBrowserRunner();
|
|
1223
1432
|
try {
|
|
1224
1433
|
const discovered = await discoverRoutes(site.deployedUrl, discoverDeps);
|
|
1225
|
-
const
|
|
1434
|
+
const probed = await runner.probe(discovered.routes);
|
|
1435
|
+
const { routes: reverifiedRoutes, reverified } = await reverifyRoutes(probed, verifyDeps);
|
|
1436
|
+
const { routes: routeResults, excused } = excuseChallengedEngineChecks(reverifiedRoutes);
|
|
1226
1437
|
const internalLinks = [...new Set(routeResults.flatMap((r) => r.links))];
|
|
1227
|
-
const
|
|
1438
|
+
const rawLinks = await runner.checkLinks(internalLinks);
|
|
1439
|
+
const { links: linkResults, reverified: linkReverified } = await reverifyLinks(
|
|
1440
|
+
rawLinks,
|
|
1441
|
+
verifyDeps
|
|
1442
|
+
);
|
|
1228
1443
|
const summary = summarizeBrowser(
|
|
1229
1444
|
routeResults,
|
|
1230
1445
|
linkResults,
|
|
1231
1446
|
discovered.familyCounts ?? familyCountsOf(discovered.routes)
|
|
1232
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` : "");
|
|
1233
1457
|
const status = summary.desktopOk && summary.mobileOk && summary.linksOk ? "pass" : "warn";
|
|
1234
1458
|
return {
|
|
1235
1459
|
audit: "browser",
|
|
1236
1460
|
site: label,
|
|
1237
1461
|
status,
|
|
1238
|
-
summary:
|
|
1239
|
-
details: {
|
|
1462
|
+
summary: note,
|
|
1463
|
+
details: {
|
|
1464
|
+
...summary,
|
|
1465
|
+
note,
|
|
1466
|
+
reverifiedUrls,
|
|
1467
|
+
excusedEngineChecks: excused,
|
|
1468
|
+
linkReverifiedUrls,
|
|
1469
|
+
checkedAt: now.toISOString()
|
|
1470
|
+
}
|
|
1240
1471
|
};
|
|
1241
1472
|
} finally {
|
|
1242
1473
|
await runner.close?.();
|
|
@@ -1259,6 +1490,25 @@ function defaultDiscoverDeps() {
|
|
|
1259
1490
|
}
|
|
1260
1491
|
};
|
|
1261
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
|
+
}
|
|
1262
1512
|
var DESKTOP_VIEWPORT = { width: 1366, height: 900 };
|
|
1263
1513
|
var PAGE_TIMEOUT_MS = 3e4;
|
|
1264
1514
|
async function defaultBrowserRunner() {
|
|
@@ -1292,11 +1542,13 @@ async function defaultBrowserRunner() {
|
|
|
1292
1542
|
const errors = [];
|
|
1293
1543
|
page.on("pageerror", (e) => errors.push(String(e)));
|
|
1294
1544
|
let ok = false;
|
|
1545
|
+
let navStatus = null;
|
|
1295
1546
|
try {
|
|
1296
1547
|
const resp = await page.goto(url, {
|
|
1297
1548
|
waitUntil: "domcontentloaded",
|
|
1298
1549
|
timeout: PAGE_TIMEOUT_MS
|
|
1299
1550
|
});
|
|
1551
|
+
navStatus = resp ? resp.status() : null;
|
|
1300
1552
|
const hasMain = await page.locator("main, [role=main]").first().isVisible().catch(() => false);
|
|
1301
1553
|
ok = !!resp && resp.ok() && errors.length === 0 && hasMain;
|
|
1302
1554
|
if (engine === "chromium") {
|
|
@@ -1320,7 +1572,7 @@ async function defaultBrowserRunner() {
|
|
|
1320
1572
|
await ctx.close().catch(() => {
|
|
1321
1573
|
});
|
|
1322
1574
|
}
|
|
1323
|
-
desktop.push({ engine, ok });
|
|
1575
|
+
desktop.push({ engine, ok, status: navStatus });
|
|
1324
1576
|
}
|
|
1325
1577
|
const mobile = [];
|
|
1326
1578
|
for (let i = 0; i < mobileTargets.length; i++) {
|
|
@@ -1331,11 +1583,13 @@ async function defaultBrowserRunner() {
|
|
|
1331
1583
|
const errors = [];
|
|
1332
1584
|
page.on("pageerror", (e) => errors.push(String(e)));
|
|
1333
1585
|
let ok = false;
|
|
1586
|
+
let navStatus = null;
|
|
1334
1587
|
try {
|
|
1335
1588
|
const resp = await page.goto(url, {
|
|
1336
1589
|
waitUntil: "domcontentloaded",
|
|
1337
1590
|
timeout: PAGE_TIMEOUT_MS
|
|
1338
1591
|
});
|
|
1592
|
+
navStatus = resp ? resp.status() : null;
|
|
1339
1593
|
const overflow = await page.evaluate("document.documentElement.scrollWidth > window.innerWidth + 2").catch(() => true);
|
|
1340
1594
|
ok = !!resp && resp.ok() && errors.length === 0 && !overflow;
|
|
1341
1595
|
} catch {
|
|
@@ -1344,7 +1598,7 @@ async function defaultBrowserRunner() {
|
|
|
1344
1598
|
await ctx.close().catch(() => {
|
|
1345
1599
|
});
|
|
1346
1600
|
}
|
|
1347
|
-
mobile.push({ device, ok });
|
|
1601
|
+
mobile.push({ device, ok, status: navStatus });
|
|
1348
1602
|
}
|
|
1349
1603
|
results.push({
|
|
1350
1604
|
url,
|
|
@@ -1391,6 +1645,7 @@ async function defaultBrowserRunner() {
|
|
|
1391
1645
|
}
|
|
1392
1646
|
|
|
1393
1647
|
// src/audits/netlify-deploy.ts
|
|
1648
|
+
var NETLIFY_FETCH_TIMEOUT_MS = 15e3;
|
|
1394
1649
|
var READY_STATES = /* @__PURE__ */ new Set(["ready"]);
|
|
1395
1650
|
var FAILED_STATES = /* @__PURE__ */ new Set(["error", "failed", "rejected"]);
|
|
1396
1651
|
function isReadyState(state) {
|
|
@@ -1415,7 +1670,8 @@ function defaultNetlifyDeployDeps(token, now, fetchImpl = fetch) {
|
|
|
1415
1670
|
let res;
|
|
1416
1671
|
try {
|
|
1417
1672
|
res = await fetchImpl(url, {
|
|
1418
|
-
headers: { Authorization: `Bearer ${token}` }
|
|
1673
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
1674
|
+
signal: AbortSignal.timeout(NETLIFY_FETCH_TIMEOUT_MS)
|
|
1419
1675
|
});
|
|
1420
1676
|
} catch {
|
|
1421
1677
|
return { ok: false };
|
|
@@ -1890,4 +2146,4 @@ export {
|
|
|
1890
2146
|
runAudits,
|
|
1891
2147
|
runAuditsAcross
|
|
1892
2148
|
};
|
|
1893
|
-
//# sourceMappingURL=chunk-
|
|
2149
|
+
//# sourceMappingURL=chunk-PERP7LSS.js.map
|