@aglyn/aglyn 1.0.0-beta.217 → 1.0.0-beta.219

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 (348) hide show
  1. package/package.json +14 -14
  2. package/src/lib/app-utils/actions.d.ts +25 -374
  3. package/src/lib/app-utils/actions.js +101 -561
  4. package/src/lib/app-utils/actions.js.map +1 -1
  5. package/src/lib/app-utils/activity-presenter.d.ts +3 -3
  6. package/src/lib/app-utils/activity-presenter.js +36 -52
  7. package/src/lib/app-utils/activity-presenter.js.map +1 -1
  8. package/src/lib/app-utils/admin-audit-index.js +2 -2
  9. package/src/lib/app-utils/admin-audit-index.js.map +1 -1
  10. package/src/lib/app-utils/analytics-events.d.ts +11 -2
  11. package/src/lib/app-utils/analytics-events.js +11 -2
  12. package/src/lib/app-utils/analytics-events.js.map +1 -1
  13. package/src/lib/app-utils/analytics-provider.d.ts +182 -0
  14. package/src/lib/app-utils/analytics-provider.js +133 -0
  15. package/src/lib/app-utils/analytics-provider.js.map +1 -0
  16. package/src/lib/app-utils/api-plugins.d.ts +46 -0
  17. package/src/lib/app-utils/api-plugins.js +19 -0
  18. package/src/lib/app-utils/api-plugins.js.map +1 -1
  19. package/src/lib/app-utils/{marketplace-overrides.d.ts → artifact-overrides.d.ts} +9 -6
  20. package/src/lib/app-utils/{marketplace-overrides.js → artifact-overrides.js} +10 -7
  21. package/src/lib/app-utils/artifact-overrides.js.map +1 -0
  22. package/src/lib/app-utils/{marketplace-provenance.d.ts → artifact-provenance.d.ts} +12 -10
  23. package/src/lib/app-utils/{marketplace-provenance.js → artifact-provenance.js} +9 -7
  24. package/src/lib/app-utils/artifact-provenance.js.map +1 -0
  25. package/src/lib/app-utils/assist-credits.js +2 -1
  26. package/src/lib/app-utils/assist-credits.js.map +1 -1
  27. package/src/lib/app-utils/binding-token-catalog.d.ts +5 -13
  28. package/src/lib/app-utils/binding-token-catalog.js +17 -41
  29. package/src/lib/app-utils/binding-token-catalog.js.map +1 -1
  30. package/src/lib/app-utils/binding-tokens.js +1 -1
  31. package/src/lib/app-utils/binding-tokens.js.map +1 -1
  32. package/src/lib/app-utils/brand-support-line.d.ts +36 -0
  33. package/src/lib/app-utils/brand-support-line.js +38 -0
  34. package/src/lib/app-utils/brand-support-line.js.map +1 -0
  35. package/src/lib/app-utils/campaign-touch.d.ts +5 -5
  36. package/src/lib/app-utils/campaign-touch.js +8 -8
  37. package/src/lib/app-utils/campaign-touch.js.map +1 -1
  38. package/src/lib/app-utils/collection-entries.d.ts +2 -2
  39. package/src/lib/app-utils/collection-entries.js.map +1 -1
  40. package/src/lib/app-utils/consent-group-change.d.ts +4 -0
  41. package/src/lib/app-utils/consent-group-change.js.map +1 -1
  42. package/src/lib/app-utils/console-record-links.d.ts +10 -89
  43. package/src/lib/app-utils/console-record-links.js +3 -107
  44. package/src/lib/app-utils/console-record-links.js.map +1 -1
  45. package/src/lib/app-utils/console-routes.d.ts +16 -7
  46. package/src/lib/app-utils/console-routes.js +9 -3
  47. package/src/lib/app-utils/console-routes.js.map +1 -1
  48. package/src/lib/app-utils/contacts.d.ts +23 -1
  49. package/src/lib/app-utils/contacts.js +21 -5
  50. package/src/lib/app-utils/contacts.js.map +1 -1
  51. package/src/lib/app-utils/cross-pool-desync.d.ts +39 -0
  52. package/src/lib/app-utils/cross-pool-desync.js +47 -0
  53. package/src/lib/app-utils/cross-pool-desync.js.map +1 -0
  54. package/src/lib/app-utils/csv-upload.d.ts +29 -0
  55. package/src/lib/app-utils/csv-upload.js +29 -0
  56. package/src/lib/app-utils/csv-upload.js.map +1 -0
  57. package/src/lib/app-utils/dataset-models.d.ts +5 -5
  58. package/src/lib/app-utils/dataset-models.js +19 -2
  59. package/src/lib/app-utils/dataset-models.js.map +1 -1
  60. package/src/lib/app-utils/datasets.d.ts +65 -11
  61. package/src/lib/app-utils/datasets.js +74 -11
  62. package/src/lib/app-utils/datasets.js.map +1 -1
  63. package/src/lib/app-utils/docs-deflection.d.ts +6 -6
  64. package/src/lib/app-utils/docs-deflection.js.map +1 -1
  65. package/src/lib/app-utils/docs-help.generated.d.ts +12 -6
  66. package/src/lib/app-utils/docs-help.generated.js +21 -6
  67. package/src/lib/app-utils/docs-help.generated.js.map +1 -1
  68. package/src/lib/app-utils/docs-index.generated.d.ts +2 -2
  69. package/src/lib/app-utils/docs-index.generated.js +514 -318
  70. package/src/lib/app-utils/docs-index.generated.js.map +1 -1
  71. package/src/lib/app-utils/docs-retrieval.d.ts +3 -3
  72. package/src/lib/app-utils/docs-retrieval.js +14 -10
  73. package/src/lib/app-utils/docs-retrieval.js.map +1 -1
  74. package/src/lib/app-utils/duplicate-resource.d.ts +4 -1
  75. package/src/lib/app-utils/duplicate-resource.js +7 -4
  76. package/src/lib/app-utils/duplicate-resource.js.map +1 -1
  77. package/src/lib/app-utils/dynamic-list-rule.d.ts +1 -1
  78. package/src/lib/app-utils/dynamic-list-rule.js.map +1 -1
  79. package/src/lib/app-utils/element-ui.d.ts +1 -1
  80. package/src/lib/app-utils/element-ui.js +1 -1
  81. package/src/lib/app-utils/element-ui.js.map +1 -1
  82. package/src/lib/app-utils/email-starter-policy.js +3 -3
  83. package/src/lib/app-utils/email-starter-policy.js.map +1 -1
  84. package/src/lib/app-utils/entity-picker-context.d.ts +10 -5
  85. package/src/lib/app-utils/entity-picker-context.js.map +1 -1
  86. package/src/lib/app-utils/error-beacon.d.ts +75 -6
  87. package/src/lib/app-utils/error-beacon.js +160 -21
  88. package/src/lib/app-utils/error-beacon.js.map +1 -1
  89. package/src/lib/app-utils/expand-repeatables.d.ts +10 -10
  90. package/src/lib/app-utils/expand-repeatables.js +10 -10
  91. package/src/lib/app-utils/expand-repeatables.js.map +1 -1
  92. package/src/lib/app-utils/host-events.d.ts +82 -0
  93. package/src/lib/app-utils/host-events.js +62 -0
  94. package/src/lib/app-utils/host-events.js.map +1 -0
  95. package/src/lib/app-utils/lockdown.d.ts +33 -1
  96. package/src/lib/app-utils/lockdown.js +45 -4
  97. package/src/lib/app-utils/lockdown.js.map +1 -1
  98. package/src/lib/app-utils/margin-utilization.d.ts +4 -69
  99. package/src/lib/app-utils/margin-utilization.js +82 -69
  100. package/src/lib/app-utils/margin-utilization.js.map +1 -1
  101. package/src/lib/app-utils/node-definition-sanitizer.d.ts +16 -16
  102. package/src/lib/app-utils/node-definition-sanitizer.js +24 -20
  103. package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -1
  104. package/src/lib/app-utils/node-interactions.d.ts +7 -7
  105. package/src/lib/app-utils/node-interactions.js.map +1 -1
  106. package/src/lib/app-utils/notifications.d.ts +78 -47
  107. package/src/lib/app-utils/notifications.js +63 -44
  108. package/src/lib/app-utils/notifications.js.map +1 -1
  109. package/src/lib/app-utils/payments-configured.d.ts +8 -21
  110. package/src/lib/app-utils/payments-configured.js +8 -23
  111. package/src/lib/app-utils/payments-configured.js.map +1 -1
  112. package/src/lib/app-utils/plan-entitlements.d.ts +43 -128
  113. package/src/lib/app-utils/plan-entitlements.js +145 -304
  114. package/src/lib/app-utils/plan-entitlements.js.map +1 -1
  115. package/src/lib/app-utils/platform-brand.d.ts +10 -0
  116. package/src/lib/app-utils/platform-brand.js +14 -3
  117. package/src/lib/app-utils/platform-brand.js.map +1 -1
  118. package/src/lib/app-utils/plugin-api-rate-limit.d.ts +7 -1
  119. package/src/lib/app-utils/plugin-api-rate-limit.js +11 -3
  120. package/src/lib/app-utils/plugin-api-rate-limit.js.map +1 -1
  121. package/src/lib/app-utils/plugin-bundle-checks.js +2 -2
  122. package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
  123. package/src/lib/app-utils/plugin-host-events.generated.d.ts +12 -0
  124. package/src/lib/app-utils/plugin-host-events.generated.js +169 -0
  125. package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -0
  126. package/src/lib/app-utils/plugin-release-flags.generated.d.ts +12 -0
  127. package/src/lib/app-utils/plugin-release-flags.generated.js +106 -0
  128. package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -0
  129. package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
  130. package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
  131. package/src/lib/app-utils/release-flags.d.ts +14 -5
  132. package/src/lib/app-utils/release-flags.js +21 -125
  133. package/src/lib/app-utils/release-flags.js.map +1 -1
  134. package/src/lib/app-utils/repeat-sources.d.ts +9 -0
  135. package/src/lib/app-utils/repeat-sources.js.map +1 -1
  136. package/src/lib/app-utils/retained-refusals.d.ts +73 -0
  137. package/src/lib/app-utils/retained-refusals.js +186 -0
  138. package/src/lib/app-utils/retained-refusals.js.map +1 -0
  139. package/src/lib/app-utils/scope-tokens.d.ts +27 -0
  140. package/src/lib/app-utils/scope-tokens.js +61 -0
  141. package/src/lib/app-utils/scope-tokens.js.map +1 -1
  142. package/src/lib/app-utils/screen-link-context.js +3 -3
  143. package/src/lib/app-utils/screen-link-context.js.map +1 -1
  144. package/src/lib/app-utils/screen-route.d.ts +15 -0
  145. package/src/lib/app-utils/screen-route.js +21 -1
  146. package/src/lib/app-utils/screen-route.js.map +1 -1
  147. package/src/lib/app-utils/seo-audit.d.ts +132 -0
  148. package/src/lib/app-utils/seo-audit.js +246 -0
  149. package/src/lib/app-utils/seo-audit.js.map +1 -0
  150. package/src/lib/app-utils/seo-keywords.d.ts +48 -0
  151. package/src/lib/app-utils/seo-keywords.js +60 -0
  152. package/src/lib/app-utils/seo-keywords.js.map +1 -0
  153. package/src/lib/app-utils/seo-page-facts.d.ts +66 -0
  154. package/src/lib/app-utils/seo-page-facts.js +155 -0
  155. package/src/lib/app-utils/seo-page-facts.js.map +1 -0
  156. package/src/lib/app-utils/seo-site-scan.d.ts +126 -0
  157. package/src/lib/app-utils/seo-site-scan.js +162 -0
  158. package/src/lib/app-utils/seo-site-scan.js.map +1 -0
  159. package/src/lib/app-utils/seo-title-variables.js +1 -1
  160. package/src/lib/app-utils/seo-title-variables.js.map +1 -1
  161. package/src/lib/app-utils/server.d.ts +6 -9
  162. package/src/lib/app-utils/server.js +17 -27
  163. package/src/lib/app-utils/server.js.map +1 -1
  164. package/src/lib/app-utils/site-interactions.d.ts +501 -0
  165. package/src/lib/app-utils/site-interactions.js +554 -0
  166. package/src/lib/app-utils/site-interactions.js.map +1 -0
  167. package/src/lib/app-utils/{marketplace-theme.d.ts → site-theme.d.ts} +13 -2
  168. package/src/lib/app-utils/{marketplace-theme.js → site-theme.js} +5 -2
  169. package/src/lib/app-utils/site-theme.js.map +1 -0
  170. package/src/lib/app-utils/sitemap.d.ts +4 -3
  171. package/src/lib/app-utils/sitemap.js +9 -7
  172. package/src/lib/app-utils/sitemap.js.map +1 -1
  173. package/src/lib/app-utils/stale-build-error.d.ts +51 -0
  174. package/src/lib/app-utils/stale-build-error.js +100 -1
  175. package/src/lib/app-utils/stale-build-error.js.map +1 -1
  176. package/src/lib/app-utils/starter-templates.d.ts +20 -0
  177. package/src/lib/app-utils/starter-templates.js +49 -0
  178. package/src/lib/app-utils/starter-templates.js.map +1 -1
  179. package/src/lib/app-utils/tax-jurisdiction-figures.d.ts +118 -0
  180. package/src/lib/app-utils/tax-jurisdiction-figures.js +123 -0
  181. package/src/lib/app-utils/tax-jurisdiction-figures.js.map +1 -0
  182. package/src/lib/app-utils/theme-library.d.ts +10 -3
  183. package/src/lib/app-utils/theme-library.js +13 -4
  184. package/src/lib/app-utils/theme-library.js.map +1 -1
  185. package/src/lib/app-utils/{campaign-attribution.d.ts → utm-attribution.d.ts} +11 -6
  186. package/src/lib/app-utils/{campaign-attribution.js → utm-attribution.js} +13 -8
  187. package/src/lib/app-utils/utm-attribution.js.map +1 -0
  188. package/src/lib/app-utils/{campaign-forwarding.d.ts → utm-forwarding.d.ts} +14 -14
  189. package/src/lib/app-utils/{campaign-forwarding.js → utm-forwarding.js} +26 -26
  190. package/src/lib/app-utils/utm-forwarding.js.map +1 -0
  191. package/src/lib/app-utils/video-object.js +6 -5
  192. package/src/lib/app-utils/video-object.js.map +1 -1
  193. package/src/lib/app-utils/visitor-consent.d.ts +41 -164
  194. package/src/lib/app-utils/visitor-consent.js +66 -277
  195. package/src/lib/app-utils/visitor-consent.js.map +1 -1
  196. package/src/lib/app-utils/web-vitals-rum.d.ts +7 -7
  197. package/src/lib/app-utils/web-vitals-rum.js +31 -63
  198. package/src/lib/app-utils/web-vitals-rum.js.map +1 -1
  199. package/src/lib/app-utils/where-used.d.ts +11 -6
  200. package/src/lib/app-utils/where-used.js +3 -8
  201. package/src/lib/app-utils/where-used.js.map +1 -1
  202. package/src/lib/canvas-manager/canvas-manager.js +3 -3
  203. package/src/lib/canvas-manager/canvas-manager.js.map +1 -1
  204. package/src/lib/foundation/definitions/components.types.d.ts +1 -1
  205. package/src/lib/foundation/definitions/components.types.js +1 -1
  206. package/src/lib/foundation/definitions/components.types.js.map +1 -1
  207. package/src/lib/foundation/definitions/org-billing.types.d.ts +18 -135
  208. package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
  209. package/src/lib/foundation/definitions/organization.types.d.ts +6 -3
  210. package/src/lib/foundation/definitions/organization.types.js.map +1 -1
  211. package/src/lib/foundation/definitions/platform.types.d.ts +27 -5
  212. package/src/lib/foundation/definitions/platform.types.js.map +1 -1
  213. package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +16 -0
  214. package/src/lib/foundation/definitions/write-deny-coverage.util.js +20 -0
  215. package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
  216. package/src/lib/foundation/index.d.ts +1 -1
  217. package/src/lib/foundation/index.js.map +1 -1
  218. package/src/lib/plugin-manager/besigner-documents.d.ts +96 -0
  219. package/src/lib/plugin-manager/besigner-documents.js +61 -0
  220. package/src/lib/plugin-manager/besigner-documents.js.map +1 -0
  221. package/src/lib/plugin-manager/computed-variables.d.ts +71 -0
  222. package/src/lib/plugin-manager/computed-variables.js +74 -0
  223. package/src/lib/plugin-manager/computed-variables.js.map +1 -0
  224. package/src/lib/plugin-manager/custom-fields.d.ts +29 -2
  225. package/src/lib/plugin-manager/custom-fields.js +47 -1
  226. package/src/lib/plugin-manager/custom-fields.js.map +1 -1
  227. package/src/lib/plugin-manager/feature-plugins.d.ts +292 -20
  228. package/src/lib/plugin-manager/feature-plugins.js +105 -17
  229. package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
  230. package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +123 -0
  231. package/src/lib/plugin-manager/first-party-plugins.generated.js +785 -14
  232. package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
  233. package/src/lib/plugin-manager/plugin-activity-actions.d.ts +12 -0
  234. package/src/lib/plugin-manager/plugin-activity-actions.js +25 -0
  235. package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -1
  236. package/src/lib/plugin-manager/plugin-contributions.d.ts +1 -1
  237. package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
  238. package/src/lib/plugin-manager/plugin-declarations-repair.d.ts +36 -0
  239. package/src/lib/plugin-manager/plugin-declarations-repair.js +64 -0
  240. package/src/lib/plugin-manager/plugin-declarations-repair.js.map +1 -0
  241. package/src/lib/plugin-manager/plugin-dependents.d.ts +101 -0
  242. package/src/lib/plugin-manager/plugin-dependents.js +75 -0
  243. package/src/lib/plugin-manager/plugin-dependents.js.map +1 -0
  244. package/src/lib/plugin-manager/plugin-distribution.d.ts +55 -0
  245. package/src/lib/plugin-manager/plugin-distribution.js +45 -0
  246. package/src/lib/plugin-manager/plugin-distribution.js.map +1 -0
  247. package/src/lib/plugin-manager/plugin-host-collections.d.ts +17 -0
  248. package/src/lib/plugin-manager/plugin-host-collections.js +6 -0
  249. package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -1
  250. package/src/lib/plugin-manager/plugin-host-resources.d.ts +133 -0
  251. package/src/lib/plugin-manager/plugin-host-resources.js +52 -0
  252. package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -0
  253. package/src/lib/plugin-manager/plugin-org-erasure.d.ts +28 -2
  254. package/src/lib/plugin-manager/plugin-org-erasure.js +61 -2
  255. package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
  256. package/src/lib/plugin-manager/plugin-plan-entitlements.d.ts +123 -0
  257. package/src/lib/plugin-manager/plugin-plan-entitlements.js +101 -0
  258. package/src/lib/plugin-manager/plugin-plan-entitlements.js.map +1 -0
  259. package/src/lib/plugin-manager/plugin-record-index.d.ts +95 -0
  260. package/src/lib/plugin-manager/plugin-record-index.js +51 -0
  261. package/src/lib/plugin-manager/plugin-record-index.js.map +1 -0
  262. package/src/lib/plugin-manager/plugin-record-written.d.ts +58 -0
  263. package/src/lib/plugin-manager/plugin-record-written.js +42 -0
  264. package/src/lib/plugin-manager/plugin-record-written.js.map +1 -0
  265. package/src/lib/plugin-manager/plugin-revenue-sources.d.ts +213 -0
  266. package/src/lib/plugin-manager/plugin-revenue-sources.js +194 -0
  267. package/src/lib/plugin-manager/plugin-revenue-sources.js.map +1 -0
  268. package/src/lib/plugin-manager/plugin-site-bundle.d.ts +145 -0
  269. package/src/lib/plugin-manager/plugin-site-bundle.js +78 -0
  270. package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -0
  271. package/src/lib/plugin-manager/plugin-site-cache.d.ts +12 -0
  272. package/src/lib/plugin-manager/plugin-site-cache.js.map +1 -1
  273. package/src/lib/plugin-manager/plugin-site-export.d.ts +81 -0
  274. package/src/lib/plugin-manager/plugin-site-export.js +40 -0
  275. package/src/lib/plugin-manager/plugin-site-export.js.map +1 -0
  276. package/src/lib/plugin-manager/plugin-sitemap-sections.d.ts +88 -0
  277. package/src/lib/plugin-manager/plugin-sitemap-sections.js +29 -0
  278. package/src/lib/plugin-manager/plugin-sitemap-sections.js.map +1 -0
  279. package/src/lib/plugin-manager/plugin-tax-profile.d.ts +20 -4
  280. package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
  281. package/src/lib/plugin-manager/plugin-tax-return-sources.d.ts +255 -0
  282. package/src/lib/plugin-manager/plugin-tax-return-sources.js +191 -0
  283. package/src/lib/plugin-manager/plugin-tax-return-sources.js.map +1 -0
  284. package/src/lib/plugin-manager/plugin-template-sources.d.ts +75 -0
  285. package/src/lib/plugin-manager/plugin-template-sources.js +37 -0
  286. package/src/lib/plugin-manager/plugin-template-sources.js.map +1 -0
  287. package/src/lib/plugin-manager/plugin-usage-axes.d.ts +107 -0
  288. package/src/lib/plugin-manager/plugin-usage-axes.js +107 -0
  289. package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -0
  290. package/src/lib/plugin-manager/plugin-zones.d.ts +4 -5
  291. package/src/lib/plugin-manager/plugin-zones.js.map +1 -1
  292. package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
  293. package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
  294. package/src/lib/plugin-manager/record-captured-contact.d.ts +1 -14
  295. package/src/lib/plugin-manager/record-captured-contact.js +10 -32
  296. package/src/lib/plugin-manager/record-captured-contact.js.map +1 -1
  297. package/src/lib/plugin-manager/repeat-rows.d.ts +124 -0
  298. package/src/lib/plugin-manager/repeat-rows.js +108 -0
  299. package/src/lib/plugin-manager/repeat-rows.js.map +1 -0
  300. package/src/lib/plugin-manager/submission-record-target.d.ts +144 -0
  301. package/src/lib/plugin-manager/submission-record-target.js +125 -0
  302. package/src/lib/plugin-manager/submission-record-target.js.map +1 -0
  303. package/src/lib/plugin-manager/video-embed-provider.d.ts +108 -0
  304. package/src/lib/plugin-manager/video-embed-provider.js +165 -0
  305. package/src/lib/plugin-manager/video-embed-provider.js.map +1 -0
  306. package/src/lib/types/nodes.d.ts +2 -2
  307. package/src/lib/types/nodes.js.map +1 -1
  308. package/src/lib/app-utils/campaign-attribution.js.map +0 -1
  309. package/src/lib/app-utils/campaign-forwarding.js.map +0 -1
  310. package/src/lib/app-utils/contact-holder.d.ts +0 -68
  311. package/src/lib/app-utils/contact-holder.js +0 -129
  312. package/src/lib/app-utils/contact-holder.js.map +0 -1
  313. package/src/lib/app-utils/contact-merge.d.ts +0 -136
  314. package/src/lib/app-utils/contact-merge.js +0 -412
  315. package/src/lib/app-utils/contact-merge.js.map +0 -1
  316. package/src/lib/app-utils/crm-csv.d.ts +0 -239
  317. package/src/lib/app-utils/crm-csv.js +0 -467
  318. package/src/lib/app-utils/crm-csv.js.map +0 -1
  319. package/src/lib/app-utils/crm-digest.d.ts +0 -185
  320. package/src/lib/app-utils/crm-digest.js +0 -272
  321. package/src/lib/app-utils/crm-digest.js.map +0 -1
  322. package/src/lib/app-utils/form-dataset-binding.d.ts +0 -50
  323. package/src/lib/app-utils/form-dataset-binding.js +0 -74
  324. package/src/lib/app-utils/form-dataset-binding.js.map +0 -1
  325. package/src/lib/app-utils/list-import.d.ts +0 -158
  326. package/src/lib/app-utils/list-import.js +0 -332
  327. package/src/lib/app-utils/list-import.js.map +0 -1
  328. package/src/lib/app-utils/marketplace-listing-visibility.d.ts +0 -122
  329. package/src/lib/app-utils/marketplace-listing-visibility.js +0 -147
  330. package/src/lib/app-utils/marketplace-listing-visibility.js.map +0 -1
  331. package/src/lib/app-utils/marketplace-overrides.js.map +0 -1
  332. package/src/lib/app-utils/marketplace-provenance.js.map +0 -1
  333. package/src/lib/app-utils/marketplace-theme.js.map +0 -1
  334. package/src/lib/app-utils/marketplace-update-state.d.ts +0 -119
  335. package/src/lib/app-utils/marketplace-update-state.js +0 -132
  336. package/src/lib/app-utils/marketplace-update-state.js.map +0 -1
  337. package/src/lib/app-utils/publisher-agreement.d.ts +0 -289
  338. package/src/lib/app-utils/publisher-agreement.js +0 -321
  339. package/src/lib/app-utils/publisher-agreement.js.map +0 -1
  340. package/src/lib/app-utils/wistia-embed.d.ts +0 -104
  341. package/src/lib/app-utils/wistia-embed.js +0 -138
  342. package/src/lib/app-utils/wistia-embed.js.map +0 -1
  343. package/src/lib/app-utils/workflows.d.ts +0 -136
  344. package/src/lib/app-utils/workflows.js +0 -346
  345. package/src/lib/app-utils/workflows.js.map +0 -1
  346. package/src/lib/plugin-manager/commerce-zone-props.d.ts +0 -172
  347. package/src/lib/plugin-manager/commerce-zone-props.js +0 -31
  348. package/src/lib/plugin-manager/commerce-zone-props.js.map +0 -1
