nfunc-mcp 0.3.0 → 0.5.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 +121 -379
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/mappers/a11yDedupe.js +38 -7
- package/dist/mappers/a11yDedupe.js.map +1 -1
- package/dist/mappers/defectFormatter.d.ts +9 -1
- package/dist/mappers/defectFormatter.js +53 -13
- package/dist/mappers/defectFormatter.js.map +1 -1
- package/dist/mappers/labFieldComparator.d.ts +62 -0
- package/dist/mappers/labFieldComparator.js +134 -0
- package/dist/mappers/labFieldComparator.js.map +1 -0
- package/dist/mappers/priorityMapper.d.ts +42 -0
- package/dist/mappers/priorityMapper.js +58 -0
- package/dist/mappers/priorityMapper.js.map +1 -1
- package/dist/mappers/psiAggregator.d.ts +130 -0
- package/dist/mappers/psiAggregator.js +293 -0
- package/dist/mappers/psiAggregator.js.map +1 -0
- package/dist/mappers/runComparator.d.ts +85 -0
- package/dist/mappers/runComparator.js +165 -0
- package/dist/mappers/runComparator.js.map +1 -0
- package/dist/mappers/wcagLevels.d.ts +73 -0
- package/dist/mappers/wcagLevels.js +320 -0
- package/dist/mappers/wcagLevels.js.map +1 -0
- package/dist/mappers/webVitalsMapper.d.ts +52 -0
- package/dist/mappers/webVitalsMapper.js +131 -0
- package/dist/mappers/webVitalsMapper.js.map +1 -0
- package/dist/tools/accessibility.d.ts +1 -0
- package/dist/tools/accessibility.js +488 -63
- package/dist/tools/accessibility.js.map +1 -1
- package/dist/tools/lighthouse.js +370 -102
- package/dist/tools/lighthouse.js.map +1 -1
- package/dist/tools/performanceAudit.d.ts +2 -0
- package/dist/tools/performanceAudit.js +446 -0
- package/dist/tools/performanceAudit.js.map +1 -0
- package/dist/tools/performanceAuditPlan.d.ts +2 -0
- package/dist/tools/performanceAuditPlan.js +438 -0
- package/dist/tools/performanceAuditPlan.js.map +1 -0
- package/dist/utils/batchState.d.ts +75 -0
- package/dist/utils/batchState.js +128 -0
- package/dist/utils/batchState.js.map +1 -0
- package/dist/utils/csvReader.d.ts +20 -0
- package/dist/utils/csvReader.js +172 -0
- package/dist/utils/csvReader.js.map +1 -0
- package/dist/utils/httpClient.d.ts +84 -0
- package/dist/utils/httpClient.js +171 -0
- package/dist/utils/httpClient.js.map +1 -0
- package/dist/utils/outputParsers.js +26 -30
- package/dist/utils/outputParsers.js.map +1 -1
- package/dist/utils/psiAuth.d.ts +26 -0
- package/dist/utils/psiAuth.js +36 -0
- package/dist/utils/psiAuth.js.map +1 -0
- package/dist/utils/psiParser.d.ts +135 -0
- package/dist/utils/psiParser.js +200 -0
- package/dist/utils/psiParser.js.map +1 -0
- package/dist/utils/publicUrl.d.ts +17 -0
- package/dist/utils/publicUrl.js +115 -0
- package/dist/utils/publicUrl.js.map +1 -0
- package/dist/utils/sitemapReader.d.ts +27 -0
- package/dist/utils/sitemapReader.js +272 -0
- package/dist/utils/sitemapReader.js.map +1 -0
- package/dist/utils/urlClassifier.d.ts +45 -0
- package/dist/utils/urlClassifier.js +267 -0
- package/dist/utils/urlClassifier.js.map +1 -0
- package/dist/utils/urlInput.d.ts +30 -0
- package/dist/utils/urlInput.js +130 -0
- package/dist/utils/urlInput.js.map +1 -0
- package/docs/manual.md +769 -0
- package/docs/psi-report-spec.md +174 -0
- package/package.json +13 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batchState.js","sourceRoot":"","sources":["../../src/utils/batchState.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,wFAAwF;AACxF,MAAM,CAAC,MAAM,4BAA4B,GAAG,GAAG,CAAC;AAehD,MAAM,UAAU,YAAY,CAAC,KAAa;IACxC,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACzE,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,MAA0B;IACrD,IAAI,CAAC,MAAM;QAAE,OAAO,CAAC,CAAC;IACtB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAmB,CAAC;QAC/F,OAAO,OAAO,MAAM,CAAC,CAAC,KAAK,QAAQ,IAAI,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACtE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,CAAC,CAAC;IACX,CAAC;AACH,CAAC;AAED,kGAAkG;AAClG,MAAM,UAAU,UAAU,CAAC,IAAc,EAAE,QAAkB;IAC3D,MAAM,KAAK,GAAgB,EAAE,CAAC;IAC9B,KAAK,MAAM,GAAG,IAAI,IAAI;QAAE,KAAK,MAAM,OAAO,IAAI,QAAQ;YAAE,KAAK,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,OAAO,EAAE,CAAC,CAAC;IACrF,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,UAAU,CAAC,GAAW;IACpC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;QAC5B,MAAM,IAAI,GAAG,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC;QACzE,MAAM,KAAK,GAAG,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,EAAE,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;QAC7F,MAAM,IAAI,GAAG,GAAG,IAAI,IAAI,UAAU,GAAG,KAAK,EAAE,CAAC,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACvF,6EAA6E;QAC7E,OAAO,IAAI,CAAC,MAAM,GAAG,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IACrE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAOD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,GAAW,EACX,OAAY,EACZ,QAAQ,GAAG,aAAa;IAExB,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACtC,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACtC,IAAI,QAAQ,GAAQ,EAAE,CAAC;IACvB,IAAI,CAAC;QACH,QAAQ,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC,CAAQ,CAAC;IAClE,CAAC;IAAC,MAAM,CAAC;QACP,QAAQ,GAAG,EAAE,CAAC;IAChB,CAAC;IACD,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACzE,KAAK,MAAM,MAAM,IAAI,OAAO;QAAE,KAAK,CAAC,GAAG,CAAC,GAAG,MAAM,CAAC,GAAG,IAAI,MAAM,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;IACnF,MAAM,MAAM,GAAG,CAAC,GAAG,KAAK,CAAC,MAAM,EAAE,CAAC,CAAC;IACnC,MAAM,SAAS,CAAC,SAAS,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC;IACpE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;AAC7C,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,GAAW,EACX,QAAQ,GAAG,aAAa;IAExB,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,QAAQ,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAQ,CAAC;IACxE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,GAAW,EACX,KAAkB,EAClB,aAAsB,EACtB,MAA0B;IAE1B,IAAI,CAAC,aAAa,IAAI,MAAM;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IAC3D,MAAM,QAAQ,GAAG,MAAM,SAAS,CAAC,GAAG,CAAC,CAAC;IACtC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;IACxD,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACnE,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAC1E,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,OAAO,EAAE,KAAK,CAAC,MAAM,GAAG,SAAS,CAAC,MAAM,EAAE,CAAC;AACxE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,SAAS,CACvB,SAAiB,EACjB,QAAgB,EAChB,SAAiB,EACjB,SAAiB;IAEjB,IAAI,SAAS,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC,CAAC,8BAA8B;IAChE,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,GAAG,SAAS,GAAG,QAAQ,CAAC;AACvD,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,SAAiB;IAC9C,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,GAAG,IAAI,CAAC,CAAC;AACrD,CAAC"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL extraction from a CSV.
|
|
3
|
+
*
|
|
4
|
+
* People arrive with a spreadsheet — an SEO export, a analytics top-pages
|
|
5
|
+
* report, a hand-written list — and the URL column is never in the same place
|
|
6
|
+
* twice. This finds it rather than demanding a fixed format, because the
|
|
7
|
+
* alternative is a tool that rejects the file the user actually has.
|
|
8
|
+
*
|
|
9
|
+
* The parser is a small RFC4180 implementation instead of a split on commas:
|
|
10
|
+
* URLs contain commas inside quoted fields often enough that naive splitting
|
|
11
|
+
* silently truncates them.
|
|
12
|
+
*/
|
|
13
|
+
export interface CsvUrlResult {
|
|
14
|
+
urls: string[];
|
|
15
|
+
/** Header name of the column used, or a positional description. */
|
|
16
|
+
column: string;
|
|
17
|
+
rowsRead: number;
|
|
18
|
+
warnings: string[];
|
|
19
|
+
}
|
|
20
|
+
export declare function readUrlsFromCsv(path: string): CsvUrlResult;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* URL extraction from a CSV.
|
|
3
|
+
*
|
|
4
|
+
* People arrive with a spreadsheet — an SEO export, a analytics top-pages
|
|
5
|
+
* report, a hand-written list — and the URL column is never in the same place
|
|
6
|
+
* twice. This finds it rather than demanding a fixed format, because the
|
|
7
|
+
* alternative is a tool that rejects the file the user actually has.
|
|
8
|
+
*
|
|
9
|
+
* The parser is a small RFC4180 implementation instead of a split on commas:
|
|
10
|
+
* URLs contain commas inside quoted fields often enough that naive splitting
|
|
11
|
+
* silently truncates them.
|
|
12
|
+
*/
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
/** Delimiters worth guessing between, in preference order. */
|
|
15
|
+
const DELIMITERS = [",", ";", "\t", "|"];
|
|
16
|
+
/** Header names that mean "this is the URL column", lowercased. */
|
|
17
|
+
const URL_HEADERS = new Set([
|
|
18
|
+
"url", "urls", "page", "page url", "page_url", "address", "link", "loc",
|
|
19
|
+
"landing page", "landing_page", "full url", "final url", "request url",
|
|
20
|
+
]);
|
|
21
|
+
function detectDelimiter(sample) {
|
|
22
|
+
let best = ",";
|
|
23
|
+
let bestCount = 0;
|
|
24
|
+
for (const d of DELIMITERS) {
|
|
25
|
+
// Count on the header line only; body rows can contain stray delimiters.
|
|
26
|
+
const count = (sample.split(d).length - 1);
|
|
27
|
+
if (count > bestCount) {
|
|
28
|
+
best = d;
|
|
29
|
+
bestCount = count;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return best;
|
|
33
|
+
}
|
|
34
|
+
/** RFC4180: quoted fields may contain the delimiter, newlines, and "" escapes. */
|
|
35
|
+
function parseCsv(text, delimiter) {
|
|
36
|
+
const rows = [];
|
|
37
|
+
let row = [];
|
|
38
|
+
let field = "";
|
|
39
|
+
let inQuotes = false;
|
|
40
|
+
for (let i = 0; i < text.length; i++) {
|
|
41
|
+
const ch = text[i];
|
|
42
|
+
if (inQuotes) {
|
|
43
|
+
if (ch === '"') {
|
|
44
|
+
if (text[i + 1] === '"') {
|
|
45
|
+
field += '"';
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
inQuotes = false;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
field += ch;
|
|
54
|
+
}
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (ch === '"') {
|
|
58
|
+
inQuotes = true;
|
|
59
|
+
}
|
|
60
|
+
else if (ch === delimiter) {
|
|
61
|
+
row.push(field);
|
|
62
|
+
field = "";
|
|
63
|
+
}
|
|
64
|
+
else if (ch === "\n") {
|
|
65
|
+
row.push(field);
|
|
66
|
+
rows.push(row);
|
|
67
|
+
row = [];
|
|
68
|
+
field = "";
|
|
69
|
+
}
|
|
70
|
+
else if (ch !== "\r") {
|
|
71
|
+
field += ch;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (field.length > 0 || row.length > 0) {
|
|
75
|
+
row.push(field);
|
|
76
|
+
rows.push(row);
|
|
77
|
+
}
|
|
78
|
+
return rows.filter((r) => r.some((c) => c.trim() !== ""));
|
|
79
|
+
}
|
|
80
|
+
function looksLikeUrl(value) {
|
|
81
|
+
const v = value.trim();
|
|
82
|
+
if (!/^https?:\/\//i.test(v))
|
|
83
|
+
return false;
|
|
84
|
+
try {
|
|
85
|
+
new URL(v);
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Pick the URL column.
|
|
94
|
+
*
|
|
95
|
+
* Header match first, because a file that names its columns is telling us
|
|
96
|
+
* where to look. Falling back to content sniffing covers headerless exports
|
|
97
|
+
* and files whose URL column is called something unguessable — the column
|
|
98
|
+
* where the most cells parse as URLs is the column, regardless of its name.
|
|
99
|
+
*/
|
|
100
|
+
function pickUrlColumn(rows) {
|
|
101
|
+
if (rows.length === 0)
|
|
102
|
+
return null;
|
|
103
|
+
const header = rows[0].map((h) => h.trim().toLowerCase());
|
|
104
|
+
const headerIsData = header.some(looksLikeUrl);
|
|
105
|
+
if (!headerIsData) {
|
|
106
|
+
const named = header.findIndex((h) => URL_HEADERS.has(h));
|
|
107
|
+
if (named >= 0) {
|
|
108
|
+
return { index: named, label: rows[0][named].trim(), hasHeader: true };
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const body = headerIsData ? rows : rows.slice(1);
|
|
112
|
+
const width = Math.max(...rows.map((r) => r.length));
|
|
113
|
+
let bestIndex = -1;
|
|
114
|
+
let bestHits = 0;
|
|
115
|
+
for (let col = 0; col < width; col++) {
|
|
116
|
+
const hits = body.filter((r) => looksLikeUrl(r[col] ?? "")).length;
|
|
117
|
+
if (hits > bestHits) {
|
|
118
|
+
bestHits = hits;
|
|
119
|
+
bestIndex = col;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (bestIndex < 0 || bestHits === 0)
|
|
123
|
+
return null;
|
|
124
|
+
return {
|
|
125
|
+
index: bestIndex,
|
|
126
|
+
label: headerIsData ? `column ${bestIndex + 1}` : (rows[0][bestIndex]?.trim() || `column ${bestIndex + 1}`),
|
|
127
|
+
hasHeader: !headerIsData,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
export function readUrlsFromCsv(path) {
|
|
131
|
+
const warnings = [];
|
|
132
|
+
let text;
|
|
133
|
+
try {
|
|
134
|
+
text = readFileSync(path, "utf8");
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
throw new Error(`Could not read CSV at ${path}: ${err.message}`);
|
|
138
|
+
}
|
|
139
|
+
// Strip a UTF-8 BOM; Excel adds one and it corrupts the first header name.
|
|
140
|
+
if (text.charCodeAt(0) === 0xfeff)
|
|
141
|
+
text = text.slice(1);
|
|
142
|
+
const firstLine = text.split(/\r?\n/, 1)[0] ?? "";
|
|
143
|
+
const rows = parseCsv(text, detectDelimiter(firstLine));
|
|
144
|
+
const column = pickUrlColumn(rows);
|
|
145
|
+
if (!column) {
|
|
146
|
+
throw new Error(`No URL column found in ${path}. Expected a column named one of ` +
|
|
147
|
+
`${[...URL_HEADERS].slice(0, 5).join(", ")}, or any column whose ` +
|
|
148
|
+
`values start with http:// or https://.`);
|
|
149
|
+
}
|
|
150
|
+
const body = column.hasHeader ? rows.slice(1) : rows;
|
|
151
|
+
const urls = [];
|
|
152
|
+
const seen = new Set();
|
|
153
|
+
let skipped = 0;
|
|
154
|
+
for (const row of body) {
|
|
155
|
+
const cell = (row[column.index] ?? "").trim();
|
|
156
|
+
if (!cell)
|
|
157
|
+
continue;
|
|
158
|
+
if (!looksLikeUrl(cell)) {
|
|
159
|
+
skipped++;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (seen.has(cell))
|
|
163
|
+
continue;
|
|
164
|
+
seen.add(cell);
|
|
165
|
+
urls.push(cell);
|
|
166
|
+
}
|
|
167
|
+
if (skipped > 0) {
|
|
168
|
+
warnings.push(`Skipped ${skipped} row(s) in "${column.label}" that did not contain an http(s) URL.`);
|
|
169
|
+
}
|
|
170
|
+
return { urls, column: column.label, rowsRead: body.length, warnings };
|
|
171
|
+
}
|
|
172
|
+
//# sourceMappingURL=csvReader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"csvReader.js","sourceRoot":"","sources":["../../src/utils/csvReader.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP counterpart to shellRunner.
|
|
3
|
+
*
|
|
4
|
+
* Every tool before this one reached the outside world by spawning a process,
|
|
5
|
+
* so `runShell` was the single choke point for timeout, error and duration
|
|
6
|
+
* handling. PSI is an HTTP API, and bending shellRunner around `curl` would
|
|
7
|
+
* trade a typed response for string parsing. This mirrors shellRunner's
|
|
8
|
+
* contract instead: never throws for a reachable-but-unhappy endpoint, always
|
|
9
|
+
* reports duration, and leaves the decision about what counts as failure to
|
|
10
|
+
* the caller.
|
|
11
|
+
*
|
|
12
|
+
* Node 18+ ships global fetch, so this adds no dependency.
|
|
13
|
+
*/
|
|
14
|
+
export interface HttpResult {
|
|
15
|
+
/** True only for a 2xx. A 404 is a result, not an exception. */
|
|
16
|
+
ok: boolean;
|
|
17
|
+
/** 0 when the request never got a response (timeout, DNS, connection refused). */
|
|
18
|
+
status: number;
|
|
19
|
+
body: string;
|
|
20
|
+
/** Raw response bytes. Populated only when `raw` was requested. */
|
|
21
|
+
bytes?: Uint8Array;
|
|
22
|
+
durationMs: number;
|
|
23
|
+
/** Total attempts made, including the successful one. 1 means no retries. */
|
|
24
|
+
attempts: number;
|
|
25
|
+
/** Present only when `ok` is false. Always redacted. */
|
|
26
|
+
error?: string;
|
|
27
|
+
}
|
|
28
|
+
export interface HttpRequestOptions {
|
|
29
|
+
timeoutMs?: number;
|
|
30
|
+
/** Extra attempts after the first. 0 disables retrying. */
|
|
31
|
+
retries?: number;
|
|
32
|
+
/** Base delay for exponential backoff; doubled each attempt. */
|
|
33
|
+
retryDelayMs?: number;
|
|
34
|
+
headers?: Record<string, string>;
|
|
35
|
+
/**
|
|
36
|
+
* Return bytes instead of decoded text. Needed for `.xml.gz` sitemaps, which
|
|
37
|
+
* are served as an opaque gzip payload rather than with Content-Encoding, so
|
|
38
|
+
* fetch does not inflate them and decoding to a string corrupts them.
|
|
39
|
+
*/
|
|
40
|
+
raw?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* Absolute ceiling across every attempt, including backoff. Where `timeoutMs`
|
|
43
|
+
* bounds one request, this bounds the whole call: each attempt is shortened
|
|
44
|
+
* to whatever is left, and retrying stops when nothing is. Without it,
|
|
45
|
+
* enabling `retryOnTimeout` silently doubles the worst case, which is exactly
|
|
46
|
+
* how a chunked runner overruns its budget.
|
|
47
|
+
*/
|
|
48
|
+
totalBudgetMs?: number;
|
|
49
|
+
/**
|
|
50
|
+
* Retry after a timeout. Off by default: a timeout means the request already
|
|
51
|
+
* spent its entire budget, so retrying multiplies the wall clock by the
|
|
52
|
+
* attempt count for a request that is unlikely to be faster next time. A 429
|
|
53
|
+
* or 5xx is different — those fail fast and often succeed on retry.
|
|
54
|
+
*/
|
|
55
|
+
retryOnTimeout?: boolean;
|
|
56
|
+
/**
|
|
57
|
+
* Literal strings to strip from every error message before it leaves this
|
|
58
|
+
* module — API keys, tokens, anything that would otherwise reach a log, a
|
|
59
|
+
* tool response, or a conversation transcript.
|
|
60
|
+
*/
|
|
61
|
+
redact?: string[];
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* A URL safe to put in an error message.
|
|
65
|
+
*
|
|
66
|
+
* The PSI request URL carries the API key in the query string, so any error
|
|
67
|
+
* that interpolates the URL leaks the key into the tool response — and from
|
|
68
|
+
* there into the conversation transcript. Redacting at the point of formatting
|
|
69
|
+
* rather than asking every call site to remember is the only version of this
|
|
70
|
+
* that stays correct.
|
|
71
|
+
*/
|
|
72
|
+
export declare function sanitizeUrl(url: string | URL): string;
|
|
73
|
+
export declare function httpGet(url: string | URL, options?: HttpRequestOptions): Promise<HttpResult>;
|
|
74
|
+
export interface HttpJsonResult<T> extends Omit<HttpResult, "body"> {
|
|
75
|
+
data: T | null;
|
|
76
|
+
/** Kept for error reporting; a caller that has `data` should not need it. */
|
|
77
|
+
body: string;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* `httpGet` plus JSON parsing, with malformed JSON demoted to `ok: false`
|
|
81
|
+
* rather than a thrown SyntaxError. A 200 carrying truncated JSON is a failed
|
|
82
|
+
* request as far as every caller is concerned.
|
|
83
|
+
*/
|
|
84
|
+
export declare function httpGetJson<T>(url: string | URL, options?: HttpRequestOptions): Promise<HttpJsonResult<T>>;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HTTP counterpart to shellRunner.
|
|
3
|
+
*
|
|
4
|
+
* Every tool before this one reached the outside world by spawning a process,
|
|
5
|
+
* so `runShell` was the single choke point for timeout, error and duration
|
|
6
|
+
* handling. PSI is an HTTP API, and bending shellRunner around `curl` would
|
|
7
|
+
* trade a typed response for string parsing. This mirrors shellRunner's
|
|
8
|
+
* contract instead: never throws for a reachable-but-unhappy endpoint, always
|
|
9
|
+
* reports duration, and leaves the decision about what counts as failure to
|
|
10
|
+
* the caller.
|
|
11
|
+
*
|
|
12
|
+
* Node 18+ ships global fetch, so this adds no dependency.
|
|
13
|
+
*/
|
|
14
|
+
/**
|
|
15
|
+
* Sites behind a bot wall reject the default Node fetch agent outright, which
|
|
16
|
+
* turns a healthy site into a 403 and a sitemap into a "not found". Naming
|
|
17
|
+
* ourselves honestly and looking like a browser gets through most of them;
|
|
18
|
+
* a caller can override it via `headers`.
|
|
19
|
+
*/
|
|
20
|
+
const DEFAULT_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 " +
|
|
21
|
+
"(KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 nfunc-mcp/qa-audit";
|
|
22
|
+
/** Query parameters whose values are stripped before a URL is quoted anywhere. */
|
|
23
|
+
const SENSITIVE_PARAMS = new Set(["key", "api_key", "apikey", "token", "access_token"]);
|
|
24
|
+
/**
|
|
25
|
+
* A URL safe to put in an error message.
|
|
26
|
+
*
|
|
27
|
+
* The PSI request URL carries the API key in the query string, so any error
|
|
28
|
+
* that interpolates the URL leaks the key into the tool response — and from
|
|
29
|
+
* there into the conversation transcript. Redacting at the point of formatting
|
|
30
|
+
* rather than asking every call site to remember is the only version of this
|
|
31
|
+
* that stays correct.
|
|
32
|
+
*/
|
|
33
|
+
export function sanitizeUrl(url) {
|
|
34
|
+
try {
|
|
35
|
+
const u = new URL(url);
|
|
36
|
+
for (const param of u.searchParams.keys()) {
|
|
37
|
+
if (SENSITIVE_PARAMS.has(param.toLowerCase())) {
|
|
38
|
+
u.searchParams.set(param, "REDACTED");
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return u.toString();
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return "[unparseable url]";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function redactAll(message, secrets) {
|
|
48
|
+
let out = message;
|
|
49
|
+
for (const secret of secrets) {
|
|
50
|
+
// A short "secret" would redact half the message; a real key is far longer.
|
|
51
|
+
if (secret && secret.length >= 8)
|
|
52
|
+
out = out.split(secret).join("REDACTED");
|
|
53
|
+
}
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
/** Retry only what a retry can plausibly fix. A 400 will be a 400 next time too. */
|
|
57
|
+
function isRetryable(status) {
|
|
58
|
+
return status === 408 || status === 429 || status >= 500;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* `Retry-After` is either a delay in seconds or an HTTP date. Google sends
|
|
62
|
+
* seconds on 429s, but the date form is legal and cheap to support.
|
|
63
|
+
*/
|
|
64
|
+
function retryAfterMs(header) {
|
|
65
|
+
if (!header)
|
|
66
|
+
return null;
|
|
67
|
+
const seconds = Number(header);
|
|
68
|
+
if (Number.isFinite(seconds) && seconds >= 0)
|
|
69
|
+
return seconds * 1000;
|
|
70
|
+
const date = Date.parse(header);
|
|
71
|
+
if (!Number.isNaN(date))
|
|
72
|
+
return Math.max(0, date - Date.now());
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
76
|
+
export async function httpGet(url, options = {}) {
|
|
77
|
+
const { timeoutMs = 60_000, retries = 2, retryDelayMs = 2_000, headers, raw = false, totalBudgetMs, retryOnTimeout = false, redact = [], } = options;
|
|
78
|
+
const start = Date.now();
|
|
79
|
+
const safeUrl = sanitizeUrl(url);
|
|
80
|
+
let lastError = "";
|
|
81
|
+
let lastStatus = 0;
|
|
82
|
+
const deadline = totalBudgetMs === undefined ? null : start + totalBudgetMs;
|
|
83
|
+
const remainingMs = () => (deadline === null ? Infinity : deadline - Date.now());
|
|
84
|
+
for (let attempt = 1; attempt <= retries + 1; attempt++) {
|
|
85
|
+
const budgetLeft = remainingMs();
|
|
86
|
+
if (budgetLeft <= 0) {
|
|
87
|
+
lastError = lastError || `Request to ${safeUrl} ran out of time budget`;
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
try {
|
|
91
|
+
const response = await fetch(url, {
|
|
92
|
+
headers: { "user-agent": DEFAULT_USER_AGENT, ...headers },
|
|
93
|
+
signal: AbortSignal.timeout(Math.min(timeoutMs, budgetLeft)),
|
|
94
|
+
});
|
|
95
|
+
lastStatus = response.status;
|
|
96
|
+
if (response.ok) {
|
|
97
|
+
const bytes = new Uint8Array(await response.arrayBuffer());
|
|
98
|
+
return {
|
|
99
|
+
ok: true,
|
|
100
|
+
status: response.status,
|
|
101
|
+
body: raw ? "" : new TextDecoder().decode(bytes),
|
|
102
|
+
...(raw ? { bytes } : {}),
|
|
103
|
+
durationMs: Date.now() - start,
|
|
104
|
+
attempts: attempt,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
// Read the body before deciding — a non-2xx from PSI carries a JSON
|
|
108
|
+
// error object that says far more than the status code does.
|
|
109
|
+
const body = await response.text().catch(() => "");
|
|
110
|
+
lastError = `HTTP ${response.status} from ${safeUrl}${body ? `: ${body.slice(0, 300)}` : ""}`;
|
|
111
|
+
if (!isRetryable(response.status) || attempt > retries) {
|
|
112
|
+
return {
|
|
113
|
+
ok: false,
|
|
114
|
+
status: response.status,
|
|
115
|
+
body,
|
|
116
|
+
durationMs: Date.now() - start,
|
|
117
|
+
attempts: attempt,
|
|
118
|
+
error: redactAll(lastError, redact),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const serverAsked = retryAfterMs(response.headers.get("retry-after"));
|
|
122
|
+
const backoff = Math.min(serverAsked ?? retryDelayMs * 2 ** (attempt - 1), Math.max(0, remainingMs()));
|
|
123
|
+
await sleep(backoff);
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
// Timeouts and network failures land here. AbortSignal.timeout raises a
|
|
127
|
+
// TimeoutError, which reads as an unexplained abort unless named.
|
|
128
|
+
const e = err;
|
|
129
|
+
const timedOut = e.name === "TimeoutError" || e.name === "AbortError";
|
|
130
|
+
lastStatus = 0;
|
|
131
|
+
lastError = timedOut
|
|
132
|
+
? `Request to ${safeUrl} timed out after ${timeoutMs} ms`
|
|
133
|
+
: `Request to ${safeUrl} failed: ${e.message ?? "unknown network error"}`;
|
|
134
|
+
if (timedOut && !retryOnTimeout)
|
|
135
|
+
break;
|
|
136
|
+
if (attempt > retries)
|
|
137
|
+
break;
|
|
138
|
+
await sleep(Math.min(retryDelayMs * 2 ** (attempt - 1), Math.max(0, remainingMs())));
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
return {
|
|
142
|
+
ok: false,
|
|
143
|
+
status: lastStatus,
|
|
144
|
+
body: "",
|
|
145
|
+
durationMs: Date.now() - start,
|
|
146
|
+
attempts: retries + 1,
|
|
147
|
+
error: redactAll(lastError, redact),
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* `httpGet` plus JSON parsing, with malformed JSON demoted to `ok: false`
|
|
152
|
+
* rather than a thrown SyntaxError. A 200 carrying truncated JSON is a failed
|
|
153
|
+
* request as far as every caller is concerned.
|
|
154
|
+
*/
|
|
155
|
+
export async function httpGetJson(url, options = {}) {
|
|
156
|
+
const result = await httpGet(url, options);
|
|
157
|
+
if (!result.ok)
|
|
158
|
+
return { ...result, data: null };
|
|
159
|
+
try {
|
|
160
|
+
return { ...result, data: JSON.parse(result.body) };
|
|
161
|
+
}
|
|
162
|
+
catch (err) {
|
|
163
|
+
return {
|
|
164
|
+
...result,
|
|
165
|
+
ok: false,
|
|
166
|
+
data: null,
|
|
167
|
+
error: redactAll(`Response from ${sanitizeUrl(url)} was not valid JSON: ${err.message}`, options.redact ?? []),
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=httpClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"httpClient.js","sourceRoot":"","sources":["../../src/utils/httpClient.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { criterionFor } from "../mappers/wcagLevels.js";
|
|
1
2
|
export function parseLighthouseJSON(rawJson) {
|
|
2
3
|
const lhr = JSON.parse(rawJson);
|
|
3
4
|
const categoryScores = {};
|
|
@@ -100,28 +101,21 @@ const WCAG_CRITERION_LEVELS = {
|
|
|
100
101
|
"4.1.2": "A",
|
|
101
102
|
"4.1.3": "AA",
|
|
102
103
|
};
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
if (prefix === "WCAG2AAA")
|
|
119
|
-
return { level: "AAA", criterion };
|
|
120
|
-
if (prefix === "WCAG2AA")
|
|
121
|
-
return { level: "AA", criterion };
|
|
122
|
-
if (prefix === "WCAG2A")
|
|
123
|
-
return { level: "A", criterion };
|
|
124
|
-
return { level: "unknown", criterion };
|
|
104
|
+
/**
|
|
105
|
+
* Resolve a finding to its WCAG success criterion and level.
|
|
106
|
+
*
|
|
107
|
+
* Both engines are handled here now. htmlcs encodes the criterion in its code;
|
|
108
|
+
* axe does not expose WCAG tags through pa11y at all, so its rule ids go
|
|
109
|
+
* through a lookup table. The previous implementation inferred the level from
|
|
110
|
+
* the code's "WCAG2AA" prefix when the criterion was unknown — but that prefix
|
|
111
|
+
* names the *standard being tested against*, not the criterion's own level, so
|
|
112
|
+
* it labelled everything AA and made a level-based priority scheme impossible.
|
|
113
|
+
*/
|
|
114
|
+
function deriveWcagLevel(code, runner) {
|
|
115
|
+
const found = criterionFor(code, runner);
|
|
116
|
+
if (found)
|
|
117
|
+
return { level: found.level, criterion: found.number };
|
|
118
|
+
return { level: "unknown", criterion: null };
|
|
125
119
|
}
|
|
126
120
|
function isAxeImpact(v) {
|
|
127
121
|
return v === "critical" || v === "serious" || v === "moderate" || v === "minor";
|
|
@@ -187,12 +181,14 @@ export function parsePa11yJSON(rawJson) {
|
|
|
187
181
|
const type = (issue.type ?? "error");
|
|
188
182
|
const runner = issue.runner === "axe" ? "axe" : "htmlcs";
|
|
189
183
|
if (runner === "axe") {
|
|
190
|
-
// axe codes are bare rule ids with no WCAG path
|
|
191
|
-
// criterion
|
|
192
|
-
// level "unknown"
|
|
193
|
-
//
|
|
194
|
-
//
|
|
184
|
+
// axe codes are bare rule ids with no WCAG path in them, so they go
|
|
185
|
+
// through the rule → criterion table rather than the code parser. This
|
|
186
|
+
// branch used to hardcode level "unknown" because that table did not
|
|
187
|
+
// exist; leaving it that way after adding the table made every axe
|
|
188
|
+
// finding read as a best-practice nit, including image-alt and
|
|
189
|
+
// color-contrast, which are 1.1.1 (A) and 1.4.3 (AA).
|
|
195
190
|
const impact = issue.runnerExtras?.impact;
|
|
191
|
+
const axeCriterion = deriveWcagLevel(code, runner);
|
|
196
192
|
violations.push({
|
|
197
193
|
code,
|
|
198
194
|
technique: code,
|
|
@@ -200,15 +196,15 @@ export function parsePa11yJSON(rawJson) {
|
|
|
200
196
|
selector: issue.selector ?? "",
|
|
201
197
|
context: issue.context ?? "",
|
|
202
198
|
type,
|
|
203
|
-
wcagLevel:
|
|
204
|
-
criterion:
|
|
199
|
+
wcagLevel: axeCriterion.level,
|
|
200
|
+
criterion: axeCriterion.criterion,
|
|
205
201
|
runner,
|
|
206
202
|
impact: isAxeImpact(impact) ? impact : undefined,
|
|
207
203
|
needsReview: issue.runnerExtras?.needsFurtherReview === true,
|
|
208
204
|
});
|
|
209
205
|
continue;
|
|
210
206
|
}
|
|
211
|
-
const { level, criterion } = deriveWcagLevel(code);
|
|
207
|
+
const { level, criterion } = deriveWcagLevel(code, runner);
|
|
212
208
|
violations.push({
|
|
213
209
|
code,
|
|
214
210
|
technique: extractTechnique(code),
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"outputParsers.js","sourceRoot":"","sources":["../../src/utils/outputParsers.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"outputParsers.js","sourceRoot":"","sources":["../../src/utils/outputParsers.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* PSI API key resolution.
|
|
3
|
+
*
|
|
4
|
+
* Precedence: explicit tool input → PAGESPEED_API_KEY → keyless.
|
|
5
|
+
*
|
|
6
|
+
* The preferred setup is the `env` block in the MCP client config, because the
|
|
7
|
+
* client launches this server and we do not control its flags — `--env-file`
|
|
8
|
+
* is not available to us. An explicit input is supported for people who want
|
|
9
|
+
* to paste a key once, but it is the worst option: it lands in the
|
|
10
|
+
* conversation transcript and in any client-side logging, so it is never
|
|
11
|
+
* echoed back and every error that touches a request URL is redacted.
|
|
12
|
+
*/
|
|
13
|
+
export type KeySource = "input" | "env" | "none";
|
|
14
|
+
export interface ResolvedKey {
|
|
15
|
+
key: string | null;
|
|
16
|
+
source: KeySource;
|
|
17
|
+
}
|
|
18
|
+
export declare const KEY_ENV_VAR = "PAGESPEED_API_KEY";
|
|
19
|
+
/**
|
|
20
|
+
* Keyless PSI is rate-limited hard enough that a batch will 429 partway
|
|
21
|
+
* through, leaving a half-finished audit and a confusing error. Refusing up
|
|
22
|
+
* front with instructions is a better failure than discovering it at run 12.
|
|
23
|
+
*/
|
|
24
|
+
export declare const KEYLESS_RUN_CAP = 4;
|
|
25
|
+
export declare function resolveApiKey(explicit?: string): ResolvedKey;
|
|
26
|
+
export declare function keylessWarning(runs: number): string;
|