@brandlab-digital/conformance 3.7.0 → 3.9.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 (290) hide show
  1. package/README.md +26 -0
  2. package/dist/current-release.js +5 -5
  3. package/dist/current-release.js.map +1 -1
  4. package/dist/engineering.js +4 -2
  5. package/dist/engineering.js.map +1 -1
  6. package/dist/public-copy.d.ts +7 -0
  7. package/dist/public-copy.d.ts.map +1 -1
  8. package/dist/public-copy.js +8 -3
  9. package/dist/public-copy.js.map +1 -1
  10. package/dist/ready-cli.d.ts +38 -0
  11. package/dist/ready-cli.d.ts.map +1 -0
  12. package/dist/ready-cli.js +327 -0
  13. package/dist/ready-cli.js.map +1 -0
  14. package/dist/site-system/checks.d.ts +389 -0
  15. package/dist/site-system/checks.d.ts.map +1 -0
  16. package/dist/site-system/checks.js +96 -0
  17. package/dist/site-system/checks.js.map +1 -0
  18. package/dist/site-system/config.d.ts +40 -0
  19. package/dist/site-system/config.d.ts.map +1 -0
  20. package/dist/site-system/config.js +214 -0
  21. package/dist/site-system/config.js.map +1 -0
  22. package/dist/site-system/index.d.ts +14 -0
  23. package/dist/site-system/index.d.ts.map +1 -0
  24. package/dist/site-system/index.js +14 -0
  25. package/dist/site-system/index.js.map +1 -0
  26. package/dist/site-system/journeys/access.d.ts +7 -0
  27. package/dist/site-system/journeys/access.d.ts.map +1 -0
  28. package/dist/site-system/journeys/access.js +263 -0
  29. package/dist/site-system/journeys/access.js.map +1 -0
  30. package/dist/site-system/journeys/browser.d.ts +79 -0
  31. package/dist/site-system/journeys/browser.d.ts.map +1 -0
  32. package/dist/site-system/journeys/browser.js +218 -0
  33. package/dist/site-system/journeys/browser.js.map +1 -0
  34. package/dist/site-system/journeys/conversion.d.ts +3 -0
  35. package/dist/site-system/journeys/conversion.d.ts.map +1 -0
  36. package/dist/site-system/journeys/conversion.js +152 -0
  37. package/dist/site-system/journeys/conversion.js.map +1 -0
  38. package/dist/site-system/journeys/menu.d.ts +11 -0
  39. package/dist/site-system/journeys/menu.d.ts.map +1 -0
  40. package/dist/site-system/journeys/menu.js +395 -0
  41. package/dist/site-system/journeys/menu.js.map +1 -0
  42. package/dist/site-system/journeys/page-helpers.d.ts +146 -0
  43. package/dist/site-system/journeys/page-helpers.d.ts.map +1 -0
  44. package/dist/site-system/journeys/page-helpers.js +426 -0
  45. package/dist/site-system/journeys/page-helpers.js.map +1 -0
  46. package/dist/site-system/journeys/plan.d.ts +82 -0
  47. package/dist/site-system/journeys/plan.d.ts.map +1 -0
  48. package/dist/site-system/journeys/plan.js +112 -0
  49. package/dist/site-system/journeys/plan.js.map +1 -0
  50. package/dist/site-system/journeys/shell.d.ts +24 -0
  51. package/dist/site-system/journeys/shell.d.ts.map +1 -0
  52. package/dist/site-system/journeys/shell.js +269 -0
  53. package/dist/site-system/journeys/shell.js.map +1 -0
  54. package/dist/site-system/playwright-report.d.ts +22 -0
  55. package/dist/site-system/playwright-report.d.ts.map +1 -0
  56. package/dist/site-system/playwright-report.js +131 -0
  57. package/dist/site-system/playwright-report.js.map +1 -0
  58. package/dist/site-system/records.d.ts +30 -0
  59. package/dist/site-system/records.d.ts.map +1 -0
  60. package/dist/site-system/records.js +98 -0
  61. package/dist/site-system/records.js.map +1 -0
  62. package/dist/site-system/report.d.ts +116 -0
  63. package/dist/site-system/report.d.ts.map +1 -0
  64. package/dist/site-system/report.js +366 -0
  65. package/dist/site-system/report.js.map +1 -0
  66. package/dist/site-system/rule-modules.d.ts +8 -0
  67. package/dist/site-system/rule-modules.d.ts.map +1 -0
  68. package/dist/site-system/rule-modules.js +33 -0
  69. package/dist/site-system/rule-modules.js.map +1 -0
  70. package/dist/site-system/rules/design/d4-distinctness.d.ts +36 -0
  71. package/dist/site-system/rules/design/d4-distinctness.d.ts.map +1 -0
  72. package/dist/site-system/rules/design/d4-distinctness.js +207 -0
  73. package/dist/site-system/rules/design/d4-distinctness.js.map +1 -0
  74. package/dist/site-system/rules/design/index.d.ts +17 -0
  75. package/dist/site-system/rules/design/index.d.ts.map +1 -0
  76. package/dist/site-system/rules/design/index.js +7 -0
  77. package/dist/site-system/rules/design/index.js.map +1 -0
  78. package/dist/site-system/rules/enquiry/c2-quote-form.d.ts +31 -0
  79. package/dist/site-system/rules/enquiry/c2-quote-form.d.ts.map +1 -0
  80. package/dist/site-system/rules/enquiry/c2-quote-form.js +211 -0
  81. package/dist/site-system/rules/enquiry/c2-quote-form.js.map +1 -0
  82. package/dist/site-system/rules/enquiry/common.d.ts +37 -0
  83. package/dist/site-system/rules/enquiry/common.d.ts.map +1 -0
  84. package/dist/site-system/rules/enquiry/common.js +59 -0
  85. package/dist/site-system/rules/enquiry/common.js.map +1 -0
  86. package/dist/site-system/rules/enquiry/e1-recorded-titles.d.ts +8 -0
  87. package/dist/site-system/rules/enquiry/e1-recorded-titles.d.ts.map +1 -0
  88. package/dist/site-system/rules/enquiry/e1-recorded-titles.js +59 -0
  89. package/dist/site-system/rules/enquiry/e1-recorded-titles.js.map +1 -0
  90. package/dist/site-system/rules/enquiry/e9-cost-guides.d.ts +22 -0
  91. package/dist/site-system/rules/enquiry/e9-cost-guides.d.ts.map +1 -0
  92. package/dist/site-system/rules/enquiry/e9-cost-guides.js +55 -0
  93. package/dist/site-system/rules/enquiry/e9-cost-guides.js.map +1 -0
  94. package/dist/site-system/rules/enquiry/html.d.ts +53 -0
  95. package/dist/site-system/rules/enquiry/html.d.ts.map +1 -0
  96. package/dist/site-system/rules/enquiry/html.js +294 -0
  97. package/dist/site-system/rules/enquiry/html.js.map +1 -0
  98. package/dist/site-system/rules/enquiry/index.d.ts +17 -0
  99. package/dist/site-system/rules/enquiry/index.d.ts.map +1 -0
  100. package/dist/site-system/rules/enquiry/index.js +26 -0
  101. package/dist/site-system/rules/enquiry/index.js.map +1 -0
  102. package/dist/site-system/rules/enquiry/k8-sections.d.ts +17 -0
  103. package/dist/site-system/rules/enquiry/k8-sections.d.ts.map +1 -0
  104. package/dist/site-system/rules/enquiry/k8-sections.js +76 -0
  105. package/dist/site-system/rules/enquiry/k8-sections.js.map +1 -0
  106. package/dist/site-system/rules/enquiry/literal.d.ts +29 -0
  107. package/dist/site-system/rules/enquiry/literal.d.ts.map +1 -0
  108. package/dist/site-system/rules/enquiry/literal.js +189 -0
  109. package/dist/site-system/rules/enquiry/literal.js.map +1 -0
  110. package/dist/site-system/rules/enquiry/t11-licence-classes.d.ts +73 -0
  111. package/dist/site-system/rules/enquiry/t11-licence-classes.d.ts.map +1 -0
  112. package/dist/site-system/rules/enquiry/t11-licence-classes.js +275 -0
  113. package/dist/site-system/rules/enquiry/t11-licence-classes.js.map +1 -0
  114. package/dist/site-system/rules/enquiry/t5-reviews.d.ts +23 -0
  115. package/dist/site-system/rules/enquiry/t5-reviews.d.ts.map +1 -0
  116. package/dist/site-system/rules/enquiry/t5-reviews.js +91 -0
  117. package/dist/site-system/rules/enquiry/t5-reviews.js.map +1 -0
  118. package/dist/site-system/rules/form/c11-contact-route.d.ts +5 -0
  119. package/dist/site-system/rules/form/c11-contact-route.d.ts.map +1 -0
  120. package/dist/site-system/rules/form/c11-contact-route.js +132 -0
  121. package/dist/site-system/rules/form/c11-contact-route.js.map +1 -0
  122. package/dist/site-system/rules/form/c7-thank-you.d.ts +20 -0
  123. package/dist/site-system/rules/form/c7-thank-you.d.ts.map +1 -0
  124. package/dist/site-system/rules/form/c7-thank-you.js +67 -0
  125. package/dist/site-system/rules/form/c7-thank-you.js.map +1 -0
  126. package/dist/site-system/rules/form/c8-forgiving-form.d.ts +19 -0
  127. package/dist/site-system/rules/form/c8-forgiving-form.d.ts.map +1 -0
  128. package/dist/site-system/rules/form/c8-forgiving-form.js +128 -0
  129. package/dist/site-system/rules/form/c8-forgiving-form.js.map +1 -0
  130. package/dist/site-system/rules/form/index.d.ts +14 -0
  131. package/dist/site-system/rules/form/index.d.ts.map +1 -0
  132. package/dist/site-system/rules/form/index.js +13 -0
  133. package/dist/site-system/rules/form/index.js.map +1 -0
  134. package/dist/site-system/rules/library/common.d.ts +36 -0
  135. package/dist/site-system/rules/library/common.d.ts.map +1 -0
  136. package/dist/site-system/rules/library/common.js +70 -0
  137. package/dist/site-system/rules/library/common.js.map +1 -0
  138. package/dist/site-system/rules/library/index.d.ts +16 -0
  139. package/dist/site-system/rules/library/index.d.ts.map +1 -0
  140. package/dist/site-system/rules/library/index.js +19 -0
  141. package/dist/site-system/rules/library/index.js.map +1 -0
  142. package/dist/site-system/rules/library/k1-shared-components.d.ts +29 -0
  143. package/dist/site-system/rules/library/k1-shared-components.d.ts.map +1 -0
  144. package/dist/site-system/rules/library/k1-shared-components.js +97 -0
  145. package/dist/site-system/rules/library/k1-shared-components.js.map +1 -0
  146. package/dist/site-system/rules/navigation/extract.d.ts +50 -0
  147. package/dist/site-system/rules/navigation/extract.d.ts.map +1 -0
  148. package/dist/site-system/rules/navigation/extract.js +220 -0
  149. package/dist/site-system/rules/navigation/extract.js.map +1 -0
  150. package/dist/site-system/rules/navigation/html.d.ts +23 -0
  151. package/dist/site-system/rules/navigation/html.d.ts.map +1 -0
  152. package/dist/site-system/rules/navigation/html.js +219 -0
  153. package/dist/site-system/rules/navigation/html.js.map +1 -0
  154. package/dist/site-system/rules/navigation/index.d.ts +37 -0
  155. package/dist/site-system/rules/navigation/index.d.ts.map +1 -0
  156. package/dist/site-system/rules/navigation/index.js +163 -0
  157. package/dist/site-system/rules/navigation/index.js.map +1 -0
  158. package/dist/site-system/rules/navigation/record.d.ts +8 -0
  159. package/dist/site-system/rules/navigation/record.d.ts.map +1 -0
  160. package/dist/site-system/rules/navigation/record.js +39 -0
  161. package/dist/site-system/rules/navigation/record.js.map +1 -0
  162. package/dist/site-system/rules/sections/exif.d.ts +22 -0
  163. package/dist/site-system/rules/sections/exif.d.ts.map +1 -0
  164. package/dist/site-system/rules/sections/exif.js +218 -0
  165. package/dist/site-system/rules/sections/exif.js.map +1 -0
  166. package/dist/site-system/rules/sections/images.d.ts +31 -0
  167. package/dist/site-system/rules/sections/images.d.ts.map +1 -0
  168. package/dist/site-system/rules/sections/images.js +96 -0
  169. package/dist/site-system/rules/sections/images.js.map +1 -0
  170. package/dist/site-system/rules/sections/index.d.ts +20 -0
  171. package/dist/site-system/rules/sections/index.d.ts.map +1 -0
  172. package/dist/site-system/rules/sections/index.js +34 -0
  173. package/dist/site-system/rules/sections/index.js.map +1 -0
  174. package/dist/site-system/rules/sections/k11-density.d.ts +23 -0
  175. package/dist/site-system/rules/sections/k11-density.d.ts.map +1 -0
  176. package/dist/site-system/rules/sections/k11-density.js +88 -0
  177. package/dist/site-system/rules/sections/k11-density.js.map +1 -0
  178. package/dist/site-system/rules/sections/k12-contact.d.ts +13 -0
  179. package/dist/site-system/rules/sections/k12-contact.d.ts.map +1 -0
  180. package/dist/site-system/rules/sections/k12-contact.js +70 -0
  181. package/dist/site-system/rules/sections/k12-contact.js.map +1 -0
  182. package/dist/site-system/rules/sections/k2-skin-coverage.d.ts +11 -0
  183. package/dist/site-system/rules/sections/k2-skin-coverage.d.ts.map +1 -0
  184. package/dist/site-system/rules/sections/k2-skin-coverage.js +126 -0
  185. package/dist/site-system/rules/sections/k2-skin-coverage.js.map +1 -0
  186. package/dist/site-system/rules/sections/n9-footer.d.ts +40 -0
  187. package/dist/site-system/rules/sections/n9-footer.d.ts.map +1 -0
  188. package/dist/site-system/rules/sections/n9-footer.js +186 -0
  189. package/dist/site-system/rules/sections/n9-footer.js.map +1 -0
  190. package/dist/site-system/rules/sections/t6-media-entries.d.ts +14 -0
  191. package/dist/site-system/rules/sections/t6-media-entries.d.ts.map +1 -0
  192. package/dist/site-system/rules/sections/t6-media-entries.js +96 -0
  193. package/dist/site-system/rules/sections/t6-media-entries.js.map +1 -0
  194. package/dist/site-system/rules/sections/t7-main-images.d.ts +13 -0
  195. package/dist/site-system/rules/sections/t7-main-images.d.ts.map +1 -0
  196. package/dist/site-system/rules/sections/t7-main-images.js +41 -0
  197. package/dist/site-system/rules/sections/t7-main-images.js.map +1 -0
  198. package/dist/site-system/rules/sections/t8-prices.d.ts +13 -0
  199. package/dist/site-system/rules/sections/t8-prices.d.ts.map +1 -0
  200. package/dist/site-system/rules/sections/t8-prices.js +92 -0
  201. package/dist/site-system/rules/sections/t8-prices.js.map +1 -0
  202. package/dist/site-system/rules/sections/t9-location-metadata.d.ts +3 -0
  203. package/dist/site-system/rules/sections/t9-location-metadata.d.ts.map +1 -0
  204. package/dist/site-system/rules/sections/t9-location-metadata.js +54 -0
  205. package/dist/site-system/rules/sections/t9-location-metadata.js.map +1 -0
  206. package/dist/site-system/rules/shell/banned-wording.d.ts +24 -0
  207. package/dist/site-system/rules/shell/banned-wording.d.ts.map +1 -0
  208. package/dist/site-system/rules/shell/banned-wording.js +212 -0
  209. package/dist/site-system/rules/shell/banned-wording.js.map +1 -0
  210. package/dist/site-system/rules/shell/budgets.d.ts +43 -0
  211. package/dist/site-system/rules/shell/budgets.d.ts.map +1 -0
  212. package/dist/site-system/rules/shell/budgets.js +297 -0
  213. package/dist/site-system/rules/shell/budgets.js.map +1 -0
  214. package/dist/site-system/rules/shell/cta-labels.d.ts +17 -0
  215. package/dist/site-system/rules/shell/cta-labels.d.ts.map +1 -0
  216. package/dist/site-system/rules/shell/cta-labels.js +86 -0
  217. package/dist/site-system/rules/shell/cta-labels.js.map +1 -0
  218. package/dist/site-system/rules/shell/facts.d.ts +33 -0
  219. package/dist/site-system/rules/shell/facts.d.ts.map +1 -0
  220. package/dist/site-system/rules/shell/facts.js +217 -0
  221. package/dist/site-system/rules/shell/facts.js.map +1 -0
  222. package/dist/site-system/rules/shell/headings.d.ts +5 -0
  223. package/dist/site-system/rules/shell/headings.d.ts.map +1 -0
  224. package/dist/site-system/rules/shell/headings.js +23 -0
  225. package/dist/site-system/rules/shell/headings.js.map +1 -0
  226. package/dist/site-system/rules/shell/hero-location.d.ts +4 -0
  227. package/dist/site-system/rules/shell/hero-location.d.ts.map +1 -0
  228. package/dist/site-system/rules/shell/hero-location.js +45 -0
  229. package/dist/site-system/rules/shell/hero-location.js.map +1 -0
  230. package/dist/site-system/rules/shell/html.d.ts +53 -0
  231. package/dist/site-system/rules/shell/html.d.ts.map +1 -0
  232. package/dist/site-system/rules/shell/html.js +341 -0
  233. package/dist/site-system/rules/shell/html.js.map +1 -0
  234. package/dist/site-system/rules/shell/index.d.ts +18 -0
  235. package/dist/site-system/rules/shell/index.d.ts.map +1 -0
  236. package/dist/site-system/rules/shell/index.js +65 -0
  237. package/dist/site-system/rules/shell/index.js.map +1 -0
  238. package/dist/site-system/rules/shell/licence.d.ts +4 -0
  239. package/dist/site-system/rules/shell/licence.d.ts.map +1 -0
  240. package/dist/site-system/rules/shell/licence.js +60 -0
  241. package/dist/site-system/rules/shell/licence.js.map +1 -0
  242. package/dist/site-system/rules/shell/location-wording.d.ts +13 -0
  243. package/dist/site-system/rules/shell/location-wording.d.ts.map +1 -0
  244. package/dist/site-system/rules/shell/location-wording.js +15 -0
  245. package/dist/site-system/rules/shell/location-wording.js.map +1 -0
  246. package/dist/site-system/rules/shell/navigation.d.ts +5 -0
  247. package/dist/site-system/rules/shell/navigation.d.ts.map +1 -0
  248. package/dist/site-system/rules/shell/navigation.js +272 -0
  249. package/dist/site-system/rules/shell/navigation.js.map +1 -0
  250. package/dist/site-system/rules/shell/overlays.d.ts +4 -0
  251. package/dist/site-system/rules/shell/overlays.d.ts.map +1 -0
  252. package/dist/site-system/rules/shell/overlays.js +93 -0
  253. package/dist/site-system/rules/shell/overlays.js.map +1 -0
  254. package/dist/site-system/rules/shell/page.d.ts +51 -0
  255. package/dist/site-system/rules/shell/page.d.ts.map +1 -0
  256. package/dist/site-system/rules/shell/page.js +256 -0
  257. package/dist/site-system/rules/shell/page.js.map +1 -0
  258. package/dist/site-system/rules/shell/phone.d.ts +13 -0
  259. package/dist/site-system/rules/shell/phone.d.ts.map +1 -0
  260. package/dist/site-system/rules/shell/phone.js +99 -0
  261. package/dist/site-system/rules/shell/phone.js.map +1 -0
  262. package/dist/site-system/rules/shell/proof-numbers.d.ts +7 -0
  263. package/dist/site-system/rules/shell/proof-numbers.d.ts.map +1 -0
  264. package/dist/site-system/rules/shell/proof-numbers.js +81 -0
  265. package/dist/site-system/rules/shell/proof-numbers.js.map +1 -0
  266. package/dist/site-system/rules/shell/types.d.ts +80 -0
  267. package/dist/site-system/rules/shell/types.d.ts.map +1 -0
  268. package/dist/site-system/rules/shell/types.js +17 -0
  269. package/dist/site-system/rules/shell/types.js.map +1 -0
  270. package/dist/site-system/runners.d.ts +37 -0
  271. package/dist/site-system/runners.d.ts.map +1 -0
  272. package/dist/site-system/runners.js +48 -0
  273. package/dist/site-system/runners.js.map +1 -0
  274. package/dist/site-system/samples.d.ts +43 -0
  275. package/dist/site-system/samples.d.ts.map +1 -0
  276. package/dist/site-system/samples.js +99 -0
  277. package/dist/site-system/samples.js.map +1 -0
  278. package/dist/site-system/steps.d.ts +66 -0
  279. package/dist/site-system/steps.d.ts.map +1 -0
  280. package/dist/site-system/steps.js +45 -0
  281. package/dist/site-system/steps.js.map +1 -0
  282. package/dist/site-system/streamed-html.d.ts +9 -0
  283. package/dist/site-system/streamed-html.d.ts.map +1 -0
  284. package/dist/site-system/streamed-html.js +43 -0
  285. package/dist/site-system/streamed-html.js.map +1 -0
  286. package/dist/site-system-e2e.d.ts +6 -0
  287. package/dist/site-system-e2e.d.ts.map +1 -0
  288. package/dist/site-system-e2e.js +54 -0
  289. package/dist/site-system-e2e.js.map +1 -0
  290. package/package.json +22 -5
