mcp-web-validator 1.3.24 → 1.3.26

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/CHANGELOG.md CHANGED
@@ -4,6 +4,21 @@ All notable changes to this project are documented here. The project follows [Se
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [1.3.26] - 2026-09-23
8
+
9
+ ### Fixed
10
+
11
+ - Escaped Markdown control syntax in generated validation-report table cells so validator and page diagnostics render literally instead of becoming active links, images, or emphasis.
12
+ - Reported missing or empty functional `alt` text on `<input type="image">` submit controls in package and hosted accessibility audits while preserving valid labels and inert template contents.
13
+
14
+ ## [1.3.25] - 2026-09-23
15
+
16
+ ### Fixed
17
+
18
+ - Preserved IANA's globally reachable `192.0.0.9/32` and `192.0.0.10/32` anycast exceptions while continuing to reject the rest of the non-global `192.0.0.0/24` IETF Protocol Assignments block.
19
+ - Reported multiple document `<title>` and meta-description declarations as ambiguous while keeping first-declaration empty/length analysis instead of merging duplicate values.
20
+ - Escaped HTML-like validator and page fragments in generated Markdown report tables so untrusted markup is rendered literally instead of as raw HTML.
21
+
7
22
  ## [1.3.24] - 2026-09-23
8
23
 
9
24
  ### Fixed
package/dist/network.js CHANGED
@@ -17,7 +17,6 @@ for (const [network, prefix] of [
17
17
  ["127.0.0.0", 8],
18
18
  ["169.254.0.0", 16],
19
19
  ["172.16.0.0", 12],
20
- ["192.0.0.0", 24],
21
20
  ["192.0.2.0", 24],
22
21
  ["192.88.99.0", 24],
23
22
  ["192.168.0.0", 16],
@@ -45,6 +44,14 @@ for (const [network, prefix] of [
45
44
  }
46
45
  const publicIpv6Addresses = new BlockList();
47
46
  publicIpv6Addresses.addSubnet("2000::", 3, "ipv6");
47
+ // IANA's 192.0.0.0/24 IETF Protocol Assignments block is not globally
48
+ // reachable by default. Only these two anycast addresses are currently
49
+ // marked globally reachable in the IANA IPv4 Special-Purpose Address Registry.
50
+ const ietfProtocolAssignmentsIpv4 = new BlockList();
51
+ ietfProtocolAssignmentsIpv4.addSubnet("192.0.0.0", 24, "ipv4");
52
+ const publicIetfProtocolAssignmentsIpv4 = new BlockList();
53
+ publicIetfProtocolAssignmentsIpv4.addAddress("192.0.0.9", "ipv4");
54
+ publicIetfProtocolAssignmentsIpv4.addAddress("192.0.0.10", "ipv4");
48
55
  // IANA's 2001::/23 IETF Protocol Assignments block is not globally reachable
49
56
  // by default. Only the more-specific allocations below are currently marked
50
57
  // globally reachable in the IANA IPv6 Special-Purpose Address Registry.
@@ -104,6 +111,10 @@ function normalizeHostname(hostname) {
104
111
  }
105
112
  function isPublicAddress(address, family) {
106
113
  if (family === 4) {
114
+ if (ietfProtocolAssignmentsIpv4.check(address, "ipv4")
115
+ && !publicIetfProtocolAssignmentsIpv4.check(address, "ipv4")) {
116
+ return false;
117
+ }
107
118
  return !blockedAddresses.check(address, "ipv4");
108
119
  }
109
120
  if (family === 6) {
@@ -1 +1 @@
1
- {"version":3,"file":"network.js","sourceRoot":"","sources":["../src/network.ts"],"names":[],"mappings":"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","sourcesContent":["import { lookup } from \"node:dns/promises\";\nimport * as fs from \"node:fs/promises\";\nimport { BlockList, isIP } from \"node:net\";\nimport * as path from \"node:path\";\nimport { getEncoding } from \"encoding-sniffer/sniffer\";\nimport { Agent } from \"undici\";\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_REDIRECTS = 5;\nconst DNS_TIMEOUT_MS = 5_000;\nconst MAX_URL_LENGTH = 8_192;\nconst HTML_ENCODING_SNIFF_BYTES = 1_024;\n\nconst blockedAddresses = new BlockList();\nfor (const [network, prefix] of [\n [\"0.0.0.0\", 8],\n [\"10.0.0.0\", 8],\n [\"100.64.0.0\", 10],\n [\"127.0.0.0\", 8],\n [\"169.254.0.0\", 16],\n [\"172.16.0.0\", 12],\n [\"192.0.0.0\", 24],\n [\"192.0.2.0\", 24],\n [\"192.88.99.0\", 24],\n [\"192.168.0.0\", 16],\n [\"198.18.0.0\", 15],\n [\"198.51.100.0\", 24],\n [\"203.0.113.0\", 24],\n [\"224.0.0.0\", 4],\n [\"240.0.0.0\", 4],\n] as const) {\n blockedAddresses.addSubnet(network, prefix, \"ipv4\");\n}\n\nfor (const [network, prefix] of [\n [\"::\", 96],\n [\"fc00::\", 7],\n [\"fe80::\", 10],\n [\"ff00::\", 8],\n [\"2001::\", 32],\n [\"2001:2::\", 48],\n [\"2001:10::\", 28],\n [\"2001:db8::\", 32],\n [\"2002::\", 16],\n [\"3fff::\", 20],\n] as const) {\n blockedAddresses.addSubnet(network, prefix, \"ipv6\");\n}\n\nconst publicIpv6Addresses = new BlockList();\npublicIpv6Addresses.addSubnet(\"2000::\", 3, \"ipv6\");\n\n// IANA's 2001::/23 IETF Protocol Assignments block is not globally reachable\n// by default. Only the more-specific allocations below are currently marked\n// globally reachable in the IANA IPv6 Special-Purpose Address Registry.\nconst ietfProtocolAssignments = new BlockList();\nietfProtocolAssignments.addSubnet(\"2001::\", 23, \"ipv6\");\n\nconst publicIetfProtocolAssignments = new BlockList();\nfor (const [network, prefix] of [\n [\"2001:1::1\", 128],\n [\"2001:1::2\", 128],\n [\"2001:1::3\", 128],\n [\"2001:3::\", 32],\n [\"2001:4:112::\", 48],\n [\"2001:20::\", 28],\n [\"2001:30::\", 28],\n] as const) {\n publicIetfProtocolAssignments.addSubnet(network, prefix, \"ipv6\");\n}\n\nconst blockedHostnames = new Set([\n \"localhost\",\n \"localhost.localdomain\",\n \"ip6-localhost\",\n \"ip6-loopback\",\n]);\n\nconst blockedHostnameSuffixes = [\n \".localhost\",\n \".local\",\n \".internal\",\n \".home.arpa\",\n \".test\",\n \".invalid\",\n \".example\",\n \".onion\",\n];\n\nconst redirectStatuses = new Set([301, 302, 303, 307, 308]);\n\nexport interface PublicFetchOptions {\n method?: \"GET\" | \"HEAD\";\n headers?: Record<string, string>;\n timeoutMs?: number;\n maxRedirects?: number;\n}\n\nexport interface PublicTextOptions extends PublicFetchOptions {\n maxBytes?: number;\n acceptedContentTypes?: readonly string[];\n}\n\nexport interface PublicTextResult {\n text: string;\n url: string;\n status: number;\n contentType: string | null;\n}\n\nexport interface PublicHttpResult {\n response: Response;\n url: URL;\n}\n\nexport interface ResolvedPublicHttpUrl {\n url: URL;\n addresses: Array<{ address: string; family: number }>;\n}\n\nexport class PublicUrlError extends Error {\n override readonly name = \"PublicUrlError\";\n}\n\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n if (error.name === \"TimeoutError\" || error.name === \"AbortError\") {\n return \"Request timed out\";\n }\n return error.message || error.name;\n }\n\n return typeof error === \"string\" && error.trim() ? error : \"Unknown error\";\n}\n\nfunction assertPositiveInteger(value: number, label: string): void {\n if (!Number.isSafeInteger(value) || value <= 0) {\n throw new Error(`${label} must be a positive integer`);\n }\n}\n\nfunction normalizeHostname(hostname: string): string {\n const withoutBrackets = hostname.startsWith(\"[\") && hostname.endsWith(\"]\")\n ? hostname.slice(1, -1)\n : hostname;\n return withoutBrackets.replace(/\\.$/, \"\").toLowerCase();\n}\n\nfunction isPublicAddress(address: string, family: number): boolean {\n if (family === 4) {\n return !blockedAddresses.check(address, \"ipv4\");\n }\n\n if (family === 6) {\n if (\n ietfProtocolAssignments.check(address, \"ipv6\")\n && !publicIetfProtocolAssignments.check(address, \"ipv6\")\n ) {\n return false;\n }\n return publicIpv6Addresses.check(address, \"ipv6\")\n && !blockedAddresses.check(address, \"ipv6\");\n }\n\n return false;\n}\n\nasync function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {\n let timeout: NodeJS.Timeout | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<T>((_, reject) => {\n timeout = setTimeout(() => reject(new Error(`${label} timed out`)), timeoutMs);\n }),\n ]);\n } finally {\n if (timeout) {\n clearTimeout(timeout);\n }\n }\n}\n\n/**\n * Parses an HTTP(S) URL and rejects hostnames that resolve to local, private,\n * documentation, multicast, or otherwise non-public address space.\n */\nexport async function resolvePublicHttpUrl(input: string | URL): Promise<ResolvedPublicHttpUrl> {\n const rawUrl = input instanceof URL ? input.href : input;\n if (typeof rawUrl !== \"string\" || rawUrl.length === 0 || rawUrl.length > MAX_URL_LENGTH) {\n throw new PublicUrlError(`URL must contain between 1 and ${MAX_URL_LENGTH} characters`);\n }\n\n let parsed: URL;\n try {\n parsed = new URL(rawUrl);\n } catch {\n throw new PublicUrlError(\"URL must be an absolute HTTP or HTTPS URL\");\n }\n\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new PublicUrlError(\"Only HTTP and HTTPS URLs are allowed\");\n }\n if (parsed.username || parsed.password) {\n throw new PublicUrlError(\"URLs containing credentials are not allowed\");\n }\n // URL normalizes matching defaults (HTTP :80 / HTTPS :443) to an empty port.\n // Any remaining port is custom or mismatched to the scheme and must be rejected.\n if (parsed.port) {\n throw new PublicUrlError(\"Only default HTTP and HTTPS ports are allowed\");\n }\n\n parsed.hash = \"\";\n const hostname = normalizeHostname(parsed.hostname);\n if (!hostname) {\n throw new PublicUrlError(\"URL hostname is required\");\n }\n if (\n blockedHostnames.has(hostname)\n || blockedHostnameSuffixes.some((suffix) => hostname.endsWith(suffix))\n ) {\n throw new PublicUrlError(`URL hostname \"${hostname}\" is not public`);\n }\n\n const literalFamily = isIP(hostname);\n const resolvedAddresses = literalFamily\n ? [{ address: hostname, family: literalFamily }]\n : await withTimeout(\n lookup(hostname, { all: true, verbatim: true }),\n DNS_TIMEOUT_MS,\n \"DNS lookup\",\n ).catch((error: unknown) => {\n throw new PublicUrlError(`Unable to resolve URL hostname: ${getErrorMessage(error)}`);\n });\n\n if (resolvedAddresses.length === 0) {\n throw new PublicUrlError(\"URL hostname did not resolve to an address\");\n }\n\n for (const record of resolvedAddresses) {\n if (!isPublicAddress(record.address, record.family)) {\n throw new PublicUrlError(`URL hostname resolves to non-public address ${record.address}`);\n }\n }\n\n return { url: parsed, addresses: resolvedAddresses };\n}\n\nexport async function assertPublicHttpUrl(input: string | URL): Promise<URL> {\n return (await resolvePublicHttpUrl(input)).url;\n}\n\nexport async function cancelResponseBody(response: Response): Promise<void> {\n if (!response.body) {\n return;\n }\n\n try {\n await response.body.cancel();\n } catch {\n // The stream may already be closed or consumed; there is nothing left to release.\n }\n}\n\nfunction getDeclaredCharacterEncoding(contentType: string | null): string | undefined {\n if (!contentType) {\n return undefined;\n }\n\n const match = /(?:^|;)\\s*charset\\s*=\\s*(?:\"([^\"]*)\"|([^;\\s]*))/i.exec(contentType);\n if (!match) {\n return undefined;\n }\n return (match[1] ?? match[2] ?? \"\").trim();\n}\n\nfunction isSupportedCharacterEncoding(encoding: string): boolean {\n try {\n new TextDecoder(encoding);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction getXmlBomCharacterEncoding(bytes: Uint8Array): string | undefined {\n if (bytes.length >= 4) {\n if (bytes[0] === 0x00 && bytes[1] === 0x00 && bytes[2] === 0xfe && bytes[3] === 0xff) {\n return \"utf-32be\";\n }\n if (bytes[0] === 0xff && bytes[1] === 0xfe && bytes[2] === 0x00 && bytes[3] === 0x00) {\n return \"utf-32le\";\n }\n }\n if (bytes.length >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) {\n return \"utf-8\";\n }\n if (bytes.length >= 2) {\n if (bytes[0] === 0xfe && bytes[1] === 0xff) {\n return \"utf-16be\";\n }\n if (bytes[0] === 0xff && bytes[1] === 0xfe) {\n return \"utf-16le\";\n }\n }\n return undefined;\n}\n\nfunction getXmlCharacterEncoding(bytes: Uint8Array): string {\n const bomEncoding = getXmlBomCharacterEncoding(bytes);\n if (bomEncoding) {\n return bomEncoding;\n }\n if (bytes.length >= 4) {\n if (bytes[0] === 0x00 && bytes[1] === 0x3c && bytes[2] === 0x00 && bytes[3] === 0x3f) {\n return \"utf-16be\";\n }\n if (bytes[0] === 0x3c && bytes[1] === 0x00 && bytes[2] === 0x3f && bytes[3] === 0x00) {\n return \"utf-16le\";\n }\n }\n\n const declarationBytes = bytes.subarray(0, HTML_ENCODING_SNIFF_BYTES);\n let declaration = \"\";\n for (const byte of declarationBytes) {\n declaration += byte < 0x80 ? String.fromCharCode(byte) : \"\\ufffd\";\n }\n const match = /^<\\?xml\\s+[^?]*\\bencoding\\s*=\\s*([\"'])([^\"']+)\\1/i.exec(declaration);\n return match?.[2].trim() || \"utf-8\";\n}\n\nexport async function readResponseText(\n response: Response,\n maxBytes: number,\n encoding?: string,\n sniffHtmlEncoding = false,\n sniffXmlEncoding = false,\n preferXmlBom = false,\n): Promise<string> {\n assertPositiveInteger(maxBytes, \"maxBytes\");\n\n const declaredLength = Number(response.headers.get(\"content-length\"));\n if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n await cancelResponseBody(response);\n throw new Error(`Response exceeds the ${maxBytes}-byte limit`);\n }\n\n if (!response.body) {\n return \"\";\n }\n\n const reader = response.body.getReader();\n let totalBytes = 0;\n let text = \"\";\n let decoder: TextDecoder | undefined;\n let pendingChunks: Uint8Array[] = [];\n let pendingBytes = 0;\n\n const createDecoder = async (encodingLabel: string): Promise<TextDecoder> => {\n try {\n return new TextDecoder(encodingLabel);\n } catch {\n await reader.cancel();\n throw new Error(`Unsupported response character encoding \"${encodingLabel}\"`);\n }\n };\n\n const startDecoder = async (): Promise<void> => {\n if (decoder) return;\n let encodingLabel = encoding ?? \"utf-8\";\n if ((encoding === undefined && (sniffHtmlEncoding || sniffXmlEncoding)) || preferXmlBom) {\n const sniffLength = Math.min(pendingBytes, HTML_ENCODING_SNIFF_BYTES);\n const sniffBytes = new Uint8Array(sniffLength);\n let copied = 0;\n for (const chunk of pendingChunks) {\n if (copied >= sniffLength) break;\n const length = Math.min(chunk.byteLength, sniffLength - copied);\n sniffBytes.set(chunk.subarray(0, length), copied);\n copied += length;\n }\n if (preferXmlBom) {\n encodingLabel = getXmlBomCharacterEncoding(sniffBytes) ?? encodingLabel;\n } else {\n encodingLabel = sniffXmlEncoding\n ? getXmlCharacterEncoding(sniffBytes)\n : getEncoding(sniffBytes, {\n maxBytes: HTML_ENCODING_SNIFF_BYTES,\n defaultEncoding: \"utf-8\",\n });\n }\n }\n decoder = await createDecoder(encodingLabel);\n for (const chunk of pendingChunks) {\n text += decoder.decode(chunk, { stream: true });\n }\n pendingChunks = [];\n pendingBytes = 0;\n };\n\n if (!preferXmlBom && (encoding !== undefined || (!sniffHtmlEncoding && !sniffXmlEncoding))) {\n await startDecoder();\n }\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n totalBytes += value.byteLength;\n if (totalBytes > maxBytes) {\n await reader.cancel();\n throw new Error(`Response exceeds the ${maxBytes}-byte limit`);\n }\n\n if (!decoder) {\n pendingChunks.push(value);\n pendingBytes += value.byteLength;\n if (pendingBytes >= HTML_ENCODING_SNIFF_BYTES) {\n await startDecoder();\n }\n } else {\n text += decoder.decode(value, { stream: true });\n }\n }\n await startDecoder();\n if (!decoder) {\n throw new Error(\"Response decoder was not initialized\");\n }\n text += decoder.decode();\n return text;\n } finally {\n reader.releaseLock();\n }\n}\n\nfunction createPinnedDispatcher(\n resolvedAddresses: ReadonlyArray<{ address: string; family: number }>,\n): Agent {\n const addresses = resolvedAddresses.map((record) => ({ ...record }));\n\n return new Agent({\n // Every validated request gets its own non-reused connection so a later DNS\n // answer cannot replace the address set approved for this hop.\n pipelining: 0,\n connect: {\n lookup: (_hostname, options, callback) => {\n const requestedFamily = options.family === 4 || options.family === 6 ? options.family : 0;\n const candidates = requestedFamily\n ? addresses.filter((record) => record.family === requestedFamily)\n : addresses;\n\n if (candidates.length === 0) {\n const error = new Error(\"No validated address is available for the requested IP family\") as NodeJS.ErrnoException;\n error.code = \"ENOTFOUND\";\n callback(error, []);\n return;\n }\n\n if (options.all) {\n callback(null, candidates);\n return;\n }\n\n const selected = candidates[0];\n callback(null, selected.address, selected.family);\n },\n },\n });\n}\n\nfunction beginClosingDispatcher(dispatcher: Agent): void {\n // close() waits for the active response stream to finish, so starting it here\n // releases the request-scoped Agent without interrupting callers reading the body.\n void dispatcher.close().catch(() => {});\n}\n\n/** Fetches a public HTTP(S) URL while validating every redirect target. */\nexport async function fetchPublicHttp(\n input: string | URL,\n options: PublicFetchOptions = {},\n): Promise<PublicHttpResult> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxRedirects = options.maxRedirects ?? DEFAULT_MAX_REDIRECTS;\n assertPositiveInteger(timeoutMs, \"timeoutMs\");\n if (!Number.isSafeInteger(maxRedirects) || maxRedirects < 0 || maxRedirects > 10) {\n throw new Error(\"maxRedirects must be an integer between 0 and 10\");\n }\n\n let currentTarget = await resolvePublicHttpUrl(input);\n for (let redirectCount = 0; ; redirectCount += 1) {\n const dispatcher = createPinnedDispatcher(currentTarget.addresses);\n let response: Response;\n try {\n response = await fetch(currentTarget.url, {\n method: options.method ?? \"GET\",\n headers: options.headers,\n redirect: \"manual\",\n signal: AbortSignal.timeout(timeoutMs),\n dispatcher,\n } as RequestInit & { dispatcher: Agent });\n } catch (error) {\n await dispatcher.close();\n throw error;\n }\n\n if (!redirectStatuses.has(response.status)) {\n beginClosingDispatcher(dispatcher);\n return { response, url: currentTarget.url };\n }\n\n const location = response.headers.get(\"location\");\n if (!location) {\n beginClosingDispatcher(dispatcher);\n return { response, url: currentTarget.url };\n }\n\n if (redirectCount >= maxRedirects) {\n beginClosingDispatcher(dispatcher);\n return { response, url: currentTarget.url };\n }\n\n await cancelResponseBody(response);\n await dispatcher.close();\n let redirectUrl: URL;\n try {\n redirectUrl = new URL(location, currentTarget.url);\n } catch {\n throw new PublicUrlError(\"Redirect target is not a valid URL\");\n }\n currentTarget = await resolvePublicHttpUrl(redirectUrl);\n }\n}\n\n/** Fetches bounded text from a public URL and rejects non-success responses. */\nexport async function fetchPublicText(\n input: string | URL,\n options: PublicTextOptions = {},\n): Promise<PublicTextResult> {\n const maxBytes = options.maxBytes ?? 2_000_000;\n assertPositiveInteger(maxBytes, \"maxBytes\");\n\n const { response, url } = await fetchPublicHttp(input, {\n method: \"GET\",\n headers: options.headers,\n timeoutMs: options.timeoutMs,\n maxRedirects: options.maxRedirects,\n });\n\n if (!response.ok) {\n const status = response.status;\n await cancelResponseBody(response);\n throw new Error(`URL returned HTTP status ${status}`);\n }\n\n const contentTypeHeader = response.headers.get(\"content-type\");\n const contentType = contentTypeHeader?.split(\";\", 1)[0]?.trim().toLowerCase() || null;\n if (\n options.acceptedContentTypes\n && !options.acceptedContentTypes.some((accepted) => accepted.trim().toLowerCase() === contentType)\n ) {\n await cancelResponseBody(response);\n throw new Error(`URL returned unsupported content type ${contentType ?? \"missing\"}`);\n }\n\n const declaredEncoding = getDeclaredCharacterEncoding(contentTypeHeader);\n const shouldSniffHtmlEncoding = contentType === \"text/html\"\n && (declaredEncoding === undefined || !isSupportedCharacterEncoding(declaredEncoding));\n const isXmlContentType = contentType !== null\n && (contentType === \"application/xml\" || contentType === \"text/xml\" || contentType.endsWith(\"+xml\"));\n const shouldSniffXmlEncoding = declaredEncoding === undefined\n && isXmlContentType;\n const shouldPreferXmlBom = declaredEncoding !== undefined && isXmlContentType;\n return {\n text: await readResponseText(\n response,\n maxBytes,\n shouldSniffHtmlEncoding || shouldSniffXmlEncoding ? undefined : declaredEncoding,\n shouldSniffHtmlEncoding,\n shouldSniffXmlEncoding,\n shouldPreferXmlBom,\n ),\n url: url.href,\n status: response.status,\n contentType: contentTypeHeader,\n };\n}\n\n/** Reads a UTF-8 file only when it is a regular file within the requested cap. */\nexport async function readTextFile(filePath: string, maxBytes: number): Promise<string> {\n assertPositiveInteger(maxBytes, \"maxBytes\");\n const resolvedPath = path.resolve(filePath);\n const stats = await fs.stat(resolvedPath);\n if (!stats.isFile()) {\n throw new Error(`Path is not a regular file: ${resolvedPath}`);\n }\n if (stats.size > maxBytes) {\n throw new Error(`File exceeds the ${maxBytes}-byte limit: ${resolvedPath}`);\n }\n\n const contents = await fs.readFile(resolvedPath);\n if (contents.byteLength > maxBytes) {\n throw new Error(`File exceeds the ${maxBytes}-byte limit: ${resolvedPath}`);\n }\n return contents.toString(\"utf8\");\n}\n"]}
1
+ {"version":3,"file":"network.js","sourceRoot":"","sources":["../src/network.ts"],"names":[],"mappings":"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","sourcesContent":["import { lookup } from \"node:dns/promises\";\nimport * as fs from \"node:fs/promises\";\nimport { BlockList, isIP } from \"node:net\";\nimport * as path from \"node:path\";\nimport { getEncoding } from \"encoding-sniffer/sniffer\";\nimport { Agent } from \"undici\";\n\nconst DEFAULT_TIMEOUT_MS = 10_000;\nconst DEFAULT_MAX_REDIRECTS = 5;\nconst DNS_TIMEOUT_MS = 5_000;\nconst MAX_URL_LENGTH = 8_192;\nconst HTML_ENCODING_SNIFF_BYTES = 1_024;\n\nconst blockedAddresses = new BlockList();\nfor (const [network, prefix] of [\n [\"0.0.0.0\", 8],\n [\"10.0.0.0\", 8],\n [\"100.64.0.0\", 10],\n [\"127.0.0.0\", 8],\n [\"169.254.0.0\", 16],\n [\"172.16.0.0\", 12],\n [\"192.0.2.0\", 24],\n [\"192.88.99.0\", 24],\n [\"192.168.0.0\", 16],\n [\"198.18.0.0\", 15],\n [\"198.51.100.0\", 24],\n [\"203.0.113.0\", 24],\n [\"224.0.0.0\", 4],\n [\"240.0.0.0\", 4],\n] as const) {\n blockedAddresses.addSubnet(network, prefix, \"ipv4\");\n}\n\nfor (const [network, prefix] of [\n [\"::\", 96],\n [\"fc00::\", 7],\n [\"fe80::\", 10],\n [\"ff00::\", 8],\n [\"2001::\", 32],\n [\"2001:2::\", 48],\n [\"2001:10::\", 28],\n [\"2001:db8::\", 32],\n [\"2002::\", 16],\n [\"3fff::\", 20],\n] as const) {\n blockedAddresses.addSubnet(network, prefix, \"ipv6\");\n}\n\nconst publicIpv6Addresses = new BlockList();\npublicIpv6Addresses.addSubnet(\"2000::\", 3, \"ipv6\");\n\n// IANA's 192.0.0.0/24 IETF Protocol Assignments block is not globally\n// reachable by default. Only these two anycast addresses are currently\n// marked globally reachable in the IANA IPv4 Special-Purpose Address Registry.\nconst ietfProtocolAssignmentsIpv4 = new BlockList();\nietfProtocolAssignmentsIpv4.addSubnet(\"192.0.0.0\", 24, \"ipv4\");\n\nconst publicIetfProtocolAssignmentsIpv4 = new BlockList();\npublicIetfProtocolAssignmentsIpv4.addAddress(\"192.0.0.9\", \"ipv4\");\npublicIetfProtocolAssignmentsIpv4.addAddress(\"192.0.0.10\", \"ipv4\");\n\n// IANA's 2001::/23 IETF Protocol Assignments block is not globally reachable\n// by default. Only the more-specific allocations below are currently marked\n// globally reachable in the IANA IPv6 Special-Purpose Address Registry.\nconst ietfProtocolAssignments = new BlockList();\nietfProtocolAssignments.addSubnet(\"2001::\", 23, \"ipv6\");\n\nconst publicIetfProtocolAssignments = new BlockList();\nfor (const [network, prefix] of [\n [\"2001:1::1\", 128],\n [\"2001:1::2\", 128],\n [\"2001:1::3\", 128],\n [\"2001:3::\", 32],\n [\"2001:4:112::\", 48],\n [\"2001:20::\", 28],\n [\"2001:30::\", 28],\n] as const) {\n publicIetfProtocolAssignments.addSubnet(network, prefix, \"ipv6\");\n}\n\nconst blockedHostnames = new Set([\n \"localhost\",\n \"localhost.localdomain\",\n \"ip6-localhost\",\n \"ip6-loopback\",\n]);\n\nconst blockedHostnameSuffixes = [\n \".localhost\",\n \".local\",\n \".internal\",\n \".home.arpa\",\n \".test\",\n \".invalid\",\n \".example\",\n \".onion\",\n];\n\nconst redirectStatuses = new Set([301, 302, 303, 307, 308]);\n\nexport interface PublicFetchOptions {\n method?: \"GET\" | \"HEAD\";\n headers?: Record<string, string>;\n timeoutMs?: number;\n maxRedirects?: number;\n}\n\nexport interface PublicTextOptions extends PublicFetchOptions {\n maxBytes?: number;\n acceptedContentTypes?: readonly string[];\n}\n\nexport interface PublicTextResult {\n text: string;\n url: string;\n status: number;\n contentType: string | null;\n}\n\nexport interface PublicHttpResult {\n response: Response;\n url: URL;\n}\n\nexport interface ResolvedPublicHttpUrl {\n url: URL;\n addresses: Array<{ address: string; family: number }>;\n}\n\nexport class PublicUrlError extends Error {\n override readonly name = \"PublicUrlError\";\n}\n\nexport function getErrorMessage(error: unknown): string {\n if (error instanceof Error) {\n if (error.name === \"TimeoutError\" || error.name === \"AbortError\") {\n return \"Request timed out\";\n }\n return error.message || error.name;\n }\n\n return typeof error === \"string\" && error.trim() ? error : \"Unknown error\";\n}\n\nfunction assertPositiveInteger(value: number, label: string): void {\n if (!Number.isSafeInteger(value) || value <= 0) {\n throw new Error(`${label} must be a positive integer`);\n }\n}\n\nfunction normalizeHostname(hostname: string): string {\n const withoutBrackets = hostname.startsWith(\"[\") && hostname.endsWith(\"]\")\n ? hostname.slice(1, -1)\n : hostname;\n return withoutBrackets.replace(/\\.$/, \"\").toLowerCase();\n}\n\nfunction isPublicAddress(address: string, family: number): boolean {\n if (family === 4) {\n if (\n ietfProtocolAssignmentsIpv4.check(address, \"ipv4\")\n && !publicIetfProtocolAssignmentsIpv4.check(address, \"ipv4\")\n ) {\n return false;\n }\n return !blockedAddresses.check(address, \"ipv4\");\n }\n\n if (family === 6) {\n if (\n ietfProtocolAssignments.check(address, \"ipv6\")\n && !publicIetfProtocolAssignments.check(address, \"ipv6\")\n ) {\n return false;\n }\n return publicIpv6Addresses.check(address, \"ipv6\")\n && !blockedAddresses.check(address, \"ipv6\");\n }\n\n return false;\n}\n\nasync function withTimeout<T>(promise: Promise<T>, timeoutMs: number, label: string): Promise<T> {\n let timeout: NodeJS.Timeout | undefined;\n try {\n return await Promise.race([\n promise,\n new Promise<T>((_, reject) => {\n timeout = setTimeout(() => reject(new Error(`${label} timed out`)), timeoutMs);\n }),\n ]);\n } finally {\n if (timeout) {\n clearTimeout(timeout);\n }\n }\n}\n\n/**\n * Parses an HTTP(S) URL and rejects hostnames that resolve to local, private,\n * documentation, multicast, or otherwise non-public address space.\n */\nexport async function resolvePublicHttpUrl(input: string | URL): Promise<ResolvedPublicHttpUrl> {\n const rawUrl = input instanceof URL ? input.href : input;\n if (typeof rawUrl !== \"string\" || rawUrl.length === 0 || rawUrl.length > MAX_URL_LENGTH) {\n throw new PublicUrlError(`URL must contain between 1 and ${MAX_URL_LENGTH} characters`);\n }\n\n let parsed: URL;\n try {\n parsed = new URL(rawUrl);\n } catch {\n throw new PublicUrlError(\"URL must be an absolute HTTP or HTTPS URL\");\n }\n\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new PublicUrlError(\"Only HTTP and HTTPS URLs are allowed\");\n }\n if (parsed.username || parsed.password) {\n throw new PublicUrlError(\"URLs containing credentials are not allowed\");\n }\n // URL normalizes matching defaults (HTTP :80 / HTTPS :443) to an empty port.\n // Any remaining port is custom or mismatched to the scheme and must be rejected.\n if (parsed.port) {\n throw new PublicUrlError(\"Only default HTTP and HTTPS ports are allowed\");\n }\n\n parsed.hash = \"\";\n const hostname = normalizeHostname(parsed.hostname);\n if (!hostname) {\n throw new PublicUrlError(\"URL hostname is required\");\n }\n if (\n blockedHostnames.has(hostname)\n || blockedHostnameSuffixes.some((suffix) => hostname.endsWith(suffix))\n ) {\n throw new PublicUrlError(`URL hostname \"${hostname}\" is not public`);\n }\n\n const literalFamily = isIP(hostname);\n const resolvedAddresses = literalFamily\n ? [{ address: hostname, family: literalFamily }]\n : await withTimeout(\n lookup(hostname, { all: true, verbatim: true }),\n DNS_TIMEOUT_MS,\n \"DNS lookup\",\n ).catch((error: unknown) => {\n throw new PublicUrlError(`Unable to resolve URL hostname: ${getErrorMessage(error)}`);\n });\n\n if (resolvedAddresses.length === 0) {\n throw new PublicUrlError(\"URL hostname did not resolve to an address\");\n }\n\n for (const record of resolvedAddresses) {\n if (!isPublicAddress(record.address, record.family)) {\n throw new PublicUrlError(`URL hostname resolves to non-public address ${record.address}`);\n }\n }\n\n return { url: parsed, addresses: resolvedAddresses };\n}\n\nexport async function assertPublicHttpUrl(input: string | URL): Promise<URL> {\n return (await resolvePublicHttpUrl(input)).url;\n}\n\nexport async function cancelResponseBody(response: Response): Promise<void> {\n if (!response.body) {\n return;\n }\n\n try {\n await response.body.cancel();\n } catch {\n // The stream may already be closed or consumed; there is nothing left to release.\n }\n}\n\nfunction getDeclaredCharacterEncoding(contentType: string | null): string | undefined {\n if (!contentType) {\n return undefined;\n }\n\n const match = /(?:^|;)\\s*charset\\s*=\\s*(?:\"([^\"]*)\"|([^;\\s]*))/i.exec(contentType);\n if (!match) {\n return undefined;\n }\n return (match[1] ?? match[2] ?? \"\").trim();\n}\n\nfunction isSupportedCharacterEncoding(encoding: string): boolean {\n try {\n new TextDecoder(encoding);\n return true;\n } catch {\n return false;\n }\n}\n\nfunction getXmlBomCharacterEncoding(bytes: Uint8Array): string | undefined {\n if (bytes.length >= 4) {\n if (bytes[0] === 0x00 && bytes[1] === 0x00 && bytes[2] === 0xfe && bytes[3] === 0xff) {\n return \"utf-32be\";\n }\n if (bytes[0] === 0xff && bytes[1] === 0xfe && bytes[2] === 0x00 && bytes[3] === 0x00) {\n return \"utf-32le\";\n }\n }\n if (bytes.length >= 3 && bytes[0] === 0xef && bytes[1] === 0xbb && bytes[2] === 0xbf) {\n return \"utf-8\";\n }\n if (bytes.length >= 2) {\n if (bytes[0] === 0xfe && bytes[1] === 0xff) {\n return \"utf-16be\";\n }\n if (bytes[0] === 0xff && bytes[1] === 0xfe) {\n return \"utf-16le\";\n }\n }\n return undefined;\n}\n\nfunction getXmlCharacterEncoding(bytes: Uint8Array): string {\n const bomEncoding = getXmlBomCharacterEncoding(bytes);\n if (bomEncoding) {\n return bomEncoding;\n }\n if (bytes.length >= 4) {\n if (bytes[0] === 0x00 && bytes[1] === 0x3c && bytes[2] === 0x00 && bytes[3] === 0x3f) {\n return \"utf-16be\";\n }\n if (bytes[0] === 0x3c && bytes[1] === 0x00 && bytes[2] === 0x3f && bytes[3] === 0x00) {\n return \"utf-16le\";\n }\n }\n\n const declarationBytes = bytes.subarray(0, HTML_ENCODING_SNIFF_BYTES);\n let declaration = \"\";\n for (const byte of declarationBytes) {\n declaration += byte < 0x80 ? String.fromCharCode(byte) : \"\\ufffd\";\n }\n const match = /^<\\?xml\\s+[^?]*\\bencoding\\s*=\\s*([\"'])([^\"']+)\\1/i.exec(declaration);\n return match?.[2].trim() || \"utf-8\";\n}\n\nexport async function readResponseText(\n response: Response,\n maxBytes: number,\n encoding?: string,\n sniffHtmlEncoding = false,\n sniffXmlEncoding = false,\n preferXmlBom = false,\n): Promise<string> {\n assertPositiveInteger(maxBytes, \"maxBytes\");\n\n const declaredLength = Number(response.headers.get(\"content-length\"));\n if (Number.isFinite(declaredLength) && declaredLength > maxBytes) {\n await cancelResponseBody(response);\n throw new Error(`Response exceeds the ${maxBytes}-byte limit`);\n }\n\n if (!response.body) {\n return \"\";\n }\n\n const reader = response.body.getReader();\n let totalBytes = 0;\n let text = \"\";\n let decoder: TextDecoder | undefined;\n let pendingChunks: Uint8Array[] = [];\n let pendingBytes = 0;\n\n const createDecoder = async (encodingLabel: string): Promise<TextDecoder> => {\n try {\n return new TextDecoder(encodingLabel);\n } catch {\n await reader.cancel();\n throw new Error(`Unsupported response character encoding \"${encodingLabel}\"`);\n }\n };\n\n const startDecoder = async (): Promise<void> => {\n if (decoder) return;\n let encodingLabel = encoding ?? \"utf-8\";\n if ((encoding === undefined && (sniffHtmlEncoding || sniffXmlEncoding)) || preferXmlBom) {\n const sniffLength = Math.min(pendingBytes, HTML_ENCODING_SNIFF_BYTES);\n const sniffBytes = new Uint8Array(sniffLength);\n let copied = 0;\n for (const chunk of pendingChunks) {\n if (copied >= sniffLength) break;\n const length = Math.min(chunk.byteLength, sniffLength - copied);\n sniffBytes.set(chunk.subarray(0, length), copied);\n copied += length;\n }\n if (preferXmlBom) {\n encodingLabel = getXmlBomCharacterEncoding(sniffBytes) ?? encodingLabel;\n } else {\n encodingLabel = sniffXmlEncoding\n ? getXmlCharacterEncoding(sniffBytes)\n : getEncoding(sniffBytes, {\n maxBytes: HTML_ENCODING_SNIFF_BYTES,\n defaultEncoding: \"utf-8\",\n });\n }\n }\n decoder = await createDecoder(encodingLabel);\n for (const chunk of pendingChunks) {\n text += decoder.decode(chunk, { stream: true });\n }\n pendingChunks = [];\n pendingBytes = 0;\n };\n\n if (!preferXmlBom && (encoding !== undefined || (!sniffHtmlEncoding && !sniffXmlEncoding))) {\n await startDecoder();\n }\n\n try {\n while (true) {\n const { done, value } = await reader.read();\n if (done) {\n break;\n }\n\n totalBytes += value.byteLength;\n if (totalBytes > maxBytes) {\n await reader.cancel();\n throw new Error(`Response exceeds the ${maxBytes}-byte limit`);\n }\n\n if (!decoder) {\n pendingChunks.push(value);\n pendingBytes += value.byteLength;\n if (pendingBytes >= HTML_ENCODING_SNIFF_BYTES) {\n await startDecoder();\n }\n } else {\n text += decoder.decode(value, { stream: true });\n }\n }\n await startDecoder();\n if (!decoder) {\n throw new Error(\"Response decoder was not initialized\");\n }\n text += decoder.decode();\n return text;\n } finally {\n reader.releaseLock();\n }\n}\n\nfunction createPinnedDispatcher(\n resolvedAddresses: ReadonlyArray<{ address: string; family: number }>,\n): Agent {\n const addresses = resolvedAddresses.map((record) => ({ ...record }));\n\n return new Agent({\n // Every validated request gets its own non-reused connection so a later DNS\n // answer cannot replace the address set approved for this hop.\n pipelining: 0,\n connect: {\n lookup: (_hostname, options, callback) => {\n const requestedFamily = options.family === 4 || options.family === 6 ? options.family : 0;\n const candidates = requestedFamily\n ? addresses.filter((record) => record.family === requestedFamily)\n : addresses;\n\n if (candidates.length === 0) {\n const error = new Error(\"No validated address is available for the requested IP family\") as NodeJS.ErrnoException;\n error.code = \"ENOTFOUND\";\n callback(error, []);\n return;\n }\n\n if (options.all) {\n callback(null, candidates);\n return;\n }\n\n const selected = candidates[0];\n callback(null, selected.address, selected.family);\n },\n },\n });\n}\n\nfunction beginClosingDispatcher(dispatcher: Agent): void {\n // close() waits for the active response stream to finish, so starting it here\n // releases the request-scoped Agent without interrupting callers reading the body.\n void dispatcher.close().catch(() => {});\n}\n\n/** Fetches a public HTTP(S) URL while validating every redirect target. */\nexport async function fetchPublicHttp(\n input: string | URL,\n options: PublicFetchOptions = {},\n): Promise<PublicHttpResult> {\n const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n const maxRedirects = options.maxRedirects ?? DEFAULT_MAX_REDIRECTS;\n assertPositiveInteger(timeoutMs, \"timeoutMs\");\n if (!Number.isSafeInteger(maxRedirects) || maxRedirects < 0 || maxRedirects > 10) {\n throw new Error(\"maxRedirects must be an integer between 0 and 10\");\n }\n\n let currentTarget = await resolvePublicHttpUrl(input);\n for (let redirectCount = 0; ; redirectCount += 1) {\n const dispatcher = createPinnedDispatcher(currentTarget.addresses);\n let response: Response;\n try {\n response = await fetch(currentTarget.url, {\n method: options.method ?? \"GET\",\n headers: options.headers,\n redirect: \"manual\",\n signal: AbortSignal.timeout(timeoutMs),\n dispatcher,\n } as RequestInit & { dispatcher: Agent });\n } catch (error) {\n await dispatcher.close();\n throw error;\n }\n\n if (!redirectStatuses.has(response.status)) {\n beginClosingDispatcher(dispatcher);\n return { response, url: currentTarget.url };\n }\n\n const location = response.headers.get(\"location\");\n if (!location) {\n beginClosingDispatcher(dispatcher);\n return { response, url: currentTarget.url };\n }\n\n if (redirectCount >= maxRedirects) {\n beginClosingDispatcher(dispatcher);\n return { response, url: currentTarget.url };\n }\n\n await cancelResponseBody(response);\n await dispatcher.close();\n let redirectUrl: URL;\n try {\n redirectUrl = new URL(location, currentTarget.url);\n } catch {\n throw new PublicUrlError(\"Redirect target is not a valid URL\");\n }\n currentTarget = await resolvePublicHttpUrl(redirectUrl);\n }\n}\n\n/** Fetches bounded text from a public URL and rejects non-success responses. */\nexport async function fetchPublicText(\n input: string | URL,\n options: PublicTextOptions = {},\n): Promise<PublicTextResult> {\n const maxBytes = options.maxBytes ?? 2_000_000;\n assertPositiveInteger(maxBytes, \"maxBytes\");\n\n const { response, url } = await fetchPublicHttp(input, {\n method: \"GET\",\n headers: options.headers,\n timeoutMs: options.timeoutMs,\n maxRedirects: options.maxRedirects,\n });\n\n if (!response.ok) {\n const status = response.status;\n await cancelResponseBody(response);\n throw new Error(`URL returned HTTP status ${status}`);\n }\n\n const contentTypeHeader = response.headers.get(\"content-type\");\n const contentType = contentTypeHeader?.split(\";\", 1)[0]?.trim().toLowerCase() || null;\n if (\n options.acceptedContentTypes\n && !options.acceptedContentTypes.some((accepted) => accepted.trim().toLowerCase() === contentType)\n ) {\n await cancelResponseBody(response);\n throw new Error(`URL returned unsupported content type ${contentType ?? \"missing\"}`);\n }\n\n const declaredEncoding = getDeclaredCharacterEncoding(contentTypeHeader);\n const shouldSniffHtmlEncoding = contentType === \"text/html\"\n && (declaredEncoding === undefined || !isSupportedCharacterEncoding(declaredEncoding));\n const isXmlContentType = contentType !== null\n && (contentType === \"application/xml\" || contentType === \"text/xml\" || contentType.endsWith(\"+xml\"));\n const shouldSniffXmlEncoding = declaredEncoding === undefined\n && isXmlContentType;\n const shouldPreferXmlBom = declaredEncoding !== undefined && isXmlContentType;\n return {\n text: await readResponseText(\n response,\n maxBytes,\n shouldSniffHtmlEncoding || shouldSniffXmlEncoding ? undefined : declaredEncoding,\n shouldSniffHtmlEncoding,\n shouldSniffXmlEncoding,\n shouldPreferXmlBom,\n ),\n url: url.href,\n status: response.status,\n contentType: contentTypeHeader,\n };\n}\n\n/** Reads a UTF-8 file only when it is a regular file within the requested cap. */\nexport async function readTextFile(filePath: string, maxBytes: number): Promise<string> {\n assertPositiveInteger(maxBytes, \"maxBytes\");\n const resolvedPath = path.resolve(filePath);\n const stats = await fs.stat(resolvedPath);\n if (!stats.isFile()) {\n throw new Error(`Path is not a regular file: ${resolvedPath}`);\n }\n if (stats.size > maxBytes) {\n throw new Error(`File exceeds the ${maxBytes}-byte limit: ${resolvedPath}`);\n }\n\n const contents = await fs.readFile(resolvedPath);\n if (contents.byteLength > maxBytes) {\n throw new Error(`File exceeds the ${maxBytes}-byte limit: ${resolvedPath}`);\n }\n return contents.toString(\"utf8\");\n}\n"]}
package/dist/report.js CHANGED
@@ -15,9 +15,11 @@ function scoreIndicator(score) {
15
15
  }
16
16
  function markdownCell(value) {
17
17
  return String(value ?? "")
18
+ .replace(/&/g, "&amp;")
19
+ .replace(/</g, "&lt;")
20
+ .replace(/>/g, "&gt;")
18
21
  .replace(/\\/g, "\\\\")
19
- .replace(/\|/g, "\\|")
20
- .replace(/`/g, "\\`")
22
+ .replace(/([`*_[\]|~])/g, "\\$1")
21
23
  .replace(/[\r\n]+/g, " ")
22
24
  .trim();
23
25
  }
@@ -1 +1 @@
1
- {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from \"node:path\";\nimport {\n getW3CMessageSeverity,\n type CSSMessage,\n type W3CMessage,\n} from \"./w3c-validator.js\";\nimport type { AuditCounts, LinkStatus, SEOIssue } from \"./seo-auditor.js\";\n\nexport const validationReportChecks = [\"input\", \"html\", \"css\", \"seo\", \"schema\", \"links\"] as const;\nexport type ValidationReportCheck = (typeof validationReportChecks)[number];\n\nexport interface ValidationReportSummary {\n overallScore: number | null;\n htmlScore: number | null;\n cssScore: number | null;\n seoScore: number | null;\n linkScore: number | null;\n htmlErrors: number;\n htmlWarnings: number;\n cssErrors: number;\n cssCompatibilityLimitations: number;\n seoErrors: number;\n seoWarnings: number;\n schemaErrors: number;\n linksChecked: number;\n brokenLinks: number;\n}\n\nexport interface ValidationReport {\n report: string;\n summary: ValidationReportSummary;\n htmlMessages: W3CMessage[];\n htmlTotalMessages: number;\n htmlTruncated: boolean;\n cssMessages: CSSMessage[];\n cssTotalMessages: number;\n cssTruncated: boolean;\n seoIssues: SEOIssue[];\n seoTotalIssues: number;\n seoTruncated: boolean;\n schemaIssues: SEOIssue[];\n schemaTotalIssues: number;\n schemaTruncated: boolean;\n links: LinkStatus[];\n failedChecks: ValidationReportCheck[];\n errors?: string[];\n}\n\nexport interface ValidationReportInput {\n htmlFilePath: string;\n cssAudited: boolean;\n htmlMessages: W3CMessage[];\n htmlTotalMessages?: number;\n htmlTruncated?: boolean;\n htmlCounts?: { error: number; warning: number; info: number };\n cssMessages: CSSMessage[];\n cssTotalMessages?: number;\n cssTruncated?: boolean;\n cssCounts?: { error: number; compatibilityLimitation: number };\n seoIssues: SEOIssue[];\n seoTotalIssues?: number;\n seoTruncated?: boolean;\n seoCounts?: AuditCounts;\n schemaIssues: SEOIssue[];\n schemaTotalIssues?: number;\n schemaTruncated?: boolean;\n schemaCounts?: AuditCounts;\n links: LinkStatus[];\n failedChecks?: ValidationReportCheck[];\n errors?: string[];\n}\n\nfunction clampScore(score: number): number {\n return Math.max(0, Math.min(100, score));\n}\n\nfunction scoreIndicator(score: number | null): string {\n if (score === null) return \"Unavailable\";\n if (score >= 90) return \"🟢\";\n if (score >= 50) return \"🟠\";\n return \"🔴\";\n}\n\nfunction markdownCell(value: unknown): string {\n return String(value ?? \"\")\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/\\|/g, \"\\\\|\")\n .replace(/`/g, \"\\\\`\")\n .replace(/[\\r\\n]+/g, \" \")\n .trim();\n}\n\nfunction isRedirect(link: LinkStatus): boolean {\n return typeof link.status === \"number\" && link.status >= 300 && link.status < 400;\n}\n\n/** Builds the human-readable report and its machine-readable equivalent. */\nexport function createValidationReport(input: ValidationReportInput): ValidationReport {\n const failedChecks = input.failedChecks ?? [];\n const checkFailed = (check: ValidationReportCheck): boolean => failedChecks.includes(check);\n const htmlErrors = input.htmlCounts?.error\n ?? input.htmlMessages.filter((message) => getW3CMessageSeverity(message) === \"error\").length;\n const htmlWarnings = input.htmlCounts?.warning\n ?? input.htmlMessages.filter((message) => getW3CMessageSeverity(message) === \"warning\").length;\n const htmlTotalMessages = input.htmlTotalMessages ?? input.htmlMessages.length;\n const htmlTruncated = input.htmlTruncated ?? htmlTotalMessages > input.htmlMessages.length;\n const cssTotalMessages = input.cssTotalMessages ?? input.cssMessages.length;\n const cssTruncated = input.cssTruncated ?? cssTotalMessages > input.cssMessages.length;\n const cssCompatibilityLimitations = input.cssCounts?.compatibilityLimitation\n ?? input.cssMessages.filter((message) => message.compatibility === \"known-validator-limitation\").length;\n const cssErrors = input.cssCounts?.error ?? input.cssMessages.length - cssCompatibilityLimitations;\n const seoTotalIssues = input.seoTotalIssues ?? input.seoIssues.length;\n const seoTruncated = input.seoTruncated ?? seoTotalIssues > input.seoIssues.length;\n const schemaTotalIssues = input.schemaTotalIssues ?? input.schemaIssues.length;\n const schemaTruncated = input.schemaTruncated ?? schemaTotalIssues > input.schemaIssues.length;\n const seoErrors = input.seoCounts?.error ?? input.seoIssues.filter((issue) => issue.severity === \"error\").length;\n const seoWarnings = input.seoCounts?.warning ?? input.seoIssues.filter((issue) => issue.severity === \"warning\").length;\n const schemaErrors = input.schemaCounts?.error ?? input.schemaIssues.filter((issue) => issue.severity === \"error\").length;\n const brokenLinks = input.links.filter((link) => !link.ok).length;\n const redirectLinks = input.links.filter(isRedirect).length;\n\n const htmlScore = checkFailed(\"html\") ? null : clampScore(100 - htmlErrors * 15 - htmlWarnings * 2);\n const cssScore = checkFailed(\"css\") || !input.cssAudited || cssCompatibilityLimitations > 0\n ? null\n : clampScore(100 - cssErrors * 20);\n const seoScore = checkFailed(\"seo\") || checkFailed(\"schema\")\n ? null\n : clampScore(100 - seoErrors * 15 - seoWarnings * 4 - schemaErrors * 15);\n const linkScore = checkFailed(\"links\") || input.links.length === 0\n ? null\n : clampScore(100 - brokenLinks * 25);\n const auditedScores = [htmlScore, seoScore, cssScore, linkScore].filter(\n (score): score is number => score !== null,\n );\n const overallScore = failedChecks.length > 0 || cssCompatibilityLimitations > 0 || auditedScores.length === 0\n ? null\n : Math.round(auditedScores.reduce((total, score) => total + score, 0) / auditedScores.length);\n\n const summary: ValidationReportSummary = {\n overallScore,\n htmlScore,\n cssScore,\n seoScore,\n linkScore,\n htmlErrors,\n htmlWarnings,\n cssErrors,\n cssCompatibilityLimitations,\n seoErrors,\n seoWarnings,\n schemaErrors,\n linksChecked: input.links.length,\n brokenLinks,\n };\n\n const report: string[] = [\n `# Web Validation & SEO Audit Report — ${overallScore === null ? \"Partial\" : `${scoreIndicator(overallScore)} **${overallScore}**/100`}`,\n `*Generated for: \\`${markdownCell(path.basename(input.htmlFilePath))}\\`*`,\n \"\",\n \"## Page health scores\",\n \"\",\n \"| Audit | Status | Score |\",\n \"| :--- | :---: | :---: |\",\n `| W3C HTML validation | ${htmlScore === null ? \"Unavailable\" : scoreIndicator(htmlScore)} | ${htmlScore === null ? \"N/A\" : `**${htmlScore}** / 100`} |`,\n `| CSS validation | ${cssScore === null ? (checkFailed(\"css\") ? \"Unavailable\" : cssCompatibilityLimitations > 0 ? \"Compatibility-limited\" : \"Not audited\") : scoreIndicator(cssScore)} | ${cssScore === null ? \"N/A\" : `**${cssScore}** / 100`} |`,\n `| SEO and accessibility | ${seoScore === null ? \"Unavailable\" : scoreIndicator(seoScore)} | ${seoScore === null ? \"N/A\" : `**${seoScore}** / 100`} |`,\n `| Link integrity | ${linkScore === null ? (checkFailed(\"links\") ? \"Unavailable\" : \"No links checked\") : scoreIndicator(linkScore)} | ${linkScore === null ? \"N/A\" : `**${linkScore}** / 100`} |`,\n \"\",\n \"## Summary\",\n \"\",\n `- HTML: ${htmlErrors} error(s), ${htmlWarnings} warning(s)` ,\n `- CSS: ${input.cssAudited ? `${cssErrors} error(s)${cssCompatibilityLimitations > 0 ? `, ${cssCompatibilityLimitations} known validator limitation(s)` : \"\"}` : \"not audited\"}`,\n `- SEO and accessibility: ${seoErrors} error(s), ${seoWarnings} warning(s)`,\n `- JSON-LD syntax: ${schemaErrors} error(s)`,\n `- Links: ${brokenLinks} broken or unreachable, ${redirectLinks} redirect${redirectLinks === 1 ? \"\" : \"s\"} to review of ${input.links.length} checked`,\n \"\",\n `## HTML diagnostics (${htmlTruncated ? `${input.htmlMessages.length} shown of ${htmlTotalMessages}` : htmlTotalMessages})`,\n ];\n\n if (failedChecks.length > 0) {\n report.splice(2, 0, \"\", \"## Partial validation report\", \"The following checks were unavailable: \" + failedChecks.join(\", \") + \". Remaining checks completed.\");\n }\n\n if (input.htmlMessages.length === 0) {\n report.push(\"No HTML validation diagnostics were returned.\");\n } else {\n if (htmlTruncated) {\n report.push(`The structured HTML diagnostics are capped; showing the first ${input.htmlMessages.length} of ${htmlTotalMessages}. Summary counts and scoring use all returned Nu diagnostics.`);\n }\n report.push(\"\", \"| Line | Column | Severity | Message | Extract |\", \"| :---: | :---: | :--- | :--- | :--- |\");\n for (const message of input.htmlMessages) {\n report.push(\n `| ${message.lastLine ?? \"N/A\"} | ${message.lastColumn ?? \"N/A\"} | ${markdownCell(getW3CMessageSeverity(message))} | ${markdownCell(message.message)} | ${markdownCell(message.extract ?? \"N/A\")} |`,\n );\n }\n }\n\n if (input.cssAudited) {\n report.push(\"\", `## CSS diagnostics (${cssTruncated ? `${input.cssMessages.length} shown of ${cssTotalMessages}` : cssTotalMessages})`);\n if (input.cssMessages.length === 0) {\n report.push(\"No CSS validation errors were returned.\");\n } else {\n if (cssTruncated) {\n report.push(`The structured CSS diagnostics are capped; showing the first ${input.cssMessages.length} of ${cssTotalMessages}. Summary counts and scoring use all returned Jigsaw diagnostics.`);\n }\n report.push(\"\", \"| Line | Context | Message |\", \"| :---: | :--- | :--- |\");\n for (const message of input.cssMessages) {\n report.push(\n `| ${message.line} | ${markdownCell(message.context ?? \"N/A\")} | ${markdownCell(message.message)} |`,\n );\n }\n if (cssCompatibilityLimitations > 0) {\n report.push(\n \"\",\n `${cssCompatibilityLimitations} known validator limitation(s) match Jigsaw's current parser gap for the standards-defined \\`@container\\` rule. The original Jigsaw diagnostic is preserved, but CSS and overall scores are withheld because this upstream limitation can report valid modern CSS as invalid.`,\n );\n }\n }\n }\n\n const combinedIssues = [...input.seoIssues, ...input.schemaIssues];\n const combinedTotalIssues = seoTotalIssues + schemaTotalIssues;\n const combinedTruncated = seoTruncated || schemaTruncated;\n report.push(\n \"\",\n `## SEO, accessibility, and JSON-LD findings (${combinedTruncated ? `${combinedIssues.length} shown of ${combinedTotalIssues}` : combinedTotalIssues})`,\n );\n if (combinedIssues.length === 0) {\n report.push(\"No SEO, accessibility, or JSON-LD syntax findings were returned.\");\n } else {\n if (seoTruncated) {\n report.push(`The structured SEO/accessibility findings are capped; showing the first ${input.seoIssues.length} of ${seoTotalIssues}. Summary counts and scoring use all findings.`);\n }\n if (schemaTruncated) {\n report.push(`The structured JSON-LD findings are capped; showing the first ${input.schemaIssues.length} of ${schemaTotalIssues}. Summary counts and scoring use all findings.`);\n }\n report.push(\"\", \"| Category | Severity | Message | Element |\", \"| :--- | :--- | :--- | :--- |\");\n for (const issue of combinedIssues) {\n report.push(\n `| ${markdownCell(issue.category)} | ${markdownCell(issue.severity)} | ${markdownCell(issue.message)} | ${markdownCell(issue.element ?? \"N/A\")} |`,\n );\n }\n }\n\n report.push(\"\", `## Link health (${input.links.length} checked)`);\n if (input.links.length === 0) {\n report.push(\"No public HTTP(S) links were checked.\");\n } else {\n report.push(\"\", \"| URL | Status | Reachable | Details |\", \"| :--- | :---: | :---: | :--- |\");\n for (const link of input.links) {\n report.push(\n `| ${markdownCell(link.url)} | ${markdownCell(link.status)} | ${link.ok ? \"Yes\" : \"No\"} | ${markdownCell(link.message ?? \"Accessible\")} |`,\n );\n }\n }\n\n return {\n report: report.join(\"\\n\"),\n summary,\n htmlMessages: input.htmlMessages,\n htmlTotalMessages,\n htmlTruncated,\n cssMessages: input.cssMessages,\n cssTotalMessages,\n cssTruncated,\n seoIssues: input.seoIssues,\n seoTotalIssues,\n seoTruncated,\n schemaIssues: input.schemaIssues,\n schemaTotalIssues,\n schemaTruncated,\n links: input.links,\n failedChecks,\n ...(input.errors && input.errors.length > 0 ? { errors: input.errors } : {}),\n };\n}\n"]}
1
+ {"version":3,"file":"report.js","sourceRoot":"","sources":["../src/report.ts"],"names":[],"mappings":"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","sourcesContent":["import * as path from \"node:path\";\nimport {\n getW3CMessageSeverity,\n type CSSMessage,\n type W3CMessage,\n} from \"./w3c-validator.js\";\nimport type { AuditCounts, LinkStatus, SEOIssue } from \"./seo-auditor.js\";\n\nexport const validationReportChecks = [\"input\", \"html\", \"css\", \"seo\", \"schema\", \"links\"] as const;\nexport type ValidationReportCheck = (typeof validationReportChecks)[number];\n\nexport interface ValidationReportSummary {\n overallScore: number | null;\n htmlScore: number | null;\n cssScore: number | null;\n seoScore: number | null;\n linkScore: number | null;\n htmlErrors: number;\n htmlWarnings: number;\n cssErrors: number;\n cssCompatibilityLimitations: number;\n seoErrors: number;\n seoWarnings: number;\n schemaErrors: number;\n linksChecked: number;\n brokenLinks: number;\n}\n\nexport interface ValidationReport {\n report: string;\n summary: ValidationReportSummary;\n htmlMessages: W3CMessage[];\n htmlTotalMessages: number;\n htmlTruncated: boolean;\n cssMessages: CSSMessage[];\n cssTotalMessages: number;\n cssTruncated: boolean;\n seoIssues: SEOIssue[];\n seoTotalIssues: number;\n seoTruncated: boolean;\n schemaIssues: SEOIssue[];\n schemaTotalIssues: number;\n schemaTruncated: boolean;\n links: LinkStatus[];\n failedChecks: ValidationReportCheck[];\n errors?: string[];\n}\n\nexport interface ValidationReportInput {\n htmlFilePath: string;\n cssAudited: boolean;\n htmlMessages: W3CMessage[];\n htmlTotalMessages?: number;\n htmlTruncated?: boolean;\n htmlCounts?: { error: number; warning: number; info: number };\n cssMessages: CSSMessage[];\n cssTotalMessages?: number;\n cssTruncated?: boolean;\n cssCounts?: { error: number; compatibilityLimitation: number };\n seoIssues: SEOIssue[];\n seoTotalIssues?: number;\n seoTruncated?: boolean;\n seoCounts?: AuditCounts;\n schemaIssues: SEOIssue[];\n schemaTotalIssues?: number;\n schemaTruncated?: boolean;\n schemaCounts?: AuditCounts;\n links: LinkStatus[];\n failedChecks?: ValidationReportCheck[];\n errors?: string[];\n}\n\nfunction clampScore(score: number): number {\n return Math.max(0, Math.min(100, score));\n}\n\nfunction scoreIndicator(score: number | null): string {\n if (score === null) return \"Unavailable\";\n if (score >= 90) return \"🟢\";\n if (score >= 50) return \"🟠\";\n return \"🔴\";\n}\n\nfunction markdownCell(value: unknown): string {\n return String(value ?? \"\")\n .replace(/&/g, \"&amp;\")\n .replace(/</g, \"&lt;\")\n .replace(/>/g, \"&gt;\")\n .replace(/\\\\/g, \"\\\\\\\\\")\n .replace(/([`*_[\\]|~])/g, \"\\\\$1\")\n .replace(/[\\r\\n]+/g, \" \")\n .trim();\n}\n\nfunction isRedirect(link: LinkStatus): boolean {\n return typeof link.status === \"number\" && link.status >= 300 && link.status < 400;\n}\n\n/** Builds the human-readable report and its machine-readable equivalent. */\nexport function createValidationReport(input: ValidationReportInput): ValidationReport {\n const failedChecks = input.failedChecks ?? [];\n const checkFailed = (check: ValidationReportCheck): boolean => failedChecks.includes(check);\n const htmlErrors = input.htmlCounts?.error\n ?? input.htmlMessages.filter((message) => getW3CMessageSeverity(message) === \"error\").length;\n const htmlWarnings = input.htmlCounts?.warning\n ?? input.htmlMessages.filter((message) => getW3CMessageSeverity(message) === \"warning\").length;\n const htmlTotalMessages = input.htmlTotalMessages ?? input.htmlMessages.length;\n const htmlTruncated = input.htmlTruncated ?? htmlTotalMessages > input.htmlMessages.length;\n const cssTotalMessages = input.cssTotalMessages ?? input.cssMessages.length;\n const cssTruncated = input.cssTruncated ?? cssTotalMessages > input.cssMessages.length;\n const cssCompatibilityLimitations = input.cssCounts?.compatibilityLimitation\n ?? input.cssMessages.filter((message) => message.compatibility === \"known-validator-limitation\").length;\n const cssErrors = input.cssCounts?.error ?? input.cssMessages.length - cssCompatibilityLimitations;\n const seoTotalIssues = input.seoTotalIssues ?? input.seoIssues.length;\n const seoTruncated = input.seoTruncated ?? seoTotalIssues > input.seoIssues.length;\n const schemaTotalIssues = input.schemaTotalIssues ?? input.schemaIssues.length;\n const schemaTruncated = input.schemaTruncated ?? schemaTotalIssues > input.schemaIssues.length;\n const seoErrors = input.seoCounts?.error ?? input.seoIssues.filter((issue) => issue.severity === \"error\").length;\n const seoWarnings = input.seoCounts?.warning ?? input.seoIssues.filter((issue) => issue.severity === \"warning\").length;\n const schemaErrors = input.schemaCounts?.error ?? input.schemaIssues.filter((issue) => issue.severity === \"error\").length;\n const brokenLinks = input.links.filter((link) => !link.ok).length;\n const redirectLinks = input.links.filter(isRedirect).length;\n\n const htmlScore = checkFailed(\"html\") ? null : clampScore(100 - htmlErrors * 15 - htmlWarnings * 2);\n const cssScore = checkFailed(\"css\") || !input.cssAudited || cssCompatibilityLimitations > 0\n ? null\n : clampScore(100 - cssErrors * 20);\n const seoScore = checkFailed(\"seo\") || checkFailed(\"schema\")\n ? null\n : clampScore(100 - seoErrors * 15 - seoWarnings * 4 - schemaErrors * 15);\n const linkScore = checkFailed(\"links\") || input.links.length === 0\n ? null\n : clampScore(100 - brokenLinks * 25);\n const auditedScores = [htmlScore, seoScore, cssScore, linkScore].filter(\n (score): score is number => score !== null,\n );\n const overallScore = failedChecks.length > 0 || cssCompatibilityLimitations > 0 || auditedScores.length === 0\n ? null\n : Math.round(auditedScores.reduce((total, score) => total + score, 0) / auditedScores.length);\n\n const summary: ValidationReportSummary = {\n overallScore,\n htmlScore,\n cssScore,\n seoScore,\n linkScore,\n htmlErrors,\n htmlWarnings,\n cssErrors,\n cssCompatibilityLimitations,\n seoErrors,\n seoWarnings,\n schemaErrors,\n linksChecked: input.links.length,\n brokenLinks,\n };\n\n const report: string[] = [\n `# Web Validation & SEO Audit Report — ${overallScore === null ? \"Partial\" : `${scoreIndicator(overallScore)} **${overallScore}**/100`}`,\n `*Generated for: \\`${markdownCell(path.basename(input.htmlFilePath))}\\`*`,\n \"\",\n \"## Page health scores\",\n \"\",\n \"| Audit | Status | Score |\",\n \"| :--- | :---: | :---: |\",\n `| W3C HTML validation | ${htmlScore === null ? \"Unavailable\" : scoreIndicator(htmlScore)} | ${htmlScore === null ? \"N/A\" : `**${htmlScore}** / 100`} |`,\n `| CSS validation | ${cssScore === null ? (checkFailed(\"css\") ? \"Unavailable\" : cssCompatibilityLimitations > 0 ? \"Compatibility-limited\" : \"Not audited\") : scoreIndicator(cssScore)} | ${cssScore === null ? \"N/A\" : `**${cssScore}** / 100`} |`,\n `| SEO and accessibility | ${seoScore === null ? \"Unavailable\" : scoreIndicator(seoScore)} | ${seoScore === null ? \"N/A\" : `**${seoScore}** / 100`} |`,\n `| Link integrity | ${linkScore === null ? (checkFailed(\"links\") ? \"Unavailable\" : \"No links checked\") : scoreIndicator(linkScore)} | ${linkScore === null ? \"N/A\" : `**${linkScore}** / 100`} |`,\n \"\",\n \"## Summary\",\n \"\",\n `- HTML: ${htmlErrors} error(s), ${htmlWarnings} warning(s)` ,\n `- CSS: ${input.cssAudited ? `${cssErrors} error(s)${cssCompatibilityLimitations > 0 ? `, ${cssCompatibilityLimitations} known validator limitation(s)` : \"\"}` : \"not audited\"}`,\n `- SEO and accessibility: ${seoErrors} error(s), ${seoWarnings} warning(s)`,\n `- JSON-LD syntax: ${schemaErrors} error(s)`,\n `- Links: ${brokenLinks} broken or unreachable, ${redirectLinks} redirect${redirectLinks === 1 ? \"\" : \"s\"} to review of ${input.links.length} checked`,\n \"\",\n `## HTML diagnostics (${htmlTruncated ? `${input.htmlMessages.length} shown of ${htmlTotalMessages}` : htmlTotalMessages})`,\n ];\n\n if (failedChecks.length > 0) {\n report.splice(2, 0, \"\", \"## Partial validation report\", \"The following checks were unavailable: \" + failedChecks.join(\", \") + \". Remaining checks completed.\");\n }\n\n if (input.htmlMessages.length === 0) {\n report.push(\"No HTML validation diagnostics were returned.\");\n } else {\n if (htmlTruncated) {\n report.push(`The structured HTML diagnostics are capped; showing the first ${input.htmlMessages.length} of ${htmlTotalMessages}. Summary counts and scoring use all returned Nu diagnostics.`);\n }\n report.push(\"\", \"| Line | Column | Severity | Message | Extract |\", \"| :---: | :---: | :--- | :--- | :--- |\");\n for (const message of input.htmlMessages) {\n report.push(\n `| ${message.lastLine ?? \"N/A\"} | ${message.lastColumn ?? \"N/A\"} | ${markdownCell(getW3CMessageSeverity(message))} | ${markdownCell(message.message)} | ${markdownCell(message.extract ?? \"N/A\")} |`,\n );\n }\n }\n\n if (input.cssAudited) {\n report.push(\"\", `## CSS diagnostics (${cssTruncated ? `${input.cssMessages.length} shown of ${cssTotalMessages}` : cssTotalMessages})`);\n if (input.cssMessages.length === 0) {\n report.push(\"No CSS validation errors were returned.\");\n } else {\n if (cssTruncated) {\n report.push(`The structured CSS diagnostics are capped; showing the first ${input.cssMessages.length} of ${cssTotalMessages}. Summary counts and scoring use all returned Jigsaw diagnostics.`);\n }\n report.push(\"\", \"| Line | Context | Message |\", \"| :---: | :--- | :--- |\");\n for (const message of input.cssMessages) {\n report.push(\n `| ${message.line} | ${markdownCell(message.context ?? \"N/A\")} | ${markdownCell(message.message)} |`,\n );\n }\n if (cssCompatibilityLimitations > 0) {\n report.push(\n \"\",\n `${cssCompatibilityLimitations} known validator limitation(s) match Jigsaw's current parser gap for the standards-defined \\`@container\\` rule. The original Jigsaw diagnostic is preserved, but CSS and overall scores are withheld because this upstream limitation can report valid modern CSS as invalid.`,\n );\n }\n }\n }\n\n const combinedIssues = [...input.seoIssues, ...input.schemaIssues];\n const combinedTotalIssues = seoTotalIssues + schemaTotalIssues;\n const combinedTruncated = seoTruncated || schemaTruncated;\n report.push(\n \"\",\n `## SEO, accessibility, and JSON-LD findings (${combinedTruncated ? `${combinedIssues.length} shown of ${combinedTotalIssues}` : combinedTotalIssues})`,\n );\n if (combinedIssues.length === 0) {\n report.push(\"No SEO, accessibility, or JSON-LD syntax findings were returned.\");\n } else {\n if (seoTruncated) {\n report.push(`The structured SEO/accessibility findings are capped; showing the first ${input.seoIssues.length} of ${seoTotalIssues}. Summary counts and scoring use all findings.`);\n }\n if (schemaTruncated) {\n report.push(`The structured JSON-LD findings are capped; showing the first ${input.schemaIssues.length} of ${schemaTotalIssues}. Summary counts and scoring use all findings.`);\n }\n report.push(\"\", \"| Category | Severity | Message | Element |\", \"| :--- | :--- | :--- | :--- |\");\n for (const issue of combinedIssues) {\n report.push(\n `| ${markdownCell(issue.category)} | ${markdownCell(issue.severity)} | ${markdownCell(issue.message)} | ${markdownCell(issue.element ?? \"N/A\")} |`,\n );\n }\n }\n\n report.push(\"\", `## Link health (${input.links.length} checked)`);\n if (input.links.length === 0) {\n report.push(\"No public HTTP(S) links were checked.\");\n } else {\n report.push(\"\", \"| URL | Status | Reachable | Details |\", \"| :--- | :---: | :---: | :--- |\");\n for (const link of input.links) {\n report.push(\n `| ${markdownCell(link.url)} | ${markdownCell(link.status)} | ${link.ok ? \"Yes\" : \"No\"} | ${markdownCell(link.message ?? \"Accessible\")} |`,\n );\n }\n }\n\n return {\n report: report.join(\"\\n\"),\n summary,\n htmlMessages: input.htmlMessages,\n htmlTotalMessages,\n htmlTruncated,\n cssMessages: input.cssMessages,\n cssTotalMessages,\n cssTruncated,\n seoIssues: input.seoIssues,\n seoTotalIssues,\n seoTruncated,\n schemaIssues: input.schemaIssues,\n schemaTotalIssues,\n schemaTruncated,\n links: input.links,\n failedChecks,\n ...(input.errors && input.errors.length > 0 ? { errors: input.errors } : {}),\n };\n}\n"]}
@@ -68,7 +68,15 @@ export function auditSeoMetadataDetailed(htmlContent) {
68
68
  });
69
69
  }
70
70
  else {
71
- const titleText = titleTag.text().trim();
71
+ if (titleTag.length > 1) {
72
+ add({
73
+ code: "seo.title.multiple",
74
+ severity: "warning",
75
+ category: "SEO",
76
+ message: "Multiple <title> elements are declared in the document head. Keep one unambiguous page title.",
77
+ });
78
+ }
79
+ const titleText = titleTag.first().text().trim();
72
80
  if (titleText.length === 0) {
73
81
  add({
74
82
  code: "seo.title.missing_or_empty",
@@ -107,7 +115,15 @@ export function auditSeoMetadataDetailed(htmlContent) {
107
115
  });
108
116
  }
109
117
  else {
110
- const descText = metaDescription.attr("content")?.trim() || "";
118
+ if (metaDescription.length > 1) {
119
+ add({
120
+ code: "seo.meta_description.multiple",
121
+ severity: "warning",
122
+ category: "SEO",
123
+ message: "Multiple meta descriptions are declared in the document head. Keep one unambiguous page description.",
124
+ });
125
+ }
126
+ const descText = metaDescription.first().attr("content")?.trim() || "";
111
127
  if (descText.length === 0) {
112
128
  add({
113
129
  code: "seo.meta_description.missing_or_empty",
@@ -260,6 +276,17 @@ export function auditSeoMetadataDetailed(htmlContent) {
260
276
  });
261
277
  }
262
278
  });
279
+ $("input[type=\"image\" i]").filter((_, element) => !isInTemplateContents(element)).each((_, element) => {
280
+ const alt = $(element).attr("alt");
281
+ if (alt === undefined || alt.trim() === "") {
282
+ add({
283
+ code: "accessibility.input_image_alt.missing_or_empty",
284
+ severity: "error",
285
+ category: "Accessibility",
286
+ message: "Image submit buttons need non-empty alt text that labels the button's function.",
287
+ });
288
+ }
289
+ });
263
290
  // --- Open Graph / Social Tags ---
264
291
  const ogTitle = $('head > meta[property="og:title"]');
265
292
  const ogImage = $('head > meta[property="og:image"]');
@@ -1 +1 @@
1
- {"version":3,"file":"seo-auditor.js","sourceRoot":"","sources":["../src/seo-auditor.ts"],"names":[],"mappings":"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","sourcesContent":["import * as cheerio from \"cheerio\";\nimport {\n assertPublicHttpUrl,\n cancelResponseBody,\n fetchPublicHttp,\n getErrorMessage,\n PublicUrlError,\n} from \"./network.js\";\nimport { PACKAGE_VERSION } from \"./version.js\";\n\nexport const MAX_AUDIT_ISSUES = 200;\nexport const MAX_LINKS_TO_CHECK = 25;\nconst LINK_CHECK_CONCURRENCY = 5;\nconst LINK_CHECK_TIMEOUT_MS = 5_000;\nconst LINK_CHECK_USER_AGENT = `mcp-web-validator/${PACKAGE_VERSION} (+https://digestseo.com/validator-mcp/)`;\n\nexport interface SEOIssue {\n code: string;\n severity: \"error\" | \"warning\" | \"info\";\n category: \"SEO\" | \"Schema\" | \"BrokenLinks\" | \"Accessibility\";\n message: string;\n element?: string;\n}\n\nexport interface LinkStatus {\n url: string;\n status: number | string;\n ok: boolean;\n message?: string;\n}\n\nexport interface AuditCounts {\n error: number;\n warning: number;\n info: number;\n}\n\nexport interface AuditDetails {\n issues: SEOIssue[];\n totalIssues: number;\n truncated: boolean;\n counts: AuditCounts;\n}\n\nfunction addIssue(issues: SEOIssue[], issue: SEOIssue): void {\n if (issues.length < MAX_AUDIT_ISSUES) {\n issues.push(issue);\n }\n}\n\nfunction createIssueCollector(): {\n issues: SEOIssue[];\n add: (issue: SEOIssue) => void;\n details: () => AuditDetails;\n} {\n const issues: SEOIssue[] = [];\n let totalIssues = 0;\n const counts: AuditCounts = { error: 0, warning: 0, info: 0 };\n return {\n issues,\n add(issue) {\n totalIssues += 1;\n counts[issue.severity] += 1;\n addIssue(issues, issue);\n },\n details() {\n return {\n issues,\n totalIssues,\n truncated: totalIssues > issues.length,\n counts: { ...counts },\n };\n },\n };\n}\n\nfunction isJsonLdScriptType(type: string | undefined): boolean {\n return type?.split(\";\", 1)[0].trim().toLowerCase() === \"application/ld+json\";\n}\n\nfunction hasJsonLdDocumentShape(value: unknown): boolean {\n if (Array.isArray(value)) {\n return value.every((entry) => typeof entry === \"object\" && entry !== null && !Array.isArray(entry));\n }\n return typeof value === \"object\" && value !== null;\n}\n\nfunction isInTemplateContents(element: unknown): boolean {\n type ParentNode = { name?: string; parent?: unknown };\n let node = element as ParentNode | null;\n while (node) {\n if (node.name?.toLowerCase() === \"template\") return true;\n node = (node.parent ?? null) as ParentNode | null;\n }\n return false;\n}\n\n/**\n * Audits technical SEO and accessibility basics on HTML content using Cheerio\n */\nexport function auditSeoMetadataDetailed(htmlContent: string): AuditDetails {\n const $ = cheerio.load(htmlContent);\n const collector = createIssueCollector();\n const { add } = collector;\n\n // --- Title Tag Audits ---\n const titleTag = $(\"head > title\");\n if (titleTag.length === 0) {\n add({\n code: \"seo.title.missing_or_empty\",\n severity: \"error\",\n category: \"SEO\",\n message: \"Missing <title> tag. Add a concise, descriptive title to help represent the page in search results.\",\n });\n } else {\n const titleText = titleTag.text().trim();\n if (titleText.length === 0) {\n add({\n code: \"seo.title.missing_or_empty\",\n severity: \"error\",\n category: \"SEO\",\n message: \"The <title> tag is empty.\",\n });\n } else if (titleText.length < 30) {\n add({\n code: \"seo.title.length\",\n severity: \"info\",\n category: \"SEO\",\n message: `Title is ${titleText.length} characters. This is shorter than the audit's common editorial range; review whether it describes the page clearly.`,\n element: `<title>${titleText}</title>`,\n });\n } else if (titleText.length > 60) {\n add({\n code: \"seo.title.length\",\n severity: \"info\",\n category: \"SEO\",\n message: `Title is ${titleText.length} characters. This is longer than the audit's common editorial range; Google title links may be shortened or rewritten depending on context and device.`,\n element: `<title>${titleText}</title>`,\n });\n }\n }\n\n // --- Meta Description Audits ---\n const metaDescription = $('head > meta[name=\"description\" i]');\n if (metaDescription.length === 0) {\n add({\n code: \"seo.meta_description.missing_or_empty\",\n severity: \"error\",\n category: \"SEO\",\n message: \"Missing <meta name=\\\"description\\\">. Add a concise, accurate page summary; Google may use page content or this description to generate a snippet.\",\n });\n } else {\n const descText = metaDescription.attr(\"content\")?.trim() || \"\";\n if (descText.length === 0) {\n add({\n code: \"seo.meta_description.missing_or_empty\",\n severity: \"error\",\n category: \"SEO\",\n message: \"Meta description content attribute is empty.\",\n });\n } else if (descText.length < 120) {\n add({\n code: \"seo.meta_description.length\",\n severity: \"info\",\n category: \"SEO\",\n message: `Meta description is ${descText.length} characters. This is shorter than the audit's common editorial range; review whether it provides a useful page summary.`,\n element: `<meta name=\"description\" content=\"${descText}\">`,\n });\n } else if (descText.length > 160) {\n add({\n code: \"seo.meta_description.length\",\n severity: \"info\",\n category: \"SEO\",\n message: `Meta description is ${descText.length} characters. This is longer than the audit's common editorial range; displayed snippets may be shortened depending on the query and device.`,\n element: `<meta name=\"description\" content=\"${descText}\">`,\n });\n }\n }\n\n // --- Canonical Link ---\n const canonicalLinks = $(\"head > link[rel]\").filter((_, element) => {\n const rel = $(element).attr(\"rel\") ?? \"\";\n return rel\n .trim()\n .split(/[\\t\\n\\f\\r ]+/)\n .some((token) => token.toLowerCase() === \"canonical\");\n });\n const nonEmptyCanonical = canonicalLinks.filter((_, element) => Boolean($(element).attr(\"href\")?.trim()));\n const usableCanonical = nonEmptyCanonical.filter(\n (_, element) => ![\"hreflang\", \"lang\", \"media\", \"type\"].some((attribute) => $(element).attr(attribute) !== undefined),\n );\n if (canonicalLinks.length === 0) {\n add({\n code: \"seo.canonical.missing\",\n severity: \"info\",\n category: \"SEO\",\n message: \"No rel=\\\"canonical\\\" preference is declared. This is optional unless the page needs an explicit canonicalization signal.\",\n });\n } else if (usableCanonical.length === 0) {\n if (nonEmptyCanonical.length > 0) {\n add({\n code: \"seo.canonical.unusable\",\n severity: \"warning\",\n category: \"SEO\",\n message: \"Canonical link is present but uses attributes Google ignores for canonicalization. Use a plain rel=\\\"canonical\\\" link in <head>.\",\n });\n } else {\n add({\n code: \"seo.canonical.missing\",\n severity: \"warning\",\n category: \"SEO\",\n message: \"Canonical link is present but its href is empty. Provide a usable canonical target or remove the declaration.\",\n });\n }\n } else if (usableCanonical.length > 1) {\n add({\n code: \"seo.canonical.multiple\",\n severity: \"warning\",\n category: \"SEO\",\n message: \"Multiple usable canonical link relations are declared. Keep one unambiguous canonical target.\",\n });\n }\n\n // Google Search respects robots meta directives in both the head and body.\n const indexBlockingRobotsMeta = $('meta[name]').filter((_, element) => {\n if (isInTemplateContents(element)) return false;\n const name = ($(element).attr(\"name\") ?? \"\").trim().toLowerCase();\n if (name !== \"robots\" && name !== \"googlebot\") return false;\n const content = $(element).attr(\"content\") ?? \"\";\n return content\n .split(\",\")\n .some((directive) => [\"noindex\", \"none\"].includes(directive.trim().toLowerCase()));\n });\n if (indexBlockingRobotsMeta.length > 0) {\n add({\n code: \"seo.robots.noindex\",\n severity: \"warning\",\n category: \"SEO\",\n message: \"A robots directive prevents Google from indexing this page. Confirm that noindex is intentional.\",\n });\n }\n\n // --- Viewport Meta Tag (Mobile Responsiveness) ---\n const viewport = $('head > meta[name=\"viewport\" i]');\n if (viewport.length === 0) {\n add({\n code: \"seo.viewport.missing\",\n severity: \"error\",\n category: \"SEO\",\n message: \"Missing <meta name=\\\"viewport\\\"> tag. Review mobile rendering; this tag helps browsers size and scale the page on mobile devices.\",\n });\n } else if (viewport.filter((_, element) => Boolean($(element).attr(\"content\")?.trim())).length === 0) {\n add({\n code: \"seo.viewport.unusable\",\n severity: \"error\",\n category: \"SEO\",\n message: \"Viewport meta tag is present but its content is empty. Provide viewport settings such as width=device-width.\",\n });\n }\n\n // --- Heading Structure ---\n const h1Tags = $(\"h1\").filter((_, element) => !isInTemplateContents(element));\n if (h1Tags.length === 0) {\n add({\n code: \"seo.h1.missing\",\n severity: \"warning\",\n category: \"SEO\",\n message: \"No <h1> heading found. Review whether the page has a clear main heading and a meaningful heading hierarchy.\",\n });\n } else if (h1Tags.length > 1) {\n add({\n code: \"seo.h1.multiple\",\n severity: \"info\",\n category: \"SEO\",\n message: `Found multiple (${h1Tags.length}) <h1> headings. Multiple H1s are not inherently an SEO error; ensure the heading hierarchy is meaningful and the main visual title is clear.`,\n });\n }\n\n // --- Images Alt Tags (SEO + Accessibility) ---\n $(\"img\").filter((_, element) => !isInTemplateContents(element)).each((_, element) => {\n const img = $(element);\n const src = img.attr(\"src\") || \"unknown-source\";\n const alt = img.attr(\"alt\");\n\n if (alt === undefined) {\n add({\n code: \"accessibility.image_alt.missing\",\n severity: \"error\",\n category: \"Accessibility\",\n message: \"Missing 'alt' attribute on image. This makes it inaccessible to screen readers.\",\n element: `<img src=\"${src}\">`,\n });\n } else if (alt.trim() === \"\") {\n // Empty alt is acceptable for purely decorative images, but worth warning\n add({\n code: \"accessibility.image_alt.empty\",\n severity: \"info\",\n category: \"Accessibility\",\n message: \"Empty 'alt' attribute found. Ensure this image is purely decorative, otherwise add descriptive text.\",\n element: `<img src=\"${src}\" alt=\"\">`,\n });\n }\n });\n\n // --- Open Graph / Social Tags ---\n const ogTitle = $('head > meta[property=\"og:title\"]');\n const ogImage = $('head > meta[property=\"og:image\"]');\n if (ogTitle.length === 0 || ogImage.length === 0) {\n add({\n code: \"seo.open_graph.missing\",\n severity: \"info\",\n category: \"SEO\",\n message: \"Missing Open Graph social metadata (og:title / og:image). Add these to control preview cards on platforms like LinkedIn and X.\",\n });\n }\n\n return collector.details();\n}\n\nexport function auditSeoMetadata(htmlContent: string): SEOIssue[] {\n return auditSeoMetadataDetailed(htmlContent).issues;\n}\n\n/**\n * Parses and validates JSON-LD Schema markup\n */\nexport function validateSchemaMarkupDetailed(htmlContent: string): AuditDetails {\n const $ = cheerio.load(htmlContent);\n const collector = createIssueCollector();\n const { add } = collector;\n const blocks = $(\"script[type]\").filter(\n (_, element) => !isInTemplateContents(element) && isJsonLdScriptType($(element).attr(\"type\")),\n );\n\n blocks.each((index, element) => {\n const scriptText = $(element).html() || \"\";\n if (scriptText.trim() === \"\") {\n add({\n code: \"schema.jsonld.empty\",\n severity: \"warning\",\n category: \"Schema\",\n message: `JSON-LD block #${index + 1} is empty.`,\n });\n return;\n }\n\n try {\n const parsed = JSON.parse(scriptText) as unknown;\n if (!hasJsonLdDocumentShape(parsed)) {\n add({\n code: \"schema.jsonld.invalid_document\",\n severity: \"error\",\n category: \"Schema\",\n message: \"Invalid JSON-LD document shape: the top level must be an object or an array of objects.\",\n element: `<script type=\"application/ld+json\">...</script>`,\n });\n }\n } catch (error: unknown) {\n add({\n code: \"schema.jsonld.invalid_json\",\n severity: \"error\",\n category: \"Schema\",\n message: `Invalid JSON-LD schema syntax: ${getErrorMessage(error)}`,\n element: `<script type=\"application/ld+json\">...</script>`,\n });\n }\n });\n\n return collector.details();\n}\n\nexport function validateSchemaMarkup(htmlContent: string): SEOIssue[] {\n return validateSchemaMarkupDetailed(htmlContent).issues;\n}\n\n/**\n * Extracts and tests links, treating 3xx responses as reachable redirects and 4xx/5xx as broken.\n */\nexport async function checkBrokenLinks(\n htmlContent: string,\n baseUrl?: string,\n maxLinks = MAX_LINKS_TO_CHECK,\n): Promise<LinkStatus[]> {\n if (!Number.isSafeInteger(maxLinks) || maxLinks <= 0) {\n throw new Error(\"maxLinks must be a positive integer\");\n }\n const linkLimit = Math.min(maxLinks, MAX_LINKS_TO_CHECK);\n const $ = cheerio.load(htmlContent);\n const fallbackBaseUrl = baseUrl ? await assertPublicHttpUrl(baseUrl) : undefined;\n let parsedBaseUrl = fallbackBaseUrl;\n const documentBaseHref = $(\"base[href]\")\n .filter((_, element) => !isInTemplateContents(element))\n .first()\n .attr(\"href\");\n if (documentBaseHref !== undefined) {\n let resolvedDocumentBase: URL | undefined;\n try {\n resolvedDocumentBase = fallbackBaseUrl\n ? new URL(documentBaseHref, fallbackBaseUrl)\n : new URL(documentBaseHref);\n } catch {\n parsedBaseUrl = undefined;\n }\n if (resolvedDocumentBase) {\n try {\n parsedBaseUrl = await assertPublicHttpUrl(resolvedDocumentBase);\n } catch (error: unknown) {\n if (!(error instanceof PublicUrlError)) {\n throw error;\n }\n parsedBaseUrl = undefined;\n }\n }\n }\n const urls: string[] = [];\n const seenUrls = new Set<string>();\n\n $(\"a, area\").filter((_, element) => !isInTemplateContents(element)).each((_, element) => {\n if (urls.length >= linkLimit) {\n return false;\n }\n\n const href = $(element).attr(\"href\")?.trim();\n if (!href || href.startsWith(\"#\")) {\n return;\n }\n\n let resolvedUrl: URL;\n try {\n resolvedUrl = parsedBaseUrl ? new URL(href, parsedBaseUrl) : new URL(href);\n } catch {\n // Relative links require a public base URL; unsupported or malformed links are skipped.\n return;\n }\n\n if (resolvedUrl.protocol !== \"http:\" && resolvedUrl.protocol !== \"https:\") {\n return;\n }\n\n resolvedUrl.hash = \"\";\n const normalizedUrl = resolvedUrl.href;\n if (!seenUrls.has(normalizedUrl)) {\n seenUrls.add(normalizedUrl);\n urls.push(normalizedUrl);\n }\n });\n\n const results = new Array<LinkStatus>(urls.length);\n let nextIndex = 0;\n\n async function checkLink(url: string): Promise<LinkStatus> {\n try {\n const headResult = await fetchPublicHttp(url, {\n method: \"HEAD\",\n headers: { \"User-Agent\": LINK_CHECK_USER_AGENT },\n timeoutMs: LINK_CHECK_TIMEOUT_MS,\n maxRedirects: 0,\n });\n const headStatus = headResult.response.status;\n const headFinalUrl = headResult.url.href;\n await cancelResponseBody(headResult.response);\n\n // Some sites reject or do not implement HEAD even when the linked resource is available.\n if (headStatus === 405 || headStatus === 403 || headStatus === 501) {\n let getResult = await fetchPublicHttp(url, {\n method: \"GET\",\n headers: {\n Range: \"bytes=0-0\",\n \"User-Agent\": LINK_CHECK_USER_AGENT,\n },\n timeoutMs: LINK_CHECK_TIMEOUT_MS,\n maxRedirects: 0,\n });\n if (getResult.response.status === 416) {\n await cancelResponseBody(getResult.response);\n getResult = await fetchPublicHttp(url, {\n method: \"GET\",\n headers: { \"User-Agent\": LINK_CHECK_USER_AGENT },\n timeoutMs: LINK_CHECK_TIMEOUT_MS,\n maxRedirects: 0,\n });\n }\n\n const status = getResult.response.status;\n const ok = status >= 200 && status < 400;\n const finalUrl = getResult.url.href;\n await cancelResponseBody(getResult.response);\n return {\n url,\n status,\n ok,\n message:\n status >= 300 && status < 400\n ? \"Redirect not followed\"\n : finalUrl !== url\n ? `Redirected to ${finalUrl}`\n : undefined,\n };\n }\n\n return {\n url,\n status: headStatus,\n ok: headStatus >= 200 && headStatus < 400,\n message:\n headStatus >= 300 && headStatus < 400\n ? \"Redirect not followed\"\n : headFinalUrl !== url\n ? `Redirected to ${headFinalUrl}`\n : undefined,\n };\n } catch (error: unknown) {\n return {\n url,\n status: error instanceof PublicUrlError ? \"blocked\" : \"failed\",\n ok: false,\n message: getErrorMessage(error),\n };\n }\n }\n\n async function worker(): Promise<void> {\n while (true) {\n const index = nextIndex;\n nextIndex += 1;\n if (index >= urls.length) {\n return;\n }\n results[index] = await checkLink(urls[index]);\n }\n }\n\n const workerCount = Math.min(LINK_CHECK_CONCURRENCY, urls.length);\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n return results;\n}\n"]}
1
+ {"version":3,"file":"seo-auditor.js","sourceRoot":"","sources":["../src/seo-auditor.ts"],"names":[],"mappings":"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","sourcesContent":["import * as cheerio from \"cheerio\";\nimport {\n assertPublicHttpUrl,\n cancelResponseBody,\n fetchPublicHttp,\n getErrorMessage,\n PublicUrlError,\n} from \"./network.js\";\nimport { PACKAGE_VERSION } from \"./version.js\";\n\nexport const MAX_AUDIT_ISSUES = 200;\nexport const MAX_LINKS_TO_CHECK = 25;\nconst LINK_CHECK_CONCURRENCY = 5;\nconst LINK_CHECK_TIMEOUT_MS = 5_000;\nconst LINK_CHECK_USER_AGENT = `mcp-web-validator/${PACKAGE_VERSION} (+https://digestseo.com/validator-mcp/)`;\n\nexport interface SEOIssue {\n code: string;\n severity: \"error\" | \"warning\" | \"info\";\n category: \"SEO\" | \"Schema\" | \"BrokenLinks\" | \"Accessibility\";\n message: string;\n element?: string;\n}\n\nexport interface LinkStatus {\n url: string;\n status: number | string;\n ok: boolean;\n message?: string;\n}\n\nexport interface AuditCounts {\n error: number;\n warning: number;\n info: number;\n}\n\nexport interface AuditDetails {\n issues: SEOIssue[];\n totalIssues: number;\n truncated: boolean;\n counts: AuditCounts;\n}\n\nfunction addIssue(issues: SEOIssue[], issue: SEOIssue): void {\n if (issues.length < MAX_AUDIT_ISSUES) {\n issues.push(issue);\n }\n}\n\nfunction createIssueCollector(): {\n issues: SEOIssue[];\n add: (issue: SEOIssue) => void;\n details: () => AuditDetails;\n} {\n const issues: SEOIssue[] = [];\n let totalIssues = 0;\n const counts: AuditCounts = { error: 0, warning: 0, info: 0 };\n return {\n issues,\n add(issue) {\n totalIssues += 1;\n counts[issue.severity] += 1;\n addIssue(issues, issue);\n },\n details() {\n return {\n issues,\n totalIssues,\n truncated: totalIssues > issues.length,\n counts: { ...counts },\n };\n },\n };\n}\n\nfunction isJsonLdScriptType(type: string | undefined): boolean {\n return type?.split(\";\", 1)[0].trim().toLowerCase() === \"application/ld+json\";\n}\n\nfunction hasJsonLdDocumentShape(value: unknown): boolean {\n if (Array.isArray(value)) {\n return value.every((entry) => typeof entry === \"object\" && entry !== null && !Array.isArray(entry));\n }\n return typeof value === \"object\" && value !== null;\n}\n\nfunction isInTemplateContents(element: unknown): boolean {\n type ParentNode = { name?: string; parent?: unknown };\n let node = element as ParentNode | null;\n while (node) {\n if (node.name?.toLowerCase() === \"template\") return true;\n node = (node.parent ?? null) as ParentNode | null;\n }\n return false;\n}\n\n/**\n * Audits technical SEO and accessibility basics on HTML content using Cheerio\n */\nexport function auditSeoMetadataDetailed(htmlContent: string): AuditDetails {\n const $ = cheerio.load(htmlContent);\n const collector = createIssueCollector();\n const { add } = collector;\n\n // --- Title Tag Audits ---\n const titleTag = $(\"head > title\");\n if (titleTag.length === 0) {\n add({\n code: \"seo.title.missing_or_empty\",\n severity: \"error\",\n category: \"SEO\",\n message: \"Missing <title> tag. Add a concise, descriptive title to help represent the page in search results.\",\n });\n } else {\n if (titleTag.length > 1) {\n add({\n code: \"seo.title.multiple\",\n severity: \"warning\",\n category: \"SEO\",\n message: \"Multiple <title> elements are declared in the document head. Keep one unambiguous page title.\",\n });\n }\n const titleText = titleTag.first().text().trim();\n if (titleText.length === 0) {\n add({\n code: \"seo.title.missing_or_empty\",\n severity: \"error\",\n category: \"SEO\",\n message: \"The <title> tag is empty.\",\n });\n } else if (titleText.length < 30) {\n add({\n code: \"seo.title.length\",\n severity: \"info\",\n category: \"SEO\",\n message: `Title is ${titleText.length} characters. This is shorter than the audit's common editorial range; review whether it describes the page clearly.`,\n element: `<title>${titleText}</title>`,\n });\n } else if (titleText.length > 60) {\n add({\n code: \"seo.title.length\",\n severity: \"info\",\n category: \"SEO\",\n message: `Title is ${titleText.length} characters. This is longer than the audit's common editorial range; Google title links may be shortened or rewritten depending on context and device.`,\n element: `<title>${titleText}</title>`,\n });\n }\n }\n\n // --- Meta Description Audits ---\n const metaDescription = $('head > meta[name=\"description\" i]');\n if (metaDescription.length === 0) {\n add({\n code: \"seo.meta_description.missing_or_empty\",\n severity: \"error\",\n category: \"SEO\",\n message: \"Missing <meta name=\\\"description\\\">. Add a concise, accurate page summary; Google may use page content or this description to generate a snippet.\",\n });\n } else {\n if (metaDescription.length > 1) {\n add({\n code: \"seo.meta_description.multiple\",\n severity: \"warning\",\n category: \"SEO\",\n message: \"Multiple meta descriptions are declared in the document head. Keep one unambiguous page description.\",\n });\n }\n const descText = metaDescription.first().attr(\"content\")?.trim() || \"\";\n if (descText.length === 0) {\n add({\n code: \"seo.meta_description.missing_or_empty\",\n severity: \"error\",\n category: \"SEO\",\n message: \"Meta description content attribute is empty.\",\n });\n } else if (descText.length < 120) {\n add({\n code: \"seo.meta_description.length\",\n severity: \"info\",\n category: \"SEO\",\n message: `Meta description is ${descText.length} characters. This is shorter than the audit's common editorial range; review whether it provides a useful page summary.`,\n element: `<meta name=\"description\" content=\"${descText}\">`,\n });\n } else if (descText.length > 160) {\n add({\n code: \"seo.meta_description.length\",\n severity: \"info\",\n category: \"SEO\",\n message: `Meta description is ${descText.length} characters. This is longer than the audit's common editorial range; displayed snippets may be shortened depending on the query and device.`,\n element: `<meta name=\"description\" content=\"${descText}\">`,\n });\n }\n }\n\n // --- Canonical Link ---\n const canonicalLinks = $(\"head > link[rel]\").filter((_, element) => {\n const rel = $(element).attr(\"rel\") ?? \"\";\n return rel\n .trim()\n .split(/[\\t\\n\\f\\r ]+/)\n .some((token) => token.toLowerCase() === \"canonical\");\n });\n const nonEmptyCanonical = canonicalLinks.filter((_, element) => Boolean($(element).attr(\"href\")?.trim()));\n const usableCanonical = nonEmptyCanonical.filter(\n (_, element) => ![\"hreflang\", \"lang\", \"media\", \"type\"].some((attribute) => $(element).attr(attribute) !== undefined),\n );\n if (canonicalLinks.length === 0) {\n add({\n code: \"seo.canonical.missing\",\n severity: \"info\",\n category: \"SEO\",\n message: \"No rel=\\\"canonical\\\" preference is declared. This is optional unless the page needs an explicit canonicalization signal.\",\n });\n } else if (usableCanonical.length === 0) {\n if (nonEmptyCanonical.length > 0) {\n add({\n code: \"seo.canonical.unusable\",\n severity: \"warning\",\n category: \"SEO\",\n message: \"Canonical link is present but uses attributes Google ignores for canonicalization. Use a plain rel=\\\"canonical\\\" link in <head>.\",\n });\n } else {\n add({\n code: \"seo.canonical.missing\",\n severity: \"warning\",\n category: \"SEO\",\n message: \"Canonical link is present but its href is empty. Provide a usable canonical target or remove the declaration.\",\n });\n }\n } else if (usableCanonical.length > 1) {\n add({\n code: \"seo.canonical.multiple\",\n severity: \"warning\",\n category: \"SEO\",\n message: \"Multiple usable canonical link relations are declared. Keep one unambiguous canonical target.\",\n });\n }\n\n // Google Search respects robots meta directives in both the head and body.\n const indexBlockingRobotsMeta = $('meta[name]').filter((_, element) => {\n if (isInTemplateContents(element)) return false;\n const name = ($(element).attr(\"name\") ?? \"\").trim().toLowerCase();\n if (name !== \"robots\" && name !== \"googlebot\") return false;\n const content = $(element).attr(\"content\") ?? \"\";\n return content\n .split(\",\")\n .some((directive) => [\"noindex\", \"none\"].includes(directive.trim().toLowerCase()));\n });\n if (indexBlockingRobotsMeta.length > 0) {\n add({\n code: \"seo.robots.noindex\",\n severity: \"warning\",\n category: \"SEO\",\n message: \"A robots directive prevents Google from indexing this page. Confirm that noindex is intentional.\",\n });\n }\n\n // --- Viewport Meta Tag (Mobile Responsiveness) ---\n const viewport = $('head > meta[name=\"viewport\" i]');\n if (viewport.length === 0) {\n add({\n code: \"seo.viewport.missing\",\n severity: \"error\",\n category: \"SEO\",\n message: \"Missing <meta name=\\\"viewport\\\"> tag. Review mobile rendering; this tag helps browsers size and scale the page on mobile devices.\",\n });\n } else if (viewport.filter((_, element) => Boolean($(element).attr(\"content\")?.trim())).length === 0) {\n add({\n code: \"seo.viewport.unusable\",\n severity: \"error\",\n category: \"SEO\",\n message: \"Viewport meta tag is present but its content is empty. Provide viewport settings such as width=device-width.\",\n });\n }\n\n // --- Heading Structure ---\n const h1Tags = $(\"h1\").filter((_, element) => !isInTemplateContents(element));\n if (h1Tags.length === 0) {\n add({\n code: \"seo.h1.missing\",\n severity: \"warning\",\n category: \"SEO\",\n message: \"No <h1> heading found. Review whether the page has a clear main heading and a meaningful heading hierarchy.\",\n });\n } else if (h1Tags.length > 1) {\n add({\n code: \"seo.h1.multiple\",\n severity: \"info\",\n category: \"SEO\",\n message: `Found multiple (${h1Tags.length}) <h1> headings. Multiple H1s are not inherently an SEO error; ensure the heading hierarchy is meaningful and the main visual title is clear.`,\n });\n }\n\n // --- Images Alt Tags (SEO + Accessibility) ---\n $(\"img\").filter((_, element) => !isInTemplateContents(element)).each((_, element) => {\n const img = $(element);\n const src = img.attr(\"src\") || \"unknown-source\";\n const alt = img.attr(\"alt\");\n\n if (alt === undefined) {\n add({\n code: \"accessibility.image_alt.missing\",\n severity: \"error\",\n category: \"Accessibility\",\n message: \"Missing 'alt' attribute on image. This makes it inaccessible to screen readers.\",\n element: `<img src=\"${src}\">`,\n });\n } else if (alt.trim() === \"\") {\n // Empty alt is acceptable for purely decorative images, but worth warning\n add({\n code: \"accessibility.image_alt.empty\",\n severity: \"info\",\n category: \"Accessibility\",\n message: \"Empty 'alt' attribute found. Ensure this image is purely decorative, otherwise add descriptive text.\",\n element: `<img src=\"${src}\" alt=\"\">`,\n });\n }\n });\n\n $(\"input[type=\\\"image\\\" i]\").filter((_, element) => !isInTemplateContents(element)).each((_, element) => {\n const alt = $(element).attr(\"alt\");\n if (alt === undefined || alt.trim() === \"\") {\n add({\n code: \"accessibility.input_image_alt.missing_or_empty\",\n severity: \"error\",\n category: \"Accessibility\",\n message: \"Image submit buttons need non-empty alt text that labels the button's function.\",\n });\n }\n });\n\n // --- Open Graph / Social Tags ---\n const ogTitle = $('head > meta[property=\"og:title\"]');\n const ogImage = $('head > meta[property=\"og:image\"]');\n if (ogTitle.length === 0 || ogImage.length === 0) {\n add({\n code: \"seo.open_graph.missing\",\n severity: \"info\",\n category: \"SEO\",\n message: \"Missing Open Graph social metadata (og:title / og:image). Add these to control preview cards on platforms like LinkedIn and X.\",\n });\n }\n\n return collector.details();\n}\n\nexport function auditSeoMetadata(htmlContent: string): SEOIssue[] {\n return auditSeoMetadataDetailed(htmlContent).issues;\n}\n\n/**\n * Parses and validates JSON-LD Schema markup\n */\nexport function validateSchemaMarkupDetailed(htmlContent: string): AuditDetails {\n const $ = cheerio.load(htmlContent);\n const collector = createIssueCollector();\n const { add } = collector;\n const blocks = $(\"script[type]\").filter(\n (_, element) => !isInTemplateContents(element) && isJsonLdScriptType($(element).attr(\"type\")),\n );\n\n blocks.each((index, element) => {\n const scriptText = $(element).html() || \"\";\n if (scriptText.trim() === \"\") {\n add({\n code: \"schema.jsonld.empty\",\n severity: \"warning\",\n category: \"Schema\",\n message: `JSON-LD block #${index + 1} is empty.`,\n });\n return;\n }\n\n try {\n const parsed = JSON.parse(scriptText) as unknown;\n if (!hasJsonLdDocumentShape(parsed)) {\n add({\n code: \"schema.jsonld.invalid_document\",\n severity: \"error\",\n category: \"Schema\",\n message: \"Invalid JSON-LD document shape: the top level must be an object or an array of objects.\",\n element: `<script type=\"application/ld+json\">...</script>`,\n });\n }\n } catch (error: unknown) {\n add({\n code: \"schema.jsonld.invalid_json\",\n severity: \"error\",\n category: \"Schema\",\n message: `Invalid JSON-LD schema syntax: ${getErrorMessage(error)}`,\n element: `<script type=\"application/ld+json\">...</script>`,\n });\n }\n });\n\n return collector.details();\n}\n\nexport function validateSchemaMarkup(htmlContent: string): SEOIssue[] {\n return validateSchemaMarkupDetailed(htmlContent).issues;\n}\n\n/**\n * Extracts and tests links, treating 3xx responses as reachable redirects and 4xx/5xx as broken.\n */\nexport async function checkBrokenLinks(\n htmlContent: string,\n baseUrl?: string,\n maxLinks = MAX_LINKS_TO_CHECK,\n): Promise<LinkStatus[]> {\n if (!Number.isSafeInteger(maxLinks) || maxLinks <= 0) {\n throw new Error(\"maxLinks must be a positive integer\");\n }\n const linkLimit = Math.min(maxLinks, MAX_LINKS_TO_CHECK);\n const $ = cheerio.load(htmlContent);\n const fallbackBaseUrl = baseUrl ? await assertPublicHttpUrl(baseUrl) : undefined;\n let parsedBaseUrl = fallbackBaseUrl;\n const documentBaseHref = $(\"base[href]\")\n .filter((_, element) => !isInTemplateContents(element))\n .first()\n .attr(\"href\");\n if (documentBaseHref !== undefined) {\n let resolvedDocumentBase: URL | undefined;\n try {\n resolvedDocumentBase = fallbackBaseUrl\n ? new URL(documentBaseHref, fallbackBaseUrl)\n : new URL(documentBaseHref);\n } catch {\n parsedBaseUrl = undefined;\n }\n if (resolvedDocumentBase) {\n try {\n parsedBaseUrl = await assertPublicHttpUrl(resolvedDocumentBase);\n } catch (error: unknown) {\n if (!(error instanceof PublicUrlError)) {\n throw error;\n }\n parsedBaseUrl = undefined;\n }\n }\n }\n const urls: string[] = [];\n const seenUrls = new Set<string>();\n\n $(\"a, area\").filter((_, element) => !isInTemplateContents(element)).each((_, element) => {\n if (urls.length >= linkLimit) {\n return false;\n }\n\n const href = $(element).attr(\"href\")?.trim();\n if (!href || href.startsWith(\"#\")) {\n return;\n }\n\n let resolvedUrl: URL;\n try {\n resolvedUrl = parsedBaseUrl ? new URL(href, parsedBaseUrl) : new URL(href);\n } catch {\n // Relative links require a public base URL; unsupported or malformed links are skipped.\n return;\n }\n\n if (resolvedUrl.protocol !== \"http:\" && resolvedUrl.protocol !== \"https:\") {\n return;\n }\n\n resolvedUrl.hash = \"\";\n const normalizedUrl = resolvedUrl.href;\n if (!seenUrls.has(normalizedUrl)) {\n seenUrls.add(normalizedUrl);\n urls.push(normalizedUrl);\n }\n });\n\n const results = new Array<LinkStatus>(urls.length);\n let nextIndex = 0;\n\n async function checkLink(url: string): Promise<LinkStatus> {\n try {\n const headResult = await fetchPublicHttp(url, {\n method: \"HEAD\",\n headers: { \"User-Agent\": LINK_CHECK_USER_AGENT },\n timeoutMs: LINK_CHECK_TIMEOUT_MS,\n maxRedirects: 0,\n });\n const headStatus = headResult.response.status;\n const headFinalUrl = headResult.url.href;\n await cancelResponseBody(headResult.response);\n\n // Some sites reject or do not implement HEAD even when the linked resource is available.\n if (headStatus === 405 || headStatus === 403 || headStatus === 501) {\n let getResult = await fetchPublicHttp(url, {\n method: \"GET\",\n headers: {\n Range: \"bytes=0-0\",\n \"User-Agent\": LINK_CHECK_USER_AGENT,\n },\n timeoutMs: LINK_CHECK_TIMEOUT_MS,\n maxRedirects: 0,\n });\n if (getResult.response.status === 416) {\n await cancelResponseBody(getResult.response);\n getResult = await fetchPublicHttp(url, {\n method: \"GET\",\n headers: { \"User-Agent\": LINK_CHECK_USER_AGENT },\n timeoutMs: LINK_CHECK_TIMEOUT_MS,\n maxRedirects: 0,\n });\n }\n\n const status = getResult.response.status;\n const ok = status >= 200 && status < 400;\n const finalUrl = getResult.url.href;\n await cancelResponseBody(getResult.response);\n return {\n url,\n status,\n ok,\n message:\n status >= 300 && status < 400\n ? \"Redirect not followed\"\n : finalUrl !== url\n ? `Redirected to ${finalUrl}`\n : undefined,\n };\n }\n\n return {\n url,\n status: headStatus,\n ok: headStatus >= 200 && headStatus < 400,\n message:\n headStatus >= 300 && headStatus < 400\n ? \"Redirect not followed\"\n : headFinalUrl !== url\n ? `Redirected to ${headFinalUrl}`\n : undefined,\n };\n } catch (error: unknown) {\n return {\n url,\n status: error instanceof PublicUrlError ? \"blocked\" : \"failed\",\n ok: false,\n message: getErrorMessage(error),\n };\n }\n }\n\n async function worker(): Promise<void> {\n while (true) {\n const index = nextIndex;\n nextIndex += 1;\n if (index >= urls.length) {\n return;\n }\n results[index] = await checkLink(urls[index]);\n }\n }\n\n const workerCount = Math.min(LINK_CHECK_CONCURRENCY, urls.length);\n await Promise.all(Array.from({ length: workerCount }, () => worker()));\n return results;\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mcp-web-validator",
3
- "version": "1.3.24",
3
+ "version": "1.3.26",
4
4
  "description": "W3C HTML/CSS Validator and Technical SEO Audit MCP Server for AI coding assistants.",
5
5
  "main": "dist/index.js",
6
6
  "bin": {