@nuasite/checks 0.16.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (124) hide show
  1. package/dist/types/check-runner.d.ts +16 -0
  2. package/dist/types/check-runner.d.ts.map +1 -0
  3. package/dist/types/checks/accessibility/aria-landmarks-check.d.ts +3 -0
  4. package/dist/types/checks/accessibility/aria-landmarks-check.d.ts.map +1 -0
  5. package/dist/types/checks/accessibility/form-label-check.d.ts +3 -0
  6. package/dist/types/checks/accessibility/form-label-check.d.ts.map +1 -0
  7. package/dist/types/checks/accessibility/index.d.ts +6 -0
  8. package/dist/types/checks/accessibility/index.d.ts.map +1 -0
  9. package/dist/types/checks/accessibility/lang-attribute-check.d.ts +3 -0
  10. package/dist/types/checks/accessibility/lang-attribute-check.d.ts.map +1 -0
  11. package/dist/types/checks/accessibility/link-text-check.d.ts +3 -0
  12. package/dist/types/checks/accessibility/link-text-check.d.ts.map +1 -0
  13. package/dist/types/checks/accessibility/tabindex-check.d.ts +3 -0
  14. package/dist/types/checks/accessibility/tabindex-check.d.ts.map +1 -0
  15. package/dist/types/checks/geo/agents-md-check.d.ts.map +1 -0
  16. package/dist/types/checks/geo/content-quality-check.d.ts +4 -0
  17. package/dist/types/checks/geo/content-quality-check.d.ts.map +1 -0
  18. package/dist/types/checks/geo/index.d.ts +3 -0
  19. package/dist/types/checks/geo/index.d.ts.map +1 -0
  20. package/dist/types/checks/geo/llms-txt-check.d.ts +3 -0
  21. package/dist/types/checks/geo/llms-txt-check.d.ts.map +1 -0
  22. package/dist/types/checks/performance/html-size-check.d.ts +3 -0
  23. package/dist/types/checks/performance/html-size-check.d.ts.map +1 -0
  24. package/dist/types/checks/performance/image-optimization-check.d.ts +4 -0
  25. package/dist/types/checks/performance/image-optimization-check.d.ts.map +1 -0
  26. package/dist/types/checks/performance/index.d.ts +7 -0
  27. package/dist/types/checks/performance/index.d.ts.map +1 -0
  28. package/dist/types/checks/performance/inline-size-check.d.ts +3 -0
  29. package/dist/types/checks/performance/inline-size-check.d.ts.map +1 -0
  30. package/dist/types/checks/performance/lazy-loading-check.d.ts +3 -0
  31. package/dist/types/checks/performance/lazy-loading-check.d.ts.map +1 -0
  32. package/dist/types/checks/performance/render-blocking-check.d.ts +3 -0
  33. package/dist/types/checks/performance/render-blocking-check.d.ts.map +1 -0
  34. package/dist/types/checks/performance/total-requests-check.d.ts +3 -0
  35. package/dist/types/checks/performance/total-requests-check.d.ts.map +1 -0
  36. package/dist/types/checks/seo/broken-internal-links-check.d.ts +3 -0
  37. package/dist/types/checks/seo/broken-internal-links-check.d.ts.map +1 -0
  38. package/dist/types/checks/seo/canonical-check.d.ts +5 -0
  39. package/dist/types/checks/seo/canonical-check.d.ts.map +1 -0
  40. package/dist/types/checks/seo/description-check.d.ts +4 -0
  41. package/dist/types/checks/seo/description-check.d.ts.map +1 -0
  42. package/dist/types/checks/seo/heading-hierarchy-check.d.ts +5 -0
  43. package/dist/types/checks/seo/heading-hierarchy-check.d.ts.map +1 -0
  44. package/dist/types/checks/seo/image-alt-check.d.ts +3 -0
  45. package/dist/types/checks/seo/image-alt-check.d.ts.map +1 -0
  46. package/dist/types/checks/seo/image-alt-quality-check.d.ts +3 -0
  47. package/dist/types/checks/seo/image-alt-quality-check.d.ts.map +1 -0
  48. package/dist/types/checks/seo/index.d.ts +15 -0
  49. package/dist/types/checks/seo/index.d.ts.map +1 -0
  50. package/dist/types/checks/seo/json-ld-check.d.ts +3 -0
  51. package/dist/types/checks/seo/json-ld-check.d.ts.map +1 -0
  52. package/dist/types/checks/seo/meta-duplicates-check.d.ts +3 -0
  53. package/dist/types/checks/seo/meta-duplicates-check.d.ts.map +1 -0
  54. package/dist/types/checks/seo/noindex-check.d.ts +3 -0
  55. package/dist/types/checks/seo/noindex-check.d.ts.map +1 -0
  56. package/dist/types/checks/seo/open-graph-check.d.ts +5 -0
  57. package/dist/types/checks/seo/open-graph-check.d.ts.map +1 -0
  58. package/dist/types/checks/seo/sitemap-robots-check.d.ts +4 -0
  59. package/dist/types/checks/seo/sitemap-robots-check.d.ts.map +1 -0
  60. package/dist/types/checks/seo/title-check.d.ts +5 -0
  61. package/dist/types/checks/seo/title-check.d.ts.map +1 -0
  62. package/dist/types/checks/seo/twitter-card-check.d.ts +3 -0
  63. package/dist/types/checks/seo/twitter-card-check.d.ts.map +1 -0
  64. package/dist/types/checks/seo/viewport-check.d.ts +3 -0
  65. package/dist/types/checks/seo/viewport-check.d.ts.map +1 -0
  66. package/dist/types/checks-integration.d.ts +4 -0
  67. package/dist/types/checks-integration.d.ts.map +1 -0
  68. package/dist/types/config.d.ts +3 -0
  69. package/dist/types/config.d.ts.map +1 -0
  70. package/dist/types/html-analyzer.d.ts +11 -0
  71. package/dist/types/html-analyzer.d.ts.map +1 -0
  72. package/dist/types/i18n/poor-texts.d.ts +14 -0
  73. package/dist/types/i18n/poor-texts.d.ts.map +1 -0
  74. package/dist/types/index.d.ts +4 -0
  75. package/dist/types/index.d.ts.map +1 -0
  76. package/dist/types/register.d.ts +4 -0
  77. package/dist/types/register.d.ts.map +1 -0
  78. package/dist/types/report.d.ts +5 -0
  79. package/dist/types/report.d.ts.map +1 -0
  80. package/dist/types/tsconfig.tsbuildinfo +1 -0
  81. package/dist/types/types.d.ts +220 -0
  82. package/dist/types/types.d.ts.map +1 -0
  83. package/package.json +49 -0
  84. package/src/check-runner.ts +92 -0
  85. package/src/checks/accessibility/aria-landmarks-check.ts +23 -0
  86. package/src/checks/accessibility/form-label-check.ts +29 -0
  87. package/src/checks/accessibility/index.ts +5 -0
  88. package/src/checks/accessibility/lang-attribute-check.ts +22 -0
  89. package/src/checks/accessibility/link-text-check.ts +30 -0
  90. package/src/checks/accessibility/tabindex-check.ts +28 -0
  91. package/src/checks/geo/content-quality-check.ts +48 -0
  92. package/src/checks/geo/index.ts +2 -0
  93. package/src/checks/geo/llms-txt-check.ts +37 -0
  94. package/src/checks/performance/html-size-check.ts +26 -0
  95. package/src/checks/performance/image-optimization-check.ts +82 -0
  96. package/src/checks/performance/index.ts +6 -0
  97. package/src/checks/performance/inline-size-check.ts +29 -0
  98. package/src/checks/performance/lazy-loading-check.ts +29 -0
  99. package/src/checks/performance/render-blocking-check.ts +27 -0
  100. package/src/checks/performance/total-requests-check.ts +27 -0
  101. package/src/checks/seo/broken-internal-links-check.ts +49 -0
  102. package/src/checks/seo/canonical-check.ts +77 -0
  103. package/src/checks/seo/description-check.ts +55 -0
  104. package/src/checks/seo/heading-hierarchy-check.ts +76 -0
  105. package/src/checks/seo/image-alt-check.ts +28 -0
  106. package/src/checks/seo/image-alt-quality-check.ts +31 -0
  107. package/src/checks/seo/index.ts +14 -0
  108. package/src/checks/seo/json-ld-check.ts +26 -0
  109. package/src/checks/seo/meta-duplicates-check.ts +44 -0
  110. package/src/checks/seo/noindex-check.ts +22 -0
  111. package/src/checks/seo/open-graph-check.ts +55 -0
  112. package/src/checks/seo/sitemap-robots-check.ts +55 -0
  113. package/src/checks/seo/title-check.ts +63 -0
  114. package/src/checks/seo/twitter-card-check.ts +22 -0
  115. package/src/checks/seo/viewport-check.ts +22 -0
  116. package/src/checks-integration.ts +126 -0
  117. package/src/config.ts +27 -0
  118. package/src/html-analyzer.ts +325 -0
  119. package/src/i18n/poor-texts.ts +66 -0
  120. package/src/index.ts +22 -0
  121. package/src/register.ts +110 -0
  122. package/src/report.ts +78 -0
  123. package/src/tsconfig.json +6 -0
  124. package/src/types.ts +244 -0
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Locale-keyed collections of generic/non-descriptive text patterns.
3
+ * Used by accessibility and SEO checks to detect poor link text and image alt text.
4
+ */
5
+
6
+ export const poorLinkTexts: Record<string, string[]> = {
7
+ en: ['click here', 'read more', 'here', 'link', 'more', 'learn more'],
8
+ es: ['haz clic aquí', 'clic aquí', 'leer más', 'aquí', 'enlace', 'más', 'más información'],
9
+ fr: ['cliquez ici', 'lire la suite', 'en savoir plus', 'ici', 'lien', 'plus'],
10
+ de: ['hier klicken', 'klicken sie hier', 'mehr lesen', 'weiterlesen', 'hier', 'mehr', 'mehr erfahren'],
11
+ it: ['clicca qui', 'leggi di più', 'continua a leggere', 'qui', 'collegamento', 'più'],
12
+ pt: ['clique aqui', 'leia mais', 'saiba mais', 'aqui', 'mais'],
13
+ nl: ['klik hier', 'lees meer', 'meer'],
14
+ cs: ['klikněte zde', 'klikni zde', 'čtěte dále', 'více', 'zde', 'odkaz', 'zjistit více'],
15
+ ko: ['여기를 클릭', '더 보기', '자세히 보기', '여기', '링크', '더보기', '클릭'],
16
+ }
17
+
18
+ export const poorImageAlts: Record<string, string[]> = {
19
+ en: ['image', 'photo', 'picture', 'img', 'untitled', 'screenshot', 'banner', 'logo', 'icon', 'graphic', 'thumbnail'],
20
+ es: ['imagen', 'foto', 'fotografía', 'captura de pantalla', 'logotipo', 'ícono'],
21
+ fr: ['image', 'photo', "capture d'écran", 'logo', 'icône', 'graphique', 'vignette'],
22
+ de: ['bild', 'foto', 'grafik', 'bildschirmfoto', 'logo', 'symbol'],
23
+ it: ['immagine', 'foto', 'fotografia', 'schermata', 'logo', 'icona'],
24
+ pt: ['imagem', 'foto', 'fotografia', 'captura de tela', 'logotipo', 'ícone'],
25
+ nl: ['afbeelding', 'foto', 'schermafbeelding', 'logo', 'icoon'],
26
+ cs: ['obrázek', 'foto', 'fotografie', 'snímek obrazovky', 'logo', 'ikona'],
27
+ ko: ['이미지', '사진', '스크린샷', '로고', '아이콘', '그래픽', '썸네일'],
28
+ }
29
+
30
+ const cache = new WeakMap<Record<string, string[]>, Map<string, Set<string>>>()
31
+
32
+ /**
33
+ * Build a Set of poor texts, optionally filtered by page locale.
34
+ * When a locale is provided, returns texts for that language + English as fallback.
35
+ * When no locale is provided or locale is unrecognized, returns English texts only.
36
+ * Results are cached by dictionary + locale.
37
+ */
38
+ export function buildPoorTextSet(texts: Record<string, string[]>, locale?: string): Set<string> {
39
+ const key = locale?.toLowerCase().split('-')[0] ?? '*'
40
+
41
+ let localeMap = cache.get(texts)
42
+ if (!localeMap) {
43
+ localeMap = new Map()
44
+ cache.set(texts, localeMap)
45
+ }
46
+
47
+ const cached = localeMap.get(key)
48
+ if (cached) return cached
49
+
50
+ const en = texts.en ?? []
51
+ let result: Set<string>
52
+
53
+ if (key === '*') {
54
+ const set = new Set<string>()
55
+ for (const arr of Object.values(texts)) {
56
+ for (const s of arr) set.add(s)
57
+ }
58
+ result = set
59
+ } else {
60
+ const matched = texts[key]
61
+ result = matched ? new Set([...matched, ...en]) : new Set(en)
62
+ }
63
+
64
+ localeMap.set(key, result)
65
+ return result
66
+ }
package/src/index.ts ADDED
@@ -0,0 +1,22 @@
1
+ export { checks } from './checks-integration'
2
+ export { checks as default } from './checks-integration'
3
+ export type {
4
+ AccessibilityChecksConfig,
5
+ AiChecksConfig,
6
+ Check,
7
+ CheckDomain,
8
+ CheckIssue,
9
+ CheckMode,
10
+ CheckReport,
11
+ CheckResult,
12
+ CheckSeverity,
13
+ ChecksOptions,
14
+ ExtractedPageData,
15
+ GeoChecksConfig,
16
+ PageCheckContext,
17
+ PerformanceChecksConfig,
18
+ SeoChecksConfig,
19
+ SiteCheck,
20
+ SiteCheckContext,
21
+ SiteCheckIssue,
22
+ } from './types'
@@ -0,0 +1,110 @@
1
+ import type { CheckRunner } from './check-runner'
2
+ import type { ResolvedChecksOptions } from './types'
3
+
4
+ // SEO checks
5
+ import { createBrokenInternalLinksCheck } from './checks/seo/broken-internal-links-check'
6
+ import { createCanonicalInvalidCheck, createCanonicalMismatchCheck, createCanonicalMissingCheck } from './checks/seo/canonical-check'
7
+ import { createDescriptionLengthCheck, createDescriptionMissingCheck } from './checks/seo/description-check'
8
+ import { createHeadingSkipCheck, createMultipleH1Check, createNoH1Check } from './checks/seo/heading-hierarchy-check'
9
+ import { createImageAltMissingCheck } from './checks/seo/image-alt-check'
10
+ import { createImageAltQualityCheck } from './checks/seo/image-alt-quality-check'
11
+ import { createJsonLdInvalidCheck } from './checks/seo/json-ld-check'
12
+ import { createMetaDuplicateCheck } from './checks/seo/meta-duplicates-check'
13
+ import { createNoindexDetectedCheck } from './checks/seo/noindex-check'
14
+ import { createOgDescriptionCheck, createOgImageCheck, createOgTitleCheck } from './checks/seo/open-graph-check'
15
+ import { createRobotsTxtCheck, createSitemapXmlCheck } from './checks/seo/sitemap-robots-check'
16
+ import { createTitleEmptyCheck, createTitleLengthCheck, createTitleMissingCheck } from './checks/seo/title-check'
17
+ import { createTwitterCardCheck } from './checks/seo/twitter-card-check'
18
+ import { createViewportMissingCheck } from './checks/seo/viewport-check'
19
+
20
+ // GEO checks
21
+ import { createContentTooShortCheck, createInsufficientHeadingsCheck } from './checks/geo/content-quality-check'
22
+ import { createLlmsTxtCheck } from './checks/geo/llms-txt-check'
23
+
24
+ // Performance checks
25
+ import { createHtmlSizeCheck } from './checks/performance/html-size-check'
26
+ import { createImageFormatCheck, createImageSizeCheck } from './checks/performance/image-optimization-check'
27
+ import { createInlineSizeCheck } from './checks/performance/inline-size-check'
28
+ import { createLazyLoadingCheck } from './checks/performance/lazy-loading-check'
29
+ import { createRenderBlockingScriptCheck } from './checks/performance/render-blocking-check'
30
+ import { createTotalRequestsCheck } from './checks/performance/total-requests-check'
31
+
32
+ // Accessibility checks
33
+ import { createAriaLandmarksCheck } from './checks/accessibility/aria-landmarks-check'
34
+ import { createFormLabelCheck } from './checks/accessibility/form-label-check'
35
+ import { createLangAttributeCheck } from './checks/accessibility/lang-attribute-check'
36
+ import { createLinkTextCheck } from './checks/accessibility/link-text-check'
37
+ import { createTabindexCheck } from './checks/accessibility/tabindex-check'
38
+
39
+ export function registerAllChecks(runner: CheckRunner, options: ResolvedChecksOptions): void {
40
+ // SEO checks
41
+ if (options.seo !== false) {
42
+ const seo = options.seo
43
+ const titleMax = seo.titleMaxLength ?? 60
44
+ const descMax = seo.descriptionMaxLength ?? 160
45
+ const descMin = seo.descriptionMinLength ?? 50
46
+
47
+ runner.registerCheck(createTitleMissingCheck())
48
+ runner.registerCheck(createTitleEmptyCheck())
49
+ runner.registerCheck(createTitleLengthCheck(titleMax))
50
+ runner.registerCheck(createDescriptionMissingCheck())
51
+ runner.registerCheck(createDescriptionLengthCheck(descMin, descMax))
52
+ runner.registerCheck(createCanonicalMissingCheck())
53
+ runner.registerCheck(createCanonicalInvalidCheck())
54
+ runner.registerCheck(createCanonicalMismatchCheck())
55
+ runner.registerCheck(createJsonLdInvalidCheck())
56
+ runner.registerCheck(createMultipleH1Check())
57
+ runner.registerCheck(createNoH1Check())
58
+ runner.registerCheck(createHeadingSkipCheck())
59
+ runner.registerCheck(createOgTitleCheck())
60
+ runner.registerCheck(createOgDescriptionCheck())
61
+ runner.registerCheck(createOgImageCheck())
62
+ runner.registerCheck(createImageAltMissingCheck())
63
+ runner.registerCheck(createImageAltQualityCheck())
64
+ runner.registerCheck(createMetaDuplicateCheck())
65
+ runner.registerCheck(createViewportMissingCheck())
66
+ runner.registerCheck(createNoindexDetectedCheck())
67
+ runner.registerCheck(createTwitterCardCheck())
68
+ runner.registerSiteCheck(createRobotsTxtCheck())
69
+ runner.registerSiteCheck(createSitemapXmlCheck())
70
+ runner.registerSiteCheck(createBrokenInternalLinksCheck())
71
+ }
72
+
73
+ // GEO checks
74
+ if (options.geo !== false) {
75
+ const geo = options.geo
76
+ const minContent = geo.minContentLength ?? 300
77
+ const minHeadings = geo.minHeadings ?? 2
78
+
79
+ runner.registerSiteCheck(createLlmsTxtCheck())
80
+ runner.registerCheck(createContentTooShortCheck(minContent))
81
+ runner.registerCheck(createInsufficientHeadingsCheck(minHeadings))
82
+ }
83
+
84
+ // Performance checks
85
+ if (options.performance !== false) {
86
+ const perf = options.performance
87
+ const maxHtml = perf.maxHtmlSize ?? 100_000
88
+ const maxImg = perf.maxImageSize ?? 500_000
89
+ const formats = perf.allowedImageFormats ?? ['webp', 'avif', 'svg']
90
+ const maxInline = perf.maxInlineSize ?? 50_000
91
+ const maxRequests = perf.maxExternalRequests ?? 20
92
+
93
+ runner.registerCheck(createHtmlSizeCheck(maxHtml))
94
+ runner.registerCheck(createImageFormatCheck(formats))
95
+ runner.registerCheck(createImageSizeCheck(maxImg))
96
+ runner.registerCheck(createLazyLoadingCheck())
97
+ runner.registerCheck(createRenderBlockingScriptCheck())
98
+ runner.registerCheck(createInlineSizeCheck(maxInline))
99
+ runner.registerCheck(createTotalRequestsCheck(maxRequests))
100
+ }
101
+
102
+ // Accessibility checks
103
+ if (options.accessibility !== false) {
104
+ runner.registerCheck(createLangAttributeCheck())
105
+ runner.registerCheck(createFormLabelCheck())
106
+ runner.registerCheck(createAriaLandmarksCheck())
107
+ runner.registerCheck(createLinkTextCheck())
108
+ runner.registerCheck(createTabindexCheck())
109
+ }
110
+ }
package/src/report.ts ADDED
@@ -0,0 +1,78 @@
1
+ import type { AstroIntegrationLogger } from 'astro'
2
+ import fs from 'node:fs/promises'
3
+ import path from 'node:path'
4
+ import type { CheckReport, CheckResult } from './types'
5
+
6
+ export function logReport(report: CheckReport, logger: AstroIntegrationLogger): void {
7
+ const { totalPages, totalChecks, errors, warnings, infos } = report
8
+
9
+ if (errors.length === 0 && warnings.length === 0 && infos.length === 0) {
10
+ logger.info(`Checked ${totalPages} pages (${totalChecks} rules) — all passed`)
11
+ return
12
+ }
13
+
14
+ logger.info(`Checked ${totalPages} pages (${totalChecks} rules)`)
15
+
16
+ if (errors.length > 0) {
17
+ logger.error(`${errors.length} error(s):`)
18
+ for (const r of errors) {
19
+ logger.error(formatResult(r))
20
+ }
21
+ }
22
+
23
+ if (warnings.length > 0) {
24
+ logger.warn(`${warnings.length} warning(s):`)
25
+ for (const r of warnings) {
26
+ logger.warn(formatResult(r))
27
+ }
28
+ }
29
+
30
+ if (infos.length > 0) {
31
+ logger.info(`${infos.length} info(s):`)
32
+ for (const r of infos) {
33
+ logger.info(formatResult(r))
34
+ }
35
+ }
36
+
37
+ const parts = [
38
+ errors.length > 0 ? `${errors.length} error(s)` : null,
39
+ warnings.length > 0 ? `${warnings.length} warning(s)` : null,
40
+ infos.length > 0 ? `${infos.length} info(s)` : null,
41
+ ].filter(Boolean)
42
+
43
+ logger.info(`Summary: ${parts.join(', ')}`)
44
+ }
45
+
46
+ export async function writeJsonReport(report: CheckReport, distDir: string, filename: string): Promise<string> {
47
+ const filePath = path.join(distDir, filename)
48
+ const json = {
49
+ timestamp: new Date().toISOString(),
50
+ totalPages: report.totalPages,
51
+ totalChecks: report.totalChecks,
52
+ passed: report.passed,
53
+ summary: {
54
+ errors: report.errors.length,
55
+ warnings: report.warnings.length,
56
+ infos: report.infos.length,
57
+ },
58
+ results: report.results.map(r => ({
59
+ checkId: r.checkId,
60
+ severity: r.severity,
61
+ domain: r.domain,
62
+ message: r.message,
63
+ suggestion: r.suggestion,
64
+ pagePath: r.pagePath,
65
+ line: r.line,
66
+ actual: r.actual,
67
+ expected: r.expected,
68
+ })),
69
+ }
70
+ await fs.writeFile(filePath, JSON.stringify(json, null, '\t'), 'utf8')
71
+ return filePath
72
+ }
73
+
74
+ function formatResult(r: CheckResult): string {
75
+ const location = r.line ? `${r.pagePath}:${r.line}` : r.pagePath
76
+ const suggestion = r.suggestion ? ` → ${r.suggestion}` : ''
77
+ return ` ${r.checkId.padEnd(30)} ${location.padEnd(30)} ${r.message}${suggestion}`
78
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "extends": "../tsconfig.settings.json",
3
+ "compilerOptions": {
4
+ "outDir": "../dist/types"
5
+ }
6
+ }
package/src/types.ts ADDED
@@ -0,0 +1,244 @@
1
+ import type { HTMLElement as ParsedHTMLElement } from 'node-html-parser'
2
+
3
+ // ── Severity & Domain ──────────────────────────────────────────────────────────
4
+
5
+ export type CheckSeverity = 'error' | 'warning' | 'info'
6
+ export type CheckDomain = 'seo' | 'geo' | 'performance' | 'accessibility' | 'ai'
7
+ export type CheckMode = 'auto' | 'full' | 'essential'
8
+
9
+ // ── Check Issue (returned by checks) ────────────────────────────────────────────
10
+
11
+ /** Lightweight result returned by a check's run() method. The runner enriches it into a full CheckResult. */
12
+ export interface CheckIssue {
13
+ message: string
14
+ suggestion?: string
15
+ pagePath?: string
16
+ filePath?: string
17
+ line?: number
18
+ actual?: string
19
+ expected?: string
20
+ }
21
+
22
+ /** Issue returned by a site-level check — pagePath is required since there's no single page context. */
23
+ export type SiteCheckIssue = CheckIssue & { pagePath: string }
24
+
25
+ // ── Check Result (enriched by runner) ───────────────────────────────────────────
26
+
27
+ export interface CheckResult {
28
+ checkId: string
29
+ ruleName: string
30
+ domain: CheckDomain
31
+ severity: CheckSeverity
32
+ message: string
33
+ suggestion?: string
34
+ pagePath: string
35
+ filePath?: string
36
+ line?: number
37
+ actual?: string
38
+ expected?: string
39
+ }
40
+
41
+ // ── Check Interfaces ───────────────────────────────────────────────────────────
42
+
43
+ /** Per-page check — runs once for each HTML page */
44
+ export interface Check {
45
+ kind: 'page'
46
+ id: string
47
+ name: string
48
+ domain: CheckDomain
49
+ defaultSeverity: CheckSeverity
50
+ description: string
51
+ /** Whether this check runs in essential mode (true) or only in full mode (false) */
52
+ essential: boolean
53
+ run(context: PageCheckContext): CheckIssue[] | Promise<CheckIssue[]>
54
+ }
55
+
56
+ /** Site-level check — runs once after all pages are processed */
57
+ export interface SiteCheck {
58
+ kind: 'site'
59
+ id: string
60
+ name: string
61
+ domain: CheckDomain
62
+ defaultSeverity: CheckSeverity
63
+ description: string
64
+ essential: boolean
65
+ run(context: SiteCheckContext): SiteCheckIssue[] | Promise<SiteCheckIssue[]>
66
+ }
67
+
68
+ // ── Check Contexts ─────────────────────────────────────────────────────────────
69
+
70
+ export interface PageCheckContext {
71
+ pagePath: string
72
+ filePath: string
73
+ distDir: string
74
+ html: string
75
+ root: ParsedHTMLElement
76
+ pageData: ExtractedPageData
77
+ }
78
+
79
+ export interface SiteCheckContext {
80
+ distDir: string
81
+ projectRoot: string
82
+ pages: Map<string, ExtractedPageData>
83
+ siteUrl?: string
84
+ }
85
+
86
+ // ── Extracted Page Data ────────────────────────────────────────────────────────
87
+
88
+ export interface ExtractedPageData {
89
+ title?: { content: string; line: number }
90
+ metaDescription?: { content: string; line: number }
91
+ metaTags: MetaTagData[]
92
+ openGraph: Record<string, { content: string; line: number }>
93
+ twitterCard: Record<string, { content: string; line: number }>
94
+ canonical?: { href: string; line: number }
95
+ jsonLd: JsonLdData[]
96
+ headings: HeadingData[]
97
+ images: ImageData[]
98
+ links: LinkData[]
99
+ scripts: ScriptData[]
100
+ stylesheets: StylesheetData[]
101
+ forms: ExtractedFormData[]
102
+ htmlLang?: string
103
+ htmlSize: number
104
+ bodyTextLength: number
105
+ hasViewport: boolean
106
+ hasNoindex: boolean
107
+ inlineScriptBytes: number
108
+ inlineStyleBytes: number
109
+ }
110
+
111
+ export interface MetaTagData {
112
+ name?: string
113
+ property?: string
114
+ content: string
115
+ line: number
116
+ }
117
+
118
+ export interface JsonLdData {
119
+ type: string
120
+ raw: string
121
+ valid: boolean
122
+ error?: string
123
+ line: number
124
+ }
125
+
126
+ export interface HeadingData {
127
+ level: number
128
+ text: string
129
+ line: number
130
+ }
131
+
132
+ export interface ImageData {
133
+ src: string
134
+ alt?: string
135
+ loading?: string
136
+ line: number
137
+ }
138
+
139
+ export interface LinkData {
140
+ href: string
141
+ text: string
142
+ rel?: string
143
+ line: number
144
+ }
145
+
146
+ export interface ScriptData {
147
+ src?: string
148
+ type?: string
149
+ isAsync: boolean
150
+ isDefer: boolean
151
+ isInline: boolean
152
+ size: number
153
+ line: number
154
+ }
155
+
156
+ export interface StylesheetData {
157
+ href: string
158
+ media?: string
159
+ line: number
160
+ }
161
+
162
+ export interface ExtractedFormData {
163
+ inputs: FormInputData[]
164
+ line: number
165
+ }
166
+
167
+ export interface FormInputData {
168
+ type: string
169
+ name?: string
170
+ id?: string
171
+ hasLabel: boolean
172
+ line: number
173
+ }
174
+
175
+ // ── Configuration ──────────────────────────────────────────────────────────────
176
+
177
+ export interface ChecksOptions {
178
+ mode?: CheckMode
179
+ seo?: boolean | SeoChecksConfig
180
+ geo?: boolean | GeoChecksConfig
181
+ performance?: boolean | PerformanceChecksConfig
182
+ accessibility?: boolean | AccessibilityChecksConfig
183
+ ai?: AiChecksConfig | false
184
+ failOnError?: boolean
185
+ failOnWarning?: boolean
186
+ overrides?: Record<string, CheckSeverity | false>
187
+ customChecks?: (Check | SiteCheck)[]
188
+ /** Write a JSON report to the dist dir. `true` writes `checks-report.json`, a string sets a custom filename. */
189
+ reportJson?: boolean | string
190
+ }
191
+
192
+ export interface SeoChecksConfig {
193
+ titleMaxLength?: number
194
+ descriptionMaxLength?: number
195
+ descriptionMinLength?: number
196
+ }
197
+
198
+ export interface GeoChecksConfig {
199
+ minContentLength?: number
200
+ minHeadings?: number
201
+ }
202
+
203
+ export interface PerformanceChecksConfig {
204
+ maxHtmlSize?: number
205
+ maxImageSize?: number
206
+ allowedImageFormats?: string[]
207
+ maxInlineSize?: number
208
+ maxExternalRequests?: number
209
+ }
210
+
211
+ export type AccessibilityChecksConfig = {}
212
+
213
+ export interface AiChecksConfig {
214
+ apiKey?: string
215
+ checks?: string[]
216
+ maxPages?: number
217
+ cache?: boolean
218
+ }
219
+
220
+ export interface ResolvedChecksOptions {
221
+ mode: CheckMode
222
+ seo: SeoChecksConfig | false
223
+ geo: GeoChecksConfig | false
224
+ performance: PerformanceChecksConfig | false
225
+ accessibility: AccessibilityChecksConfig | false
226
+ ai: AiChecksConfig | false
227
+ failOnError: boolean
228
+ failOnWarning: boolean
229
+ overrides: Record<string, CheckSeverity | false>
230
+ customChecks: (Check | SiteCheck)[]
231
+ reportJson: string | false
232
+ }
233
+
234
+ // ── Report ─────────────────────────────────────────────────────────────────────
235
+
236
+ export interface CheckReport {
237
+ totalPages: number
238
+ totalChecks: number
239
+ results: CheckResult[]
240
+ errors: CheckResult[]
241
+ warnings: CheckResult[]
242
+ infos: CheckResult[]
243
+ passed: boolean
244
+ }