@getvitops/astro 8.2.0-alpha.0 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/CHANGELOG.md +485 -26
  2. package/dist/config-adapters-CbTywgEY.mjs +71 -0
  3. package/dist/config-adapters-CbTywgEY.mjs.map +1 -0
  4. package/dist/exports/tracking.d.mts +1 -1
  5. package/dist/exports/tracking.mjs +1 -1
  6. package/dist/index.d.mts +33 -43
  7. package/dist/index.d.mts.map +1 -1
  8. package/dist/index.mjs +90 -18
  9. package/dist/index.mjs.map +1 -1
  10. package/dist/middleware.d.mts +0 -2
  11. package/dist/middleware.d.mts.map +1 -1
  12. package/dist/middleware.mjs +1 -2
  13. package/dist/routes/conversion.d.mts +71 -15
  14. package/dist/routes/conversion.d.mts.map +1 -1
  15. package/dist/routes/conversion.mjs +79 -68
  16. package/dist/routes/conversion.mjs.map +1 -1
  17. package/dist/{tracking-M2PqlMpw.mjs → tracking-Cb3A0BrC.mjs} +2 -3
  18. package/dist/tracking-Cb3A0BrC.mjs.map +1 -0
  19. package/dist/{tracking-BNKKBqOe.d.mts → tracking-D2xOzjFD.d.mts} +5 -25
  20. package/dist/tracking-D2xOzjFD.d.mts.map +1 -0
  21. package/package.json +7 -6
  22. package/src/ads.ts +15 -22
  23. package/src/components/CookieConsent.astro +2 -1
  24. package/src/components/ReviewCardBody.astro +5 -2
  25. package/src/schemas/Article.astro +2 -1
  26. package/src/schemas/BookActions.astro +2 -1
  27. package/src/schemas/Breadcrumb.astro +2 -1
  28. package/src/schemas/Carousel.astro +2 -1
  29. package/src/schemas/CourseList.astro +2 -1
  30. package/src/schemas/Dataset.astro +2 -1
  31. package/src/schemas/DiscussionForum.astro +2 -1
  32. package/src/schemas/EducationQA.astro +2 -1
  33. package/src/schemas/EmployerAggregateRating.astro +2 -1
  34. package/src/schemas/Event.astro +2 -1
  35. package/src/schemas/FAQ.astro +2 -1
  36. package/src/schemas/FactCheck.astro +2 -1
  37. package/src/schemas/ImageMetadata.astro +2 -1
  38. package/src/schemas/JobPosting.astro +2 -1
  39. package/src/schemas/LocalBusiness.astro +2 -1
  40. package/src/schemas/MathSolver.astro +2 -1
  41. package/src/schemas/MovieCarousel.astro +2 -1
  42. package/src/schemas/Organization.astro +2 -1
  43. package/src/schemas/Product.astro +2 -1
  44. package/src/schemas/ProfilePage.astro +2 -1
  45. package/src/schemas/QAPage.astro +2 -1
  46. package/src/schemas/Recipe.astro +2 -1
  47. package/src/schemas/Review.astro +2 -1
  48. package/src/schemas/SoftwareApp.astro +2 -1
  49. package/src/seo.ts +6 -5
  50. package/src/tracking.ts +5 -9
  51. package/dist/tracking-BNKKBqOe.d.mts.map +0 -1
  52. package/dist/tracking-M2PqlMpw.mjs.map +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getvitops/astro",
3
- "version": "8.2.0-alpha.0",
3
+ "version": "8.2.0",
4
4
  "description": "Astro integration for the Vitops design system — favicons/PWA, web-component scripts, and CSS, via a getvitops() integration + <Head /> component.",
5
5
  "homepage": "https://docs.vitops.ca",
6
6
  "bugs": "https://github.com/getvitops/design-system/issues",
@@ -98,15 +98,16 @@
98
98
  "access": "public"
99
99
  },
100
100
  "dependencies": {
101
- "@getvitops/core": "8.2.0-alpha.0",
102
- "@getvitops/generator": "8.2.0-alpha.0",
103
- "@getvitops/utils": "8.2.0-alpha.0",
104
- "@getvitops/vite": "8.2.0-alpha.0"
101
+ "@getvitops/config": "8.2.0",
102
+ "@getvitops/core": "8.2.0",
103
+ "@getvitops/generator": "8.2.0",
104
+ "@getvitops/utils": "8.2.0",
105
+ "@getvitops/vite": "8.2.0"
105
106
  },