@@ -0,0 +1 @@
1
+ {"version":3,"file":"budgets.js","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/budgets.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve, sep } from \"node:path\";\nimport { gzipSync } from \"node:zlib\";\nimport { descendants, findAll, isWithin, type HtmlElement } from \"./html.js\";\nimport type { ShellPage } from \"./page.js\";\nimport {\n failFinding, passFinding, warningFinding, type ShellAssetReader, type ShellFinding,\n} from \"./types.js\";\n\n/**\n * B1: lab budgets read from the built page HTML (spec 5.8): JavaScript, third-party requests, the hero image and the\n * HTML itself, with no chat widget, pixel, heatmap or tag manager unless the project records the decision. Script and\n * image sizes come from the project's build output, then the base URL; a size that can't be read is left out of the\n * total and said so, never guessed.\n */\n\n/** First-party JavaScript per page, gzip: a warning from here. Starting value, calibrated in the R1 canaries. */\nexport const B1_FIRST_PARTY_JS_GZIP_WARN_BYTES = 180 * 1024;\n/** First-party JavaScript per page, gzip: the spec's starting budget; over it blocks. */\nexport const B1_FIRST_PARTY_JS_GZIP_FAIL_BYTES = 250 * 1024;\nexport const B1_HTML_MAX_BYTES = 500 * 1024;\nexport const B1_HERO_IMAGE_MAX_BYTES = 200 * 1024;\n/** The viewport width the hero image is judged at. */\nexport const B1_HERO_VIEWPORT_PX = 1440;\n\ntype ThirdPartyKind = \"chat\" | \"pixel\" | \"heatmap\" | \"tag-manager\";\n\ninterface Category {\n kind: ThirdPartyKind;\n label: string;\n hosts: readonly string[];\n}\n\nconst CATEGORIES: readonly Category[] = [\n { kind: \"chat\", label: \"a chat widget\", hosts: [\n \"widget.intercom.io\", \"js.intercomcdn.com\", \"intercom.io\", \"tawk.to\", \"crisp.chat\", \"livechatinc.com\", \"tidio.co\",\n \"driftt.com\", \"drift.com\", \"usemessages.com\", \"zdassets.com\", \"freshchat.com\", \"olark.com\", \"smartsuppchat.com\",\n \"chatra.io\", \"jivosite.com\", \"manychat.com\", \"chatwoot.com\",\n ] },\n { kind: \"pixel\", label: \"an advertising pixel\", hosts: [\n \"connect.facebook.net\", \"snap.licdn.com\", \"px.ads.linkedin.com\", \"static.ads-twitter.com\", \"analytics.twitter.com\",\n \"analytics.tiktok.com\", \"s.pinimg.com\", \"bat.bing.com\", \"sc-static.net\", \"tr.snapchat.com\", \"adroll.com\",\n \"criteo.net\", \"outbrain.com\", \"taboola.com\",\n ] },\n { kind: \"heatmap\", label: \"a heatmap or session recorder\", hosts: [\n \"hotjar.com\", \"hotjar.io\", \"clarity.ms\", \"mouseflow.com\", \"crazyegg.com\", \"fullstory.com\", \"logrocket.com\",\n \"lr-ingest.io\", \"luckyorange.com\", \"luckyorange.net\", \"smartlook.com\", \"inspectlet.com\", \"heapanalytics.com\",\n ] },\n { kind: \"tag-manager\", label: \"a tag manager\", hosts: [\n \"tags.tiqcdn.com\", \"tiqcdn.com\", \"cdn.segment.com\", \"assets.adobedtm.com\", \"cdn.tealium.com\",\n ] },\n];\n\n/** Hosts the shared analytics, Ads, maps and form-protection setup uses (see the Content-Security-Policy in site-next). */\nconst ALLOWED_HOSTS: readonly string[] = [\n \"google-analytics.com\", \"analytics.google.com\", \"googleadservices.com\", \"doubleclick.net\", \"googlesyndication.com\",\n \"maps.googleapis.com\", \"maps.gstatic.com\", \"gstatic.com\", \"challenges.cloudflare.com\",\n];\n\nconst INLINE_SIGNATURES: readonly { kind: ThirdPartyKind; label: string; regex: RegExp }[] = [\n { kind: \"pixel\", label: \"a Meta pixel\", regex: /\\bfbq\\s*\\(/ },\n { kind: \"tag-manager\", label: \"a tag manager\", regex: /gtm\\.start|googletagmanager\\.com\\/gtm\\.js/ },\n { kind: \"heatmap\", label: \"a heatmap or session recorder\", regex: /\\bhj\\s*\\(\\s*['\"]|static\\.hotjar\\.com|clarity\\.ms|\\bclarity\\s*\\(/ },\n { kind: \"chat\", label: \"a chat widget\", regex: /\\bTawk_API\\b|\\$crisp\\b|window\\.Intercom|\\bIntercom\\s*\\(|drift\\.load|LiveChatWidget|tidioChatApi|\\bzE\\s*\\(|\\bfcWidget\\b/ },\n];\n\nfunction hostMatches(host: string, domain: string): boolean {\n return host === domain || host.endsWith(`.${domain}`);\n}\n\ninterface Verdict {\n kind: ThirdPartyKind | \"other\";\n label: string;\n}\n\n/** What a third-party URL is, or null when the shared setup allows it. */\nexport function judgeThirdParty(url: URL): Verdict | null {\n const host = url.hostname.toLowerCase();\n if (hostMatches(host, \"googletagmanager.com\")) {\n return /^\\/gtag\\/js/.test(url.pathname) ? null : { kind: \"tag-manager\", label: \"a tag manager\" };\n }\n if (host === \"www.google.com\" || host === \"google.com\") {\n return /^\\/(?:maps|recaptcha)\\//.test(url.pathname) ? null : { kind: \"other\", label: \"a third-party request\" };\n }\n for (const category of CATEGORIES) {\n if (category.hosts.some((domain) => hostMatches(host, domain))) return { kind: category.kind, label: category.label };\n }\n if (ALLOWED_HOSTS.some((domain) => hostMatches(host, domain))) return null;\n return { kind: \"other\", label: \"a third-party request\" };\n}\n\nfunction isRecorded(recorded: readonly string[], url: URL, verdict: Verdict): boolean {\n const host = url.hostname.toLowerCase();\n return recorded.some((entry) => {\n const value = entry.trim().toLowerCase();\n return value !== \"\" && (value === verdict.kind || hostMatches(host, value));\n });\n}\n\nfunction sameSite(host: string, baseHost: string): boolean {\n const strip = (value: string) => value.replace(/^www\\./, \"\");\n return strip(host.toLowerCase()) === strip(baseHost.toLowerCase());\n}\n\n/**\n * The origin standing in for the site when there is no base URL (build-output mode): relative asset URLs resolve to it,\n * so they count as first-party and are read from the project's build output alone.\n */\nexport const BUILD_OUTPUT_ORIGIN = \"https://build-output.invalid\";\n\nfunction parseBase(baseUrl: string): URL | null {\n try {\n return new URL(baseUrl);\n } catch {\n return null;\n }\n}\n\n/**\n * The default reader: the project's build output for its own paths (`.next/static`, `public`, `out`), then the base\n * URL when there is one. Never reads other hosts.\n */\nexport const readBuiltAsset: ShellAssetReader = async (url, { projectRoot, baseUrl }) => {\n const given = parseBase(baseUrl);\n const base = given ?? new URL(BUILD_OUTPUT_ORIGIN);\n if (!sameSite(url.hostname, base.hostname)) return undefined;\n let pathname = url.pathname;\n try {\n pathname = decodeURIComponent(pathname);\n } catch {\n // Keep the encoded path.\n }\n const root = resolve(projectRoot);\n const candidates = pathname.startsWith(\"/_next/static/\")\n ? [join(root, \".next\", pathname.slice(\"/_next/\".length))]\n : pathname.startsWith(\"/_next/\") ? [] : [join(root, \"public\", pathname), join(root, \"out\", pathname)];\n for (const candidate of candidates) {\n const resolved = resolve(candidate);\n if (resolved.startsWith(root + sep) && existsSync(resolved)) {\n try {\n return new Uint8Array(readFileSync(resolved));\n } catch {\n // Fall through to the network.\n }\n }\n }\n if (!given || !/^https?:$/.test(url.protocol)) return undefined;\n try {\n const response = await fetch(url, { signal: AbortSignal.timeout(15_000) });\n return response.ok ? new Uint8Array(await response.arrayBuffer()) : undefined;\n } catch {\n return undefined;\n }\n};\n\ninterface Resource {\n url: URL;\n kind: \"script\" | \"other\";\n}\n\nfunction resolveUrl(value: string | undefined, page: URL): URL | null {\n if (!value || /^\\s*(?:data|blob|about|javascript|mailto|tel):/i.test(value)) return null;\n try {\n return new URL(value.trim(), page);\n } catch {\n return null;\n }\n}\n\n/** The scripts, frames and stylesheets a page requests. */\nfunction pageResources(page: ShellPage, pageUrl: URL): Resource[] {\n const resources: Resource[] = [];\n for (const element of descendants(page.doc.root)) {\n const { tag, attrs } = element;\n if (tag === \"script\" && attrs.src) {\n if (attrs.nomodule !== undefined) continue;\n const url = resolveUrl(attrs.src, pageUrl);\n if (url) resources.push({ url, kind: \"script\" });\n } else if (tag === \"link\") {\n const rel = (attrs.rel ?? \"\").toLowerCase().split(/\\s+/);\n const wantsScript = rel.includes(\"modulepreload\") || (rel.includes(\"preload\") && attrs.as === \"script\");\n const wantsOther = rel.includes(\"stylesheet\") || (rel.includes(\"preload\") && attrs.as !== \"script\" && attrs.as !== undefined && attrs.as !== \"image\");\n if (wantsScript || wantsOther) {\n const url = resolveUrl(attrs.href, pageUrl);\n if (url) resources.push({ url, kind: wantsScript ? \"script\" : \"other\" });\n }\n } else if (tag === \"iframe\" || tag === \"embed\" || tag === \"object\") {\n const url = resolveUrl(attrs.src ?? attrs.data, pageUrl);\n if (url) resources.push({ url, kind: \"other\" });\n }\n }\n return resources;\n}\n\nfunction inlineScripts(page: ShellPage): string[] {\n return findAll(page.doc.root, (element) =>\n element.tag === \"script\" && !element.attrs.src && !/(?:json|importmap|speculationrules)/i.test(element.attrs.type ?? \"\"))\n .map((script) => script.children.map((child) => (child.kind === \"text\" ? child.text : \"\")).join(\"\"))\n // The framework's streamed page data holds the page's words, not code that runs a widget.\n .filter((text) => !text.includes(\"__next_f\"));\n}\n\nfunction srcsetCandidates(srcset: string): { url: string; width: number }[] {\n return srcset.split(\",\").map((part) => part.trim().split(/\\s+/)).filter((parts) => parts[0])\n .map(([url, descriptor]) => ({ url: url!, width: descriptor?.endsWith(\"w\") ? Number.parseInt(descriptor, 10) : descriptor?.endsWith(\"x\") ? Number.parseFloat(descriptor) * 1000 : 0 }));\n}\n\n/** The image the hero shows on a wide screen: the smallest candidate at least 1440 px wide, else the widest. */\nfunction heroImage(page: ShellPage): { element: HtmlElement; source: string | undefined } | null {\n const hero = page.hero;\n if (!hero) return null;\n const images = findAll(hero, (element) => element.tag === \"img\" && !page.headers.some((header) => isWithin(element, header)) && element.attrs[\"data-bl\"] !== \"logo\");\n if (images.length === 0) return null;\n const width = (image: HtmlElement) => Number.parseInt(image.attrs.width ?? \"0\", 10) || 0;\n const element = images.find((image) => image.attrs.fetchpriority === \"high\") ?? images.reduce((best, image) => (width(image) > width(best) ? image : best), images[0]!);\n const candidates = srcsetCandidates(element.attrs.srcset ?? \"\");\n const wide = candidates.filter((candidate) => candidate.width >= B1_HERO_VIEWPORT_PX).sort((left, right) => left.width - right.width)[0]\n ?? candidates.sort((left, right) => right.width - left.width)[0];\n return { element, source: wide?.url ?? element.attrs.src };\n}\n\nfunction gzipBytes(bytes: Uint8Array): number {\n return gzipSync(Buffer.from(bytes)).length;\n}\n\nfunction kilobytes(bytes: number): string {\n return `${Math.round(bytes / 1024)} KB`;\n}\n\nexport interface BudgetOptions {\n baseUrl: string;\n projectRoot: string;\n recordedThirdParties?: readonly string[];\n readAsset?: ShellAssetReader;\n}\n\n/** B1 over the sampled pages. */\nexport async function checkLabBudgets(pages: readonly ShellPage[], options: BudgetOptions): Promise<ShellFinding[]> {\n const read = options.readAsset ?? readBuiltAsset;\n const recorded = options.recordedThirdParties ?? [];\n const cache = new Map<string, Promise<Uint8Array | undefined>>();\n const readCached = (url: URL) => {\n const key = url.href;\n if (!cache.has(key)) cache.set(key, read(url, { projectRoot: options.projectRoot, baseUrl: options.baseUrl }).catch(() => undefined));\n return cache.get(key)!;\n };\n const base = parseBase(options.baseUrl) ?? new URL(BUILD_OUTPUT_ORIGIN);\n const failures: ShellFinding[] = [];\n const warnings: ShellFinding[] = [];\n const hostsSeen = new Set<string>();\n let largestJs = 0;\n let jsPages = 0;\n\n for (const page of pages) {\n const pageUrl = new URL(page.path, base);\n const htmlBytes = Buffer.byteLength(page.html);\n if (htmlBytes > B1_HTML_MAX_BYTES) {\n failures.push(failFinding(\"B1\", page.path, `The page ${page.path} is ${kilobytes(htmlBytes)} of HTML, over the ${kilobytes(B1_HTML_MAX_BYTES)} budget.`));\n }\n\n const resources = pageResources(page, pageUrl);\n const flagged = new Set<string>();\n for (const resource of resources) {\n if (sameSite(resource.url.hostname, base.hostname)) continue;\n hostsSeen.add(resource.url.hostname);\n const verdict = judgeThirdParty(resource.url);\n if (!verdict || isRecorded(recorded, resource.url, verdict)) continue;\n const key = `${resource.url.hostname}\\u0000${verdict.kind}`;\n if (flagged.has(key)) continue;\n flagged.add(key);\n failures.push(failFinding(\"B1\", page.path, verdict.kind === \"other\"\n ? `The page ${page.path} requests ${resource.url.hostname}, a third party outside analytics, maps and the reviews feed, with no recorded decision.`\n : `The page ${page.path} loads ${verdict.label} from ${resource.url.hostname} with no recorded decision.`));\n }\n for (const script of inlineScripts(page)) {\n for (const signature of INLINE_SIGNATURES) {\n const key = `inline\\u0000${signature.kind}`;\n if (flagged.has(key) || !signature.regex.test(script)) continue;\n if (recorded.some((entry) => entry.trim().toLowerCase() === signature.kind)) continue;\n flagged.add(key);\n failures.push(failFinding(\"B1\", page.path, `The page ${page.path} runs ${signature.label} from an inline script with no recorded decision.`));\n }\n }\n\n const scripts = [...new Map(resources.filter((resource) => resource.kind === \"script\" && sameSite(resource.url.hostname, base.hostname)).map((resource) => [resource.url.href, resource.url])).values()];\n if (scripts.length > 0) {\n const sizes = await Promise.all(scripts.map(readCached));\n const measured = sizes.filter((bytes): bytes is Uint8Array => bytes !== undefined);\n const total = measured.reduce((sum, bytes) => sum + gzipBytes(bytes), 0);\n if (measured.length > 0) {\n jsPages += 1;\n largestJs = Math.max(largestJs, total);\n const unread = scripts.length - measured.length;\n const note = unread > 0 ? ` (${unread} of ${scripts.length} script file${scripts.length === 1 ? \"\" : \"s\"} could not be read)` : \"\";\n if (total > B1_FIRST_PARTY_JS_GZIP_FAIL_BYTES) {\n failures.push(failFinding(\"B1\", page.path, `The page ${page.path} loads ${kilobytes(total)} of first-party JavaScript (gzip)${note}, over the ${kilobytes(B1_FIRST_PARTY_JS_GZIP_FAIL_BYTES)} budget.`));\n } else if (total > B1_FIRST_PARTY_JS_GZIP_WARN_BYTES) {\n warnings.push(warningFinding(\"B1\", `Warning: the page ${page.path} loads ${kilobytes(total)} of first-party JavaScript (gzip)${note}, over the ${kilobytes(B1_FIRST_PARTY_JS_GZIP_WARN_BYTES)} warning threshold.`, page.path));\n }\n }\n }\n\n const hero = heroImage(page);\n if (hero) {\n const { element, source } = hero;\n if (element.attrs.loading === \"lazy\" || element.attrs.fetchpriority === \"low\" || /\\blazy(?:load|loaded)?\\b/.test(element.attrs.class ?? \"\")) {\n failures.push(failFinding(\"B1\", page.path, `The hero image on ${page.path} is set to load lazily; the first-screen image must load straight away.`));\n }\n const url = resolveUrl(source, pageUrl);\n if (url && sameSite(url.hostname, base.hostname)) {\n const bytes = await readCached(url);\n if (bytes && bytes.length > B1_HERO_IMAGE_MAX_BYTES) {\n failures.push(failFinding(\"B1\", page.path, `The hero image on ${page.path} is ${kilobytes(bytes.length)}, over the ${kilobytes(B1_HERO_IMAGE_MAX_BYTES)} budget at ${B1_HERO_VIEWPORT_PX} px.`));\n }\n }\n }\n }\n if (failures.length > 0) return [...failures, ...warnings];\n const jsNote = jsPages > 0 ? ` The heaviest page loads ${kilobytes(largestJs)} of first-party JavaScript (gzip).` : \" Script sizes could not be read from the build output or the base URL, so the JavaScript budget was not measured.\";\n const hostNote = hostsSeen.size > 0 ? ` Third-party hosts seen: ${[...hostsSeen].sort().join(\", \")}.` : \" No third-party requests.\";\n const within = pages.length === 1 ? \"The sampled page is\" : `The ${pages.length} sampled pages are`;\n return [passFinding(\"B1\", `${within} within the HTML, hero image and third-party budgets.${jsNote}${hostNote}`), ...warnings];\n}\n"]}
@@ -0,0 +1,17 @@
1
+ import type { ShellPage } from "./page.js";
2
+ import { type ShellFinding } from "./types.js";
3
+ /**
4
+ * C5: call-to-action labels come from the closed list or a label the project records. The quote label is "Get a free
5
+ * quote" when free quotes are offered and "Get a quote" otherwise (Romie's rule from six sites); a site can record a
6
+ * different label where Romie set one. Every quote button (`data-bl="quote-button"`) and every link or button that
7
+ * reads like a quote call to action is held to the list. A form's own submit button is the form's label (C2).
8
+ */
9
+ export declare const QUOTE_LABEL_FREE = "Get a free quote";
10
+ export declare const QUOTE_LABEL_PLAIN = "Get a quote";
11
+ /** Whether a link or button label reads like a quote call to action. */
12
+ export declare function isQuoteLike(label: string): boolean;
13
+ export declare function checkCtaLabels(pages: readonly ShellPage[], options?: {
14
+ labels?: readonly string[];
15
+ freeQuotes?: boolean;
16
+ }): ShellFinding[];
17
+ //# sourceMappingURL=cta-labels.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cta-labels.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/cta-labels.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAAkD,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/F;;;;;GAKG;AAEH,eAAO,MAAM,gBAAgB,qBAAqB,CAAC;AACnD,eAAO,MAAM,iBAAiB,gBAAgB,CAAC;AAc/C,wEAAwE;AACxE,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAGlD;AA2BD,wBAAgB,cAAc,CAC5B,KAAK,EAAE,SAAS,SAAS,EAAE,EAC3B,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAAC,UAAU,CAAC,EAAE,OAAO,CAAA;CAAO,GACjE,YAAY,EAAE,CAiChB"}
@@ -0,0 +1,86 @@
1
+ import { closest, findAll, textOf } from "./html.js";
2
+ import { failFinding, notApplicableFinding, passFinding } from "./types.js";
3
+ /**
4
+ * C5: call-to-action labels come from the closed list or a label the project records. The quote label is "Get a free
5
+ * quote" when free quotes are offered and "Get a quote" otherwise (Romie's rule from six sites); a site can record a
6
+ * different label where Romie set one. Every quote button (`data-bl="quote-button"`) and every link or button that
7
+ * reads like a quote call to action is held to the list. A form's own submit button is the form's label (C2).
8
+ */
9
+ export const QUOTE_LABEL_FREE = "Get a free quote";
10
+ export const QUOTE_LABEL_PLAIN = "Get a quote";
11
+ const QUOTE_WORD = /\b(?:quotes?|quotation|estimates?|enquir(?:e|y|ies)|inquir(?:e|y|ies))\b/i;
12
+ const CTA_START = /^(?:get|request|book|send|ask|claim|start|receive|arrange|schedule|obtain|enquire|inquire|free|your|online|instant|fast|quick|easy)\b/i;
13
+ const MAX_WORDS = 6;
14
+ function cleanLabel(label) {
15
+ return label.replace(/[\s ]+/g, " ").replace(/\s*[→›»>]+$/, "").replace(/[.!]+$/, "").trim();
16
+ }
17
+ function isButtonLike(element) {
18
+ return element.tag === "a" || element.tag === "button" || element.attrs.role === "button";
19
+ }
20
+ /** Whether a link or button label reads like a quote call to action. */
21
+ export function isQuoteLike(label) {
22
+ if (label === "" || label.split(" ").length > MAX_WORDS || !QUOTE_WORD.test(label))
23
+ return false;
24
+ return CTA_START.test(label) || /^(?:quotes?|estimates?)$/i.test(label);
25
+ }
26
+ /** A form's own button: type="submit", or a button with no type inside a form (which submits by default). */
27
+ function isSubmitButton(element) {
28
+ if (element.attrs.type === "submit")
29
+ return true;
30
+ return element.tag === "button" && element.attrs.type === undefined && closest(element, (ancestor) => ancestor.tag === "form") !== null;
31
+ }
32
+ function collectQuoteCtas(pages) {
33
+ const ctas = [];
34
+ for (const page of pages) {
35
+ for (const element of findAll(page.body, isButtonLike)) {
36
+ if (/^\s*tel:/i.test(element.attrs.href ?? "") || element.attrs["data-bl"] === "phone-link")
37
+ continue;
38
+ const label = cleanLabel(textOf(element));
39
+ const declared = element.attrs["data-bl"] === "quote-button";
40
+ if (!declared && (isSubmitButton(element) || !isQuoteLike(label)))
41
+ continue;
42
+ if (label !== "")
43
+ ctas.push({ path: page.path, label });
44
+ }
45
+ }
46
+ return ctas;
47
+ }
48
+ export function checkCtaLabels(pages, options = {}) {
49
+ const ctas = collectQuoteCtas(pages);
50
+ if (ctas.length === 0)
51
+ return [notApplicableFinding("C5", "The sampled pages have no quote button or quote call to action to check.")];
52
+ const recorded = new Set((options.labels ?? []).map((label) => label.trim().toLowerCase()));
53
+ const closed = options.freeQuotes === true
54
+ ? [QUOTE_LABEL_FREE]
55
+ : options.freeQuotes === false
56
+ ? [QUOTE_LABEL_PLAIN]
57
+ : [QUOTE_LABEL_FREE, QUOTE_LABEL_PLAIN];
58
+ const allowed = new Set([...closed.map((label) => label.toLowerCase()), ...recorded]);
59
+ const findings = [];
60
+ const reported = new Set();
61
+ for (const cta of ctas) {
62
+ const key = `${cta.path}\u0000${cta.label.toLowerCase()}`;
63
+ if (allowed.has(cta.label.toLowerCase()) || reported.has(key))
64
+ continue;
65
+ reported.add(key);
66
+ const expected = options.freeQuotes === true ? `"${QUOTE_LABEL_FREE}"` : options.freeQuotes === false ? `"${QUOTE_LABEL_PLAIN}"` : `"${QUOTE_LABEL_FREE}" or "${QUOTE_LABEL_PLAIN}"`;
67
+ findings.push(failFinding("C5", cta.path, `The quote call to action "${cta.label}" on ${cta.path} is not ${expected}, and the project does not record it as a label.`));
68
+ }
69
+ if (findings.length === 0 && options.freeQuotes === undefined) {
70
+ const used = new Map();
71
+ for (const cta of ctas) {
72
+ const label = cta.label.toLowerCase();
73
+ if ((label === QUOTE_LABEL_FREE.toLowerCase() || label === QUOTE_LABEL_PLAIN.toLowerCase()) && !used.has(label))
74
+ used.set(label, cta.path);
75
+ }
76
+ if (used.size > 1) {
77
+ const free = used.get(QUOTE_LABEL_FREE.toLowerCase());
78
+ const plain = used.get(QUOTE_LABEL_PLAIN.toLowerCase());
79
+ findings.push(failFinding("C5", plain, `The site uses both "${QUOTE_LABEL_FREE}" (on ${free}) and "${QUOTE_LABEL_PLAIN}" (on ${plain}); it needs one of them throughout.`));
80
+ }
81
+ }
82
+ if (findings.length > 0)
83
+ return findings;
84
+ return [passFinding("C5", `${ctas.length === 1 ? "The one quote call to action uses" : `All ${ctas.length} quote calls to action use`} a label from the closed list or a recorded one.`)];
85
+ }
86
+ //# sourceMappingURL=cta-labels.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cta-labels.js","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/cta-labels.ts"],"names":[],"mappings":"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","sourcesContent":["import { closest, findAll, textOf, type HtmlElement } from \"./html.js\";\nimport type { ShellPage } from \"./page.js\";\nimport { failFinding, notApplicableFinding, passFinding, type ShellFinding } from \"./types.js\";\n\n/**\n * C5: call-to-action labels come from the closed list or a label the project records. The quote label is \"Get a free\n * quote\" when free quotes are offered and \"Get a quote\" otherwise (Romie's rule from six sites); a site can record a\n * different label where Romie set one. Every quote button (`data-bl=\"quote-button\"`) and every link or button that\n * reads like a quote call to action is held to the list. A form's own submit button is the form's label (C2).\n */\n\nexport const QUOTE_LABEL_FREE = \"Get a free quote\";\nexport const QUOTE_LABEL_PLAIN = \"Get a quote\";\n\nconst QUOTE_WORD = /\\b(?:quotes?|quotation|estimates?|enquir(?:e|y|ies)|inquir(?:e|y|ies))\\b/i;\nconst CTA_START = /^(?:get|request|book|send|ask|claim|start|receive|arrange|schedule|obtain|enquire|inquire|free|your|online|instant|fast|quick|easy)\\b/i;\nconst MAX_WORDS = 6;\n\nfunction cleanLabel(label: string): string {\n return label.replace(/[\\s ]+/g, \" \").replace(/\\s*[→›»>]+$/, \"\").replace(/[.!]+$/, \"\").trim();\n}\n\nfunction isButtonLike(element: HtmlElement): boolean {\n return element.tag === \"a\" || element.tag === \"button\" || element.attrs.role === \"button\";\n}\n\n/** Whether a link or button label reads like a quote call to action. */\nexport function isQuoteLike(label: string): boolean {\n if (label === \"\" || label.split(\" \").length > MAX_WORDS || !QUOTE_WORD.test(label)) return false;\n return CTA_START.test(label) || /^(?:quotes?|estimates?)$/i.test(label);\n}\n\n/** A form's own button: type=\"submit\", or a button with no type inside a form (which submits by default). */\nfunction isSubmitButton(element: HtmlElement): boolean {\n if (element.attrs.type === \"submit\") return true;\n return element.tag === \"button\" && element.attrs.type === undefined && closest(element, (ancestor) => ancestor.tag === \"form\") !== null;\n}\n\ninterface QuoteCta {\n path: string;\n label: string;\n}\n\nfunction collectQuoteCtas(pages: readonly ShellPage[]): QuoteCta[] {\n const ctas: QuoteCta[] = [];\n for (const page of pages) {\n for (const element of findAll(page.body, isButtonLike)) {\n if (/^\\s*tel:/i.test(element.attrs.href ?? \"\") || element.attrs[\"data-bl\"] === \"phone-link\") continue;\n const label = cleanLabel(textOf(element));\n const declared = element.attrs[\"data-bl\"] === \"quote-button\";\n if (!declared && (isSubmitButton(element) || !isQuoteLike(label))) continue;\n if (label !== \"\") ctas.push({ path: page.path, label });\n }\n }\n return ctas;\n}\n\nexport function checkCtaLabels(\n pages: readonly ShellPage[],\n options: { labels?: readonly string[]; freeQuotes?: boolean } = {},\n): ShellFinding[] {\n const ctas = collectQuoteCtas(pages);\n if (ctas.length === 0) return [notApplicableFinding(\"C5\", \"The sampled pages have no quote button or quote call to action to check.\")];\n const recorded = new Set((options.labels ?? []).map((label) => label.trim().toLowerCase()));\n const closed = options.freeQuotes === true\n ? [QUOTE_LABEL_FREE]\n : options.freeQuotes === false\n ? [QUOTE_LABEL_PLAIN]\n : [QUOTE_LABEL_FREE, QUOTE_LABEL_PLAIN];\n const allowed = new Set([...closed.map((label) => label.toLowerCase()), ...recorded]);\n const findings: ShellFinding[] = [];\n const reported = new Set<string>();\n for (const cta of ctas) {\n const key = `${cta.path}\\u0000${cta.label.toLowerCase()}`;\n if (allowed.has(cta.label.toLowerCase()) || reported.has(key)) continue;\n reported.add(key);\n const expected = options.freeQuotes === true ? `\"${QUOTE_LABEL_FREE}\"` : options.freeQuotes === false ? `\"${QUOTE_LABEL_PLAIN}\"` : `\"${QUOTE_LABEL_FREE}\" or \"${QUOTE_LABEL_PLAIN}\"`;\n findings.push(failFinding(\"C5\", cta.path, `The quote call to action \"${cta.label}\" on ${cta.path} is not ${expected}, and the project does not record it as a label.`));\n }\n if (findings.length === 0 && options.freeQuotes === undefined) {\n const used = new Map<string, string>();\n for (const cta of ctas) {\n const label = cta.label.toLowerCase();\n if ((label === QUOTE_LABEL_FREE.toLowerCase() || label === QUOTE_LABEL_PLAIN.toLowerCase()) && !used.has(label)) used.set(label, cta.path);\n }\n if (used.size > 1) {\n const free = used.get(QUOTE_LABEL_FREE.toLowerCase())!;\n const plain = used.get(QUOTE_LABEL_PLAIN.toLowerCase())!;\n findings.push(failFinding(\"C5\", plain, `The site uses both \"${QUOTE_LABEL_FREE}\" (on ${free}) and \"${QUOTE_LABEL_PLAIN}\" (on ${plain}); it needs one of them throughout.`));\n }\n }\n if (findings.length > 0) return findings;\n return [passFinding(\"C5\", `${ctas.length === 1 ? \"The one quote call to action uses\" : `All ${ctas.length} quote calls to action use`} a label from the closed list or a recorded one.`)];\n}\n"]}
@@ -0,0 +1,33 @@
1
+ import { type SiteSystemSettings } from "../../config.js";
2
+ import type { ShellProjectFacts } from "./types.js";
3
+ /** `tel:` href for an E.164 number the way the shared business facts derive it. */
4
+ export declare function telHrefFor(e164: string): string;
5
+ export interface ShellFactsResult {
6
+ facts: ShellProjectFacts | null;
7
+ /** Why the facts are missing, in one plain sentence; empty when they were found. */
8
+ reason: string;
9
+ }
10
+ /**
11
+ * The phone, trading name and licences from the project's business facts. The display values file is read first and
12
+ * the evidence file fills any gap; the licences come from T11's reader (evidence file first). Reports why when neither
13
+ * file exists, and marks licence data it cannot read.
14
+ */
15
+ export declare function loadShellProjectFacts(projectRoot: string): ShellFactsResult;
16
+ /**
17
+ * What the project records under `siteSystem` in brandlab.config.ts for these rules: the quote labels it allows, whether
18
+ * it offers free quotes, the third-party hosts it recorded a decision for, and extra phone numbers. Taken from the
19
+ * gate's own parsed settings, so the shell rules never read the config more loosely than the gate does.
20
+ */
21
+ export interface SiteSystemRecords {
22
+ labels?: readonly string[];
23
+ freeQuotes?: boolean;
24
+ thirdParties?: readonly string[];
25
+ phones?: readonly string[];
26
+ }
27
+ export declare function siteSystemRecordsFrom(settings: SiteSystemSettings | null): SiteSystemRecords;
28
+ /**
29
+ * The parsed `siteSystem` settings, for a caller that did not pass them: null when the project has no brandlab.config.ts,
30
+ * declares no `siteSystem`, or the gate's parser rejects it (brandlab-ready reports that itself).
31
+ */
32
+ export declare function loadSiteSystemSettings(projectRoot: string): SiteSystemSettings | null;
33
+ //# sourceMappingURL=facts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facts.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/facts.ts"],"names":[],"mappings":"AAGA,OAAO,EAA+B,KAAK,kBAAkB,EAAE,MAAM,iBAAiB,CAAC;AAEvF,OAAO,KAAK,EAAoB,iBAAiB,EAAE,MAAM,YAAY,CAAC;AAoHtE,mFAAmF;AACnF,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE/C;AA0BD,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,iBAAiB,GAAG,IAAI,CAAC;IAChC,oFAAoF;IACpF,MAAM,EAAE,MAAM,CAAC;CAChB;AAID;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,gBAAgB,CA6B3E;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACjC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;CAC5B;AAED,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,kBAAkB,GAAG,IAAI,GAAG,iBAAiB,CAQ5F;AAED;;;GAGG;AACH,wBAAgB,sBAAsB,CAAC,WAAW,EAAE,MAAM,GAAG,kBAAkB,GAAG,IAAI,CAOrF"}
@@ -0,0 +1,217 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import ts from "typescript";
4
+ import { readSiteSystemProjectConfig } from "../../config.js";
5
+ import { readLicences } from "../enquiry/t11-licence-classes.js";
6
+ /**
7
+ * The project's business facts, read from source without running it. A site declares them in `src/data/business.ts`
8
+ * (display values) and `src/data/businessEvidence.ts` (the same facts wrapped in evidence), both plain literals, so
9
+ * the facts are read from the syntax tree: object and array literals, constants in the same file, `as const` and
10
+ * `satisfies`, and the `verifiedFact(value, evidence)` and `defineBrandlabBusiness(...)` wrappers.
11
+ */
12
+ const UNRESOLVED = Symbol("unresolved");
13
+ function unwrap(expression) {
14
+ let current = expression;
15
+ for (;;) {
16
+ if (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isSatisfiesExpression(current) || ts.isNonNullExpression(current) || ts.isTypeAssertionExpression(current)) {
17
+ current = current.expression;
18
+ }
19
+ else {
20
+ return current;
21
+ }
22
+ }
23
+ }
24
+ function propertyKey(name) {
25
+ if (ts.isIdentifier(name) || ts.isStringLiteralLike(name) || ts.isNumericLiteral(name))
26
+ return name.text;
27
+ return null;
28
+ }
29
+ /** Evaluate a literal expression; anything that is not a literal is UNRESOLVED. */
30
+ function evaluate(node, scope, depth = 0) {
31
+ if (depth > 24)
32
+ return UNRESOLVED;
33
+ const expression = unwrap(node);
34
+ if (ts.isStringLiteralLike(expression))
35
+ return expression.text;
36
+ if (ts.isNumericLiteral(expression))
37
+ return Number(expression.text);
38
+ if (expression.kind === ts.SyntaxKind.TrueKeyword)
39
+ return true;
40
+ if (expression.kind === ts.SyntaxKind.FalseKeyword)
41
+ return false;
42
+ if (expression.kind === ts.SyntaxKind.NullKeyword)
43
+ return null;
44
+ if (ts.isArrayLiteralExpression(expression)) {
45
+ return expression.elements.map((element) => (ts.isSpreadElement(element) ? UNRESOLVED : evaluate(element, scope, depth + 1)));
46
+ }
47
+ if (ts.isObjectLiteralExpression(expression)) {
48
+ const record = {};
49
+ for (const property of expression.properties) {
50
+ if (ts.isPropertyAssignment(property)) {
51
+ const key = propertyKey(property.name);
52
+ if (key !== null)
53
+ record[key] = evaluate(property.initializer, scope, depth + 1);
54
+ }
55
+ else if (ts.isShorthandPropertyAssignment(property)) {
56
+ const target = scope.get(property.name.text);
57
+ record[property.name.text] = target ? evaluate(target, scope, depth + 1) : UNRESOLVED;
58
+ }
59
+ }
60
+ return record;
61
+ }
62
+ if (ts.isIdentifier(expression)) {
63
+ const target = scope.get(expression.text);
64
+ return target ? evaluate(target, scope, depth + 1) : UNRESOLVED;
65
+ }
66
+ if (ts.isPropertyAccessExpression(expression)) {
67
+ const base = evaluate(expression.expression, scope, depth + 1);
68
+ return base && typeof base === "object" && !Array.isArray(base) ? (base[expression.name.text] ?? UNRESOLVED) : UNRESOLVED;
69
+ }
70
+ if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression)) {
71
+ const callee = expression.expression.text;
72
+ if (callee === "verifiedFact" && expression.arguments[0])
73
+ return evaluate(expression.arguments[0], scope, depth + 1);
74
+ if (callee === "defineBrandlabBusiness" && expression.arguments[0])
75
+ return evaluate(expression.arguments[0], scope, depth + 1);
76
+ if (callee === "unknownFact" || callee === "notApplicableFact")
77
+ return undefined;
78
+ }
79
+ return UNRESOLVED;
80
+ }
81
+ function topLevelConstants(source) {
82
+ const constants = new Map();
83
+ for (const statement of source.statements) {
84
+ if (!ts.isVariableStatement(statement))
85
+ continue;
86
+ for (const declaration of statement.declarationList.declarations) {
87
+ if (ts.isIdentifier(declaration.name) && declaration.initializer)
88
+ constants.set(declaration.name.text, declaration.initializer);
89
+ }
90
+ }
91
+ return constants;
92
+ }
93
+ function asRecord(value) {
94
+ return value && typeof value === "object" && !Array.isArray(value) ? value : null;
95
+ }
96
+ function text(value) {
97
+ return typeof value === "string" && value !== "" ? value : undefined;
98
+ }
99
+ function readSource(root, path) {
100
+ const file = join(root, path);
101
+ if (!existsSync(file))
102
+ return null;
103
+ try {
104
+ return ts.createSourceFile(path, readFileSync(file, "utf8"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);
105
+ }
106
+ catch {
107
+ return null;
108
+ }
109
+ }
110
+ /** The business values object in a source file: `businessFactValues`, or the argument of `defineBrandlabBusiness`. */
111
+ function businessObject(source) {
112
+ const scope = topLevelConstants(source);
113
+ const named = scope.get("businessFactValues");
114
+ if (named) {
115
+ const value = asRecord(evaluate(named, scope));
116
+ if (value)
117
+ return value;
118
+ }
119
+ for (const expression of scope.values()) {
120
+ const value = asRecord(evaluate(expression, scope));
121
+ if (value && value.identity !== undefined && (value.contact !== undefined || value.registration !== undefined))
122
+ return value;
123
+ }
124
+ return null;
125
+ }
126
+ const NON_GEOGRAPHIC = /^\+61(?:1[38]00\d{6}|13(?!00)\d{4})$/;
127
+ /** `tel:` href for an E.164 number the way the shared business facts derive it. */
128
+ export function telHrefFor(e164) {
129
+ return NON_GEOGRAPHIC.test(e164) ? `tel:${e164.slice(3)}` : `tel:${e164}`;
130
+ }
131
+ function readPhone(value) {
132
+ const phone = asRecord(value);
133
+ const display = text(phone?.display);
134
+ const e164 = text(phone?.e164);
135
+ return display && e164 ? { display, e164, telHref: telHrefFor(e164) } : undefined;
136
+ }
137
+ /** The licences, by the same reader T11 uses, so T3 and T11 agree on what the business holds. */
138
+ function shellLicences(projectRoot) {
139
+ const read = readLicences(projectRoot);
140
+ if (read.status === "unreadable")
141
+ return { licences: [], unreadable: read.reason };
142
+ if (read.status === "none")
143
+ return { licences: [] };
144
+ return {
145
+ licences: read.licences.map((licence) => ({
146
+ name: licence.name,
147
+ identifier: licence.identifier,
148
+ ...(licence.jurisdiction ? { jurisdiction: licence.jurisdiction } : {}),
149
+ ...(licence.licensee ? { holder: licence.licensee } : {}),
150
+ ...(licence.expiresOn ? { expiresOn: licence.expiresOn } : {}),
151
+ ...(licence.checkedOn ? { checkedOn: licence.checkedOn } : {}),
152
+ })),
153
+ };
154
+ }
155
+ const FACT_FILES = ["src/data/business.ts", "src/data/businessEvidence.ts"];
156
+ /**
157
+ * The phone, trading name and licences from the project's business facts. The display values file is read first and
158
+ * the evidence file fills any gap; the licences come from T11's reader (evidence file first). Reports why when neither
159
+ * file exists, and marks licence data it cannot read.
160
+ */
161
+ export function loadShellProjectFacts(projectRoot) {
162
+ const present = [];
163
+ let tradingName;
164
+ let phone;
165
+ for (const path of FACT_FILES) {
166
+ const source = readSource(projectRoot, path);
167
+ if (!source)
168
+ continue;
169
+ present.push(path);
170
+ const business = businessObject(source);
171
+ if (!business)
172
+ continue;
173
+ const identity = asRecord(business.identity);
174
+ const contact = asRecord(business.contact);
175
+ tradingName ??= text(identity?.tradingName);
176
+ phone ??= readPhone(contact?.phone);
177
+ }
178
+ if (present.length === 0) {
179
+ return { facts: null, reason: "The project has no src/data/business.ts or src/data/businessEvidence.ts to read the business facts from." };
180
+ }
181
+ const { licences, unreadable } = shellLicences(projectRoot);
182
+ return {
183
+ facts: {
184
+ ...(tradingName ? { tradingName } : {}),
185
+ ...(phone ? { phone } : {}),
186
+ licences,
187
+ ...(unreadable ? { licencesUnreadable: unreadable } : {}),
188
+ sources: present,
189
+ },
190
+ reason: "",
191
+ };
192
+ }
193
+ export function siteSystemRecordsFrom(settings) {
194
+ if (!settings)
195
+ return {};
196
+ return {
197
+ ...(settings.labels.length > 0 ? { labels: settings.labels } : {}),
198
+ ...(settings.freeQuotes !== null ? { freeQuotes: settings.freeQuotes } : {}),
199
+ ...(settings.thirdParties.length > 0 ? { thirdParties: settings.thirdParties } : {}),
200
+ ...(settings.phones.length > 0 ? { phones: settings.phones } : {}),
201
+ };
202
+ }
203
+ /**
204
+ * The parsed `siteSystem` settings, for a caller that did not pass them: null when the project has no brandlab.config.ts,
205
+ * declares no `siteSystem`, or the gate's parser rejects it (brandlab-ready reports that itself).
206
+ */
207
+ export function loadSiteSystemSettings(projectRoot) {
208
+ if (!existsSync(join(projectRoot, "brandlab.config.ts")))
209
+ return null;
210
+ try {
211
+ return readSiteSystemProjectConfig(projectRoot).siteSystem;
212
+ }
213
+ catch {
214
+ return null;
215
+ }
216
+ }
217
+ //# sourceMappingURL=facts.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facts.js","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/facts.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport ts from \"typescript\";\nimport { readSiteSystemProjectConfig, type SiteSystemSettings } from \"../../config.js\";\nimport { readLicences } from \"../enquiry/t11-licence-classes.js\";\nimport type { ShellLicenceFact, ShellProjectFacts } from \"./types.js\";\n\n/**\n * The project's business facts, read from source without running it. A site declares them in `src/data/business.ts`\n * (display values) and `src/data/businessEvidence.ts` (the same facts wrapped in evidence), both plain literals, so\n * the facts are read from the syntax tree: object and array literals, constants in the same file, `as const` and\n * `satisfies`, and the `verifiedFact(value, evidence)` and `defineBrandlabBusiness(...)` wrappers.\n */\n\nconst UNRESOLVED = Symbol(\"unresolved\");\ntype Evaluated = unknown;\n\nfunction unwrap(expression: ts.Expression): ts.Expression {\n let current = expression;\n for (;;) {\n if (ts.isParenthesizedExpression(current) || ts.isAsExpression(current) || ts.isSatisfiesExpression(current) || ts.isNonNullExpression(current) || ts.isTypeAssertionExpression(current)) {\n current = current.expression;\n } else {\n return current;\n }\n }\n}\n\nfunction propertyKey(name: ts.PropertyName): string | null {\n if (ts.isIdentifier(name) || ts.isStringLiteralLike(name) || ts.isNumericLiteral(name)) return name.text;\n return null;\n}\n\n/** Evaluate a literal expression; anything that is not a literal is UNRESOLVED. */\nfunction evaluate(node: ts.Expression, scope: ReadonlyMap<string, ts.Expression>, depth = 0): Evaluated {\n if (depth > 24) return UNRESOLVED;\n const expression = unwrap(node);\n if (ts.isStringLiteralLike(expression)) return expression.text;\n if (ts.isNumericLiteral(expression)) return Number(expression.text);\n if (expression.kind === ts.SyntaxKind.TrueKeyword) return true;\n if (expression.kind === ts.SyntaxKind.FalseKeyword) return false;\n if (expression.kind === ts.SyntaxKind.NullKeyword) return null;\n if (ts.isArrayLiteralExpression(expression)) {\n return expression.elements.map((element) => (ts.isSpreadElement(element) ? UNRESOLVED : evaluate(element, scope, depth + 1)));\n }\n if (ts.isObjectLiteralExpression(expression)) {\n const record: Record<string, Evaluated> = {};\n for (const property of expression.properties) {\n if (ts.isPropertyAssignment(property)) {\n const key = propertyKey(property.name);\n if (key !== null) record[key] = evaluate(property.initializer, scope, depth + 1);\n } else if (ts.isShorthandPropertyAssignment(property)) {\n const target = scope.get(property.name.text);\n record[property.name.text] = target ? evaluate(target, scope, depth + 1) : UNRESOLVED;\n }\n }\n return record;\n }\n if (ts.isIdentifier(expression)) {\n const target = scope.get(expression.text);\n return target ? evaluate(target, scope, depth + 1) : UNRESOLVED;\n }\n if (ts.isPropertyAccessExpression(expression)) {\n const base = evaluate(expression.expression, scope, depth + 1);\n return base && typeof base === \"object\" && !Array.isArray(base) ? ((base as Record<string, Evaluated>)[expression.name.text] ?? UNRESOLVED) : UNRESOLVED;\n }\n if (ts.isCallExpression(expression) && ts.isIdentifier(expression.expression)) {\n const callee = expression.expression.text;\n if (callee === \"verifiedFact\" && expression.arguments[0]) return evaluate(expression.arguments[0], scope, depth + 1);\n if (callee === \"defineBrandlabBusiness\" && expression.arguments[0]) return evaluate(expression.arguments[0], scope, depth + 1);\n if (callee === \"unknownFact\" || callee === \"notApplicableFact\") return undefined;\n }\n return UNRESOLVED;\n}\n\nfunction topLevelConstants(source: ts.SourceFile): Map<string, ts.Expression> {\n const constants = new Map<string, ts.Expression>();\n for (const statement of source.statements) {\n if (!ts.isVariableStatement(statement)) continue;\n for (const declaration of statement.declarationList.declarations) {\n if (ts.isIdentifier(declaration.name) && declaration.initializer) constants.set(declaration.name.text, declaration.initializer);\n }\n }\n return constants;\n}\n\nfunction asRecord(value: Evaluated): Record<string, Evaluated> | null {\n return value && typeof value === \"object\" && !Array.isArray(value) ? (value as Record<string, Evaluated>) : null;\n}\n\nfunction text(value: Evaluated): string | undefined {\n return typeof value === \"string\" && value !== \"\" ? value : undefined;\n}\n\nfunction readSource(root: string, path: string): ts.SourceFile | null {\n const file = join(root, path);\n if (!existsSync(file)) return null;\n try {\n return ts.createSourceFile(path, readFileSync(file, \"utf8\"), ts.ScriptTarget.Latest, true, ts.ScriptKind.TS);\n } catch {\n return null;\n }\n}\n\n/** The business values object in a source file: `businessFactValues`, or the argument of `defineBrandlabBusiness`. */\nfunction businessObject(source: ts.SourceFile): Record<string, Evaluated> | null {\n const scope = topLevelConstants(source);\n const named = scope.get(\"businessFactValues\");\n if (named) {\n const value = asRecord(evaluate(named, scope));\n if (value) return value;\n }\n for (const expression of scope.values()) {\n const value = asRecord(evaluate(expression, scope));\n if (value && value.identity !== undefined && (value.contact !== undefined || value.registration !== undefined)) return value;\n }\n return null;\n}\n\nconst NON_GEOGRAPHIC = /^\\+61(?:1[38]00\\d{6}|13(?!00)\\d{4})$/;\n\n/** `tel:` href for an E.164 number the way the shared business facts derive it. */\nexport function telHrefFor(e164: string): string {\n return NON_GEOGRAPHIC.test(e164) ? `tel:${e164.slice(3)}` : `tel:${e164}`;\n}\n\nfunction readPhone(value: Evaluated): ShellProjectFacts[\"phone\"] | undefined {\n const phone = asRecord(value);\n const display = text(phone?.display);\n const e164 = text(phone?.e164);\n return display && e164 ? { display, e164, telHref: telHrefFor(e164) } : undefined;\n}\n\n/** The licences, by the same reader T11 uses, so T3 and T11 agree on what the business holds. */\nfunction shellLicences(projectRoot: string): { licences: ShellLicenceFact[]; unreadable?: string } {\n const read = readLicences(projectRoot);\n if (read.status === \"unreadable\") return { licences: [], unreadable: read.reason };\n if (read.status === \"none\") return { licences: [] };\n return {\n licences: read.licences.map((licence) => ({\n name: licence.name,\n identifier: licence.identifier,\n ...(licence.jurisdiction ? { jurisdiction: licence.jurisdiction } : {}),\n ...(licence.licensee ? { holder: licence.licensee } : {}),\n ...(licence.expiresOn ? { expiresOn: licence.expiresOn } : {}),\n ...(licence.checkedOn ? { checkedOn: licence.checkedOn } : {}),\n })),\n };\n}\n\nexport interface ShellFactsResult {\n facts: ShellProjectFacts | null;\n /** Why the facts are missing, in one plain sentence; empty when they were found. */\n reason: string;\n}\n\nconst FACT_FILES = [\"src/data/business.ts\", \"src/data/businessEvidence.ts\"] as const;\n\n/**\n * The phone, trading name and licences from the project's business facts. The display values file is read first and\n * the evidence file fills any gap; the licences come from T11's reader (evidence file first). Reports why when neither\n * file exists, and marks licence data it cannot read.\n */\nexport function loadShellProjectFacts(projectRoot: string): ShellFactsResult {\n const present: string[] = [];\n let tradingName: string | undefined;\n let phone: ShellProjectFacts[\"phone\"] | undefined;\n for (const path of FACT_FILES) {\n const source = readSource(projectRoot, path);\n if (!source) continue;\n present.push(path);\n const business = businessObject(source);\n if (!business) continue;\n const identity = asRecord(business.identity);\n const contact = asRecord(business.contact);\n tradingName ??= text(identity?.tradingName);\n phone ??= readPhone(contact?.phone);\n }\n if (present.length === 0) {\n return { facts: null, reason: \"The project has no src/data/business.ts or src/data/businessEvidence.ts to read the business facts from.\" };\n }\n const { licences, unreadable } = shellLicences(projectRoot);\n return {\n facts: {\n ...(tradingName ? { tradingName } : {}),\n ...(phone ? { phone } : {}),\n licences,\n ...(unreadable ? { licencesUnreadable: unreadable } : {}),\n sources: present,\n },\n reason: \"\",\n };\n}\n\n/**\n * What the project records under `siteSystem` in brandlab.config.ts for these rules: the quote labels it allows, whether\n * it offers free quotes, the third-party hosts it recorded a decision for, and extra phone numbers. Taken from the\n * gate's own parsed settings, so the shell rules never read the config more loosely than the gate does.\n */\nexport interface SiteSystemRecords {\n labels?: readonly string[];\n freeQuotes?: boolean;\n thirdParties?: readonly string[];\n phones?: readonly string[];\n}\n\nexport function siteSystemRecordsFrom(settings: SiteSystemSettings | null): SiteSystemRecords {\n if (!settings) return {};\n return {\n ...(settings.labels.length > 0 ? { labels: settings.labels } : {}),\n ...(settings.freeQuotes !== null ? { freeQuotes: settings.freeQuotes } : {}),\n ...(settings.thirdParties.length > 0 ? { thirdParties: settings.thirdParties } : {}),\n ...(settings.phones.length > 0 ? { phones: settings.phones } : {}),\n };\n}\n\n/**\n * The parsed `siteSystem` settings, for a caller that did not pass them: null when the project has no brandlab.config.ts,\n * declares no `siteSystem`, or the gate's parser rejects it (brandlab-ready reports that itself).\n */\nexport function loadSiteSystemSettings(projectRoot: string): SiteSystemSettings | null {\n if (!existsSync(join(projectRoot, \"brandlab.config.ts\"))) return null;\n try {\n return readSiteSystemProjectConfig(projectRoot).siteSystem;\n } catch {\n return null;\n }\n}\n"]}
@@ -0,0 +1,5 @@
1
+ import type { ShellPage } from "./page.js";
2
+ import { type ShellFinding } from "./types.js";
3
+ /** E2: exactly one H1 on every page, rendered once. */
4
+ export declare function checkSingleH1(pages: readonly ShellPage[]): ShellFinding[];
5
+ //# sourceMappingURL=headings.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headings.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/headings.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAC3C,OAAO,EAA4B,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEzE,uDAAuD;AACvD,wBAAgB,aAAa,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,YAAY,EAAE,CAezE"}
@@ -0,0 +1,23 @@
1
+ import { textOf } from "./html.js";
2
+ import { failFinding, passFinding } from "./types.js";
3
+ /** E2: exactly one H1 on every page, rendered once. */
4
+ export function checkSingleH1(pages) {
5
+ const findings = [];
6
+ for (const page of pages) {
7
+ const count = page.h1s.length;
8
+ if (count === 0) {
9
+ findings.push(failFinding("E2", page.path, `The page ${page.path} has no H1 heading; every page needs exactly one.`));
10
+ }
11
+ else if (count > 1) {
12
+ const names = page.h1s.slice(0, 3).map((heading) => `"${textOf(heading).slice(0, 40)}"`).join(", ");
13
+ findings.push(failFinding("E2", page.path, `The page ${page.path} has ${count} H1 headings (${names}); every page needs exactly one.`));
14
+ }
15
+ else if (textOf(page.h1s[0]) === "" && !(page.h1s[0].attrs["aria-label"] ?? "").trim()) {
16
+ findings.push(failFinding("E2", page.path, `The page ${page.path} has an H1 heading with no text.`));
17
+ }
18
+ }
19
+ if (findings.length > 0)
20
+ return findings;
21
+ return [passFinding("E2", pages.length === 1 ? "The sampled page has exactly one H1 heading." : `Every one of the ${pages.length} sampled pages has exactly one H1 heading.`)];
22
+ }
23
+ //# sourceMappingURL=headings.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"headings.js","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/headings.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,OAAO,EAAE,WAAW,EAAE,WAAW,EAAqB,MAAM,YAAY,CAAC;AAEzE,uDAAuD;AACvD,MAAM,UAAU,aAAa,CAAC,KAA2B;IACvD,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;QAC9B,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC;YAChB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,IAAI,CAAC,IAAI,mDAAmD,CAAC,CAAC,CAAC;QACxH,CAAC;aAAM,IAAI,KAAK,GAAG,CAAC,EAAE,CAAC;YACrB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YACpG,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,IAAI,CAAC,IAAI,QAAQ,KAAK,iBAAiB,KAAK,kCAAkC,CAAC,CAAC,CAAC;QAC1I,CAAC;aAAM,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAE,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC;YAC3F,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,YAAY,IAAI,CAAC,IAAI,kCAAkC,CAAC,CAAC,CAAC;QACvG,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IACzC,OAAO,CAAC,WAAW,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,8CAA8C,CAAC,CAAC,CAAC,oBAAoB,KAAK,CAAC,MAAM,4CAA4C,CAAC,CAAC,CAAC;AACjL,CAAC","sourcesContent":["import { textOf } from \"./html.js\";\nimport type { ShellPage } from \"./page.js\";\nimport { failFinding, passFinding, type ShellFinding } from \"./types.js\";\n\n/** E2: exactly one H1 on every page, rendered once. */\nexport function checkSingleH1(pages: readonly ShellPage[]): ShellFinding[] {\n const findings: ShellFinding[] = [];\n for (const page of pages) {\n const count = page.h1s.length;\n if (count === 0) {\n findings.push(failFinding(\"E2\", page.path, `The page ${page.path} has no H1 heading; every page needs exactly one.`));\n } else if (count > 1) {\n const names = page.h1s.slice(0, 3).map((heading) => `\"${textOf(heading).slice(0, 40)}\"`).join(\", \");\n findings.push(failFinding(\"E2\", page.path, `The page ${page.path} has ${count} H1 headings (${names}); every page needs exactly one.`));\n } else if (textOf(page.h1s[0]!) === \"\" && !(page.h1s[0]!.attrs[\"aria-label\"] ?? \"\").trim()) {\n findings.push(failFinding(\"E2\", page.path, `The page ${page.path} has an H1 heading with no text.`));\n }\n }\n if (findings.length > 0) return findings;\n return [passFinding(\"E2\", pages.length === 1 ? \"The sampled page has exactly one H1 heading.\" : `Every one of the ${pages.length} sampled pages has exactly one H1 heading.`)];\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import { type ShellPage } from "./page.js";
2
+ import { type ShellFinding } from "./types.js";
3
+ export declare function checkHeroLocationCopy(pages: readonly ShellPage[]): ShellFinding[];
4
+ //# sourceMappingURL=hero-location.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hero-location.d.ts","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/hero-location.ts"],"names":[],"mappings":"AACA,OAAO,EAAsB,KAAK,SAAS,EAAE,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAgE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAe7G,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,GAAG,YAAY,EAAE,CAyBjF"}
@@ -0,0 +1,45 @@
1
+ import { BASED_IN, KM_FIGURE, KM_FROM, globally } from "./location-wording.js";
2
+ import { elementAt, excerpt } from "./page.js";
3
+ import { failFinding, notApplicableFinding, passFinding, sampledPages } from "./types.js";
4
+ import { isWithin } from "./html.js";
5
+ /**
6
+ * L10-hero: no kilometre figures, "km from" or "based in" in hero copy. The hero is the section marked
7
+ * `data-bl-section="hero"`, or the first section that holds the H1. The contact page is exempt, as it is from the
8
+ * banned list's layer 2. Location copy is the full tier's L10.
9
+ */
10
+ const HERO_PATTERNS = [
11
+ { label: "a kilometre figure", regex: globally(KM_FIGURE) },
12
+ { label: "\"km from\"", regex: globally(KM_FROM) },
13
+ { label: "\"based in\"", regex: globally(BASED_IN) },
14
+ ];
15
+ export function checkHeroLocationCopy(pages) {
16
+ const findings = [];
17
+ let heroes = 0;
18
+ for (const page of pages) {
19
+ const hero = page.hero;
20
+ if (!hero || page.family === "contact")
21
+ continue;
22
+ heroes += 1;
23
+ const labels = new Set();
24
+ let quote = "";
25
+ for (const unit of [...page.units, ...page.attributeCopy]) {
26
+ for (const { label, regex } of HERO_PATTERNS) {
27
+ for (const match of unit.text.matchAll(regex)) {
28
+ if (!isWithin(elementAt(unit, match.index), hero))
29
+ continue;
30
+ labels.add(label);
31
+ quote ||= excerpt(unit.text, match.index, match[0].length);
32
+ }
33
+ }
34
+ }
35
+ if (labels.size > 0) {
36
+ findings.push(failFinding("L10-hero", page.path, `The hero on ${page.path} has ${[...labels].join(" and ")} ("${quote}"), which hero copy must not carry.`));
37
+ }
38
+ }
39
+ if (findings.length > 0)
40
+ return findings;
41
+ if (heroes === 0)
42
+ return [notApplicableFinding("L10-hero", "None of the sampled pages has a hero or an H1 section to check.")];
43
+ return [passFinding("L10-hero", `No kilometre figures, "km from" or "based in" in the hero copy of ${sampledPages(heroes)}.`)];
44
+ }
45
+ //# sourceMappingURL=hero-location.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hero-location.js","sourceRoot":"","sources":["../../../../src/site-system/rules/shell/hero-location.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAC/E,OAAO,EAAE,SAAS,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAC;AAC/D,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,WAAW,EAAE,YAAY,EAAqB,MAAM,YAAY,CAAC;AAC7G,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAErC;;;;GAIG;AAEH,MAAM,aAAa,GAAgD;IACjE,EAAE,KAAK,EAAE,oBAAoB,EAAE,KAAK,EAAE,QAAQ,CAAC,SAAS,CAAC,EAAE;IAC3D,EAAE,KAAK,EAAE,aAAa,EAAE,KAAK,EAAE,QAAQ,CAAC,OAAO,CAAC,EAAE;IAClD,EAAE,KAAK,EAAE,cAAc,EAAE,KAAK,EAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE;CACrD,CAAC;AAEF,MAAM,UAAU,qBAAqB,CAAC,KAA2B;IAC/D,MAAM,QAAQ,GAAmB,EAAE,CAAC;IACpC,IAAI,MAAM,GAAG,CAAC,CAAC;IACf,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACvB,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,SAAS;YAAE,SAAS;QACjD,MAAM,IAAI,CAAC,CAAC;QACZ,MAAM,MAAM,GAAG,IAAI,GAAG,EAAU,CAAC;QACjC,IAAI,KAAK,GAAG,EAAE,CAAC;QACf,KAAK,MAAM,IAAI,IAAI,CAAC,GAAG,IAAI,CAAC,KAAK,EAAE,GAAG,IAAI,CAAC,aAAa,CAAC,EAAE,CAAC;YAC1D,KAAK,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,aAAa,EAAE,CAAC;gBAC7C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC9C,IAAI,CAAC,QAAQ,CAAC,SAAS,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,CAAC,EAAE,IAAI,CAAC;wBAAE,SAAS;oBAC5D,MAAM,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;oBAClB,KAAK,KAAK,OAAO,CAAC,IAAI,CAAC,IAAI,EAAE,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;gBAC7D,CAAC;YACH,CAAC;QACH,CAAC;QACD,IAAI,MAAM,CAAC,IAAI,GAAG,CAAC,EAAE,CAAC;YACpB,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,UAAU,EAAE,IAAI,CAAC,IAAI,EAAE,eAAe,IAAI,CAAC,IAAI,QAAQ,CAAC,GAAG,MAAM,CAAC,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,KAAK,qCAAqC,CAAC,CAAC,CAAC;QAC/J,CAAC;IACH,CAAC;IACD,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IACzC,IAAI,MAAM,KAAK,CAAC;QAAE,OAAO,CAAC,oBAAoB,CAAC,UAAU,EAAE,iEAAiE,CAAC,CAAC,CAAC;IAC/H,OAAO,CAAC,WAAW,CAAC,UAAU,EAAE,qEAAqE,YAAY,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;AACjI,CAAC","sourcesContent":["import { BASED_IN, KM_FIGURE, KM_FROM, globally } from \"./location-wording.js\";\nimport { elementAt, excerpt, type ShellPage } from \"./page.js\";\nimport { failFinding, notApplicableFinding, passFinding, sampledPages, type ShellFinding } from \"./types.js\";\nimport { isWithin } from \"./html.js\";\n\n/**\n * L10-hero: no kilometre figures, \"km from\" or \"based in\" in hero copy. The hero is the section marked\n * `data-bl-section=\"hero\"`, or the first section that holds the H1. The contact page is exempt, as it is from the\n * banned list's layer 2. Location copy is the full tier's L10.\n */\n\nconst HERO_PATTERNS: readonly { label: string; regex: RegExp }[] = [\n { label: \"a kilometre figure\", regex: globally(KM_FIGURE) },\n { label: \"\\\"km from\\\"\", regex: globally(KM_FROM) },\n { label: \"\\\"based in\\\"\", regex: globally(BASED_IN) },\n];\n\nexport function checkHeroLocationCopy(pages: readonly ShellPage[]): ShellFinding[] {\n const findings: ShellFinding[] = [];\n let heroes = 0;\n for (const page of pages) {\n const hero = page.hero;\n if (!hero || page.family === \"contact\") continue;\n heroes += 1;\n const labels = new Set<string>();\n let quote = \"\";\n for (const unit of [...page.units, ...page.attributeCopy]) {\n for (const { label, regex } of HERO_PATTERNS) {\n for (const match of unit.text.matchAll(regex)) {\n if (!isWithin(elementAt(unit, match.index), hero)) continue;\n labels.add(label);\n quote ||= excerpt(unit.text, match.index, match[0].length);\n }\n }\n }\n if (labels.size > 0) {\n findings.push(failFinding(\"L10-hero\", page.path, `The hero on ${page.path} has ${[...labels].join(\" and \")} (\"${quote}\"), which hero copy must not carry.`));\n }\n }\n if (findings.length > 0) return findings;\n if (heroes === 0) return [notApplicableFinding(\"L10-hero\", \"None of the sampled pages has a hero or an H1 section to check.\")];\n return [passFinding(\"L10-hero\", `No kilometre figures, \"km from\" or \"based in\" in the hero copy of ${sampledPages(heroes)}.`)];\n}\n"]}