mcp-web-validator 1.3.18 → 1.3.19
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 +8 -0
- package/dist/index.d.ts +5 -2
- package/dist/index.js +92 -16
- package/dist/index.js.map +1 -1
- package/dist/network.d.ts +1 -1
- package/dist/network.js +47 -8
- package/dist/network.js.map +1 -1
- package/dist/presentation.d.ts +6 -3
- package/dist/presentation.js +33 -16
- package/dist/presentation.js.map +1 -1
- package/dist/report.d.ts +19 -1
- package/dist/report.js +31 -7
- package/dist/report.js.map +1 -1
- package/dist/seo-auditor.d.ts +6 -0
- package/dist/seo-auditor.js +3 -0
- package/dist/seo-auditor.js.map +1 -1
- package/dist/w3c-validator.d.ts +12 -3
- package/dist/w3c-validator.js +17 -5
- package/dist/w3c-validator.js.map +1 -1
- package/package.json +1 -1
package/dist/report.js.map
CHANGED
|
@@ -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 { 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 seoIssues: SEOIssue[];\n schemaIssues: SEOIssue[];\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 seoIssues: SEOIssue[];\n schemaIssues: SEOIssue[];\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 cssCompatibilityLimitations = input.cssMessages.filter(\n (message) => message.compatibility === \"known-validator-limitation\",\n ).length;\n const cssErrors = input.cssMessages.length - cssCompatibilityLimitations;\n const seoErrors = input.seoIssues.filter((issue) => issue.severity === \"error\").length;\n const seoWarnings = input.seoIssues.filter((issue) => issue.severity === \"warning\").length;\n const schemaErrors = 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 (${input.cssMessages.length})`);\n if (input.cssMessages.length === 0) {\n report.push(\"No CSS validation errors were returned.\");\n } else {\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 report.push(\"\", `## SEO, accessibility, and JSON-LD findings (${combinedIssues.length})`);\n if (combinedIssues.length === 0) {\n report.push(\"No SEO, accessibility, or JSON-LD syntax findings were returned.\");\n } else {\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 seoIssues: input.seoIssues,\n schemaIssues: input.schemaIssues,\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, \"\\\\\\\\\")\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"]}
|
package/dist/seo-auditor.d.ts
CHANGED
|
@@ -13,10 +13,16 @@ export interface LinkStatus {
|
|
|
13
13
|
ok: boolean;
|
|
14
14
|
message?: string;
|
|
15
15
|
}
|
|
16
|
+
export interface AuditCounts {
|
|
17
|
+
error: number;
|
|
18
|
+
warning: number;
|
|
19
|
+
info: number;
|
|
20
|
+
}
|
|
16
21
|
export interface AuditDetails {
|
|
17
22
|
issues: SEOIssue[];
|
|
18
23
|
totalIssues: number;
|
|
19
24
|
truncated: boolean;
|
|
25
|
+
counts: AuditCounts;
|
|
20
26
|
}
|
|
21
27
|
/**
|
|
22
28
|
* Audits technical SEO and accessibility basics on HTML content using Cheerio
|
package/dist/seo-auditor.js
CHANGED
|
@@ -14,10 +14,12 @@ function addIssue(issues, issue) {
|
|
|
14
14
|
function createIssueCollector() {
|
|
15
15
|
const issues = [];
|
|
16
16
|
let totalIssues = 0;
|
|
17
|
+
const counts = { error: 0, warning: 0, info: 0 };
|
|
17
18
|
return {
|
|
18
19
|
issues,
|
|
19
20
|
add(issue) {
|
|
20
21
|
totalIssues += 1;
|
|
22
|
+
counts[issue.severity] += 1;
|
|
21
23
|
addIssue(issues, issue);
|
|
22
24
|
},
|
|
23
25
|
details() {
|
|
@@ -25,6 +27,7 @@ function createIssueCollector() {
|
|
|
25
27
|
issues,
|
|
26
28
|
totalIssues,
|
|
27
29
|
truncated: totalIssues > issues.length,
|
|
30
|
+
counts: { ...counts },
|
|
28
31
|
};
|
|
29
32
|
},
|
|
30
33
|
};
|
package/dist/seo-auditor.js.map
CHANGED
|
@@ -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 AuditDetails {\n issues: SEOIssue[];\n totalIssues: number;\n truncated: boolean;\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 return {\n issues,\n add(issue) {\n totalIssues += 1;\n addIssue(issues, issue);\n },\n details() {\n return {\n issues,\n totalIssues,\n truncated: totalIssues > issues.length,\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\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 usableCanonical = canonicalLinks.filter((_, element) => Boolean($(element).attr(\"href\")?.trim()));\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 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\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 }\n\n // --- Heading Structure ---\n const h1Tags = $(\"h1\");\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\").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((_, element) => isJsonLdScriptType($(element).attr(\"type\")));\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]\").first().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\").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\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 usableCanonical = canonicalLinks.filter((_, element) => Boolean($(element).attr(\"href\")?.trim()));\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 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\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 }\n\n // --- Heading Structure ---\n const h1Tags = $(\"h1\");\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\").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((_, element) => isJsonLdScriptType($(element).attr(\"type\")));\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]\").first().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\").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/dist/w3c-validator.d.ts
CHANGED
|
@@ -23,13 +23,22 @@ export interface CSSMessage {
|
|
|
23
23
|
context?: string;
|
|
24
24
|
compatibility?: "known-validator-limitation";
|
|
25
25
|
}
|
|
26
|
+
export interface CssValidationResult {
|
|
27
|
+
messages: CSSMessage[];
|
|
28
|
+
total: number;
|
|
29
|
+
truncated: boolean;
|
|
30
|
+
counts: {
|
|
31
|
+
error: number;
|
|
32
|
+
compatibilityLimitation: number;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
26
35
|
export declare const MAX_CSS_VALIDATION_BYTES = 128000;
|
|
27
36
|
export declare const MAX_VALIDATION_MESSAGES = 200;
|
|
28
37
|
/** Validates HTML using the W3C Nu HTML Checker API and retains bounded-output metadata. */
|
|
29
38
|
export declare function validateHtmlContentDetailed(htmlContent: string): Promise<HtmlValidationResult>;
|
|
30
39
|
/** Backwards-compatible convenience API returning capped diagnostics only. */
|
|
31
40
|
export declare function validateHtmlContent(htmlContent: string): Promise<W3CMessage[]>;
|
|
32
|
-
/**
|
|
33
|
-
|
|
34
|
-
*/
|
|
41
|
+
/** Validates CSS using the W3C Jigsaw CSS Validator API and retains bounded-output metadata. */
|
|
42
|
+
export declare function validateCssContentDetailed(cssContent: string): Promise<CssValidationResult>;
|
|
43
|
+
/** Backwards-compatible convenience API returning capped CSS diagnostics only. */
|
|
35
44
|
export declare function validateCssContent(cssContent: string): Promise<CSSMessage[]>;
|
package/dist/w3c-validator.js
CHANGED
|
@@ -99,10 +99,8 @@ export async function validateHtmlContentDetailed(htmlContent) {
|
|
|
99
99
|
export async function validateHtmlContent(htmlContent) {
|
|
100
100
|
return (await validateHtmlContentDetailed(htmlContent)).messages;
|
|
101
101
|
}
|
|
102
|
-
/**
|
|
103
|
-
|
|
104
|
-
*/
|
|
105
|
-
export async function validateCssContent(cssContent) {
|
|
102
|
+
/** Validates CSS using the W3C Jigsaw CSS Validator API and retains bounded-output metadata. */
|
|
103
|
+
export async function validateCssContentDetailed(cssContent) {
|
|
106
104
|
const url = "https://jigsaw.w3.org/css-validator/validator";
|
|
107
105
|
try {
|
|
108
106
|
assertContentSize(cssContent, MAX_CSS_VALIDATION_BYTES, "CSS content");
|
|
@@ -134,7 +132,7 @@ export async function validateCssContent(cssContent) {
|
|
|
134
132
|
throw new Error("W3C CSS validator returned an invalid response shape");
|
|
135
133
|
}
|
|
136
134
|
const errors = data.cssvalidation.errors || [];
|
|
137
|
-
|
|
135
|
+
const normalized = errors.map((err) => {
|
|
138
136
|
const message = err.message ? err.message.trim() : "Unknown CSS validation error";
|
|
139
137
|
return {
|
|
140
138
|
line: err.line || 0,
|
|
@@ -146,9 +144,23 @@ export async function validateCssContent(cssContent) {
|
|
|
146
144
|
: {}),
|
|
147
145
|
};
|
|
148
146
|
});
|
|
147
|
+
const compatibilityLimitation = normalized.filter((message) => message.compatibility === "known-validator-limitation").length;
|
|
148
|
+
return {
|
|
149
|
+
messages: normalized.slice(0, MAX_VALIDATION_MESSAGES),
|
|
150
|
+
total: normalized.length,
|
|
151
|
+
truncated: normalized.length > MAX_VALIDATION_MESSAGES,
|
|
152
|
+
counts: {
|
|
153
|
+
error: normalized.length - compatibilityLimitation,
|
|
154
|
+
compatibilityLimitation,
|
|
155
|
+
},
|
|
156
|
+
};
|
|
149
157
|
}
|
|
150
158
|
catch (error) {
|
|
151
159
|
throw new Error(`CSS validation failed: ${getErrorMessage(error)}`);
|
|
152
160
|
}
|
|
153
161
|
}
|
|
162
|
+
/** Backwards-compatible convenience API returning capped CSS diagnostics only. */
|
|
163
|
+
export async function validateCssContent(cssContent) {
|
|
164
|
+
return (await validateCssContentDetailed(cssContent)).messages;
|
|
165
|
+
}
|
|
154
166
|
//# sourceMappingURL=w3c-validator.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"w3c-validator.js","sourceRoot":"","sources":["../src/w3c-validator.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n cancelResponseBody,\n getErrorMessage,\n readResponseText,\n} from \"./network.js\";\nimport { PACKAGE_VERSION } from \"./version.js\";\n\nexport interface W3CMessage {\n type: string;\n subType?: string;\n lastLine?: number;\n lastColumn?: number;\n firstLine?: number;\n firstColumn?: number;\n message: string;\n extract?: string;\n}\n\nexport type W3CMessageSeverity = \"error\" | \"warning\" | \"info\";\n\nexport interface HtmlValidationResult {\n messages: W3CMessage[];\n total: number;\n truncated: boolean;\n counts: Record<W3CMessageSeverity, number>;\n}\n\nexport function getW3CMessageSeverity(\n message: Pick<W3CMessage, \"type\" | \"subType\">,\n): W3CMessageSeverity {\n const type = message.type.trim().toLowerCase();\n if (type === \"error\" || type === \"non-document-error\") return \"error\";\n if (type === \"warning\" || (type === \"info\" && message.subType?.trim().toLowerCase() === \"warning\")) {\n return \"warning\";\n }\n return \"info\";\n}\n\nexport interface CSSMessage {\n line: number;\n type: string;\n message: string;\n context?: string;\n compatibility?: \"known-validator-limitation\";\n}\n\nconst VALIDATOR_TIMEOUT_MS = 20_000;\nconst MAX_HTML_BYTES = 2_000_000;\nexport const MAX_CSS_VALIDATION_BYTES = 128_000;\nconst MAX_VALIDATOR_RESPONSE_BYTES = 5_000_000;\nconst USER_AGENT = `mcp-web-validator/${PACKAGE_VERSION} (+https://digestseo.com/validator-mcp/)`;\nexport const MAX_VALIDATION_MESSAGES = 200;\n\nfunction isKnownCssValidatorLimitation(type: string | undefined, message: string): boolean {\n if (type?.trim().toLowerCase() !== \"at-rule\") return false;\n const normalized = message.trim().toLowerCase().replace(/[“”\"'‘’]/g, \"\");\n return normalized === \"unrecognized at-rule @container\";\n}\n\nfunction assertContentSize(content: string, maxBytes: number, label: string): void {\n const size = Buffer.byteLength(content, \"utf8\");\n if (size > maxBytes) {\n throw new Error(`${label} exceeds the ${maxBytes}-byte validation limit`);\n }\n}\n\nfunction normalizeW3CMessage(message: unknown): W3CMessage {\n if (typeof message !== \"object\" || message === null) {\n throw new Error(\"W3C HTML validator returned a malformed message\");\n }\n\n const candidate = message as Record<string, unknown>;\n if (typeof candidate.type !== \"string\" || typeof candidate.message !== \"string\") {\n throw new Error(\"W3C HTML validator returned a malformed message\");\n }\n\n const normalized: W3CMessage = {\n type: candidate.type,\n message: candidate.message,\n };\n\n for (const field of [\"lastLine\", \"lastColumn\", \"firstLine\", \"firstColumn\"] as const) {\n if (typeof candidate[field] === \"number\" && Number.isInteger(candidate[field])) {\n normalized[field] = candidate[field];\n }\n }\n\n if (typeof candidate.extract === \"string\") {\n normalized.extract = candidate.extract;\n }\n\n if (typeof candidate.subType === \"string\") {\n normalized.subType = candidate.subType;\n }\n\n return normalized;\n}\n\n/** Validates HTML using the W3C Nu HTML Checker API and retains bounded-output metadata. */\nexport async function validateHtmlContentDetailed(htmlContent: string): Promise<HtmlValidationResult> {\n const url = \"https://validator.w3.org/nu/?out=json\";\n\n try {\n assertContentSize(htmlContent, MAX_HTML_BYTES, \"HTML content\");\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"User-Agent\": USER_AGENT,\n },\n body: htmlContent,\n redirect: \"error\",\n signal: AbortSignal.timeout(VALIDATOR_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n await cancelResponseBody(response);\n throw new Error(`W3C HTML validator returned HTTP status ${response.status}`);\n }\n\n const text = await readResponseText(response, MAX_VALIDATOR_RESPONSE_BYTES);\n const data = JSON.parse(text) as { messages?: unknown };\n if (data.messages === undefined) {\n throw new Error(\"W3C HTML validator returned an invalid response shape\");\n }\n if (!Array.isArray(data.messages)) {\n throw new Error(\"W3C HTML validator returned an invalid response shape\");\n }\n const normalized = data.messages.map(normalizeW3CMessage);\n const counts: HtmlValidationResult[\"counts\"] = { error: 0, warning: 0, info: 0 };\n for (const message of normalized) {\n counts[getW3CMessageSeverity(message)] += 1;\n }\n return {\n messages: normalized.slice(0, MAX_VALIDATION_MESSAGES),\n total: normalized.length,\n truncated: normalized.length > MAX_VALIDATION_MESSAGES,\n counts,\n };\n } catch (error: unknown) {\n throw new Error(`HTML validation failed: ${getErrorMessage(error)}`);\n }\n}\n\n/** Backwards-compatible convenience API returning capped diagnostics only. */\nexport async function validateHtmlContent(htmlContent: string): Promise<W3CMessage[]> {\n return (await validateHtmlContentDetailed(htmlContent)).messages;\n}\n\n/**\n * Validates CSS using the W3C Jigsaw CSS Validator API\n */\nexport async function validateCssContent(cssContent: string): Promise<CSSMessage[]> {\n const url = \"https://jigsaw.w3.org/css-validator/validator\";\n\n try {\n assertContentSize(cssContent, MAX_CSS_VALIDATION_BYTES, \"CSS content\");\n const form = new FormData();\n form.set(\"text\", cssContent);\n form.set(\"output\", \"json\");\n form.set(\"warning\", \"0\");\n form.set(\"profile\", \"css3svg\");\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"User-Agent\": USER_AGENT,\n },\n body: form,\n redirect: \"error\",\n signal: AbortSignal.timeout(VALIDATOR_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n await cancelResponseBody(response);\n throw new Error(`W3C CSS validator returned HTTP status ${response.status}`);\n }\n\n const text = await readResponseText(response, MAX_VALIDATOR_RESPONSE_BYTES);\n\n // An empty upstream response is indeterminate and must never be presented as a clean result.\n if (!text || text.trim() === \"\") {\n throw new Error(\"W3C CSS validator returned an empty response\");\n }\n\n const data = JSON.parse(text) as {\n cssvalidation?: {\n errors?: Array<{\n line: number;\n message: string;\n context?: string;\n type?: string;\n }>;\n warnings?: Array<{\n line: number;\n message: string;\n context?: string;\n type?: string;\n }>;\n };\n };\n\n if (!data.cssvalidation || typeof data.cssvalidation !== \"object\") {\n throw new Error(\"W3C CSS validator returned an invalid response shape\");\n }\n\n const errors = data.cssvalidation.errors || [];\n return errors.slice(0, MAX_VALIDATION_MESSAGES).map((err) => {\n const message = err.message ? err.message.trim() : \"Unknown CSS validation error\";\n return {\n line: err.line || 0,\n type: \"error\",\n message,\n context: err.context || undefined,\n ...(isKnownCssValidatorLimitation(err.type, message)\n ? { compatibility: \"known-validator-limitation\" as const }\n : {}),\n };\n });\n } catch (error: unknown) {\n throw new Error(`CSS validation failed: ${getErrorMessage(error)}`);\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"w3c-validator.js","sourceRoot":"","sources":["../src/w3c-validator.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n cancelResponseBody,\n getErrorMessage,\n readResponseText,\n} from \"./network.js\";\nimport { PACKAGE_VERSION } from \"./version.js\";\n\nexport interface W3CMessage {\n type: string;\n subType?: string;\n lastLine?: number;\n lastColumn?: number;\n firstLine?: number;\n firstColumn?: number;\n message: string;\n extract?: string;\n}\n\nexport type W3CMessageSeverity = \"error\" | \"warning\" | \"info\";\n\nexport interface HtmlValidationResult {\n messages: W3CMessage[];\n total: number;\n truncated: boolean;\n counts: Record<W3CMessageSeverity, number>;\n}\n\nexport function getW3CMessageSeverity(\n message: Pick<W3CMessage, \"type\" | \"subType\">,\n): W3CMessageSeverity {\n const type = message.type.trim().toLowerCase();\n if (type === \"error\" || type === \"non-document-error\") return \"error\";\n if (type === \"warning\" || (type === \"info\" && message.subType?.trim().toLowerCase() === \"warning\")) {\n return \"warning\";\n }\n return \"info\";\n}\n\nexport interface CSSMessage {\n line: number;\n type: string;\n message: string;\n context?: string;\n compatibility?: \"known-validator-limitation\";\n}\n\nexport interface CssValidationResult {\n messages: CSSMessage[];\n total: number;\n truncated: boolean;\n counts: {\n error: number;\n compatibilityLimitation: number;\n };\n}\n\nconst VALIDATOR_TIMEOUT_MS = 20_000;\nconst MAX_HTML_BYTES = 2_000_000;\nexport const MAX_CSS_VALIDATION_BYTES = 128_000;\nconst MAX_VALIDATOR_RESPONSE_BYTES = 5_000_000;\nconst USER_AGENT = `mcp-web-validator/${PACKAGE_VERSION} (+https://digestseo.com/validator-mcp/)`;\nexport const MAX_VALIDATION_MESSAGES = 200;\n\nfunction isKnownCssValidatorLimitation(type: string | undefined, message: string): boolean {\n if (type?.trim().toLowerCase() !== \"at-rule\") return false;\n const normalized = message.trim().toLowerCase().replace(/[“”\"'‘’]/g, \"\");\n return normalized === \"unrecognized at-rule @container\";\n}\n\nfunction assertContentSize(content: string, maxBytes: number, label: string): void {\n const size = Buffer.byteLength(content, \"utf8\");\n if (size > maxBytes) {\n throw new Error(`${label} exceeds the ${maxBytes}-byte validation limit`);\n }\n}\n\nfunction normalizeW3CMessage(message: unknown): W3CMessage {\n if (typeof message !== \"object\" || message === null) {\n throw new Error(\"W3C HTML validator returned a malformed message\");\n }\n\n const candidate = message as Record<string, unknown>;\n if (typeof candidate.type !== \"string\" || typeof candidate.message !== \"string\") {\n throw new Error(\"W3C HTML validator returned a malformed message\");\n }\n\n const normalized: W3CMessage = {\n type: candidate.type,\n message: candidate.message,\n };\n\n for (const field of [\"lastLine\", \"lastColumn\", \"firstLine\", \"firstColumn\"] as const) {\n if (typeof candidate[field] === \"number\" && Number.isInteger(candidate[field])) {\n normalized[field] = candidate[field];\n }\n }\n\n if (typeof candidate.extract === \"string\") {\n normalized.extract = candidate.extract;\n }\n\n if (typeof candidate.subType === \"string\") {\n normalized.subType = candidate.subType;\n }\n\n return normalized;\n}\n\n/** Validates HTML using the W3C Nu HTML Checker API and retains bounded-output metadata. */\nexport async function validateHtmlContentDetailed(htmlContent: string): Promise<HtmlValidationResult> {\n const url = \"https://validator.w3.org/nu/?out=json\";\n\n try {\n assertContentSize(htmlContent, MAX_HTML_BYTES, \"HTML content\");\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"text/html; charset=utf-8\",\n \"User-Agent\": USER_AGENT,\n },\n body: htmlContent,\n redirect: \"error\",\n signal: AbortSignal.timeout(VALIDATOR_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n await cancelResponseBody(response);\n throw new Error(`W3C HTML validator returned HTTP status ${response.status}`);\n }\n\n const text = await readResponseText(response, MAX_VALIDATOR_RESPONSE_BYTES);\n const data = JSON.parse(text) as { messages?: unknown };\n if (data.messages === undefined) {\n throw new Error(\"W3C HTML validator returned an invalid response shape\");\n }\n if (!Array.isArray(data.messages)) {\n throw new Error(\"W3C HTML validator returned an invalid response shape\");\n }\n const normalized = data.messages.map(normalizeW3CMessage);\n const counts: HtmlValidationResult[\"counts\"] = { error: 0, warning: 0, info: 0 };\n for (const message of normalized) {\n counts[getW3CMessageSeverity(message)] += 1;\n }\n return {\n messages: normalized.slice(0, MAX_VALIDATION_MESSAGES),\n total: normalized.length,\n truncated: normalized.length > MAX_VALIDATION_MESSAGES,\n counts,\n };\n } catch (error: unknown) {\n throw new Error(`HTML validation failed: ${getErrorMessage(error)}`);\n }\n}\n\n/** Backwards-compatible convenience API returning capped diagnostics only. */\nexport async function validateHtmlContent(htmlContent: string): Promise<W3CMessage[]> {\n return (await validateHtmlContentDetailed(htmlContent)).messages;\n}\n\n/** Validates CSS using the W3C Jigsaw CSS Validator API and retains bounded-output metadata. */\nexport async function validateCssContentDetailed(cssContent: string): Promise<CssValidationResult> {\n const url = \"https://jigsaw.w3.org/css-validator/validator\";\n\n try {\n assertContentSize(cssContent, MAX_CSS_VALIDATION_BYTES, \"CSS content\");\n const form = new FormData();\n form.set(\"text\", cssContent);\n form.set(\"output\", \"json\");\n form.set(\"warning\", \"0\");\n form.set(\"profile\", \"css3svg\");\n\n const response = await fetch(url, {\n method: \"POST\",\n headers: {\n \"User-Agent\": USER_AGENT,\n },\n body: form,\n redirect: \"error\",\n signal: AbortSignal.timeout(VALIDATOR_TIMEOUT_MS),\n });\n\n if (!response.ok) {\n await cancelResponseBody(response);\n throw new Error(`W3C CSS validator returned HTTP status ${response.status}`);\n }\n\n const text = await readResponseText(response, MAX_VALIDATOR_RESPONSE_BYTES);\n\n // An empty upstream response is indeterminate and must never be presented as a clean result.\n if (!text || text.trim() === \"\") {\n throw new Error(\"W3C CSS validator returned an empty response\");\n }\n\n const data = JSON.parse(text) as {\n cssvalidation?: {\n errors?: Array<{\n line: number;\n message: string;\n context?: string;\n type?: string;\n }>;\n warnings?: Array<{\n line: number;\n message: string;\n context?: string;\n type?: string;\n }>;\n };\n };\n\n if (!data.cssvalidation || typeof data.cssvalidation !== \"object\") {\n throw new Error(\"W3C CSS validator returned an invalid response shape\");\n }\n\n const errors = data.cssvalidation.errors || [];\n const normalized = errors.map((err) => {\n const message = err.message ? err.message.trim() : \"Unknown CSS validation error\";\n return {\n line: err.line || 0,\n type: \"error\",\n message,\n context: err.context || undefined,\n ...(isKnownCssValidatorLimitation(err.type, message)\n ? { compatibility: \"known-validator-limitation\" as const }\n : {}),\n };\n });\n const compatibilityLimitation = normalized.filter(\n (message) => message.compatibility === \"known-validator-limitation\",\n ).length;\n return {\n messages: normalized.slice(0, MAX_VALIDATION_MESSAGES),\n total: normalized.length,\n truncated: normalized.length > MAX_VALIDATION_MESSAGES,\n counts: {\n error: normalized.length - compatibilityLimitation,\n compatibilityLimitation,\n },\n };\n } catch (error: unknown) {\n throw new Error(`CSS validation failed: ${getErrorMessage(error)}`);\n }\n}\n\n/** Backwards-compatible convenience API returning capped CSS diagnostics only. */\nexport async function validateCssContent(cssContent: string): Promise<CSSMessage[]> {\n return (await validateCssContentDetailed(cssContent)).messages;\n}\n"]}
|