@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.
- package/package.json +14 -14
- package/src/lib/app-utils/actions.d.ts +25 -374
- package/src/lib/app-utils/actions.js +101 -561
- package/src/lib/app-utils/actions.js.map +1 -1
- package/src/lib/app-utils/activity-presenter.d.ts +3 -3
- package/src/lib/app-utils/activity-presenter.js +36 -52
- package/src/lib/app-utils/activity-presenter.js.map +1 -1
- package/src/lib/app-utils/admin-audit-index.js +2 -2
- package/src/lib/app-utils/admin-audit-index.js.map +1 -1
- package/src/lib/app-utils/analytics-events.d.ts +11 -2
- package/src/lib/app-utils/analytics-events.js +11 -2
- package/src/lib/app-utils/analytics-events.js.map +1 -1
- package/src/lib/app-utils/analytics-provider.d.ts +182 -0
- package/src/lib/app-utils/analytics-provider.js +133 -0
- package/src/lib/app-utils/analytics-provider.js.map +1 -0
- package/src/lib/app-utils/api-plugins.d.ts +46 -0
- package/src/lib/app-utils/api-plugins.js +19 -0
- package/src/lib/app-utils/api-plugins.js.map +1 -1
- package/src/lib/app-utils/{marketplace-overrides.d.ts → artifact-overrides.d.ts} +9 -6
- package/src/lib/app-utils/{marketplace-overrides.js → artifact-overrides.js} +10 -7
- package/src/lib/app-utils/artifact-overrides.js.map +1 -0
- package/src/lib/app-utils/{marketplace-provenance.d.ts → artifact-provenance.d.ts} +12 -10
- package/src/lib/app-utils/{marketplace-provenance.js → artifact-provenance.js} +9 -7
- package/src/lib/app-utils/artifact-provenance.js.map +1 -0
- package/src/lib/app-utils/assist-credits.js +2 -1
- package/src/lib/app-utils/assist-credits.js.map +1 -1
- package/src/lib/app-utils/binding-token-catalog.d.ts +5 -13
- package/src/lib/app-utils/binding-token-catalog.js +17 -41
- package/src/lib/app-utils/binding-token-catalog.js.map +1 -1
- package/src/lib/app-utils/binding-tokens.js +1 -1
- package/src/lib/app-utils/binding-tokens.js.map +1 -1
- package/src/lib/app-utils/brand-support-line.d.ts +36 -0
- package/src/lib/app-utils/brand-support-line.js +38 -0
- package/src/lib/app-utils/brand-support-line.js.map +1 -0
- package/src/lib/app-utils/campaign-touch.d.ts +5 -5
- package/src/lib/app-utils/campaign-touch.js +8 -8
- package/src/lib/app-utils/campaign-touch.js.map +1 -1
- package/src/lib/app-utils/collection-entries.d.ts +2 -2
- package/src/lib/app-utils/collection-entries.js.map +1 -1
- package/src/lib/app-utils/consent-group-change.d.ts +4 -0
- package/src/lib/app-utils/consent-group-change.js.map +1 -1
- package/src/lib/app-utils/console-record-links.d.ts +10 -89
- package/src/lib/app-utils/console-record-links.js +3 -107
- package/src/lib/app-utils/console-record-links.js.map +1 -1
- package/src/lib/app-utils/console-routes.d.ts +16 -7
- package/src/lib/app-utils/console-routes.js +9 -3
- package/src/lib/app-utils/console-routes.js.map +1 -1
- package/src/lib/app-utils/contacts.d.ts +23 -1
- package/src/lib/app-utils/contacts.js +21 -5
- package/src/lib/app-utils/contacts.js.map +1 -1
- package/src/lib/app-utils/cross-pool-desync.d.ts +39 -0
- package/src/lib/app-utils/cross-pool-desync.js +47 -0
- package/src/lib/app-utils/cross-pool-desync.js.map +1 -0
- package/src/lib/app-utils/csv-upload.d.ts +29 -0
- package/src/lib/app-utils/csv-upload.js +29 -0
- package/src/lib/app-utils/csv-upload.js.map +1 -0
- package/src/lib/app-utils/dataset-models.d.ts +5 -5
- package/src/lib/app-utils/dataset-models.js +19 -2
- package/src/lib/app-utils/dataset-models.js.map +1 -1
- package/src/lib/app-utils/datasets.d.ts +65 -11
- package/src/lib/app-utils/datasets.js +74 -11
- package/src/lib/app-utils/datasets.js.map +1 -1
- package/src/lib/app-utils/docs-deflection.d.ts +6 -6
- package/src/lib/app-utils/docs-deflection.js.map +1 -1
- package/src/lib/app-utils/docs-help.generated.d.ts +12 -6
- package/src/lib/app-utils/docs-help.generated.js +21 -6
- package/src/lib/app-utils/docs-help.generated.js.map +1 -1
- package/src/lib/app-utils/docs-index.generated.d.ts +2 -2
- package/src/lib/app-utils/docs-index.generated.js +514 -318
- package/src/lib/app-utils/docs-index.generated.js.map +1 -1
- package/src/lib/app-utils/docs-retrieval.d.ts +3 -3
- package/src/lib/app-utils/docs-retrieval.js +14 -10
- package/src/lib/app-utils/docs-retrieval.js.map +1 -1
- package/src/lib/app-utils/duplicate-resource.d.ts +4 -1
- package/src/lib/app-utils/duplicate-resource.js +7 -4
- package/src/lib/app-utils/duplicate-resource.js.map +1 -1
- package/src/lib/app-utils/dynamic-list-rule.d.ts +1 -1
- package/src/lib/app-utils/dynamic-list-rule.js.map +1 -1
- package/src/lib/app-utils/element-ui.d.ts +1 -1
- package/src/lib/app-utils/element-ui.js +1 -1
- package/src/lib/app-utils/element-ui.js.map +1 -1
- package/src/lib/app-utils/email-starter-policy.js +3 -3
- package/src/lib/app-utils/email-starter-policy.js.map +1 -1
- package/src/lib/app-utils/entity-picker-context.d.ts +10 -5
- package/src/lib/app-utils/entity-picker-context.js.map +1 -1
- package/src/lib/app-utils/error-beacon.d.ts +75 -6
- package/src/lib/app-utils/error-beacon.js +160 -21
- package/src/lib/app-utils/error-beacon.js.map +1 -1
- package/src/lib/app-utils/expand-repeatables.d.ts +10 -10
- package/src/lib/app-utils/expand-repeatables.js +10 -10
- package/src/lib/app-utils/expand-repeatables.js.map +1 -1
- package/src/lib/app-utils/host-events.d.ts +82 -0
- package/src/lib/app-utils/host-events.js +62 -0
- package/src/lib/app-utils/host-events.js.map +1 -0
- package/src/lib/app-utils/lockdown.d.ts +33 -1
- package/src/lib/app-utils/lockdown.js +45 -4
- package/src/lib/app-utils/lockdown.js.map +1 -1
- package/src/lib/app-utils/margin-utilization.d.ts +4 -69
- package/src/lib/app-utils/margin-utilization.js +82 -69
- package/src/lib/app-utils/margin-utilization.js.map +1 -1
- package/src/lib/app-utils/node-definition-sanitizer.d.ts +16 -16
- package/src/lib/app-utils/node-definition-sanitizer.js +24 -20
- package/src/lib/app-utils/node-definition-sanitizer.js.map +1 -1
- package/src/lib/app-utils/node-interactions.d.ts +7 -7
- package/src/lib/app-utils/node-interactions.js.map +1 -1
- package/src/lib/app-utils/notifications.d.ts +78 -47
- package/src/lib/app-utils/notifications.js +63 -44
- package/src/lib/app-utils/notifications.js.map +1 -1
- package/src/lib/app-utils/payments-configured.d.ts +8 -21
- package/src/lib/app-utils/payments-configured.js +8 -23
- package/src/lib/app-utils/payments-configured.js.map +1 -1
- package/src/lib/app-utils/plan-entitlements.d.ts +43 -128
- package/src/lib/app-utils/plan-entitlements.js +145 -304
- package/src/lib/app-utils/plan-entitlements.js.map +1 -1
- package/src/lib/app-utils/platform-brand.d.ts +10 -0
- package/src/lib/app-utils/platform-brand.js +14 -3
- package/src/lib/app-utils/platform-brand.js.map +1 -1
- package/src/lib/app-utils/plugin-api-rate-limit.d.ts +7 -1
- package/src/lib/app-utils/plugin-api-rate-limit.js +11 -3
- package/src/lib/app-utils/plugin-api-rate-limit.js.map +1 -1
- package/src/lib/app-utils/plugin-bundle-checks.js +2 -2
- package/src/lib/app-utils/plugin-bundle-checks.js.map +1 -1
- package/src/lib/app-utils/plugin-host-events.generated.d.ts +12 -0
- package/src/lib/app-utils/plugin-host-events.generated.js +169 -0
- package/src/lib/app-utils/plugin-host-events.generated.js.map +1 -0
- package/src/lib/app-utils/plugin-release-flags.generated.d.ts +12 -0
- package/src/lib/app-utils/plugin-release-flags.generated.js +106 -0
- package/src/lib/app-utils/plugin-release-flags.generated.js.map +1 -0
- package/src/lib/app-utils/realm-host-surface.generated.js +2 -0
- package/src/lib/app-utils/realm-host-surface.generated.js.map +1 -1
- package/src/lib/app-utils/release-flags.d.ts +14 -5
- package/src/lib/app-utils/release-flags.js +21 -125
- package/src/lib/app-utils/release-flags.js.map +1 -1
- package/src/lib/app-utils/repeat-sources.d.ts +9 -0
- package/src/lib/app-utils/repeat-sources.js.map +1 -1
- package/src/lib/app-utils/retained-refusals.d.ts +73 -0
- package/src/lib/app-utils/retained-refusals.js +186 -0
- package/src/lib/app-utils/retained-refusals.js.map +1 -0
- package/src/lib/app-utils/scope-tokens.d.ts +27 -0
- package/src/lib/app-utils/scope-tokens.js +61 -0
- package/src/lib/app-utils/scope-tokens.js.map +1 -1
- package/src/lib/app-utils/screen-link-context.js +3 -3
- package/src/lib/app-utils/screen-link-context.js.map +1 -1
- package/src/lib/app-utils/screen-route.d.ts +15 -0
- package/src/lib/app-utils/screen-route.js +21 -1
- package/src/lib/app-utils/screen-route.js.map +1 -1
- package/src/lib/app-utils/seo-audit.d.ts +132 -0
- package/src/lib/app-utils/seo-audit.js +246 -0
- package/src/lib/app-utils/seo-audit.js.map +1 -0
- package/src/lib/app-utils/seo-keywords.d.ts +48 -0
- package/src/lib/app-utils/seo-keywords.js +60 -0
- package/src/lib/app-utils/seo-keywords.js.map +1 -0
- package/src/lib/app-utils/seo-page-facts.d.ts +66 -0
- package/src/lib/app-utils/seo-page-facts.js +155 -0
- package/src/lib/app-utils/seo-page-facts.js.map +1 -0
- package/src/lib/app-utils/seo-site-scan.d.ts +126 -0
- package/src/lib/app-utils/seo-site-scan.js +162 -0
- package/src/lib/app-utils/seo-site-scan.js.map +1 -0
- package/src/lib/app-utils/seo-title-variables.js +1 -1
- package/src/lib/app-utils/seo-title-variables.js.map +1 -1
- package/src/lib/app-utils/server.d.ts +6 -9
- package/src/lib/app-utils/server.js +17 -27
- package/src/lib/app-utils/server.js.map +1 -1
- package/src/lib/app-utils/site-interactions.d.ts +501 -0
- package/src/lib/app-utils/site-interactions.js +554 -0
- package/src/lib/app-utils/site-interactions.js.map +1 -0
- package/src/lib/app-utils/{marketplace-theme.d.ts → site-theme.d.ts} +13 -2
- package/src/lib/app-utils/{marketplace-theme.js → site-theme.js} +5 -2
- package/src/lib/app-utils/site-theme.js.map +1 -0
- package/src/lib/app-utils/sitemap.d.ts +4 -3
- package/src/lib/app-utils/sitemap.js +9 -7
- package/src/lib/app-utils/sitemap.js.map +1 -1
- package/src/lib/app-utils/stale-build-error.d.ts +51 -0
- package/src/lib/app-utils/stale-build-error.js +100 -1
- package/src/lib/app-utils/stale-build-error.js.map +1 -1
- package/src/lib/app-utils/starter-templates.d.ts +20 -0
- package/src/lib/app-utils/starter-templates.js +49 -0
- package/src/lib/app-utils/starter-templates.js.map +1 -1
- package/src/lib/app-utils/tax-jurisdiction-figures.d.ts +118 -0
- package/src/lib/app-utils/tax-jurisdiction-figures.js +123 -0
- package/src/lib/app-utils/tax-jurisdiction-figures.js.map +1 -0
- package/src/lib/app-utils/theme-library.d.ts +10 -3
- package/src/lib/app-utils/theme-library.js +13 -4
- package/src/lib/app-utils/theme-library.js.map +1 -1
- package/src/lib/app-utils/{campaign-attribution.d.ts → utm-attribution.d.ts} +11 -6
- package/src/lib/app-utils/{campaign-attribution.js → utm-attribution.js} +13 -8
- package/src/lib/app-utils/utm-attribution.js.map +1 -0
- package/src/lib/app-utils/{campaign-forwarding.d.ts → utm-forwarding.d.ts} +14 -14
- package/src/lib/app-utils/{campaign-forwarding.js → utm-forwarding.js} +26 -26
- package/src/lib/app-utils/utm-forwarding.js.map +1 -0
- package/src/lib/app-utils/video-object.js +6 -5
- package/src/lib/app-utils/video-object.js.map +1 -1
- package/src/lib/app-utils/visitor-consent.d.ts +41 -164
- package/src/lib/app-utils/visitor-consent.js +66 -277
- package/src/lib/app-utils/visitor-consent.js.map +1 -1
- package/src/lib/app-utils/web-vitals-rum.d.ts +7 -7
- package/src/lib/app-utils/web-vitals-rum.js +31 -63
- package/src/lib/app-utils/web-vitals-rum.js.map +1 -1
- package/src/lib/app-utils/where-used.d.ts +11 -6
- package/src/lib/app-utils/where-used.js +3 -8
- package/src/lib/app-utils/where-used.js.map +1 -1
- package/src/lib/canvas-manager/canvas-manager.js +3 -3
- package/src/lib/canvas-manager/canvas-manager.js.map +1 -1
- package/src/lib/foundation/definitions/components.types.d.ts +1 -1
- package/src/lib/foundation/definitions/components.types.js +1 -1
- package/src/lib/foundation/definitions/components.types.js.map +1 -1
- package/src/lib/foundation/definitions/org-billing.types.d.ts +18 -135
- package/src/lib/foundation/definitions/org-billing.types.js.map +1 -1
- package/src/lib/foundation/definitions/organization.types.d.ts +6 -3
- package/src/lib/foundation/definitions/organization.types.js.map +1 -1
- package/src/lib/foundation/definitions/platform.types.d.ts +27 -5
- package/src/lib/foundation/definitions/platform.types.js.map +1 -1
- package/src/lib/foundation/definitions/write-deny-coverage.util.d.ts +16 -0
- package/src/lib/foundation/definitions/write-deny-coverage.util.js +20 -0
- package/src/lib/foundation/definitions/write-deny-coverage.util.js.map +1 -1
- package/src/lib/foundation/index.d.ts +1 -1
- package/src/lib/foundation/index.js.map +1 -1
- package/src/lib/plugin-manager/besigner-documents.d.ts +96 -0
- package/src/lib/plugin-manager/besigner-documents.js +61 -0
- package/src/lib/plugin-manager/besigner-documents.js.map +1 -0
- package/src/lib/plugin-manager/computed-variables.d.ts +71 -0
- package/src/lib/plugin-manager/computed-variables.js +74 -0
- package/src/lib/plugin-manager/computed-variables.js.map +1 -0
- package/src/lib/plugin-manager/custom-fields.d.ts +29 -2
- package/src/lib/plugin-manager/custom-fields.js +47 -1
- package/src/lib/plugin-manager/custom-fields.js.map +1 -1
- package/src/lib/plugin-manager/feature-plugins.d.ts +292 -20
- package/src/lib/plugin-manager/feature-plugins.js +105 -17
- package/src/lib/plugin-manager/feature-plugins.js.map +1 -1
- package/src/lib/plugin-manager/first-party-plugins.generated.d.ts +123 -0
- package/src/lib/plugin-manager/first-party-plugins.generated.js +785 -14
- package/src/lib/plugin-manager/first-party-plugins.generated.js.map +1 -1
- package/src/lib/plugin-manager/plugin-activity-actions.d.ts +12 -0
- package/src/lib/plugin-manager/plugin-activity-actions.js +25 -0
- package/src/lib/plugin-manager/plugin-activity-actions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-contributions.d.ts +1 -1
- package/src/lib/plugin-manager/plugin-contributions.js.map +1 -1
- package/src/lib/plugin-manager/plugin-declarations-repair.d.ts +36 -0
- package/src/lib/plugin-manager/plugin-declarations-repair.js +64 -0
- package/src/lib/plugin-manager/plugin-declarations-repair.js.map +1 -0
- package/src/lib/plugin-manager/plugin-dependents.d.ts +101 -0
- package/src/lib/plugin-manager/plugin-dependents.js +75 -0
- package/src/lib/plugin-manager/plugin-dependents.js.map +1 -0
- package/src/lib/plugin-manager/plugin-distribution.d.ts +55 -0
- package/src/lib/plugin-manager/plugin-distribution.js +45 -0
- package/src/lib/plugin-manager/plugin-distribution.js.map +1 -0
- package/src/lib/plugin-manager/plugin-host-collections.d.ts +17 -0
- package/src/lib/plugin-manager/plugin-host-collections.js +6 -0
- package/src/lib/plugin-manager/plugin-host-collections.js.map +1 -1
- package/src/lib/plugin-manager/plugin-host-resources.d.ts +133 -0
- package/src/lib/plugin-manager/plugin-host-resources.js +52 -0
- package/src/lib/plugin-manager/plugin-host-resources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-org-erasure.d.ts +28 -2
- package/src/lib/plugin-manager/plugin-org-erasure.js +61 -2
- package/src/lib/plugin-manager/plugin-org-erasure.js.map +1 -1
- package/src/lib/plugin-manager/plugin-plan-entitlements.d.ts +123 -0
- package/src/lib/plugin-manager/plugin-plan-entitlements.js +101 -0
- package/src/lib/plugin-manager/plugin-plan-entitlements.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-index.d.ts +95 -0
- package/src/lib/plugin-manager/plugin-record-index.js +51 -0
- package/src/lib/plugin-manager/plugin-record-index.js.map +1 -0
- package/src/lib/plugin-manager/plugin-record-written.d.ts +58 -0
- package/src/lib/plugin-manager/plugin-record-written.js +42 -0
- package/src/lib/plugin-manager/plugin-record-written.js.map +1 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.d.ts +213 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.js +194 -0
- package/src/lib/plugin-manager/plugin-revenue-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-bundle.d.ts +145 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js +78 -0
- package/src/lib/plugin-manager/plugin-site-bundle.js.map +1 -0
- package/src/lib/plugin-manager/plugin-site-cache.d.ts +12 -0
- package/src/lib/plugin-manager/plugin-site-cache.js.map +1 -1
- package/src/lib/plugin-manager/plugin-site-export.d.ts +81 -0
- package/src/lib/plugin-manager/plugin-site-export.js +40 -0
- package/src/lib/plugin-manager/plugin-site-export.js.map +1 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.d.ts +88 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.js +29 -0
- package/src/lib/plugin-manager/plugin-sitemap-sections.js.map +1 -0
- package/src/lib/plugin-manager/plugin-tax-profile.d.ts +20 -4
- package/src/lib/plugin-manager/plugin-tax-profile.js.map +1 -1
- package/src/lib/plugin-manager/plugin-tax-return-sources.d.ts +255 -0
- package/src/lib/plugin-manager/plugin-tax-return-sources.js +191 -0
- package/src/lib/plugin-manager/plugin-tax-return-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-template-sources.d.ts +75 -0
- package/src/lib/plugin-manager/plugin-template-sources.js +37 -0
- package/src/lib/plugin-manager/plugin-template-sources.js.map +1 -0
- package/src/lib/plugin-manager/plugin-usage-axes.d.ts +107 -0
- package/src/lib/plugin-manager/plugin-usage-axes.js +107 -0
- package/src/lib/plugin-manager/plugin-usage-axes.js.map +1 -0
- package/src/lib/plugin-manager/plugin-zones.d.ts +4 -5
- package/src/lib/plugin-manager/plugin-zones.js.map +1 -1
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js +2 -0
- package/src/lib/plugin-manager/realm-host-aglyn.generated.js.map +1 -1
- package/src/lib/plugin-manager/record-captured-contact.d.ts +1 -14
- package/src/lib/plugin-manager/record-captured-contact.js +10 -32
- package/src/lib/plugin-manager/record-captured-contact.js.map +1 -1
- package/src/lib/plugin-manager/repeat-rows.d.ts +124 -0
- package/src/lib/plugin-manager/repeat-rows.js +108 -0
- package/src/lib/plugin-manager/repeat-rows.js.map +1 -0
- package/src/lib/plugin-manager/submission-record-target.d.ts +144 -0
- package/src/lib/plugin-manager/submission-record-target.js +125 -0
- package/src/lib/plugin-manager/submission-record-target.js.map +1 -0
- package/src/lib/plugin-manager/video-embed-provider.d.ts +108 -0
- package/src/lib/plugin-manager/video-embed-provider.js +165 -0
- package/src/lib/plugin-manager/video-embed-provider.js.map +1 -0
- package/src/lib/types/nodes.d.ts +2 -2
- package/src/lib/types/nodes.js.map +1 -1
- package/src/lib/app-utils/campaign-attribution.js.map +0 -1
- package/src/lib/app-utils/campaign-forwarding.js.map +0 -1
- package/src/lib/app-utils/contact-holder.d.ts +0 -68
- package/src/lib/app-utils/contact-holder.js +0 -129
- package/src/lib/app-utils/contact-holder.js.map +0 -1
- package/src/lib/app-utils/contact-merge.d.ts +0 -136
- package/src/lib/app-utils/contact-merge.js +0 -412
- package/src/lib/app-utils/contact-merge.js.map +0 -1
- package/src/lib/app-utils/crm-csv.d.ts +0 -239
- package/src/lib/app-utils/crm-csv.js +0 -467
- package/src/lib/app-utils/crm-csv.js.map +0 -1
- package/src/lib/app-utils/crm-digest.d.ts +0 -185
- package/src/lib/app-utils/crm-digest.js +0 -272
- package/src/lib/app-utils/crm-digest.js.map +0 -1
- package/src/lib/app-utils/form-dataset-binding.d.ts +0 -50
- package/src/lib/app-utils/form-dataset-binding.js +0 -74
- package/src/lib/app-utils/form-dataset-binding.js.map +0 -1
- package/src/lib/app-utils/list-import.d.ts +0 -158
- package/src/lib/app-utils/list-import.js +0 -332
- package/src/lib/app-utils/list-import.js.map +0 -1
- package/src/lib/app-utils/marketplace-listing-visibility.d.ts +0 -122
- package/src/lib/app-utils/marketplace-listing-visibility.js +0 -147
- package/src/lib/app-utils/marketplace-listing-visibility.js.map +0 -1
- package/src/lib/app-utils/marketplace-overrides.js.map +0 -1
- package/src/lib/app-utils/marketplace-provenance.js.map +0 -1
- package/src/lib/app-utils/marketplace-theme.js.map +0 -1
- package/src/lib/app-utils/marketplace-update-state.d.ts +0 -119
- package/src/lib/app-utils/marketplace-update-state.js +0 -132
- package/src/lib/app-utils/marketplace-update-state.js.map +0 -1
- package/src/lib/app-utils/publisher-agreement.d.ts +0 -289
- package/src/lib/app-utils/publisher-agreement.js +0 -321
- package/src/lib/app-utils/publisher-agreement.js.map +0 -1
- package/src/lib/app-utils/wistia-embed.d.ts +0 -104
- package/src/lib/app-utils/wistia-embed.js +0 -138
- package/src/lib/app-utils/wistia-embed.js.map +0 -1
- package/src/lib/app-utils/workflows.d.ts +0 -136
- package/src/lib/app-utils/workflows.js +0 -346
- package/src/lib/app-utils/workflows.js.map +0 -1
- package/src/lib/plugin-manager/commerce-zone-props.d.ts +0 -172
- package/src/lib/plugin-manager/commerce-zone-props.js +0 -31
- package/src/lib/plugin-manager/commerce-zone-props.js.map +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/seo-page-facts.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 {\n buildPageMarkdown,\n headingLevelOf,\n isPageChromeNode,\n pageContentRootId,\n type PageMarkdownNode,\n type PageMarkdownNodes,\n} from './page-markdown'\n\n/**\n * What a page SAYS, read off its node tree (AGL-2910): the text a search\n * listing summarizes, the headings, and the images — each heading and image\n * by node id, so a finding can name the element it is about and a fix can\n * address the element it changes.\n *\n * Pure, and the platform's: the SEO check (`seo-audit.ts`) judges a page by\n * these facts, and anything that proposes a listing or a content fix reads\n * the same ones rather than walking the tree its own way.\n *\n * The text is the page's Markdown representation, the one an agent is\n * served, so what the listing summarizes is what a reader of the page gets.\n * The walk starts where that serializer starts — the content region — and\n * skips what it skips, through the same chrome predicate: a heading in the\n * site's navigation is not the page's heading.\n */\n\n/** How much of a page's text the facts carry. A listing summarizes; it does not need the page. */\nexport const SEO_PAGE_TEXT_MAX_CHARS = 2_000\n\n/** How much text around an image describes it. */\nconst IMAGE_CONTEXT_MAX_CHARS = 160\n\n/** The deepest a walk goes, and it stops at a cycle — a node map is data. */\nconst MAX_DEPTH = 64\n\n/** The text components whose heading level `headingLevelOf` reads. */\nconst TEXT_COMPONENTS: ReadonlySet<string> = new Set(['muiTypography', 'muiInlineText', 'muiListItemText'])\n\nexport interface SeoHeading {\n /** The element, or `null` for a heading inside rich text a fix cannot address. */\n nodeId: string | null\n level: number\n text: string\n /** Whether a content fix can change it: a text component with plain text. */\n editable: boolean\n}\n\nexport interface SeoImage {\n nodeId: string\n src: string\n alt: string\n /** The nearest heading or text before it, which is what the image illustrates. */\n context: string\n}\n\nexport interface SeoPageFacts {\n /** The page's Markdown body, capped. */\n text: string\n wordCount: number\n headings: SeoHeading[]\n h1s: SeoHeading[]\n images: SeoImage[]\n imagesMissingAlt: SeoImage[]\n /** The node the content region starts at; a new heading goes first inside it. */\n contentRootId: string | null\n}\n\nconst collapse = (value: string): string => value.replace(/\\s+/g, ' ').trim()\n\n/** Author HTML to its text, for a heading stored as rich text. */\nfunction htmlText(html: string): string {\n return collapse(html.replace(/<[^>]*>/g, ' ').replace(/ /g, ' ').replace(/&/g, '&'))\n}\n\n/** A text component's own text, and whether it is plain (a fix can rewrite it). */\nfunction textOf(props: Record<string, unknown>): { text: string; plain: boolean } {\n const html = props['html']\n if (typeof html === 'string' && html.trim()) return { text: htmlText(html), plain: false }\n const children = props['children']\n return typeof children === 'string' ? { text: collapse(children), plain: true } : { text: '', plain: true }\n}\n\nconst childIdsOf = (node: PageMarkdownNode | undefined): string[] =>\n Array.isArray(node?.nodes) ? (node?.nodes as unknown[]).filter((id): id is string => typeof id === 'string') : []\n\n/** Read the facts a listing and the SEO check need from one page's node map. */\nexport function seoPageFacts(\n nodes: PageMarkdownNodes | null | undefined,\n options: { rootId?: string; maxChars?: number } = {},\n): SeoPageFacts {\n const empty: SeoPageFacts = {\n text: '',\n wordCount: 0,\n headings: [],\n h1s: [],\n images: [],\n imagesMissingAlt: [],\n contentRootId: null,\n }\n if (!nodes) return empty\n const contentRootId = pageContentRootId(nodes, options.rootId)\n if (!contentRootId) return empty\n\n const markdown = buildPageMarkdown({ nodes, rootId: options.rootId })\n const text = markdown.trim().slice(0, options.maxChars ?? SEO_PAGE_TEXT_MAX_CHARS)\n const headings: SeoHeading[] = []\n const images: SeoImage[] = []\n let lastText = ''\n\n const seen = new Set<string>()\n const visit = (id: string, depth: number): void => {\n if (depth > MAX_DEPTH || seen.has(id)) return\n const node = nodes[id]\n if (!node) return\n seen.add(id)\n if (isPageChromeNode(node)) return\n const props = (node.props ?? {}) as Record<string, unknown>\n const componentId = String(node.componentId ?? '')\n if (TEXT_COMPONENTS.has(componentId)) {\n const { text: own, plain } = textOf(props)\n const level = headingLevelOf(props)\n if (own) {\n if (level > 0) headings.push({ nodeId: id, level, text: own, editable: plain })\n lastText = own\n }\n } else if (componentId === 'markdown' && typeof props['content'] === 'string') {\n for (const line of String(props['content']).split('\\n')) {\n const match = /^(#{1,6})\\s+(.+)$/.exec(line.trim())\n if (match) {\n headings.push({ nodeId: null, level: match[1].length, text: collapse(match[2]), editable: false })\n }\n }\n lastText = collapse(String(props['content'])).slice(0, IMAGE_CONTEXT_MAX_CHARS)\n } else if (componentId === 'custom-html' && typeof (props['html'] ?? props['children']) === 'string') {\n const html = String(props['html'] ?? props['children'])\n for (const match of html.matchAll(/<h([1-6])[^>]*>([\\s\\S]*?)<\\/h\\1>/gi)) {\n headings.push({ nodeId: null, level: Number(match[1]), text: htmlText(match[2]), editable: false })\n }\n } else if (componentId === 'image') {\n const src = String(props['src'] ?? '').trim()\n if (src) {\n images.push({\n nodeId: id,\n src,\n alt: collapse(String(props['alt'] ?? '')),\n context: lastText.slice(0, IMAGE_CONTEXT_MAX_CHARS),\n })\n }\n }\n for (const childId of childIdsOf(node)) visit(childId, depth + 1)\n }\n visit(contentRootId, 0)\n\n return {\n text,\n wordCount: text ? text.split(/\\s+/).filter(Boolean).length : 0,\n headings,\n h1s: headings.filter((heading) => heading.level === 1),\n images,\n imagesMissingAlt: images.filter((image) => !image.alt),\n contentRootId,\n }\n}\n"],"names":["buildPageMarkdown","headingLevelOf","isPageChromeNode","pageContentRootId","SEO_PAGE_TEXT_MAX_CHARS","IMAGE_CONTEXT_MAX_CHARS","MAX_DEPTH","TEXT_COMPONENTS","Set","collapse","value","replace","trim","htmlText","html","textOf","props","text","plain","children","childIdsOf","node","Array","isArray","nodes","filter","id","seoPageFacts","options","empty","wordCount","headings","h1s","images","imagesMissingAlt","contentRootId","rootId","markdown","slice","maxChars","lastText","seen","visit","depth","has","add","componentId","String","own","level","push","nodeId","editable","line","split","match","exec","length","matchAll","Number","src","alt","context","childId","Boolean","heading","image"],"mappings":"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"}
|
|
@@ -0,0 +1,126 @@
|
|
|
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 SeoAuditPage, type SeoAuditSite } from './seo-audit';
|
|
18
|
+
/**
|
|
19
|
+
* Read a site for the SEO check (`seo-audit.ts`) the way its sitemap lists
|
|
20
|
+
* it: every screen the routing map publishes, less the template screens, the
|
|
21
|
+
* status pages and every screen that is not public — through the same shared
|
|
22
|
+
* predicates the tenant's `sitemap.xml` and `/llms.txt` apply, so the check
|
|
23
|
+
* covers exactly the pages a crawler is handed. Then each page's published
|
|
24
|
+
* version, and the shared layouts, for the links that decide which pages
|
|
25
|
+
* nothing points to.
|
|
26
|
+
*
|
|
27
|
+
* It knows nothing about the Firestore SDK. A caller hands it a
|
|
28
|
+
* {@link SeoScanStore} — the Admin SDK's `Firestore` satisfies it — and the
|
|
29
|
+
* template screen ids, which the tenant runtime resolves; so this module stays
|
|
30
|
+
* pure enough to import from anywhere, and a spec hands it a fake.
|
|
31
|
+
*
|
|
32
|
+
* Reads: one projected query of the screens, one of the layouts, and one
|
|
33
|
+
* version document per checked page and per layout — at most
|
|
34
|
+
* {@link SEO_AUDIT_MAX_PAGES} + {@link SEO_SCAN_LAYOUT_LIMIT} + 2.
|
|
35
|
+
*/
|
|
36
|
+
/** Shared layouts read for the links they carry. */
|
|
37
|
+
export declare const SEO_SCAN_LAYOUT_LIMIT = 20;
|
|
38
|
+
export interface SeoScanSnapshot {
|
|
39
|
+
readonly exists: boolean;
|
|
40
|
+
get(field: string): unknown;
|
|
41
|
+
}
|
|
42
|
+
export interface SeoScanQueryDocument {
|
|
43
|
+
readonly id: string;
|
|
44
|
+
data(): unknown;
|
|
45
|
+
get(field: string): unknown;
|
|
46
|
+
readonly ref: SeoScanDocumentRef;
|
|
47
|
+
}
|
|
48
|
+
export interface SeoScanQuery {
|
|
49
|
+
limit(count: number): SeoScanQuery;
|
|
50
|
+
get(): Promise<{
|
|
51
|
+
readonly docs: readonly SeoScanQueryDocument[];
|
|
52
|
+
}>;
|
|
53
|
+
}
|
|
54
|
+
export interface SeoScanCollection {
|
|
55
|
+
select(...fields: string[]): SeoScanQuery;
|
|
56
|
+
doc(id: string): SeoScanDocumentRef;
|
|
57
|
+
}
|
|
58
|
+
export interface SeoScanDocumentRef {
|
|
59
|
+
collection(name: string): SeoScanCollection;
|
|
60
|
+
get(): Promise<SeoScanSnapshot>;
|
|
61
|
+
}
|
|
62
|
+
/** What the scan reads through; the Admin SDK's `Firestore` is one. */
|
|
63
|
+
export interface SeoScanStore {
|
|
64
|
+
collection(name: string): SeoScanCollection;
|
|
65
|
+
}
|
|
66
|
+
/** The host document fields the scan and the site findings read. */
|
|
67
|
+
export interface SeoSiteHost {
|
|
68
|
+
displayName?: string;
|
|
69
|
+
screens?: Record<string, string>;
|
|
70
|
+
errorScreens?: Record<string, string>;
|
|
71
|
+
notFoundScreenId?: string | null;
|
|
72
|
+
seo?: {
|
|
73
|
+
title?: string;
|
|
74
|
+
separator?: string;
|
|
75
|
+
discourageSearchEngines?: boolean;
|
|
76
|
+
entity?: {
|
|
77
|
+
name?: string;
|
|
78
|
+
description?: string;
|
|
79
|
+
};
|
|
80
|
+
agent?: {
|
|
81
|
+
whenToUse?: string;
|
|
82
|
+
};
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** The screen document fields the scan reads. */
|
|
86
|
+
interface SeoScreenDocument {
|
|
87
|
+
displayName?: string;
|
|
88
|
+
description?: string;
|
|
89
|
+
versionId?: string;
|
|
90
|
+
visibility?: unknown;
|
|
91
|
+
deletedAt?: unknown;
|
|
92
|
+
seo?: {
|
|
93
|
+
title?: string;
|
|
94
|
+
description?: string;
|
|
95
|
+
breadcrumb?: string;
|
|
96
|
+
image?: string;
|
|
97
|
+
imageAlt?: string;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
export interface SeoScannedPage extends SeoAuditPage {
|
|
101
|
+
/** The listing as the screen document stores it: a title's variables unresolved. */
|
|
102
|
+
storedSeo: NonNullable<SeoScreenDocument['seo']>;
|
|
103
|
+
/** The page's node map as checked. */
|
|
104
|
+
nodes: Record<string, unknown> | null;
|
|
105
|
+
}
|
|
106
|
+
export interface SeoSiteScan {
|
|
107
|
+
pages: SeoScannedPage[];
|
|
108
|
+
/** Published pages past {@link SEO_AUDIT_MAX_PAGES}. */
|
|
109
|
+
skipped: number;
|
|
110
|
+
/** Customer-safe lines: a keyword line naming a page the site does not publish. */
|
|
111
|
+
notes: string[];
|
|
112
|
+
}
|
|
113
|
+
/** The site as the check reads it, from its host document. */
|
|
114
|
+
export declare function seoAuditSiteOf(host: SeoSiteHost): SeoAuditSite;
|
|
115
|
+
/**
|
|
116
|
+
* Read `hostId`'s published pages for the check.
|
|
117
|
+
*
|
|
118
|
+
* `keywords` are the target keyword lines a person typed (`/path: a, b`);
|
|
119
|
+
* `templateScreenIds` are the screens a collection, the store or the host
|
|
120
|
+
* renders other URLs through, which are not pages of their own.
|
|
121
|
+
*/
|
|
122
|
+
export declare function scanSeoSite(store: SeoScanStore, hostId: string, host: SeoSiteHost, options?: {
|
|
123
|
+
keywords?: unknown;
|
|
124
|
+
templateScreenIds?: Iterable<string>;
|
|
125
|
+
}): Promise<SeoSiteScan>;
|
|
126
|
+
export {};
|
|
@@ -0,0 +1,162 @@
|
|
|
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 { screenRoutePathToUrl } from "./screen-route.js";
|
|
18
|
+
import { nodesReferenceScreen } from "./screen-link-value.js";
|
|
19
|
+
import { isScreenIndexable, isSearchDiscouraged, statusPageScreenIds } from "./search-indexing.js";
|
|
20
|
+
import { SEO_AUDIT_MAX_PAGES, parseSeoKeywordLines, seoNormalizePath } from "./seo-audit.js";
|
|
21
|
+
import { seoPageFacts } from "./seo-page-facts.js";
|
|
22
|
+
import { resolveSeoTitleVariables } from "./seo-title-variables.js";
|
|
23
|
+
import { decodeStoredNodes } from "./stored-nodes.js";
|
|
24
|
+
/**
|
|
25
|
+
* Read a site for the SEO check (`seo-audit.ts`) the way its sitemap lists
|
|
26
|
+
* it: every screen the routing map publishes, less the template screens, the
|
|
27
|
+
* status pages and every screen that is not public — through the same shared
|
|
28
|
+
* predicates the tenant's `sitemap.xml` and `/llms.txt` apply, so the check
|
|
29
|
+
* covers exactly the pages a crawler is handed. Then each page's published
|
|
30
|
+
* version, and the shared layouts, for the links that decide which pages
|
|
31
|
+
* nothing points to.
|
|
32
|
+
*
|
|
33
|
+
* It knows nothing about the Firestore SDK. A caller hands it a
|
|
34
|
+
* {@link SeoScanStore} — the Admin SDK's `Firestore` satisfies it — and the
|
|
35
|
+
* template screen ids, which the tenant runtime resolves; so this module stays
|
|
36
|
+
* pure enough to import from anywhere, and a spec hands it a fake.
|
|
37
|
+
*
|
|
38
|
+
* Reads: one projected query of the screens, one of the layouts, and one
|
|
39
|
+
* version document per checked page and per layout — at most
|
|
40
|
+
* {@link SEO_AUDIT_MAX_PAGES} + {@link SEO_SCAN_LAYOUT_LIMIT} + 2.
|
|
41
|
+
*/ /** Shared layouts read for the links they carry. */ export const SEO_SCAN_LAYOUT_LIMIT = 20;
|
|
42
|
+
/** Version documents read at once. */ const READ_CHUNK = 10;
|
|
43
|
+
/** Screen documents the projected query reads. */ const SCREEN_SCAN_LIMIT = 1000;
|
|
44
|
+
const str = (value)=>typeof value === 'string' ? value.trim() : '';
|
|
45
|
+
/** The site as the check reads it, from its host document. */ export function seoAuditSiteOf(host) {
|
|
46
|
+
var _ref, _ref1;
|
|
47
|
+
var _host_seo, _host_seo1;
|
|
48
|
+
return {
|
|
49
|
+
discouraged: isSearchDiscouraged(host),
|
|
50
|
+
entity: (_ref = (_host_seo = host.seo) == null ? void 0 : _host_seo.entity) != null ? _ref : {},
|
|
51
|
+
agent: (_ref1 = (_host_seo1 = host.seo) == null ? void 0 : _host_seo1.agent) != null ? _ref1 : {}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
/** A version's node map and root, or `null` when it cannot be read. */ async function readVersionNodes(parent, versionId) {
|
|
55
|
+
if (!versionId) return null;
|
|
56
|
+
const snapshot = await parent.collection('versions').doc(versionId).get();
|
|
57
|
+
if (!snapshot.exists) return null;
|
|
58
|
+
const nodes = decodeStoredNodes(snapshot.get('nodes'));
|
|
59
|
+
if (!nodes) return null;
|
|
60
|
+
const rootId = snapshot.get('rootId');
|
|
61
|
+
return _extends({
|
|
62
|
+
nodes
|
|
63
|
+
}, typeof rootId === 'string' ? {
|
|
64
|
+
rootId
|
|
65
|
+
} : {});
|
|
66
|
+
}
|
|
67
|
+
async function inChunks(items, run) {
|
|
68
|
+
for(let start = 0; start < items.length; start += READ_CHUNK){
|
|
69
|
+
await Promise.all(items.slice(start, start + READ_CHUNK).map(run));
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Read `hostId`'s published pages for the check.
|
|
74
|
+
*
|
|
75
|
+
* `keywords` are the target keyword lines a person typed (`/path: a, b`);
|
|
76
|
+
* `templateScreenIds` are the screens a collection, the store or the host
|
|
77
|
+
* renders other URLs through, which are not pages of their own.
|
|
78
|
+
*/ export async function scanSeoSite(store, hostId, host, options = {}) {
|
|
79
|
+
var _host_screens, _options_templateScreenIds;
|
|
80
|
+
var _host_seo;
|
|
81
|
+
const hostRef = store.collection('hosts').doc(hostId);
|
|
82
|
+
const routing = (_host_screens = host.screens) != null ? _host_screens : {};
|
|
83
|
+
const [screenDocs, layoutDocs] = await Promise.all([
|
|
84
|
+
hostRef.collection('screens').select('visibility', 'seo', 'description', 'displayName', 'versionId', 'deletedAt').limit(SCREEN_SCAN_LIMIT).get(),
|
|
85
|
+
hostRef.collection('layouts').select('versionId').limit(SEO_SCAN_LAYOUT_LIMIT).get()
|
|
86
|
+
]);
|
|
87
|
+
const screens = new Map(screenDocs.docs.map((doc)=>{
|
|
88
|
+
var _doc_data;
|
|
89
|
+
return [
|
|
90
|
+
doc.id,
|
|
91
|
+
(_doc_data = doc.data()) != null ? _doc_data : {}
|
|
92
|
+
];
|
|
93
|
+
}));
|
|
94
|
+
const excluded = new Set([
|
|
95
|
+
...statusPageScreenIds(host),
|
|
96
|
+
...(_options_templateScreenIds = options.templateScreenIds) != null ? _options_templateScreenIds : []
|
|
97
|
+
]);
|
|
98
|
+
const published = Object.entries(routing).filter(([screenId])=>!excluded.has(screenId)).filter(([screenId])=>{
|
|
99
|
+
const screen = screens.get(screenId);
|
|
100
|
+
return Boolean(screen) && !(screen == null ? void 0 : screen.deletedAt) && isScreenIndexable(screen);
|
|
101
|
+
}).map(([screenId, route])=>({
|
|
102
|
+
screenId,
|
|
103
|
+
path: screenRoutePathToUrl(String(route))
|
|
104
|
+
})).sort((a, b)=>a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
|
|
105
|
+
const checked = published.slice(0, SEO_AUDIT_MAX_PAGES);
|
|
106
|
+
const versions = new Map();
|
|
107
|
+
await inChunks(checked, async ({ screenId })=>{
|
|
108
|
+
var _screens_get;
|
|
109
|
+
versions.set(screenId, await readVersionNodes(hostRef.collection('screens').doc(screenId), str((_screens_get = screens.get(screenId)) == null ? void 0 : _screens_get.versionId)));
|
|
110
|
+
});
|
|
111
|
+
const layoutNodes = [];
|
|
112
|
+
await inChunks(layoutDocs.docs.filter((doc)=>str(doc.get('versionId'))), async (doc)=>{
|
|
113
|
+
const version = await readVersionNodes(doc.ref, str(doc.get('versionId')));
|
|
114
|
+
if (version) layoutNodes.push(version.nodes);
|
|
115
|
+
});
|
|
116
|
+
const keywordsByPath = parseSeoKeywordLines(options.keywords);
|
|
117
|
+
const checkedPaths = new Set(checked.map((page)=>seoNormalizePath(page.path)));
|
|
118
|
+
const notes = Object.keys(keywordsByPath).filter((path)=>!checkedPaths.has(path)).map((path)=>`Keywords for ${path} were not used: no checked page is published at that address.`);
|
|
119
|
+
const siteName = str((_host_seo = host.seo) == null ? void 0 : _host_seo.title) || str(host.displayName);
|
|
120
|
+
const pages = checked.map(({ screenId, path })=>{
|
|
121
|
+
var _screens_get, _versions_get, _screen_seo, _keywordsByPath_seoNormalizePath, _ref;
|
|
122
|
+
var _host_seo;
|
|
123
|
+
const screen = (_screens_get = screens.get(screenId)) != null ? _screens_get : {};
|
|
124
|
+
const version = (_versions_get = versions.get(screenId)) != null ? _versions_get : null;
|
|
125
|
+
const storedSeo = (_screen_seo = screen.seo) != null ? _screen_seo : {};
|
|
126
|
+
const linkedFrom = layoutNodes.some((nodes)=>nodesReferenceScreen(nodes, screenId)) || checked.some((other)=>{
|
|
127
|
+
var _ref;
|
|
128
|
+
var _versions_get;
|
|
129
|
+
return other.screenId !== screenId && nodesReferenceScreen((_ref = (_versions_get = versions.get(other.screenId)) == null ? void 0 : _versions_get.nodes) != null ? _ref : null, screenId);
|
|
130
|
+
});
|
|
131
|
+
return {
|
|
132
|
+
screenId,
|
|
133
|
+
path,
|
|
134
|
+
name: str(screen.displayName) || path,
|
|
135
|
+
versionId: str(screen.versionId) || null,
|
|
136
|
+
// Judged as the head renders it: `{{page.name}} – {{site.name}}` on two
|
|
137
|
+
// pages is two different titles, and its length is the rendered one.
|
|
138
|
+
seo: _extends({}, storedSeo, {
|
|
139
|
+
title: resolveSeoTitleVariables(storedSeo.title, {
|
|
140
|
+
'page.name': str(screen.displayName),
|
|
141
|
+
'site.name': siteName,
|
|
142
|
+
'site.separator': str((_host_seo = host.seo) == null ? void 0 : _host_seo.separator)
|
|
143
|
+
})
|
|
144
|
+
}),
|
|
145
|
+
storedSeo,
|
|
146
|
+
description: str(screen.description),
|
|
147
|
+
facts: seoPageFacts(version == null ? void 0 : version.nodes, {
|
|
148
|
+
rootId: version == null ? void 0 : version.rootId
|
|
149
|
+
}),
|
|
150
|
+
linkedFrom,
|
|
151
|
+
keywords: (_keywordsByPath_seoNormalizePath = keywordsByPath[seoNormalizePath(path)]) != null ? _keywordsByPath_seoNormalizePath : [],
|
|
152
|
+
nodes: (_ref = version == null ? void 0 : version.nodes) != null ? _ref : null
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
pages,
|
|
157
|
+
skipped: Math.max(0, published.length - checked.length),
|
|
158
|
+
notes
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
//# sourceMappingURL=seo-site-scan.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/seo-site-scan.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 { screenRoutePathToUrl } from './screen-route'\nimport { nodesReferenceScreen } from './screen-link-value'\nimport {\n isScreenIndexable,\n isSearchDiscouraged,\n statusPageScreenIds,\n type SearchIndexingScreen,\n} from './search-indexing'\nimport {\n SEO_AUDIT_MAX_PAGES,\n parseSeoKeywordLines,\n seoNormalizePath,\n type SeoAuditPage,\n type SeoAuditSite,\n} from './seo-audit'\nimport { seoPageFacts } from './seo-page-facts'\nimport type { PageMarkdownNodes } from './page-markdown'\nimport { resolveSeoTitleVariables } from './seo-title-variables'\nimport { decodeStoredNodes } from './stored-nodes'\n\n/**\n * Read a site for the SEO check (`seo-audit.ts`) the way its sitemap lists\n * it: every screen the routing map publishes, less the template screens, the\n * status pages and every screen that is not public — through the same shared\n * predicates the tenant's `sitemap.xml` and `/llms.txt` apply, so the check\n * covers exactly the pages a crawler is handed. Then each page's published\n * version, and the shared layouts, for the links that decide which pages\n * nothing points to.\n *\n * It knows nothing about the Firestore SDK. A caller hands it a\n * {@link SeoScanStore} — the Admin SDK's `Firestore` satisfies it — and the\n * template screen ids, which the tenant runtime resolves; so this module stays\n * pure enough to import from anywhere, and a spec hands it a fake.\n *\n * Reads: one projected query of the screens, one of the layouts, and one\n * version document per checked page and per layout — at most\n * {@link SEO_AUDIT_MAX_PAGES} + {@link SEO_SCAN_LAYOUT_LIMIT} + 2.\n */\n\n/** Shared layouts read for the links they carry. */\nexport const SEO_SCAN_LAYOUT_LIMIT = 20\n\n/** Version documents read at once. */\nconst READ_CHUNK = 10\n\n/** Screen documents the projected query reads. */\nconst SCREEN_SCAN_LIMIT = 1000\n\n/* ---- The store, structurally ---------------------------------------------- */\n\nexport interface SeoScanSnapshot {\n readonly exists: boolean\n get(field: string): unknown\n}\n\nexport interface SeoScanQueryDocument {\n readonly id: string\n data(): unknown\n get(field: string): unknown\n readonly ref: SeoScanDocumentRef\n}\n\nexport interface SeoScanQuery {\n limit(count: number): SeoScanQuery\n get(): Promise<{ readonly docs: readonly SeoScanQueryDocument[] }>\n}\n\nexport interface SeoScanCollection {\n select(...fields: string[]): SeoScanQuery\n doc(id: string): SeoScanDocumentRef\n}\n\nexport interface SeoScanDocumentRef {\n collection(name: string): SeoScanCollection\n get(): Promise<SeoScanSnapshot>\n}\n\n/** What the scan reads through; the Admin SDK's `Firestore` is one. */\nexport interface SeoScanStore {\n collection(name: string): SeoScanCollection\n}\n\n/* ---- What it reads --------------------------------------------------------- */\n\n/** The host document fields the scan and the site findings read. */\nexport interface SeoSiteHost {\n displayName?: string\n screens?: Record<string, string>\n errorScreens?: Record<string, string>\n notFoundScreenId?: string | null\n seo?: {\n title?: string\n separator?: string\n discourageSearchEngines?: boolean\n entity?: { name?: string; description?: string }\n agent?: { whenToUse?: string }\n }\n}\n\n/** The screen document fields the scan reads. */\ninterface SeoScreenDocument {\n displayName?: string\n description?: string\n versionId?: string\n visibility?: unknown\n deletedAt?: unknown\n seo?: {\n title?: string\n description?: string\n breadcrumb?: string\n image?: string\n imageAlt?: string\n }\n}\n\nexport interface SeoScannedPage extends SeoAuditPage {\n /** The listing as the screen document stores it: a title's variables unresolved. */\n storedSeo: NonNullable<SeoScreenDocument['seo']>\n /** The page's node map as checked. */\n nodes: Record<string, unknown> | null\n}\n\nexport interface SeoSiteScan {\n pages: SeoScannedPage[]\n /** Published pages past {@link SEO_AUDIT_MAX_PAGES}. */\n skipped: number\n /** Customer-safe lines: a keyword line naming a page the site does not publish. */\n notes: string[]\n}\n\nconst str = (value: unknown): string => (typeof value === 'string' ? value.trim() : '')\n\n/** The site as the check reads it, from its host document. */\nexport function seoAuditSiteOf(host: SeoSiteHost): SeoAuditSite {\n return {\n discouraged: isSearchDiscouraged(host),\n entity: host.seo?.entity ?? {},\n agent: host.seo?.agent ?? {},\n }\n}\n\n/** A version's node map and root, or `null` when it cannot be read. */\nasync function readVersionNodes(\n parent: SeoScanDocumentRef,\n versionId: string,\n): Promise<{ nodes: Record<string, unknown>; rootId?: string } | null> {\n if (!versionId) return null\n const snapshot = await parent.collection('versions').doc(versionId).get()\n if (!snapshot.exists) return null\n const nodes = decodeStoredNodes<Record<string, unknown>>(snapshot.get('nodes'))\n if (!nodes) return null\n const rootId = snapshot.get('rootId')\n return { nodes, ...(typeof rootId === 'string' ? { rootId } : {}) }\n}\n\nasync function inChunks<T>(items: readonly T[], run: (item: T) => Promise<void>): Promise<void> {\n for (let start = 0; start < items.length; start += READ_CHUNK) {\n await Promise.all(items.slice(start, start + READ_CHUNK).map(run))\n }\n}\n\n/**\n * Read `hostId`'s published pages for the check.\n *\n * `keywords` are the target keyword lines a person typed (`/path: a, b`);\n * `templateScreenIds` are the screens a collection, the store or the host\n * renders other URLs through, which are not pages of their own.\n */\nexport async function scanSeoSite(\n store: SeoScanStore,\n hostId: string,\n host: SeoSiteHost,\n options: { keywords?: unknown; templateScreenIds?: Iterable<string> } = {},\n): Promise<SeoSiteScan> {\n const hostRef = store.collection('hosts').doc(hostId)\n const routing = host.screens ?? {}\n const [screenDocs, layoutDocs] = await Promise.all([\n hostRef\n .collection('screens')\n .select('visibility', 'seo', 'description', 'displayName', 'versionId', 'deletedAt')\n .limit(SCREEN_SCAN_LIMIT)\n .get(),\n hostRef.collection('layouts').select('versionId').limit(SEO_SCAN_LAYOUT_LIMIT).get(),\n ])\n const screens = new Map(screenDocs.docs.map((doc) => [doc.id, (doc.data() ?? {}) as SeoScreenDocument]))\n const excluded = new Set<string>([...statusPageScreenIds(host), ...(options.templateScreenIds ?? [])])\n const published = Object.entries(routing)\n .filter(([screenId]) => !excluded.has(screenId))\n .filter(([screenId]) => {\n const screen = screens.get(screenId)\n return Boolean(screen) && !screen?.deletedAt && isScreenIndexable(screen as SearchIndexingScreen)\n })\n .map(([screenId, route]) => ({ screenId, path: screenRoutePathToUrl(String(route)) }))\n .sort((a, b) => (a.path < b.path ? -1 : a.path > b.path ? 1 : 0))\n const checked = published.slice(0, SEO_AUDIT_MAX_PAGES)\n\n const versions = new Map<string, { nodes: Record<string, unknown>; rootId?: string } | null>()\n await inChunks(checked, async ({ screenId }) => {\n versions.set(\n screenId,\n await readVersionNodes(hostRef.collection('screens').doc(screenId), str(screens.get(screenId)?.versionId)),\n )\n })\n const layoutNodes: Record<string, unknown>[] = []\n await inChunks(\n layoutDocs.docs.filter((doc) => str(doc.get('versionId'))),\n async (doc) => {\n const version = await readVersionNodes(doc.ref, str(doc.get('versionId')))\n if (version) layoutNodes.push(version.nodes)\n },\n )\n\n const keywordsByPath = parseSeoKeywordLines(options.keywords)\n const checkedPaths = new Set(checked.map((page) => seoNormalizePath(page.path)))\n const notes = Object.keys(keywordsByPath)\n .filter((path) => !checkedPaths.has(path))\n .map((path) => `Keywords for ${path} were not used: no checked page is published at that address.`)\n\n const siteName = str(host.seo?.title) || str(host.displayName)\n const pages: SeoScannedPage[] = checked.map(({ screenId, path }) => {\n const screen = screens.get(screenId) ?? {}\n const version = versions.get(screenId) ?? null\n const storedSeo = screen.seo ?? {}\n const linkedFrom =\n layoutNodes.some((nodes) => nodesReferenceScreen(nodes, screenId)) ||\n checked.some(\n (other) =>\n other.screenId !== screenId &&\n nodesReferenceScreen(versions.get(other.screenId)?.nodes ?? null, screenId),\n )\n return {\n screenId,\n path,\n name: str(screen.displayName) || path,\n versionId: str(screen.versionId) || null,\n // Judged as the head renders it: `{{page.name}} – {{site.name}}` on two\n // pages is two different titles, and its length is the rendered one.\n seo: {\n ...storedSeo,\n title: resolveSeoTitleVariables(storedSeo.title, {\n 'page.name': str(screen.displayName),\n 'site.name': siteName,\n 'site.separator': str(host.seo?.separator),\n }),\n },\n storedSeo,\n description: str(screen.description),\n facts: seoPageFacts(version?.nodes as PageMarkdownNodes | undefined, { rootId: version?.rootId }),\n linkedFrom,\n keywords: keywordsByPath[seoNormalizePath(path)] ?? [],\n nodes: version?.nodes ?? null,\n }\n })\n return { pages, skipped: Math.max(0, published.length - checked.length), notes }\n}\n"],"names":["screenRoutePathToUrl","nodesReferenceScreen","isScreenIndexable","isSearchDiscouraged","statusPageScreenIds","SEO_AUDIT_MAX_PAGES","parseSeoKeywordLines","seoNormalizePath","seoPageFacts","resolveSeoTitleVariables","decodeStoredNodes","SEO_SCAN_LAYOUT_LIMIT","READ_CHUNK","SCREEN_SCAN_LIMIT","str","value","trim","seoAuditSiteOf","host","discouraged","entity","seo","agent","readVersionNodes","parent","versionId","snapshot","collection","doc","get","exists","nodes","rootId","inChunks","items","run","start","length","Promise","all","slice","map","scanSeoSite","store","hostId","options","hostRef","routing","screens","screenDocs","layoutDocs","select","limit","Map","docs","id","data","excluded","Set","templateScreenIds","published","Object","entries","filter","screenId","has","screen","Boolean","deletedAt","route","path","String","sort","a","b","checked","versions","set","layoutNodes","version","ref","push","keywordsByPath","keywords","checkedPaths","page","notes","keys","siteName","title","displayName","pages","storedSeo","linkedFrom","some","other","name","separator","description","facts","skipped","Math","max"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/seo-title-variables.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 * The variables a page title may be written in terms of, and how a written\n * title becomes a rendered one.\n *\n * ## Why this exists\n *\n * A title is verbatim (AGL-1341, and that rule is right — see `seo-title`), so\n * an author who wants the brand in a title TYPES it. Fifty screens on the\n * marketing site end in a literal ` | Aglyn`, which means the site name and\n * the character in front of it are stored fifty times, and changing either one\n * is fifty edits that nothing reminds you to make. `seo.separator` already\n * exists as a setting and every one of those titles ignores it.\n *\n * So a title may now be WRITTEN in terms of what it depends on:\n *\n * ```\n * {{page.name}} {{site.separator}} {{site.name}}\n * ```\n *\n * and a site-name change is one edit in Host setup → SEO.\n *\n * ## ⛔ No rendered title changes because this shipped\n *\n * The whole feature is opt-in at the string level: a title with no `{{` in it\n * is returned unchanged, character for character, by the same code path that\n * returned it before. Nothing migrates the fifty existing titles — a migration\n * could not be output-preserving anyway, because ` | Aglyn` is not what\n * `{{site.separator}}{{site.name}}` resolves to on a host whose separator is\n * `-` and whose site title is the long form. They keep working as literals\n * until somebody decides otherwise, one at a time.\n *\n * `DEFAULT_TITLE_PATTERN` is held to the same standard: it is the composition\n * `resolveSeoTitle` already performed for a page with no authored title, said\n * in tokens instead of in code, and `seo-title.spec` pins that the two agree.\n *\n * ## The grammar is `{{…}}`, not `[…]`\n *\n * Because the platform already has one: the besigner's attribute fields, the\n * markdown blocks and the campaign editors all serialize `{{…}}` and the\n * people writing SEO titles are the same people who write those. A second\n * grammar for the same idea is a second thing to learn and a second parser to\n * keep in step.\n *\n * ## What is NOT a variable here\n *\n * Anything per-visitor or per-request. A `<title>` is rendered into a cached,\n * shared HTML document and read by crawlers, so a variable that resolved to\n * something about the reader would be wrong for everyone it was cached for.\n * These three are properties of the page and the site, both of which are the\n * same for everybody who asks for that URL.\n */\n\n/** A variable's name, as it is written between the braces. */\nexport type SeoTitleVariableName = 'page.name' | 'site.name' | 'site.separator'\n\nexport interface SeoTitleVariableDefinition {\n name: SeoTitleVariableName\n /** What the insert menu calls it. */\n label: string\n /** One line under the label, saying where the value comes from. */\n description: string\n}\n\n/**\n * Every variable a title may use, in the order a picker should offer them —\n * which is the order they appear in the default pattern, so the menu reads as\n * the sentence it builds.\n */\nexport const SEO_TITLE_VARIABLES: readonly SeoTitleVariableDefinition[] = [\n {\n name: 'page.name',\n label: 'Page name',\n description: 'What this page is called — a screen’s display name.',\n },\n {\n name: 'site.separator',\n label: 'Separator',\n description: 'The character from Host setup → SEO that joins the two.',\n },\n {\n name: 'site.name',\n label: 'Site name',\n description: 'The site title from Host setup → SEO.',\n },\n]\n\n/**\n * The composition every untitled page gets when the site sets no pattern of\n * its own.\n *\n * This is `resolveSeoTitle`'s previous hard-coded composition, written out:\n * name, separator padded by a space on each side, site title. It is a constant\n * rather than a string built at the call site so that the setting's default,\n * the placeholder the editor shows and the value the renderer falls back to\n * are one thing.\n */\nexport const DEFAULT_TITLE_PATTERN = '{{page.name}} {{site.separator}} {{site.name}}'\n\n/** The values the variables stand for, for one page of one site. */\nexport interface SeoTitleVariableValues {\n 'page.name'?: string | null\n 'site.name'?: string | null\n 'site.separator'?: string | null\n}\n\n/** Trimmed, or `''` for anything that is not a live string. */\nfunction clean(value?: string | null): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\n/**\n * The separator a site uses when it has set none.\n *\n * An en dash, which is what `seo-title` has padded around since AGL-1341. It\n * lives here because `{{site.separator}}` has to resolve to the SAME character\n * the code composed with — a variable that stood for \"the separator, unless\n * it is unset, in which case nothing\" would silently change the title of every\n * untitled page on every site that never filled the field in.\n */\nexport const DEFAULT_TITLE_SEPARATOR = '\\u2013'\n\n/** The site's separator, defaulted the one way everything defaults it. */\nexport function seoSeparatorOrDefault(separator?: string | null): string {\n return clean(separator) || DEFAULT_TITLE_SEPARATOR\n}\n\n/**\n * Whether a written value uses any variable at all.\n *\n * The cheap gate in front of every path here: a title with no `{{` is returned\n * by identity, which is what makes \"nothing renders differently\" a property of\n * the code rather than a claim about the data.\n */\nexport function hasSeoTitleVariables(value?: string | null): boolean {\n return typeof value === 'string' && value.includes('{{')\n}\n\n/** Whether a name is one this catalog defines. */\nexport function isSeoTitleVariable(name: string): name is SeoTitleVariableName {\n return SEO_TITLE_VARIABLES.some((variable) => variable.name === name)\n}\n\n/**\n * `{{ name }}` — the whole token, and the name inside it with its padding\n * trimmed, so a human who typed a space after the braces is not punished for\n * it.\n */\nconst TOKEN = /\\{\\{\\s*([a-zA-Z][\\w.]*)\\s*\\}\\}/g\n\ninterface Part {\n /** What renders here, once the variable is resolved. */\n text: string\n /** The variable this came from, if it came from one. */\n variable?: string\n}\n\n/** The written string as literal runs and variable slots, in order. */\nfunction partsOf(source: string, values: SeoTitleVariableValues): Part[] {\n const parts: Part[] = []\n let at = 0\n TOKEN.lastIndex = 0\n for (let match = TOKEN.exec(source); match; match = TOKEN.exec(source)) {\n if (match.index > at) parts.push({ text: source.slice(at, match.index) })\n const name = match[1]\n parts.push({\n variable: name,\n // An unknown variable renders AS ITSELF — see the resolver's docblock.\n text: !isSeoTitleVariable(name)\n ? match[0]\n : name === 'site.separator'\n ? seoSeparatorOrDefault(values[name])\n : clean(values[name]),\n })\n at = match.index + match[0].length\n }\n if (at < source.length) parts.push({ text: source.slice(at) })\n return parts\n}\n\n/**\n * Resolve a written title into the one that is rendered.\n *\n * ## An unknown variable is left alone\n *\n * `{{page.subtitle}}` renders as itself, visibly, rather than as an empty gap.\n * A typo that vanished would produce a title that is silently missing a piece,\n * and the author's next look at the live page would show them a sentence that\n * merely reads a bit short. Leaving it visible is the version somebody\n * notices. The editor's picker and autocomplete are what keep it from\n * happening; this is what happens when they are bypassed.\n *\n * ## A separator needs something on BOTH sides\n *\n * This is `resolveSeoTitle`'s own rule (AGL-1341) and it survives the move into\n * a pattern, because it is the rule that stops `<title>` from beginning with a\n * dash on a page whose name is missing. A `{{site.separator}}` with nothing\n * rendered on one side of it is dropped, whatever order the pattern put the\n * pieces in — which is why the parts are walked rather than regex-substituted:\n * a substitution cannot see its neighbours.\n *\n * ## An empty variable takes its spacing with it\n *\n * Otherwise a site with no name set renders `Pricing ` — the gap where the\n * missing piece was, and a trailing separator's worth of whitespace. Runs\n * collapse to one space and the result is trimmed.\n */\nexport function resolveSeoTitleVariables(\n written: string | null | undefined,\n values: SeoTitleVariableValues,\n): string {\n const source = typeof written === 'string' ? written : ''\n if (!hasSeoTitleVariables(source)) return source\n\n const parts = partsOf(source, values)\n const rendered = parts.map((part) => part.text)\n // A separator survives only where something renders on each side of it.\n parts.forEach((part, index) => {\n if (part.variable !== 'site.separator') return\n const live = (from: number, to: number) =>\n rendered.slice(from, to).some((text) => text.trim())\n if (!live(0, index) || !live(index + 1, rendered.length)) rendered[index] = ''\n })\n\n return rendered.join('').replace(/\\s{2,}/g, ' ').trim()\n}\n"],"names":["SEO_TITLE_VARIABLES","name","label","description","DEFAULT_TITLE_PATTERN","clean","value","trim","DEFAULT_TITLE_SEPARATOR","seoSeparatorOrDefault","separator","hasSeoTitleVariables","includes","isSeoTitleVariable","some","variable","TOKEN","partsOf","source","values","parts","at","lastIndex","match","exec","index","push","text","slice","length","resolveSeoTitleVariables","written","rendered","map","part","forEach","live","from","to","join","replace"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../../../libs/aglyn/src/lib/app-utils/seo-title-variables.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 * The variables a page title may be written in terms of, and how a written\n * title becomes a rendered one.\n *\n * ## Why this exists\n *\n * A title is verbatim (AGL-1341, and that rule is right — see `seo-title`), so\n * an author who wants the brand in a title TYPES it. Fifty screens on the\n * marketing site end in a literal ` | Aglyn`, which means the site name and\n * the character in front of it are stored fifty times, and changing either one\n * is fifty edits that nothing reminds you to make. `seo.separator` already\n * exists as a setting and every one of those titles ignores it.\n *\n * So a title may now be WRITTEN in terms of what it depends on:\n *\n * ```\n * {{page.name}} {{site.separator}} {{site.name}}\n * ```\n *\n * and a site-name change is one edit in Host setup → SEO.\n *\n * ## ⛔ No rendered title changes because this shipped\n *\n * The whole feature is opt-in at the string level: a title with no `{{` in it\n * is returned unchanged, character for character, by the same code path that\n * returned it before. Nothing migrates the fifty existing titles — a migration\n * could not be output-preserving anyway, because ` | Aglyn` is not what\n * `{{site.separator}}{{site.name}}` resolves to on a host whose separator is\n * `-` and whose site title is the long form. They keep working as literals\n * until somebody decides otherwise, one at a time.\n *\n * `DEFAULT_TITLE_PATTERN` is held to the same standard: it is the composition\n * `resolveSeoTitle` already performed for a page with no authored title, said\n * in tokens instead of in code, and `seo-title.spec` pins that the two agree.\n *\n * ## The grammar is `{{…}}`, not `[…]`\n *\n * Because the platform already has one: the besigner's attribute fields, the\n * markdown blocks and the campaign editors all serialize `{{…}}` and the\n * people writing SEO titles are the same people who write those. A second\n * grammar for the same idea is a second thing to learn and a second parser to\n * keep in step.\n *\n * ## What is NOT a variable here\n *\n * Anything per-visitor or per-request. A `<title>` is rendered into a cached,\n * shared HTML document and read by crawlers, so a variable that resolved to\n * something about the reader would be wrong for everyone it was cached for.\n * These three are properties of the page and the site, both of which are the\n * same for everybody who asks for that URL.\n */\n\n/** A variable's name, as it is written between the braces. */\nexport type SeoTitleVariableName = 'page.name' | 'site.name' | 'site.separator'\n\nexport interface SeoTitleVariableDefinition {\n name: SeoTitleVariableName\n /** What the insert menu calls it. */\n label: string\n /** One line under the label, saying where the value comes from. */\n description: string\n}\n\n/**\n * Every variable a title may use, in the order a picker should offer them —\n * which is the order they appear in the default pattern, so the menu reads as\n * the sentence it builds.\n */\nexport const SEO_TITLE_VARIABLES: readonly SeoTitleVariableDefinition[] = [\n {\n name: 'page.name',\n label: 'Page name',\n description: 'What this page is called — its display name.',\n },\n {\n name: 'site.separator',\n label: 'Separator',\n description: 'The character from Host setup → SEO that joins the two.',\n },\n {\n name: 'site.name',\n label: 'Site name',\n description: 'The site title from Host setup → SEO.',\n },\n]\n\n/**\n * The composition every untitled page gets when the site sets no pattern of\n * its own.\n *\n * This is `resolveSeoTitle`'s previous hard-coded composition, written out:\n * name, separator padded by a space on each side, site title. It is a constant\n * rather than a string built at the call site so that the setting's default,\n * the placeholder the editor shows and the value the renderer falls back to\n * are one thing.\n */\nexport const DEFAULT_TITLE_PATTERN = '{{page.name}} {{site.separator}} {{site.name}}'\n\n/** The values the variables stand for, for one page of one site. */\nexport interface SeoTitleVariableValues {\n 'page.name'?: string | null\n 'site.name'?: string | null\n 'site.separator'?: string | null\n}\n\n/** Trimmed, or `''` for anything that is not a live string. */\nfunction clean(value?: string | null): string {\n return typeof value === 'string' ? value.trim() : ''\n}\n\n/**\n * The separator a site uses when it has set none.\n *\n * An en dash, which is what `seo-title` has padded around since AGL-1341. It\n * lives here because `{{site.separator}}` has to resolve to the SAME character\n * the code composed with — a variable that stood for \"the separator, unless\n * it is unset, in which case nothing\" would silently change the title of every\n * untitled page on every site that never filled the field in.\n */\nexport const DEFAULT_TITLE_SEPARATOR = '\\u2013'\n\n/** The site's separator, defaulted the one way everything defaults it. */\nexport function seoSeparatorOrDefault(separator?: string | null): string {\n return clean(separator) || DEFAULT_TITLE_SEPARATOR\n}\n\n/**\n * Whether a written value uses any variable at all.\n *\n * The cheap gate in front of every path here: a title with no `{{` is returned\n * by identity, which is what makes \"nothing renders differently\" a property of\n * the code rather than a claim about the data.\n */\nexport function hasSeoTitleVariables(value?: string | null): boolean {\n return typeof value === 'string' && value.includes('{{')\n}\n\n/** Whether a name is one this catalog defines. */\nexport function isSeoTitleVariable(name: string): name is SeoTitleVariableName {\n return SEO_TITLE_VARIABLES.some((variable) => variable.name === name)\n}\n\n/**\n * `{{ name }}` — the whole token, and the name inside it with its padding\n * trimmed, so a human who typed a space after the braces is not punished for\n * it.\n */\nconst TOKEN = /\\{\\{\\s*([a-zA-Z][\\w.]*)\\s*\\}\\}/g\n\ninterface Part {\n /** What renders here, once the variable is resolved. */\n text: string\n /** The variable this came from, if it came from one. */\n variable?: string\n}\n\n/** The written string as literal runs and variable slots, in order. */\nfunction partsOf(source: string, values: SeoTitleVariableValues): Part[] {\n const parts: Part[] = []\n let at = 0\n TOKEN.lastIndex = 0\n for (let match = TOKEN.exec(source); match; match = TOKEN.exec(source)) {\n if (match.index > at) parts.push({ text: source.slice(at, match.index) })\n const name = match[1]\n parts.push({\n variable: name,\n // An unknown variable renders AS ITSELF — see the resolver's docblock.\n text: !isSeoTitleVariable(name)\n ? match[0]\n : name === 'site.separator'\n ? seoSeparatorOrDefault(values[name])\n : clean(values[name]),\n })\n at = match.index + match[0].length\n }\n if (at < source.length) parts.push({ text: source.slice(at) })\n return parts\n}\n\n/**\n * Resolve a written title into the one that is rendered.\n *\n * ## An unknown variable is left alone\n *\n * `{{page.subtitle}}` renders as itself, visibly, rather than as an empty gap.\n * A typo that vanished would produce a title that is silently missing a piece,\n * and the author's next look at the live page would show them a sentence that\n * merely reads a bit short. Leaving it visible is the version somebody\n * notices. The editor's picker and autocomplete are what keep it from\n * happening; this is what happens when they are bypassed.\n *\n * ## A separator needs something on BOTH sides\n *\n * This is `resolveSeoTitle`'s own rule (AGL-1341) and it survives the move into\n * a pattern, because it is the rule that stops `<title>` from beginning with a\n * dash on a page whose name is missing. A `{{site.separator}}` with nothing\n * rendered on one side of it is dropped, whatever order the pattern put the\n * pieces in — which is why the parts are walked rather than regex-substituted:\n * a substitution cannot see its neighbours.\n *\n * ## An empty variable takes its spacing with it\n *\n * Otherwise a site with no name set renders `Pricing ` — the gap where the\n * missing piece was, and a trailing separator's worth of whitespace. Runs\n * collapse to one space and the result is trimmed.\n */\nexport function resolveSeoTitleVariables(\n written: string | null | undefined,\n values: SeoTitleVariableValues,\n): string {\n const source = typeof written === 'string' ? written : ''\n if (!hasSeoTitleVariables(source)) return source\n\n const parts = partsOf(source, values)\n const rendered = parts.map((part) => part.text)\n // A separator survives only where something renders on each side of it.\n parts.forEach((part, index) => {\n if (part.variable !== 'site.separator') return\n const live = (from: number, to: number) =>\n rendered.slice(from, to).some((text) => text.trim())\n if (!live(0, index) || !live(index + 1, rendered.length)) rendered[index] = ''\n })\n\n return rendered.join('').replace(/\\s{2,}/g, ' ').trim()\n}\n"],"names":["SEO_TITLE_VARIABLES","name","label","description","DEFAULT_TITLE_PATTERN","clean","value","trim","DEFAULT_TITLE_SEPARATOR","seoSeparatorOrDefault","separator","hasSeoTitleVariables","includes","isSeoTitleVariable","some","variable","TOKEN","partsOf","source","values","parts","at","lastIndex","match","exec","index","push","text","slice","length","resolveSeoTitleVariables","written","rendered","map","part","forEach","live","from","to","join","replace"],"mappings":"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"}
|
|
@@ -49,12 +49,8 @@ export * from './email-state';
|
|
|
49
49
|
export * from './crm';
|
|
50
50
|
export * from './crm-email-templates';
|
|
51
51
|
export * from './crm-booking';
|
|
52
|
-
export * from './contact-merge';
|
|
53
52
|
export * from './person-erasure';
|
|
54
53
|
export * from './crm-reports';
|
|
55
|
-
export * from './crm-csv';
|
|
56
|
-
export * from './contact-holder';
|
|
57
|
-
export * from './crm-digest';
|
|
58
54
|
export * from './crm-task-reminders';
|
|
59
55
|
export * from './crm-inbound';
|
|
60
56
|
export * from './member-email-aliases';
|
|
@@ -64,8 +60,8 @@ export type { ImportChunkResult, ImportDroppedValue, ImportSkippedRow, } from '.
|
|
|
64
60
|
export * from './contact-custom-fields';
|
|
65
61
|
export * from './consent-groups';
|
|
66
62
|
export * from './marketing-consent';
|
|
63
|
+
export * from './retained-refusals';
|
|
67
64
|
export * from './list-assignment-policy';
|
|
68
|
-
export * from './list-import';
|
|
69
65
|
export * from './dynamic-list-rule';
|
|
70
66
|
export * from './email-topics';
|
|
71
67
|
export * from './compose-layout-nodes';
|
|
@@ -90,7 +86,7 @@ export * from './host-permissions';
|
|
|
90
86
|
export * from './password-policy';
|
|
91
87
|
export * from './idp-profile';
|
|
92
88
|
export * from './onboarding-deep-link';
|
|
93
|
-
export * from './
|
|
89
|
+
export * from './utm-attribution';
|
|
94
90
|
export * from './campaign-membership';
|
|
95
91
|
export * from './deployment-shape';
|
|
96
92
|
export * from './upload-cors';
|
|
@@ -117,7 +113,7 @@ export * from './repeat-infringer';
|
|
|
117
113
|
export * from './org-override-reason';
|
|
118
114
|
export * from './host-tokens';
|
|
119
115
|
export * from './variables';
|
|
120
|
-
export * from './
|
|
116
|
+
export * from './host-events';
|
|
121
117
|
export * from './datasets';
|
|
122
118
|
export * from './expand-repeatables';
|
|
123
119
|
export * from './org-roles';
|
|
@@ -140,6 +136,7 @@ export * from './name-match';
|
|
|
140
136
|
export * from './api-plugins';
|
|
141
137
|
export * from './plugin-api-rate-limit';
|
|
142
138
|
export * from './plugin-api-cross-origin';
|
|
139
|
+
export * from './site-interactions';
|
|
143
140
|
export * from './actions';
|
|
144
141
|
export * from './element-animation';
|
|
145
142
|
export * from './attribution-guard';
|
|
@@ -155,8 +152,8 @@ export * from './author-css';
|
|
|
155
152
|
export * from './author-html';
|
|
156
153
|
export * from './dataset-models';
|
|
157
154
|
export * from './dataset-csv';
|
|
158
|
-
export * from './
|
|
159
|
-
export * from './
|
|
155
|
+
export * from './csv-upload';
|
|
156
|
+
export * from './artifact-provenance';
|
|
160
157
|
export * from './dataset-query';
|
|
161
158
|
export * from './plugin-manifest';
|
|
162
159
|
export * from './email-starter-policy';
|
|
@@ -49,9 +49,9 @@ export * from "./child-contract-compose.js";
|
|
|
49
49
|
// The one reading of `restrictChildren`/`restrictParent`, shared by the
|
|
50
50
|
// besigner's drop check and the AI plugin's node-tree validator (AGL-2905).
|
|
51
51
|
export * from "./lineal-order.js";
|
|
52
|
-
// What a node tree must satisfy before it lands on a canvas that did
|
|
53
|
-
// author it (AGL-2939):
|
|
54
|
-
//
|
|
52
|
+
// What a portable node tree must satisfy before it lands on a canvas that did
|
|
53
|
+
// not author it (AGL-2939): a tree installed from another workspace and a tree
|
|
54
|
+
// a model generated both pass through it.
|
|
55
55
|
export * from "./node-definition-sanitizer.js";
|
|
56
56
|
export * from "./console-routes.js";
|
|
57
57
|
export * from "./console-record-links.js";
|
|
@@ -80,10 +80,6 @@ export * from "./crm-email-templates.js";
|
|
|
80
80
|
// link carries, the record reference inside it, and the wording and due
|
|
81
81
|
// date of what a booking files back on the record.
|
|
82
82
|
export * from "./crm-booking.js";
|
|
83
|
-
// Two contact documents folded into one (AGL-2625): the plan the console
|
|
84
|
-
// previews and the server writes, reading the facet shape from `contacts`
|
|
85
|
-
// and the company mirror from `crm`.
|
|
86
|
-
export * from "./contact-merge.js";
|
|
87
83
|
// A person's privacy erasure from one workspace (AGL-2623): the request's
|
|
88
84
|
// shape and id, the marker a record carries while it waits, and the lists
|
|
89
85
|
// the dialog and the docs share. Pure like `crm`; the sweep is server-side.
|
|
@@ -93,27 +89,14 @@ export * from "./person-erasure.js";
|
|
|
93
89
|
// and beside it because it reads the stage list and the deal weighting from
|
|
94
90
|
// there rather than restating either.
|
|
95
91
|
export * from "./crm-reports.js";
|
|
96
|
-
// Every CRM CSV, written once (AGL-2662): the column lists and the cells
|
|
97
|
-
// each section's Export button writes, and the server's whole-collection
|
|
98
|
-
// export streams — one implementation, because the console app may not
|
|
99
|
-
// import the plugin whose sections used to hold it.
|
|
100
|
-
export * from "./crm-csv.js";
|
|
101
|
-
// Which holder a cross-site reader sees one contact through (AGL-2630),
|
|
102
|
-
// beside the files that write it: the org-level list and the server's
|
|
103
|
-
// export both flatten a contact this way.
|
|
104
|
-
export * from "./contact-holder.js";
|
|
105
|
-
// What one member is owed this morning — the day's window in a named zone,
|
|
106
|
-
// the task and lead bucketing, and the words the notification and the mail
|
|
107
|
-
// say (AGL-2619). Pure like `crm-reports`, read by the scheduled route.
|
|
108
|
-
export * from "./crm-digest.js";
|
|
109
92
|
// A task's reminder at its own time (AGL-2659): which reminder a save
|
|
110
93
|
// leaves, whether one is owed, and the words the notification and the mail
|
|
111
|
-
// say. Pure like `crm-
|
|
94
|
+
// say. Pure like `crm-reports`, read by the hourly route and by every
|
|
112
95
|
// writer of the field.
|
|
113
96
|
export * from "./crm-task-reminders.js";
|
|
114
97
|
// Email capture (AGL-2657): the address's shape and token, which of a
|
|
115
98
|
// message's addresses is the correspondent, the excerpt, and the row a
|
|
116
|
-
// captured message becomes. Pure like `crm-
|
|
99
|
+
// captured message becomes. Pure like `crm-reports`; the webhook route and
|
|
117
100
|
// the address route are the readers with Firestore.
|
|
118
101
|
export * from "./crm-inbound.js";
|
|
119
102
|
// A member's own addresses in a workspace (AGL-2975): the ones they have
|
|
@@ -139,6 +122,9 @@ export * from "./contact-custom-fields.js";
|
|
|
139
122
|
// belongs to.
|
|
140
123
|
export * from "./consent-groups.js";
|
|
141
124
|
export * from "./marketing-consent.js";
|
|
125
|
+
// A refusal kept after the record it was written on is deleted (AGL-3338),
|
|
126
|
+
// read back through the same parser as the record's own.
|
|
127
|
+
export * from "./retained-refusals.js";
|
|
142
128
|
// The ENROLLMENT-time half of the same question, beside its reader for the
|
|
143
129
|
// same reason: `marketing-consent` decides whether a recorded basis lets us
|
|
144
130
|
// mail somebody, and this decides what basis putting them on a list may
|
|
@@ -150,7 +136,6 @@ export * from "./list-assignment-policy.js";
|
|
|
150
136
|
// mechanical screening only, so that an import asks the enrollment question
|
|
151
137
|
// through the same module the one-address add path asks it through rather
|
|
152
138
|
// than answering it a second way.
|
|
153
|
-
export * from "./list-import.js";
|
|
154
139
|
// The dynamic-list rule (§3b/§3c), beside it for the same reasons: pure, and
|
|
155
140
|
// it composes `contacts` for the segment vocabulary rather than restating it.
|
|
156
141
|
export * from "./dynamic-list-rule.js";
|
|
@@ -192,7 +177,7 @@ export * from "./onboarding-deep-link.js";
|
|
|
192
177
|
// Where an account came from (AGL-1731). Beside the plan intent because
|
|
193
178
|
// they are the same hop — the marketing CTA's query string — and both are
|
|
194
179
|
// remembered on `users/{uid}` across the verification wall.
|
|
195
|
-
export * from "./
|
|
180
|
+
export * from "./utm-attribution.js";
|
|
196
181
|
// The edge between a campaign and the forms, screens and contacts a push is
|
|
197
182
|
// coordinated across. Beside the attribution above because the two answer
|
|
198
183
|
// opposite questions about the same word: attribution records where somebody
|
|
@@ -256,7 +241,9 @@ export * from "./repeat-infringer.js";
|
|
|
256
241
|
export * from "./org-override-reason.js";
|
|
257
242
|
export * from "./host-tokens.js";
|
|
258
243
|
export * from "./variables.js";
|
|
259
|
-
|
|
244
|
+
// What happened on a site that an automation can start on: the platform's
|
|
245
|
+
// own event and the ones plugins declare (AGL-3080).
|
|
246
|
+
export * from "./host-events.js";
|
|
260
247
|
export * from "./datasets.js";
|
|
261
248
|
export * from "./expand-repeatables.js";
|
|
262
249
|
export * from "./org-roles.js";
|
|
@@ -284,6 +271,9 @@ export * from "./plugin-api-rate-limit.js";
|
|
|
284
271
|
// `plugin-api-rate-limit`, whose `isMachinePluginApiPath` it reuses so the
|
|
285
272
|
// two gates exempt exactly the same machine surfaces.
|
|
286
273
|
export * from "./plugin-api-cross-origin.js";
|
|
274
|
+
// What a published page does when a visitor does something (AGL-3080): the
|
|
275
|
+
// platform's interaction vocabulary, which `./actions` builds its automations on.
|
|
276
|
+
export * from "./site-interactions.js";
|
|
287
277
|
export * from "./actions.js";
|
|
288
278
|
export * from "./element-animation.js";
|
|
289
279
|
export * from "./attribution-guard.js";
|
|
@@ -301,8 +291,8 @@ export * from "./author-css.js";
|
|
|
301
291
|
export * from "./author-html.js";
|
|
302
292
|
export * from "./dataset-models.js";
|
|
303
293
|
export * from "./dataset-csv.js";
|
|
304
|
-
export * from "./
|
|
305
|
-
export * from "./
|
|
294
|
+
export * from "./csv-upload.js";
|
|
295
|
+
export * from "./artifact-provenance.js";
|
|
306
296
|
export * from "./dataset-query.js";
|
|
307
297
|
export * from "./plugin-manifest.js";
|
|
308
298
|
// After `plugin-manifest`, whose revocation predicates it asks the kill
|