@@ -0,0 +1,246 @@
1
+ import { _ as _extends } from "@swc/helpers/_/_extends";
2
+ /**
3
+ * @license
4
+ * Copyright 2026 Aglyn LLC
5
+ *
6
+ * Licensed under the Apache License, Version 2.0 (the "License");
7
+ * you may not use this file except in compliance with the License.
8
+ * You may obtain a copy of the License at
9
+ *
10
+ * http://www.apache.org/licenses/LICENSE-2.0
11
+ *
12
+ * Unless required by applicable law or agreed to in writing, software
13
+ * distributed under the License is distributed on an "AS IS" BASIS,
14
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15
+ * See the License for the specific language governing permissions and
16
+ * limitations under the License.
17
+ */ import { SEO_LISTING_FIELDS, seoListingFieldTooLong } from "./seo-listing-fields.js";
18
+ import { seoKeywordCoverage, seoKeywordList } from "./seo-keywords.js";
19
+ /**
20
+ * The SEO check: every page a site publishes held to what a search result
21
+ * and a crawler need, and the site held to what a search engine and an agent
22
+ * read about it. Pure and provider-free — nothing here reads, writes or
23
+ * generates — so any site owner is told what is wrong, and anything that
24
+ * proposes a fix answers the same findings rather than judging a page its
25
+ * own way.
26
+ *
27
+ * Per page: a title and a description of its own, within the lengths the
28
+ * listing editor enforces (`seo-listing-fields.ts`) and used on no other
29
+ * page; one main heading that says something; a description on every image;
30
+ * a link from somewhere else on the site; and the target keywords a person
31
+ * named, reported where the page already says them.
32
+ * Per site: whether search engines are told to stay away, whether the
33
+ * structured data names and describes the publisher, and whether agents get
34
+ * guidance of the author's own in `/llms.txt`.
35
+ *
36
+ * Keyword coverage is reported, never forced: a page that does not say a
37
+ * keyword is told so, and told to use it only if the page is about it.
38
+ *
39
+ * The pages come from `seo-site-scan.ts`, which reads a site the way its
40
+ * sitemap lists it.
41
+ */ /** Pages one check covers. A larger site is checked on its first pages by path, and says how many it left. */ export const SEO_AUDIT_MAX_PAGES = 150;
42
+ /** What each finding is called where it is listed. */ export const SEO_FINDING_LABELS = {
43
+ 'title-missing': 'No search title',
44
+ 'title-too-long': 'Title too long',
45
+ 'title-duplicate': 'Title used on another page',
46
+ 'description-missing': 'No search description',
47
+ 'description-too-long': 'Description too long',
48
+ 'description-duplicate': 'Description used on another page',
49
+ 'h1-missing': 'No main heading',
50
+ 'h1-multiple': 'More than one main heading',
51
+ 'h1-thin': 'Main heading says little',
52
+ 'image-alt-missing': 'Images without a description',
53
+ orphan: 'No page links here',
54
+ 'keyword-missing': 'Target keyword not covered',
55
+ 'search-discouraged': 'Search engines are discouraged',
56
+ 'entity-incomplete': 'Structured data incomplete',
57
+ 'llms-guidance-missing': 'No guidance for AI agents'
58
+ };
59
+ /** What each severity takes off a page's score. */ const SEVERITY_WEIGHT = {
60
+ high: 25,
61
+ medium: 10,
62
+ low: 3
63
+ };
64
+ /** A main heading this short or this generic says nothing a searcher can use. */ const THIN_H1_MIN_CHARS = 10;
65
+ const GENERIC_H1 = new Set([
66
+ 'home',
67
+ 'homepage',
68
+ 'home page',
69
+ 'welcome',
70
+ 'hello',
71
+ 'untitled',
72
+ 'new page',
73
+ 'page',
74
+ 'title',
75
+ 'heading'
76
+ ]);
77
+ const blank = (value)=>!String(value != null ? value : '').trim();
78
+ const normalized = (value)=>String(value != null ? value : '').replace(/\s+/g, ' ').trim().toLowerCase();
79
+ /** A path as the keyword lines and the routing map both spell it: leading slash, no trailing one. */ export function seoNormalizePath(path) {
80
+ const trimmed = String(path != null ? path : '').trim();
81
+ if (!trimmed || trimmed === '/') return '/';
82
+ return `/${trimmed.replace(/^\/+/, '').replace(/\/+$/, '')}`;
83
+ }
84
+ /**
85
+ * Target keywords per page, one line a page: `/pricing: plans, pricing`.
86
+ * A line without a path names no page; a path the site does not publish is
87
+ * reported by the scan that matches the lines to pages.
88
+ */ export function parseSeoKeywordLines(raw) {
89
+ const out = {};
90
+ for (const line of String(raw != null ? raw : '').split('\n')){
91
+ var _out_path;
92
+ const at = line.indexOf(':');
93
+ if (at <= 0) continue;
94
+ const path = seoNormalizePath(line.slice(0, at));
95
+ const keywords = seoKeywordList(line.slice(at + 1));
96
+ if (keywords.length) out[path] = seoKeywordList([
97
+ ...(_out_path = out[path]) != null ? _out_path : [],
98
+ ...keywords
99
+ ]);
100
+ }
101
+ return out;
102
+ }
103
+ function finding(code, severity, message, extra = {}) {
104
+ return _extends({
105
+ code,
106
+ severity,
107
+ message
108
+ }, extra);
109
+ }
110
+ /** The pages that share one value, by that value. */ function sharedValues(pages, read) {
111
+ const groups = new Map();
112
+ for (const page of pages){
113
+ var _groups_get;
114
+ const key = normalized(read(page));
115
+ if (!key) continue;
116
+ groups.set(key, [
117
+ ...(_groups_get = groups.get(key)) != null ? _groups_get : [],
118
+ page.screenId
119
+ ]);
120
+ }
121
+ return new Map([
122
+ ...groups
123
+ ].filter(([, ids])=>ids.length > 1));
124
+ }
125
+ function coverageOf(page) {
126
+ var _page_facts_h1s_;
127
+ return seoKeywordCoverage(page.keywords, {
128
+ title: page.seo.title,
129
+ description: page.seo.description,
130
+ h1: (_page_facts_h1s_ = page.facts.h1s[0]) == null ? void 0 : _page_facts_h1s_.text,
131
+ body: page.facts.text
132
+ });
133
+ }
134
+ function pageFindings(page, titles, descriptions, coverage) {
135
+ const out = [];
136
+ const title = page.seo.title;
137
+ if (blank(title)) {
138
+ out.push(finding('title-missing', 'medium', 'This page has no search title, so results show its name beside the site title.'));
139
+ } else {
140
+ var _titles_get;
141
+ if (seoListingFieldTooLong('title', title)) {
142
+ out.push(finding('title-too-long', 'medium', `The search title is over ${SEO_LISTING_FIELDS.title.maxLength} characters and will be cut off in results.`));
143
+ }
144
+ const twins = (_titles_get = titles.get(normalized(title))) == null ? void 0 : _titles_get.filter((id)=>id !== page.screenId);
145
+ if (twins == null ? void 0 : twins.length) {
146
+ out.push(finding('title-duplicate', 'high', 'Another page uses the same search title, so results cannot tell them apart.', {
147
+ screenIds: twins
148
+ }));
149
+ }
150
+ }
151
+ const description = page.seo.description;
152
+ if (blank(description) && blank(page.description)) {
153
+ out.push(finding('description-missing', 'medium', 'This page has no search description, so results show the site’s.'));
154
+ } else if (!blank(description)) {
155
+ var _descriptions_get;
156
+ if (seoListingFieldTooLong('description', description)) {
157
+ out.push(finding('description-too-long', 'low', `The search description is over ${SEO_LISTING_FIELDS.description.maxLength} characters and will be cut off.`));
158
+ }
159
+ const twins = (_descriptions_get = descriptions.get(normalized(description))) == null ? void 0 : _descriptions_get.filter((id)=>id !== page.screenId);
160
+ if (twins == null ? void 0 : twins.length) {
161
+ out.push(finding('description-duplicate', 'medium', 'Another page uses the same search description.', {
162
+ screenIds: twins
163
+ }));
164
+ }
165
+ }
166
+ const { h1s } = page.facts;
167
+ if (!h1s.length) {
168
+ out.push(finding('h1-missing', 'high', 'This page has no main heading.'));
169
+ } else {
170
+ if (h1s.length > 1) {
171
+ out.push(finding('h1-multiple', 'medium', `This page has ${h1s.length} main headings; it should have one.`, {
172
+ nodeIds: h1s.slice(1).map((heading)=>heading.nodeId).filter((id)=>Boolean(id))
173
+ }));
174
+ }
175
+ const first = h1s[0];
176
+ if (first.text.length < THIN_H1_MIN_CHARS || GENERIC_H1.has(normalized(first.text))) {
177
+ out.push(finding('h1-thin', 'medium', `The main heading “${first.text}” says little about the page.`, {
178
+ nodeIds: first.nodeId ? [
179
+ first.nodeId
180
+ ] : []
181
+ }));
182
+ }
183
+ }
184
+ const missingAlt = page.facts.imagesMissingAlt;
185
+ if (missingAlt.length) {
186
+ out.push(finding('image-alt-missing', 'medium', `${missingAlt.length} ${missingAlt.length === 1 ? 'image has' : 'images have'} no description for readers who cannot see ${missingAlt.length === 1 ? 'it' : 'them'}.`, {
187
+ nodeIds: missingAlt.map((image)=>image.nodeId)
188
+ }));
189
+ }
190
+ if (!page.linkedFrom && page.path !== '/') {
191
+ out.push(finding('orphan', 'medium', 'No other page or shared layout links here, so visitors and crawlers may never find it.'));
192
+ }
193
+ for (const entry of coverage){
194
+ if (!entry.inTitle && !entry.inDescription && !entry.inH1 && !entry.inBody) {
195
+ out.push(finding('keyword-missing', 'low', `The page never says “${entry.keyword}”. Only use it if the page is about it.`, {
196
+ keyword: entry.keyword
197
+ }));
198
+ }
199
+ }
200
+ return out;
201
+ }
202
+ function scoreOf(findings) {
203
+ return Math.max(0, 100 - findings.reduce((sum, entry)=>sum + SEVERITY_WEIGHT[entry.severity], 0));
204
+ }
205
+ /** Check the pages and the site; nothing here reads, writes or generates. */ export function seoAudit(pages, site, options = {}) {
206
+ var _options_skipped, _options_notes;
207
+ const titles = sharedValues(pages, (page)=>page.seo.title);
208
+ const descriptions = sharedValues(pages, (page)=>page.seo.description);
209
+ const reports = pages.map((page)=>{
210
+ const keywords = coverageOf(page);
211
+ const findings = pageFindings(page, titles, descriptions, keywords);
212
+ return {
213
+ screenId: page.screenId,
214
+ path: page.path,
215
+ name: page.name,
216
+ versionId: page.versionId,
217
+ score: scoreOf(findings),
218
+ findings,
219
+ keywords
220
+ };
221
+ });
222
+ const siteFindings = [];
223
+ if (site.discouraged) {
224
+ siteFindings.push(finding('search-discouraged', 'high', 'Search engines are asked not to index this site, so none of its pages appear in results.'));
225
+ }
226
+ if (blank(site.entity.name) || blank(site.entity.description)) {
227
+ siteFindings.push(finding('entity-incomplete', 'low', 'The structured data does not name and describe who publishes the site.'));
228
+ }
229
+ if (blank(site.agent.whenToUse)) {
230
+ siteFindings.push(finding('llms-guidance-missing', 'low', '/llms.txt carries no guidance of your own for AI agents.'));
231
+ }
232
+ return {
233
+ pages: reports,
234
+ skipped: Math.max(0, (_options_skipped = options.skipped) != null ? _options_skipped : 0),
235
+ site: siteFindings,
236
+ score: reports.length ? Math.round(reports.reduce((sum, report)=>sum + report.score, 0) / reports.length) : 100,
237
+ notes: [
238
+ ...(_options_notes = options.notes) != null ? _options_notes : []
239
+ ]
240
+ };
241
+ }
242
+ /** How many findings a report holds, the site's included. */ export function seoAuditFindingCount(report) {
243
+ return report.pages.reduce((sum, page)=>sum + page.findings.length, report.site.length);
244
+ }
245
+
246
+ //# sourceMappingURL=seo-audit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/seo-audit.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { SEO_LISTING_FIELDS, seoListingFieldTooLong } from './seo-listing-fields'\nimport { seoKeywordCoverage, seoKeywordList, type SeoKeywordCoverage } from './seo-keywords'\nimport type { SeoPageFacts } from './seo-page-facts'\n\n/**\n * The SEO check: every page a site publishes held to what a search result\n * and a crawler need, and the site held to what a search engine and an agent\n * read about it. Pure and provider-free — nothing here reads, writes or\n * generates — so any site owner is told what is wrong, and anything that\n * proposes a fix answers the same findings rather than judging a page its\n * own way.\n *\n * Per page: a title and a description of its own, within the lengths the\n * listing editor enforces (`seo-listing-fields.ts`) and used on no other\n * page; one main heading that says something; a description on every image;\n * a link from somewhere else on the site; and the target keywords a person\n * named, reported where the page already says them.\n * Per site: whether search engines are told to stay away, whether the\n * structured data names and describes the publisher, and whether agents get\n * guidance of the author's own in `/llms.txt`.\n *\n * Keyword coverage is reported, never forced: a page that does not say a\n * keyword is told so, and told to use it only if the page is about it.\n *\n * The pages come from `seo-site-scan.ts`, which reads a site the way its\n * sitemap lists it.\n */\n\n/** Pages one check covers. A larger site is checked on its first pages by path, and says how many it left. */\nexport const SEO_AUDIT_MAX_PAGES = 150\n\nexport type SeoFindingCode =\n | 'title-missing'\n | 'title-too-long'\n | 'title-duplicate'\n | 'description-missing'\n | 'description-too-long'\n | 'description-duplicate'\n | 'h1-missing'\n | 'h1-multiple'\n | 'h1-thin'\n | 'image-alt-missing'\n | 'orphan'\n | 'keyword-missing'\n | 'search-discouraged'\n | 'entity-incomplete'\n | 'llms-guidance-missing'\n\nexport type SeoSeverity = 'high' | 'medium' | 'low'\n\nexport interface SeoFinding {\n code: SeoFindingCode\n severity: SeoSeverity\n /** One customer-safe sentence. */\n message: string\n /** The elements it is about: the images without a description, the extra headings. */\n nodeIds?: string[]\n /** The other pages it involves: a duplicate's twins. */\n screenIds?: string[]\n /** The target keyword, for `keyword-missing`. */\n keyword?: string\n}\n\n/** One checked page. */\nexport interface SeoPageReport {\n screenId: string\n /** The public path, `/` for the home page. */\n path: string\n name: string\n /** The published version the page was checked on. */\n versionId: string | null\n /** 100 with nothing found, less for each finding by its severity. */\n score: number\n findings: SeoFinding[]\n keywords: SeoKeywordCoverage[]\n}\n\n/** The check of a site: every checked page, and what was found about the site itself. */\nexport interface SeoAuditReport {\n pages: SeoPageReport[]\n /** Published pages past {@link SEO_AUDIT_MAX_PAGES}, not checked. */\n skipped: number\n /** Findings about the site rather than a page. */\n site: SeoFinding[]\n /** The average page score. */\n score: number\n /** Customer-safe lines about the check itself: a keyword line naming no page, say. */\n notes: string[]\n}\n\n/** What each finding is called where it is listed. */\nexport const SEO_FINDING_LABELS: Readonly<Record<SeoFindingCode, string>> = {\n 'title-missing': 'No search title',\n 'title-too-long': 'Title too long',\n 'title-duplicate': 'Title used on another page',\n 'description-missing': 'No search description',\n 'description-too-long': 'Description too long',\n 'description-duplicate': 'Description used on another page',\n 'h1-missing': 'No main heading',\n 'h1-multiple': 'More than one main heading',\n 'h1-thin': 'Main heading says little',\n 'image-alt-missing': 'Images without a description',\n orphan: 'No page links here',\n 'keyword-missing': 'Target keyword not covered',\n 'search-discouraged': 'Search engines are discouraged',\n 'entity-incomplete': 'Structured data incomplete',\n 'llms-guidance-missing': 'No guidance for AI agents',\n}\n\n/** What each severity takes off a page's score. */\nconst SEVERITY_WEIGHT: Record<SeoSeverity, number> = { high: 25, medium: 10, low: 3 }\n\n/** A main heading this short or this generic says nothing a searcher can use. */\nconst THIN_H1_MIN_CHARS = 10\nconst GENERIC_H1: ReadonlySet<string> = new Set([\n 'home',\n 'homepage',\n 'home page',\n 'welcome',\n 'hello',\n 'untitled',\n 'new page',\n 'page',\n 'title',\n 'heading',\n])\n\n/** One page as the check reads it. */\nexport interface SeoAuditPage {\n screenId: string\n /** The public path, `/` for the home page. */\n path: string\n name: string\n versionId: string | null\n /** The listing as the page's head renders it: a title's variables already resolved. */\n seo: {\n title?: string | null\n description?: string | null\n breadcrumb?: string | null\n image?: string | null\n imageAlt?: string | null\n }\n /** The screen's own description, which the head falls back to. */\n description?: string | null\n facts: SeoPageFacts\n /** Whether another checked page or a shared layout links here. */\n linkedFrom: boolean\n keywords: readonly string[]\n}\n\n/** The site as the check reads it. */\nexport interface SeoAuditSite {\n discouraged: boolean\n entity: { name?: string | null; description?: string | null }\n agent: { whenToUse?: string | null }\n}\n\nconst blank = (value: string | null | undefined): boolean => !String(value ?? '').trim()\nconst normalized = (value: string | null | undefined): string =>\n String(value ?? '').replace(/\\s+/g, ' ').trim().toLowerCase()\n\n/** A path as the keyword lines and the routing map both spell it: leading slash, no trailing one. */\nexport function seoNormalizePath(path: string): string {\n const trimmed = String(path ?? '').trim()\n if (!trimmed || trimmed === '/') return '/'\n return `/${trimmed.replace(/^\\/+/, '').replace(/\\/+$/, '')}`\n}\n\n/**\n * Target keywords per page, one line a page: `/pricing: plans, pricing`.\n * A line without a path names no page; a path the site does not publish is\n * reported by the scan that matches the lines to pages.\n */\nexport function parseSeoKeywordLines(raw: unknown): Record<string, string[]> {\n const out: Record<string, string[]> = {}\n for (const line of String(raw ?? '').split('\\n')) {\n const at = line.indexOf(':')\n if (at <= 0) continue\n const path = seoNormalizePath(line.slice(0, at))\n const keywords = seoKeywordList(line.slice(at + 1))\n if (keywords.length) out[path] = seoKeywordList([...(out[path] ?? []), ...keywords])\n }\n return out\n}\n\nfunction finding(\n code: SeoFindingCode,\n severity: SeoSeverity,\n message: string,\n extra: Partial<Pick<SeoFinding, 'nodeIds' | 'screenIds' | 'keyword'>> = {},\n): SeoFinding {\n return { code, severity, message, ...extra }\n}\n\n/** The pages that share one value, by that value. */\nfunction sharedValues(\n pages: readonly SeoAuditPage[],\n read: (page: SeoAuditPage) => string | null | undefined,\n): Map<string, string[]> {\n const groups = new Map<string, string[]>()\n for (const page of pages) {\n const key = normalized(read(page))\n if (!key) continue\n groups.set(key, [...(groups.get(key) ?? []), page.screenId])\n }\n return new Map([...groups].filter(([, ids]) => ids.length > 1))\n}\n\nfunction coverageOf(page: SeoAuditPage): SeoKeywordCoverage[] {\n return seoKeywordCoverage(page.keywords, {\n title: page.seo.title,\n description: page.seo.description,\n h1: page.facts.h1s[0]?.text,\n body: page.facts.text,\n })\n}\n\nfunction pageFindings(\n page: SeoAuditPage,\n titles: Map<string, string[]>,\n descriptions: Map<string, string[]>,\n coverage: readonly SeoKeywordCoverage[],\n): SeoFinding[] {\n const out: SeoFinding[] = []\n const title = page.seo.title\n if (blank(title)) {\n out.push(finding('title-missing', 'medium', 'This page has no search title, so results show its name beside the site title.'))\n } else {\n if (seoListingFieldTooLong('title', title)) {\n out.push(finding('title-too-long', 'medium', `The search title is over ${SEO_LISTING_FIELDS.title.maxLength} characters and will be cut off in results.`))\n }\n const twins = titles.get(normalized(title))?.filter((id) => id !== page.screenId)\n if (twins?.length) {\n out.push(finding('title-duplicate', 'high', 'Another page uses the same search title, so results cannot tell them apart.', { screenIds: twins }))\n }\n }\n\n const description = page.seo.description\n if (blank(description) && blank(page.description)) {\n out.push(finding('description-missing', 'medium', 'This page has no search description, so results show the site’s.'))\n } else if (!blank(description)) {\n if (seoListingFieldTooLong('description', description)) {\n out.push(finding('description-too-long', 'low', `The search description is over ${SEO_LISTING_FIELDS.description.maxLength} characters and will be cut off.`))\n }\n const twins = descriptions.get(normalized(description))?.filter((id) => id !== page.screenId)\n if (twins?.length) {\n out.push(finding('description-duplicate', 'medium', 'Another page uses the same search description.', { screenIds: twins }))\n }\n }\n\n const { h1s } = page.facts\n if (!h1s.length) {\n out.push(finding('h1-missing', 'high', 'This page has no main heading.'))\n } else {\n if (h1s.length > 1) {\n out.push(\n finding('h1-multiple', 'medium', `This page has ${h1s.length} main headings; it should have one.`, {\n nodeIds: h1s.slice(1).map((heading) => heading.nodeId).filter((id): id is string => Boolean(id)),\n }),\n )\n }\n const first = h1s[0]\n if (first.text.length < THIN_H1_MIN_CHARS || GENERIC_H1.has(normalized(first.text))) {\n out.push(\n finding('h1-thin', 'medium', `The main heading “${first.text}” says little about the page.`, {\n nodeIds: first.nodeId ? [first.nodeId] : [],\n }),\n )\n }\n }\n\n const missingAlt = page.facts.imagesMissingAlt\n if (missingAlt.length) {\n out.push(\n finding(\n 'image-alt-missing',\n 'medium',\n `${missingAlt.length} ${missingAlt.length === 1 ? 'image has' : 'images have'} no description for readers who cannot see ${missingAlt.length === 1 ? 'it' : 'them'}.`,\n { nodeIds: missingAlt.map((image) => image.nodeId) },\n ),\n )\n }\n\n if (!page.linkedFrom && page.path !== '/') {\n out.push(finding('orphan', 'medium', 'No other page or shared layout links here, so visitors and crawlers may never find it.'))\n }\n\n for (const entry of coverage) {\n if (!entry.inTitle && !entry.inDescription && !entry.inH1 && !entry.inBody) {\n out.push(\n finding('keyword-missing', 'low', `The page never says “${entry.keyword}”. Only use it if the page is about it.`, {\n keyword: entry.keyword,\n }),\n )\n }\n }\n return out\n}\n\nfunction scoreOf(findings: readonly SeoFinding[]): number {\n return Math.max(0, 100 - findings.reduce((sum, entry) => sum + SEVERITY_WEIGHT[entry.severity], 0))\n}\n\n/** Check the pages and the site; nothing here reads, writes or generates. */\nexport function seoAudit(\n pages: readonly SeoAuditPage[],\n site: SeoAuditSite,\n options: { skipped?: number; notes?: readonly string[] } = {},\n): SeoAuditReport {\n const titles = sharedValues(pages, (page) => page.seo.title)\n const descriptions = sharedValues(pages, (page) => page.seo.description)\n const reports: SeoPageReport[] = pages.map((page) => {\n const keywords = coverageOf(page)\n const findings = pageFindings(page, titles, descriptions, keywords)\n return {\n screenId: page.screenId,\n path: page.path,\n name: page.name,\n versionId: page.versionId,\n score: scoreOf(findings),\n findings,\n keywords,\n }\n })\n\n const siteFindings: SeoFinding[] = []\n if (site.discouraged) {\n siteFindings.push(finding('search-discouraged', 'high', 'Search engines are asked not to index this site, so none of its pages appear in results.'))\n }\n if (blank(site.entity.name) || blank(site.entity.description)) {\n siteFindings.push(finding('entity-incomplete', 'low', 'The structured data does not name and describe who publishes the site.'))\n }\n if (blank(site.agent.whenToUse)) {\n siteFindings.push(finding('llms-guidance-missing', 'low', '/llms.txt carries no guidance of your own for AI agents.'))\n }\n\n return {\n pages: reports,\n skipped: Math.max(0, options.skipped ?? 0),\n site: siteFindings,\n score: reports.length\n ? Math.round(reports.reduce((sum, report) => sum + report.score, 0) / reports.length)\n : 100,\n notes: [...(options.notes ?? [])],\n }\n}\n\n/** How many findings a report holds, the site's included. */\nexport function seoAuditFindingCount(report: Pick<SeoAuditReport, 'pages' | 'site'>): number {\n return report.pages.reduce((sum, page) => sum + page.findings.length, report.site.length)\n}\n"],"names":["SEO_LISTING_FIELDS","seoListingFieldTooLong","seoKeywordCoverage","seoKeywordList","SEO_AUDIT_MAX_PAGES","SEO_FINDING_LABELS","orphan","SEVERITY_WEIGHT","high","medium","low","THIN_H1_MIN_CHARS","GENERIC_H1","Set","blank","value","String","trim","normalized","replace","toLowerCase","seoNormalizePath","path","trimmed","parseSeoKeywordLines","raw","out","line","split","at","indexOf","slice","keywords","length","finding","code","severity","message","extra","sharedValues","pages","read","groups","Map","page","key","set","get","screenId","filter","ids","coverageOf","title","seo","description","h1","facts","h1s","text","body","pageFindings","titles","descriptions","coverage","push","maxLength","twins","id","screenIds","nodeIds","map","heading","nodeId","Boolean","first","has","missingAlt","imagesMissingAlt","image","linkedFrom","entry","inTitle","inDescription","inH1","inBody","keyword","scoreOf","findings","Math","max","reduce","sum","seoAudit","site","options","reports","name","versionId","score","siteFindings","discouraged","entity","agent","whenToUse","skipped","round","report","notes","seoAuditFindingCount"],"mappings":"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"}
@@ -0,0 +1,48 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ /**
18
+ * Target keywords: the words a site owner wants a page found for, and where
19
+ * the page already says them.
20
+ *
21
+ * The SEO check (`seo-audit.ts`) reports a keyword a page never says, and
22
+ * anything that writes a listing holds its answer to the same count, so
23
+ * "the page says it" means one thing everywhere: the keyword as whole words,
24
+ * in any case, never inside a longer word.
25
+ */
26
+ /** Target keywords one page may name. */
27
+ export declare const SEO_MAX_KEYWORDS = 5;
28
+ /** The longest target keyword text a site's lines run to: a line a page, for a large site. */
29
+ export declare const SEO_KEYWORD_LINES_MAX_CHARS = 3000;
30
+ /** Whether, and where, a page already says a target keyword. */
31
+ export interface SeoKeywordCoverage {
32
+ keyword: string;
33
+ inTitle: boolean;
34
+ inDescription: boolean;
35
+ inH1: boolean;
36
+ inBody: boolean;
37
+ }
38
+ /** How many times `keyword` appears in `text` as whole words, any case. */
39
+ export declare function seoKeywordCount(text: string | null | undefined, keyword: string): number;
40
+ /** Keywords as a person typed them: trimmed, deduplicated, capped at {@link SEO_MAX_KEYWORDS}. */
41
+ export declare function seoKeywordList(raw: unknown): string[];
42
+ /** Where a page already says each target keyword. */
43
+ export declare function seoKeywordCoverage(keywords: readonly string[], sources: {
44
+ title?: string | null;
45
+ description?: string | null;
46
+ h1?: string | null;
47
+ body?: string | null;
48
+ }): SeoKeywordCoverage[];
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ /**
17
+ * Target keywords: the words a site owner wants a page found for, and where
18
+ * the page already says them.
19
+ *
20
+ * The SEO check (`seo-audit.ts`) reports a keyword a page never says, and
21
+ * anything that writes a listing holds its answer to the same count, so
22
+ * "the page says it" means one thing everywhere: the keyword as whole words,
23
+ * in any case, never inside a longer word.
24
+ */ /** Target keywords one page may name. */ export const SEO_MAX_KEYWORDS = 5;
25
+ /** The longest target keyword text a site's lines run to: a line a page, for a large site. */ export const SEO_KEYWORD_LINES_MAX_CHARS = 3000;
26
+ /** The longest one keyword is kept. */ const SEO_KEYWORD_MAX_CHARS = 60;
27
+ const escapeRegExp = (value)=>value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
28
+ /** How many times `keyword` appears in `text` as whole words, any case. */ export function seoKeywordCount(text, keyword) {
29
+ const needle = keyword.trim().toLowerCase();
30
+ if (!needle || !text) return 0;
31
+ const pattern = new RegExp(`(^|[^\\p{L}\\p{N}])${escapeRegExp(needle).replace(/\s+/g, '\\s+')}(?=$|[^\\p{L}\\p{N}])`, 'giu');
32
+ return [
33
+ ...text.toLowerCase().matchAll(pattern)
34
+ ].length;
35
+ }
36
+ /** Keywords as a person typed them: trimmed, deduplicated, capped at {@link SEO_MAX_KEYWORDS}. */ export function seoKeywordList(raw) {
37
+ const parts = Array.isArray(raw) ? raw.map(String) : String(raw != null ? raw : '').split(/[,\n]/);
38
+ const seen = new Set();
39
+ const out = [];
40
+ for (const part of parts){
41
+ const keyword = part.replace(/\s+/g, ' ').trim().slice(0, SEO_KEYWORD_MAX_CHARS);
42
+ const key = keyword.toLowerCase();
43
+ if (!keyword || seen.has(key)) continue;
44
+ seen.add(key);
45
+ out.push(keyword);
46
+ if (out.length >= SEO_MAX_KEYWORDS) break;
47
+ }
48
+ return out;
49
+ }
50
+ /** Where a page already says each target keyword. */ export function seoKeywordCoverage(keywords, sources) {
51
+ return keywords.map((keyword)=>({
52
+ keyword,
53
+ inTitle: seoKeywordCount(sources.title, keyword) > 0,
54
+ inDescription: seoKeywordCount(sources.description, keyword) > 0,
55
+ inH1: seoKeywordCount(sources.h1, keyword) > 0,
56
+ inBody: seoKeywordCount(sources.body, keyword) > 0
57
+ }));
58
+ }
59
+
60
+ //# sourceMappingURL=seo-keywords.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/seo-keywords.ts"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Target keywords: the words a site owner wants a page found for, and where\n * the page already says them.\n *\n * The SEO check (`seo-audit.ts`) reports a keyword a page never says, and\n * anything that writes a listing holds its answer to the same count, so\n * \"the page says it\" means one thing everywhere: the keyword as whole words,\n * in any case, never inside a longer word.\n */\n\n/** Target keywords one page may name. */\nexport const SEO_MAX_KEYWORDS = 5\n\n/** The longest target keyword text a site's lines run to: a line a page, for a large site. */\nexport const SEO_KEYWORD_LINES_MAX_CHARS = 3_000\n\n/** The longest one keyword is kept. */\nconst SEO_KEYWORD_MAX_CHARS = 60\n\n/** Whether, and where, a page already says a target keyword. */\nexport interface SeoKeywordCoverage {\n keyword: string\n inTitle: boolean\n inDescription: boolean\n inH1: boolean\n inBody: boolean\n}\n\nconst escapeRegExp = (value: string): string => value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')\n\n/** How many times `keyword` appears in `text` as whole words, any case. */\nexport function seoKeywordCount(text: string | null | undefined, keyword: string): number {\n const needle = keyword.trim().toLowerCase()\n if (!needle || !text) return 0\n const pattern = new RegExp(\n `(^|[^\\\\p{L}\\\\p{N}])${escapeRegExp(needle).replace(/\\s+/g, '\\\\s+')}(?=$|[^\\\\p{L}\\\\p{N}])`,\n 'giu',\n )\n return [...text.toLowerCase().matchAll(pattern)].length\n}\n\n/** Keywords as a person typed them: trimmed, deduplicated, capped at {@link SEO_MAX_KEYWORDS}. */\nexport function seoKeywordList(raw: unknown): string[] {\n const parts = Array.isArray(raw) ? raw.map(String) : String(raw ?? '').split(/[,\\n]/)\n const seen = new Set<string>()\n const out: string[] = []\n for (const part of parts) {\n const keyword = part.replace(/\\s+/g, ' ').trim().slice(0, SEO_KEYWORD_MAX_CHARS)\n const key = keyword.toLowerCase()\n if (!keyword || seen.has(key)) continue\n seen.add(key)\n out.push(keyword)\n if (out.length >= SEO_MAX_KEYWORDS) break\n }\n return out\n}\n\n/** Where a page already says each target keyword. */\nexport function seoKeywordCoverage(\n keywords: readonly string[],\n sources: { title?: string | null; description?: string | null; h1?: string | null; body?: string | null },\n): SeoKeywordCoverage[] {\n return keywords.map((keyword) => ({\n keyword,\n inTitle: seoKeywordCount(sources.title, keyword) > 0,\n inDescription: seoKeywordCount(sources.description, keyword) > 0,\n inH1: seoKeywordCount(sources.h1, keyword) > 0,\n inBody: seoKeywordCount(sources.body, keyword) > 0,\n }))\n}\n"],"names":["SEO_MAX_KEYWORDS","SEO_KEYWORD_LINES_MAX_CHARS","SEO_KEYWORD_MAX_CHARS","escapeRegExp","value","replace","seoKeywordCount","text","keyword","needle","trim","toLowerCase","pattern","RegExp","matchAll","length","seoKeywordList","raw","parts","Array","isArray","map","String","split","seen","Set","out","part","slice","key","has","add","push","seoKeywordCoverage","keywords","sources","inTitle","title","inDescription","description","inH1","h1","inBody","body"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GAED;;;;;;;;CAQC,GAED,uCAAuC,GACvC,OAAO,MAAMA,mBAAmB,EAAC;AAEjC,4FAA4F,GAC5F,OAAO,MAAMC,8BAA8B,KAAK;AAEhD,qCAAqC,GACrC,MAAMC,wBAAwB;AAW9B,MAAMC,eAAe,CAACC,QAA0BA,MAAMC,OAAO,CAAC,uBAAuB;AAErF,yEAAyE,GACzE,OAAO,SAASC,gBAAgBC,IAA+B,EAAEC,OAAe;IAC9E,MAAMC,SAASD,QAAQE,IAAI,GAAGC,WAAW;IACzC,IAAI,CAACF,UAAU,CAACF,MAAM,OAAO;IAC7B,MAAMK,UAAU,IAAIC,OAClB,CAAC,mBAAmB,EAAEV,aAAaM,QAAQJ,OAAO,CAAC,QAAQ,QAAQ,qBAAqB,CAAC,EACzF;IAEF,OAAO;WAAIE,KAAKI,WAAW,GAAGG,QAAQ,CAACF;KAAS,CAACG,MAAM;AACzD;AAEA,gGAAgG,GAChG,OAAO,SAASC,eAAeC,GAAY;IACzC,MAAMC,QAAQC,MAAMC,OAAO,CAACH,OAAOA,IAAII,GAAG,CAACC,UAAUA,OAAOL,cAAAA,MAAO,IAAIM,KAAK,CAAC;IAC7E,MAAMC,OAAO,IAAIC;IACjB,MAAMC,MAAgB,EAAE;IACxB,KAAK,MAAMC,QAAQT,MAAO;QACxB,MAAMV,UAAUmB,KAAKtB,OAAO,CAAC,QAAQ,KAAKK,IAAI,GAAGkB,KAAK,CAAC,GAAG1B;QAC1D,MAAM2B,MAAMrB,QAAQG,WAAW;QAC/B,IAAI,CAACH,WAAWgB,KAAKM,GAAG,CAACD,MAAM;QAC/BL,KAAKO,GAAG,CAACF;QACTH,IAAIM,IAAI,CAACxB;QACT,IAAIkB,IAAIX,MAAM,IAAIf,kBAAkB;IACtC;IACA,OAAO0B;AACT;AAEA,mDAAmD,GACnD,OAAO,SAASO,mBACdC,QAA2B,EAC3BC,OAAyG;IAEzG,OAAOD,SAASb,GAAG,CAAC,CAACb,UAAa,CAAA;YAChCA;YACA4B,SAAS9B,gBAAgB6B,QAAQE,KAAK,EAAE7B,WAAW;YACnD8B,eAAehC,gBAAgB6B,QAAQI,WAAW,EAAE/B,WAAW;YAC/DgC,MAAMlC,gBAAgB6B,QAAQM,EAAE,EAAEjC,WAAW;YAC7CkC,QAAQpC,gBAAgB6B,QAAQQ,IAAI,EAAEnC,WAAW;QACnD,CAAA;AACF"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+ import { type PageMarkdownNodes } from './page-markdown';
18
+ /**
19
+ * What a page SAYS, read off its node tree (AGL-2910): the text a search
20
+ * listing summarizes, the headings, and the images — each heading and image
21
+ * by node id, so a finding can name the element it is about and a fix can
22
+ * address the element it changes.
23
+ *
24
+ * Pure, and the platform's: the SEO check (`seo-audit.ts`) judges a page by
25
+ * these facts, and anything that proposes a listing or a content fix reads
26
+ * the same ones rather than walking the tree its own way.
27
+ *
28
+ * The text is the page's Markdown representation, the one an agent is
29
+ * served, so what the listing summarizes is what a reader of the page gets.
30
+ * The walk starts where that serializer starts — the content region — and
31
+ * skips what it skips, through the same chrome predicate: a heading in the
32
+ * site's navigation is not the page's heading.
33
+ */
34
+ /** How much of a page's text the facts carry. A listing summarizes; it does not need the page. */
35
+ export declare const SEO_PAGE_TEXT_MAX_CHARS = 2000;
36
+ export interface SeoHeading {
37
+ /** The element, or `null` for a heading inside rich text a fix cannot address. */
38
+ nodeId: string | null;
39
+ level: number;
40
+ text: string;
41
+ /** Whether a content fix can change it: a text component with plain text. */
42
+ editable: boolean;
43
+ }
44
+ export interface SeoImage {
45
+ nodeId: string;
46
+ src: string;
47
+ alt: string;
48
+ /** The nearest heading or text before it, which is what the image illustrates. */
49
+ context: string;
50
+ }
51
+ export interface SeoPageFacts {
52
+ /** The page's Markdown body, capped. */
53
+ text: string;
54
+ wordCount: number;
55
+ headings: SeoHeading[];
56
+ h1s: SeoHeading[];
57
+ images: SeoImage[];
58
+ imagesMissingAlt: SeoImage[];
59
+ /** The node the content region starts at; a new heading goes first inside it. */
60
+ contentRootId: string | null;
61
+ }
62
+ /** Read the facts a listing and the SEO check need from one page's node map. */
63
+ export declare function seoPageFacts(nodes: PageMarkdownNodes | null | undefined, options?: {
64
+ rootId?: string;
65
+ maxChars?: number;
66
+ }): SeoPageFacts;
@@ -0,0 +1,155 @@
1
+ /**
2
+ * @license
3
+ * Copyright 2026 Aglyn LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */ import { buildPageMarkdown, headingLevelOf, isPageChromeNode, pageContentRootId } from "./page-markdown.js";
17
+ /**
18
+ * What a page SAYS, read off its node tree (AGL-2910): the text a search
19
+ * listing summarizes, the headings, and the images — each heading and image
20
+ * by node id, so a finding can name the element it is about and a fix can
21
+ * address the element it changes.
22
+ *
23
+ * Pure, and the platform's: the SEO check (`seo-audit.ts`) judges a page by
24
+ * these facts, and anything that proposes a listing or a content fix reads
25
+ * the same ones rather than walking the tree its own way.
26
+ *
27
+ * The text is the page's Markdown representation, the one an agent is
28
+ * served, so what the listing summarizes is what a reader of the page gets.
29
+ * The walk starts where that serializer starts — the content region — and
30
+ * skips what it skips, through the same chrome predicate: a heading in the
31
+ * site's navigation is not the page's heading.
32
+ */ /** How much of a page's text the facts carry. A listing summarizes; it does not need the page. */ export const SEO_PAGE_TEXT_MAX_CHARS = 2000;
33
+ /** How much text around an image describes it. */ const IMAGE_CONTEXT_MAX_CHARS = 160;
34
+ /** The deepest a walk goes, and it stops at a cycle — a node map is data. */ const MAX_DEPTH = 64;
35
+ /** The text components whose heading level `headingLevelOf` reads. */ const TEXT_COMPONENTS = new Set([
36
+ 'muiTypography',
37
+ 'muiInlineText',
38
+ 'muiListItemText'
39
+ ]);
40
+ const collapse = (value)=>value.replace(/\s+/g, ' ').trim();
41
+ /** Author HTML to its text, for a heading stored as rich text. */ function htmlText(html) {
42
+ return collapse(html.replace(/<[^>]*>/g, ' ').replace(/&nbsp;/g, ' ').replace(/&amp;/g, '&'));
43
+ }
44
+ /** A text component's own text, and whether it is plain (a fix can rewrite it). */ function textOf(props) {
45
+ const html = props['html'];
46
+ if (typeof html === 'string' && html.trim()) return {
47
+ text: htmlText(html),
48
+ plain: false
49
+ };
50
+ const children = props['children'];
51
+ return typeof children === 'string' ? {
52
+ text: collapse(children),
53
+ plain: true
54
+ } : {
55
+ text: '',
56
+ plain: true
57
+ };
58
+ }
59
+ const childIdsOf = (node)=>Array.isArray(node == null ? void 0 : node.nodes) ? (node == null ? void 0 : node.nodes).filter((id)=>typeof id === 'string') : [];
60
+ /** Read the facts a listing and the SEO check need from one page's node map. */ export function seoPageFacts(nodes, options = {}) {
61
+ var _options_maxChars;
62
+ const empty = {
63
+ text: '',
64
+ wordCount: 0,
65
+ headings: [],
66
+ h1s: [],
67
+ images: [],
68
+ imagesMissingAlt: [],
69
+ contentRootId: null
70
+ };
71
+ if (!nodes) return empty;
72
+ const contentRootId = pageContentRootId(nodes, options.rootId);
73
+ if (!contentRootId) return empty;
74
+ const markdown = buildPageMarkdown({
75
+ nodes,
76
+ rootId: options.rootId
77
+ });
78
+ const text = markdown.trim().slice(0, (_options_maxChars = options.maxChars) != null ? _options_maxChars : SEO_PAGE_TEXT_MAX_CHARS);
79
+ const headings = [];
80
+ const images = [];
81
+ let lastText = '';
82
+ const seen = new Set();
83
+ const visit = (id, depth)=>{
84
+ var _node_props, _node_componentId, _props_html;
85
+ if (depth > MAX_DEPTH || seen.has(id)) return;
86
+ const node = nodes[id];
87
+ if (!node) return;
88
+ seen.add(id);
89
+ if (isPageChromeNode(node)) return;
90
+ const props = (_node_props = node.props) != null ? _node_props : {};
91
+ const componentId = String((_node_componentId = node.componentId) != null ? _node_componentId : '');
92
+ if (TEXT_COMPONENTS.has(componentId)) {
93
+ const { text: own, plain } = textOf(props);
94
+ const level = headingLevelOf(props);
95
+ if (own) {
96
+ if (level > 0) headings.push({
97
+ nodeId: id,
98
+ level,
99
+ text: own,
100
+ editable: plain
101
+ });
102
+ lastText = own;
103
+ }
104
+ } else if (componentId === 'markdown' && typeof props['content'] === 'string') {
105
+ for (const line of String(props['content']).split('\n')){
106
+ const match = /^(#{1,6})\s+(.+)$/.exec(line.trim());
107
+ if (match) {
108
+ headings.push({
109
+ nodeId: null,
110
+ level: match[1].length,
111
+ text: collapse(match[2]),
112
+ editable: false
113
+ });
114
+ }
115
+ }
116
+ lastText = collapse(String(props['content'])).slice(0, IMAGE_CONTEXT_MAX_CHARS);
117
+ } else if (componentId === 'custom-html' && typeof ((_props_html = props['html']) != null ? _props_html : props['children']) === 'string') {
118
+ var _props_html1;
119
+ const html = String((_props_html1 = props['html']) != null ? _props_html1 : props['children']);
120
+ for (const match of html.matchAll(/<h([1-6])[^>]*>([\s\S]*?)<\/h\1>/gi)){
121
+ headings.push({
122
+ nodeId: null,
123
+ level: Number(match[1]),
124
+ text: htmlText(match[2]),
125
+ editable: false
126
+ });
127
+ }
128
+ } else if (componentId === 'image') {
129
+ var _props_src;
130
+ const src = String((_props_src = props['src']) != null ? _props_src : '').trim();
131
+ if (src) {
132
+ var _props_alt;
133
+ images.push({
134
+ nodeId: id,
135
+ src,
136
+ alt: collapse(String((_props_alt = props['alt']) != null ? _props_alt : '')),
137
+ context: lastText.slice(0, IMAGE_CONTEXT_MAX_CHARS)
138
+ });
139
+ }
140
+ }
141
+ for (const childId of childIdsOf(node))visit(childId, depth + 1);
142
+ };
143
+ visit(contentRootId, 0);
144
+ return {
145
+ text,
146
+ wordCount: text ? text.split(/\s+/).filter(Boolean).length : 0,
147
+ headings,
148
+ h1s: headings.filter((heading)=>heading.level === 1),
149
+ images,
150
+ imagesMissingAlt: images.filter((image)=>!image.alt),
151
+ contentRootId
152
+ };
153
+ }
154
+
155
+ //# sourceMappingURL=seo-page-facts.js.map