106
107
  "devDependencies": {
107
108
  "@astrojs/sitemap": "^3.7.3",
108
109
  "@tailwindcss/vite": "^4.3.3",
109
- "astro": "^7.1.2",
110
+ "astro": "^7.4.4",
110
111
  "tailwindcss": "^4.3.3"
111
112
  },
112
113
  "peerDependencies": {
package/src/ads.ts CHANGED
@@ -32,32 +32,25 @@ import {
32
32
  type AdProvider,
33
33
  type ConversionLabelMap,
34
34
  } from '@getvitops/utils/ads';
35
+ import type { AdPropertyConfig } from '@getvitops/config';
35
36
  import type { AnalyticsStrategy, ConsentCategory, GatedTag } from './analytics.ts';
36
37
 
37
38
  /**
38
- * One ad property, mirroring a `site.ads` entry field for field.
39
- *
40
- * Structural rather than `SiteAdProperty` imported from the generator, matching
41
- * how `GetvitopsSeoOptions` and `GetvitopsTrackingOptions` already work: the
42
- * component surface must stay usable by a consumer who passes a plain object.
39
+ * One ad property, as `<Ads />` reads it: the fields of a `site.ads` entry that decide what is
40
+ * emitted. A subset of `AdPropertyConfig` from `@getvitops/config` — the schema's own inferred
41
+ * type, not a copy — so a plain object is still enough for a consumer to pass, and a field the
42
+ * schema renames fails here instead of silently reading `undefined`.
43
43
  */
44
- export interface GetvitopsAdProperty {
45
- accountId?: string;
46
- pixelId?: string;
47
- /**
48
- * Google Ads only: the conversion action label(s) paired with `pixelId` (the
49
- * `AW-…` conversion id) — a bare string (back-compat, treated as `call`) or a
50
- * map of action → label. `<Tracking />`'s tel:/mailto: click handler and
51
- * `vitopsFormConversion()` each fire `gtag('event', 'conversion', …)` for
52
- * their own action at the moment of the interaction — see `gtagBootstrap` in
53
- * `@getvitops/utils/ads`.
54
- */
55
- conversionLabel?: string | ConversionLabelMap;
56
- /** Default `marketing`. `analytics` for a property used purely for measurement. */
57
- category?: 'marketing' | 'analytics';
58
- /** False keeps the property on record without emitting its tag. */
59
- enabled?: boolean;
60
- }
44
+ export type GetvitopsAdProperty = Pick<
45
+ AdPropertyConfig,
46
+ | 'accountId'
47
+ | 'pixelId'
48
+ | 'conversionLabel'
49
+ | 'conversionActionId'
50
+ | 'reporting'
51
+ | 'category'
52
+ | 'enabled'
53
+ >;
61
54
 
62
55
  export type GetvitopsAdsOptions = Partial<Record<AdProvider, GetvitopsAdProperty>>;
63
56
 
@@ -28,6 +28,7 @@
28
28
  * `<Analytics />`.
29
29
  */
30
30
  // @ts-ignore — virtual module provided by the vitops() integration at build time
31
+ import { safeHref } from '@getvitops/utils/url';
31
32
  import head from 'virtual:getvitops/head';
32
33
  import type { OptionalConsentCategory } from '../analytics.ts';
33
34
 
@@ -87,7 +88,7 @@ const wording = { ...DEFAULT_LABELS, ...labels };
87
88
  losing any functionality.
88
89
  </p>
89
90
  </slot>
90
- {policyUrl && <a class="link" href={policyUrl}>{policyLabel}</a>}
91
+ {safeHref(policyUrl) && <a class="link" href={safeHref(policyUrl)}>{policyLabel}</a>}
91
92
  </div>
92
93
 
93
94
  {/*
@@ -7,6 +7,7 @@
7
7
  */
8
8
  import type { HTMLAttributes } from 'astro/types';
9
9
  import type { Review } from '@getvitops/utils/reviews';
10
+ import { safeHref } from '@getvitops/utils/url';
10
11
  import Icon from './Icon.astro';
11
12
 
12
13
  export interface Props extends Omit<HTMLAttributes<'article'>, 'slot'> {
@@ -23,6 +24,8 @@ const initials = review.author
23
24
  .map((w) => w[0]?.toUpperCase())
24
25
  .join('');
25
26
 
27
+ // Google-hosted and fetched from outside the repo, so never trusted as a URL.
28
+ const authorPhoto = safeHref(review.authorPhoto);
26
29
  const full = Math.floor(review.rating);
27
30
  const hasHalf = review.rating - full >= 0.5;
28
31
  ---
@@ -30,8 +33,8 @@ const hasHalf = review.rating - full >= 0.5;
30
33
  <article class:list={['review', className]} {...rest}>
31
34
  <header class="review__header">
32
35
  {
33
- avatar && review.authorPhoto ? (
34
- <img class="review__avatar" src={review.authorPhoto} alt="" loading="lazy" />
36
+ avatar && authorPhoto ? (
37
+ <img class="review__avatar" src={authorPhoto} alt="" loading="lazy" />
35
38
  ) : (
36
39
  <span class="review__initials" aria-hidden="true">{initials}</span>
37
40
  )
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  import { articleGraph } from '@getvitops/utils/schema';
3
4
  import type { ArticleGraphOptions } from '@getvitops/utils/schema';
4
5
 
@@ -7,5 +8,5 @@ export type Props = ArticleGraphOptions;
7
8
  const jsonLd = articleGraph(Astro.props);
8
9
  ---
9
10
 
10
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
11
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
11
12
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Props {
3
4
  name: string;
4
5
  author: string | string[];
@@ -77,5 +78,5 @@ const jsonLd = {
77
78
  };
78
79
  ---
79
80
 
80
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
81
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
81
82
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  import { breadcrumbGraph } from '@getvitops/utils/schema';
3
4
  import type { BreadcrumbGraphOptions, BreadcrumbItem } from '@getvitops/utils/schema';
4
5
 
@@ -8,5 +9,5 @@ export type Props = BreadcrumbGraphOptions;
8
9
  const jsonLd = breadcrumbGraph(Astro.props);
9
10
  ---
10
11
 
11
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
12
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
12
13
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  import { itemListGraph } from '@getvitops/utils/schema';
3
4
  import type { ItemListEntry, ItemListGraphOptions } from '@getvitops/utils/schema';
4
5
 
@@ -9,5 +10,5 @@ export type Props = ItemListGraphOptions;
9
10
  const jsonLd = itemListGraph(Astro.props);
10
11
  ---
11
12
 
12
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
13
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
13
14
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface CourseInstance {
3
4
  courseMode: 'online' | 'onsite' | 'blended';
4
5
  startDate?: string;
@@ -118,5 +119,5 @@ const jsonLd = {
118
119
  };
119
120
  ---
120
121
 
121
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
122
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
122
123
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Props {
3
4
  name: string;
4
5
  description: string;
@@ -123,5 +124,5 @@ const jsonLd = {
123
124
  };
124
125
  ---
125
126
 
126
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
127
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
127
128
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface ForumPost {
3
4
  headline: string;
4
5
  text: string;
@@ -78,5 +79,5 @@ const jsonLd = {
78
79
  };
79
80
  ---
80
81
 
81
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
82
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
82
83
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Props {
3
4
  mainEntity: {
4
5
  question: string;
@@ -59,5 +60,5 @@ const jsonLd = {
59
60
  };
60
61
  ---
61
62
 
62
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
63
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
63
64
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Props {
3
4
  name: string;
4
5
  url?: string;
@@ -50,5 +51,5 @@ const jsonLd = {
50
51
  };
51
52
  ---
52
53
 
53
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
54
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
54
55
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Props {
3
4
  name: string;
4
5
  description?: string;
@@ -111,5 +112,5 @@ const jsonLd = {
111
112
  };
112
113
  ---
113
114
 
114
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
115
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
115
116
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  /**
3
4
  * FAQ Schema Component
4
5
  *
@@ -14,5 +15,5 @@ export type Props = FAQGraphOptions;
14
15
  const jsonLd = faqGraph(Astro.props);
15
16
  ---
16
17
 
17
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
18
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
18
19
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Props {
3
4
  claimReviewed: string;
4
5
  url: string;
@@ -80,5 +81,5 @@ const jsonLd = {
80
81
  };
81
82
  ---
82
83
 
83
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
84
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
84
85
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Props {
3
4
  contentUrl: string;
4
5
  creditText?: string;
@@ -52,5 +53,5 @@ const jsonLd = {
52
53
  };
53
54
  ---
54
55
 
55
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
56
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
56
57
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Props {
3
4
  title: string;
4
5
  description: string;
@@ -134,5 +135,5 @@ const jsonLd = {
134
135
  };
135
136
  ---
136
137
 
137
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
138
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
138
139
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  import { localBusinessGraph } from '@getvitops/utils/schema';
3
4
  import type { LocalBusinessGraphOptions } from '@getvitops/utils/schema';
4
5
 
@@ -7,5 +8,5 @@ export type Props = LocalBusinessGraphOptions;
7
8
  const jsonLd = localBusinessGraph(Astro.props);
8
9
  ---
9
10
 
10
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
11
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
11
12
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Props {
3
4
  name: string;
4
5
  url: string;
@@ -50,5 +51,5 @@ const jsonLd = {
50
51
  };
51
52
  ---
52
53
 
53
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
54
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
54
55
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Movie {
3
4
  name: string;
4
5
  url: string;
@@ -88,5 +89,5 @@ const jsonLd = {
88
89
  };
89
90
  ---
90
91
 
91
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
92
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
92
93
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  import { organizationGraph } from '@getvitops/utils/schema';
3
4
  import type { OrganizationGraphOptions } from '@getvitops/utils/schema';
4
5
 
@@ -7,5 +8,5 @@ export type Props = OrganizationGraphOptions;
7
8
  const jsonLd = organizationGraph(Astro.props);
8
9
  ---
9
10
 
10
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
11
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
11
12
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Props {
3
4
  name: string;
4
5
  description?: string;
@@ -246,5 +247,5 @@ const jsonLd = {
246
247
  };
247
248
  ---
248
249
 
249
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
250
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
250
251
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Props {
3
4
  name: string;
4
5
  alternateName?: string;
@@ -143,5 +144,5 @@ const jsonLd = {
143
144
  };
144
145
  ---
145
146
 
146
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
147
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
147
148
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Answer {
3
4
  text: string;
4
5
  dateCreated: string;
@@ -67,5 +68,5 @@ const jsonLd = {
67
68
  };
68
69
  ---
69
70
 
70
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
71
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
71
72
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Props {
3
4
  name: string;
4
5
  description?: string;
@@ -174,5 +175,5 @@ const jsonLd = {
174
175
  };
175
176
  ---
176
177
 
177
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
178
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
178
179
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Props {
3
4
  itemReviewed: {
4
5
  type: 'Product' | 'LocalBusiness' | 'Organization' | 'Book' | 'Movie' | 'Restaurant' | 'Event' | 'CreativeWork' | 'SoftwareApplication' | 'MusicRecording' | 'Recipe';
@@ -134,5 +135,5 @@ const jsonLd = {
134
135
  };
135
136
  ---
136
137
 
137
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
138
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
138
139
  <slot />
@@ -1,4 +1,5 @@
1
1
  ---
2
+ import { jsonLdScript } from '../json-ld.ts';
2
3
  export interface Props {
3
4
  name: string;
4
5
  description?: string;
@@ -168,5 +169,5 @@ const jsonLd = {
168
169
  };
169
170
  ---
170
171
 
171
- <script is:inline type="application/ld+json" set:html={JSON.stringify(jsonLd)} />
172
+ <script is:inline type="application/ld+json" set:html={jsonLdScript(jsonLd)} />
172
173
  <slot />
package/src/seo.ts CHANGED
@@ -20,11 +20,12 @@ export type SeoOgType = 'website' | 'article' | 'profile';
20
20
  * Site-level SEO defaults, passed to `vitops({ seo })` and baked into the
21
21
  * `virtual:getvitops/head` module. Per-page `<Seo />` props override these.
22
22
  *
23
- * The vocabulary mirrors the site-config schema's `seo` block
24
- * (`@getvitops/generator`, `src/config.ts`) so a `Config` → these adapter would
25
- * be a flat field map — but it is not imported from there. This is the
26
- * integration's own option surface, and it must stay JSON-serialisable: it is
27
- * `JSON.stringify`d into the virtual module, so no functions, no `URL`s, no dates.
23
+ * `site.seo` in the site config is the source (`@getvitops/config`'s `SeoSchema`);
24
+ * `resolveSeoDefaults()` turns it into this shape for a build locale, and `vitops({ seo })` is merged
25
+ * over the result as an override. This interface is therefore the *resolved* shape — plain strings,
26
+ * `Localizable` already picked — and must stay JSON-serialisable: it is `JSON.stringify`d into the
27
+ * virtual module, so no functions, no `URL`s, no dates. `seo-defaults.test.ts` fails when a
28
+ * `site.seo` field is added to the schema and not read here (ADR-0002).
28
29
  */
29
30
  export interface GetvitopsSeoOptions {
30
31
  /** `og:site_name`, and the `<title>` when a page passes none. */
package/src/tracking.ts CHANGED
@@ -12,17 +12,13 @@
12
12
  * under `analytics`, and that is the consumer's call to make, not ours to guess.
13
13
  */
14
14
 
15
+ import type { TrackingConfig } from '@getvitops/config';
16
+
15
17
  /** Which consent category the attribution cookie waits on. */
16
- export type TrackingCategory = 'marketing' | 'analytics';
18
+ export type TrackingCategory = NonNullable<TrackingConfig['category']>;
17
19
 
18
- export interface GetvitopsTrackingOptions {
19
- /** Capture ad click IDs and UTMs into `_ac`. */
20
- enabled?: boolean;
21
- /** Default `marketing`. */
22
- category?: TrackingCategory;
23
- /** Informational; capture recognises every known click-ID parameter regardless. */
24
- platforms?: string[];
25
- }
20
+ /** The `site.tracking` block — the schema's own type from `@getvitops/config`, not a copy. */
21
+ export type GetvitopsTrackingOptions = TrackingConfig;
26
22
 
27
23
  /** The cookie the capture script writes. Mirrors `TRACKING_COOKIE` in utils. */
28
24
  export const TRACKING_COOKIE = '_ac';
@@ -1 +0,0 @@
1
- {"version":3,"file":"tracking-BNKKBqOe.d.mts","names":[],"sources":["../src/tracking.ts"],"mappings":";;;;;;;;;;;;;;;KAeY;UAEK;;EAEf;;EAEA,WAAW;;EAEX;;;cAIW;;;;;;;;;;;;cAaA;UAEI;EACf;EACA,UAAU;;EAEV;;EAEA;;UAGe;;EAEf;;EAEA;;;;;;;;;;;iBAYc,gBACd,MAAM,sCACN,MAAK,yBACJ"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"tracking-M2PqlMpw.mjs","names":[],"sources":["../src/tracking.ts"],"sourcesContent":["/**\n * The `<Tracking />` resolution layer — tracking config in, what to emit out.\n *\n * Separate from `Tracking.astro` for the same reason `analytics.ts` is separate\n * from `Analytics.astro`: everything decidable is a pure function asserted in\n * `tracking.test.ts`, and the component only renders what this returns.\n *\n * The one decision here is **which consent category the `_ac` cookie waits on**,\n * and it is stated rather than inferred. `_ac` is a 90-day identifier tying a\n * visitor to the ad that brought them, so `marketing` is the default — but a site\n * using click IDs purely for its own campaign reporting may reasonably file it\n * under `analytics`, and that is the consumer's call to make, not ours to guess.\n */\n\n/** Which consent category the attribution cookie waits on. */\nexport type TrackingCategory = 'marketing' | 'analytics';\n\nexport interface GetvitopsTrackingOptions {\n /** Capture ad click IDs and UTMs into `_ac`. */\n enabled?: boolean;\n /** Default `marketing`. */\n category?: TrackingCategory;\n /** Informational; capture recognises every known click-ID parameter regardless. */\n platforms?: string[];\n}\n\n/** The cookie the capture script writes. Mirrors `TRACKING_COOKIE` in utils. */\nexport const TRACKING_COOKIE = '_ac';\n\n/**\n * Where the capture script sends its `tel:`/`mailto:` conversion beacons.\n *\n * The route itself is the consumer's to write (`createConversionRoute()` in\n * `@getvitops/astro/routes`), so this is the one place the two halves agree on\n * a path. It was a bare literal inside the inlined script: a consumer who named\n * the file anything else got a silent 404 on every beacon and lost conversions\n * with no error anywhere to explain it. Exported so a consumer can assert\n * against it, mirrored in `scripts/tracking.ts` because that file is inlined\n * into the document and cannot import, and pinned by `tracking.test.ts`.\n */\nexport const TRACKING_ENDPOINT = '/api/track';\n\nexport interface ResolvedTracking {\n enabled: boolean;\n category: TrackingCategory;\n /** Cookie names to clear on revoke — rides on `data-consent-cookies`. */\n cookies: string[];\n /** Configuration problems, as prose. The integration logs these. */\n warnings: string[];\n}\n\nexport interface ResolveTrackingContext {\n /** Is the consent gate enabled on this site (`vitops({ consent })`)? */\n consent?: boolean;\n /** Categories the banner offers, so we can catch a demand with no row. */\n consentCategories?: readonly string[];\n}\n\n/**\n * Resolve the tracking config into what the component emits.\n *\n * Two warnings, both about the same failure — a persistent identifier written\n * with no way for the visitor to decline, or a permission asked for with no way\n * to answer. Returned as data rather than logged here so the check is a test\n * rather than a line someone might scroll past, exactly as `resolveAnalytics`\n * does.\n */\nexport function resolveTracking(\n opts: GetvitopsTrackingOptions | undefined,\n ctx: ResolveTrackingContext = {},\n): ResolvedTracking {\n const warnings: string[] = [];\n const category: TrackingCategory = opts?.category ?? 'marketing';\n const enabled = opts?.enabled === true;\n\n if (!enabled) return { enabled: false, category, cookies: [], warnings };\n\n if (!ctx.consent) {\n warnings.push(\n `tracking is on, so this site writes the \\`${TRACKING_COOKIE}\\` cookie — a 90-day ` +\n 'identifier tying a visitor to the ad that brought them — and `consent` is off, so it ' +\n 'is written for every visitor with no way to decline. Set `consent: true`.',\n );\n } else if (ctx.consentCategories && !ctx.consentCategories.includes(category)) {\n // The banner would be raised for a category it renders no row for: the\n // visitor is asked a question the form does not show them.\n warnings.push(\n `tracking demands the \\`${category}\\` consent category, but the banner offers ` +\n `${ctx.consentCategories.join(', ') || 'nothing'}. Add \\`${category}\\` to ` +\n '`consent.categories`, or the visitor is asked about something the banner never names.',\n );\n }\n\n return { enabled, category, cookies: [TRACKING_COOKIE], warnings };\n}\n"],"mappings":";;AA2BA,MAAa,kBAAkB;;;;;;;;;;;;AAa/B,MAAa,oBAAoB;;;;;;;;;;AA2BjC,SAAgB,gBACd,MACA,MAA8B,CAAC,GACb;CAClB,MAAM,WAAqB,CAAC;CAC5B,MAAM,WAA6B,MAAM,YAAY;CACrD,MAAM,UAAU,MAAM,YAAY;CAElC,IAAI,CAAC,SAAS,OAAO;EAAE,SAAS;EAAO;EAAU,SAAS,CAAC;EAAG;CAAS;CAEvE,IAAI,CAAC,IAAI,SACP,SAAS,KACP,mDAA6D,wLAG/D;MACK,IAAI,IAAI,qBAAqB,CAAC,IAAI,kBAAkB,SAAS,QAAQ,GAG1E,SAAS,KACP,0BAA0B,SAAS,6CAC9B,IAAI,kBAAkB,KAAK,IAAI,KAAK,UAAU,UAAU,SAAS,8FAExE;CAGF,OAAO;EAAE;EAAS;EAAU,SAAS,MAAgB;EAAG;CAAS;AACnE"}