@getvitops/astro 8.0.1 → 8.2.0-alpha.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.
package/dist/index.d.mts CHANGED
@@ -1,11 +1,10 @@
1
- import { i as TRACKING_COOKIE, n as ResolveTrackingContext, o as TrackingCategory, r as ResolvedTracking, s as resolveTracking, t as GetvitopsTrackingOptions } from "./tracking-DdEKExlE.mjs";
1
+ import { i as TRACKING_COOKIE, n as ResolveTrackingContext, o as TrackingCategory, r as ResolvedTracking, s as resolveTracking, t as GetvitopsTrackingOptions } from "./tracking-BNKKBqOe.mjs";
2
2
  import { LegalOutput, SiteFont, StylesheetFormat } from "@getvitops/generator";
3
3
  import { generateIconInclude } from "@getvitops/utils";
4
- import { AdProvider } from "@getvitops/utils/ads";
4
+ import { AdProvider, ConversionLabelMap } from "@getvitops/utils/ads";
5
5
  import { OutputKind } from "@getvitops/utils/media";
6
6
  import { AstroIntegration } from "astro";
7
7
  export * from "@getvitops/utils";
8
-
9
8
  //#region src/analytics.d.ts
10
9
  /**
11
10
  * The `<Analytics />` resolution layer — provider config in, the exact tags to
@@ -152,6 +151,19 @@ interface ResolvedAnalytics {
152
151
  interface ResolveAnalyticsContext {
153
152
  /** Is the consent gate enabled on this site (`vitops({ consent })`)? */
154
153
  consent?: boolean;
154
+ /**
155
+ * Enabled `site.experiments` entries, keyed the same way, each carrying the
156
+ * resolved cookie name `vitopsAbTesting()`'s middleware assigns
157
+ * (`_ab_<key>` unless overridden). When set, each provider's bootstrap
158
+ * reads these cookies client-side and reports the assignment via that
159
+ * provider's own custom-dimension/property mechanism — a no-op when this
160
+ * is empty, so a site not using experiments sees no behavior change.
161
+ */
162
+ experiments?: Record<string, {
163
+ cookieName: string;
164
+ }>;
165
+ /** `site.analytics.matomo.abVariantDimensions` — Matomo's pre-created custom-dimension index per experiment key. */
166
+ matomoAbVariantDimensions?: Record<string, number>;
155
167
  }
156
168
  /**
157
169
  * Resolve the configured providers into the tags to emit.
@@ -160,9 +172,9 @@ interface ResolveAnalyticsContext {
160
172
  * malformed ID should not take down a build, and a missing tag is visible in the
161
173
  * output while a failed build is just a red wall.
162
174
  */
163
- declare function resolveAnalytics(opts: GetvitopsAnalyticsOptions | undefined, ctx?: ResolveAnalyticsContext): ResolvedAnalytics;
175
+ export declare function resolveAnalytics(opts: GetvitopsAnalyticsOptions | undefined, ctx?: ResolveAnalyticsContext): ResolvedAnalytics;
164
176
  /** Which optional categories a set of tags actually needs a visitor to decide. */
165
- declare function consentCategories(tags: GatedTag[]): OptionalConsentCategory[];
177
+ export declare function consentCategories(tags: GatedTag[]): OptionalConsentCategory[];
166
178
  //#endregion
167
179
  //#region src/seo.d.ts
168
180
  /**
@@ -216,9 +228,13 @@ interface GetvitopsSeoOptions {
216
228
  /** Bing Webmaster Tools verification token. */
217
229
  bingSiteVerification?: string;
218
230
  openGraph?: {
219
- /** Default `og:type` (default `'website'`). */type?: SeoOgType; /** `og:locale`. Written either way — `en-CA` and `en_CA` both emit `en_CA`. */
220
- locale?: string; /** Additional locales the same content exists in → `og:locale:alternate`. */
221
- localeAlternates?: string[]; /** Fallback share image. Relative URLs resolve against `site`. */
231
+ /** Default `og:type` (default `'website'`). */
232
+ type?: SeoOgType;
233
+ /** `og:locale`. Written either way — `en-CA` and `en_CA` both emit `en_CA`. */
234
+ locale?: string;
235
+ /** Additional locales the same content exists in → `og:locale:alternate`. */
236
+ localeAlternates?: string[];
237
+ /** Fallback share image. Relative URLs resolve against `site`. */
222
238
  image?: {
223
239
  url: string;
224
240
  alt?: string;
@@ -228,8 +244,11 @@ interface GetvitopsSeoOptions {
228
244
  };
229
245
  };
230
246
  twitter?: {
231
- /** Card type when no image resolves (default `'summary'`). */card?: SeoTwitterCard; /** The site's own handle, `@acme`. */
232
- site?: string; /** Default author handle, `@ada`. */
247
+ /** Card type when no image resolves (default `'summary'`). */
248
+ card?: SeoTwitterCard;
249
+ /** The site's own handle, `@acme`. */
250
+ site?: string;
251
+ /** Default author handle, `@ada`. */
233
252
  creator?: string;
234
253
  };
235
254
  }
@@ -341,7 +360,7 @@ interface SeoContext {
341
360
  * through its three arguments, which is what makes it testable and what keeps the
342
361
  * package free of the consumer-global coupling that sank the previous site model.
343
362
  */
344
- declare function resolveSeo(defaults: GetvitopsSeoOptions, props: SeoProps, ctx: SeoContext): ResolvedSeo;
363
+ export declare function resolveSeo(defaults: GetvitopsSeoOptions, props: SeoProps, ctx: SeoContext): ResolvedSeo;
345
364
  //#endregion
346
365
  //#region src/integration.d.ts
347
366
  interface GetvitopsFaviconOptions {
@@ -750,7 +769,7 @@ interface GetvitopsIconsOptions {
750
769
  /** Aliases applied before the semantic map — legacy names, or values stored in CMS content. */
751
770
  overrides?: Record<string, string>;
752
771
  }
753
- declare function vitops(opts?: GetvitopsOptions): AstroIntegration;
772
+ export default function vitops(opts?: GetvitopsOptions): AstroIntegration;
754
773
  //#endregion
755
774
  //#region src/ads.d.ts
756
775
  /**
@@ -763,7 +782,15 @@ declare function vitops(opts?: GetvitopsOptions): AstroIntegration;
763
782
  interface GetvitopsAdProperty {
764
783
  accountId?: string;
765
784
  pixelId?: string;
766
- conversionLabel?: string;
785
+ /**
786
+ * Google Ads only: the conversion action label(s) paired with `pixelId` (the
787
+ * `AW-…` conversion id) — a bare string (back-compat, treated as `call`) or a
788
+ * map of action → label. `<Tracking />`'s tel:/mailto: click handler and
789
+ * `vitopsFormConversion()` each fire `gtag('event', 'conversion', …)` for
790
+ * their own action at the moment of the interaction — see `gtagBootstrap` in
791
+ * `@getvitops/utils/ads`.
792
+ */
793
+ conversionLabel?: string | ConversionLabelMap;
767
794
  /** Default `marketing`. `analytics` for a property used purely for measurement. */
768
795
  category?: 'marketing' | 'analytics';
769
796
  /** False keeps the property on record without emitting its tag. */
@@ -792,7 +819,7 @@ interface ResolveAdsContext {
792
819
  strategy?: AnalyticsStrategy;
793
820
  }
794
821
  /** Resolve the configured ad properties into the tags `<Ads />` emits. */
795
- declare function resolveAds(ads: GetvitopsAdsOptions | undefined, ctx?: ResolveAdsContext): ResolvedAds;
822
+ export declare function resolveAds(ads: GetvitopsAdsOptions | undefined, ctx?: ResolveAdsContext): ResolvedAds;
796
823
  //#endregion
797
824
  //#region src/lastmod.d.ts
798
825
  interface GitLastmodOptions {
@@ -810,9 +837,9 @@ interface GitLastmodOptions {
810
837
  * commit date describes the template rather than any of them. Stamping every
811
838
  * generated page with the template's date is exactly the "wrong date" case above.
812
839
  */
813
- declare function routeFromPage(file: string): string | undefined;
840
+ export declare function routeFromPage(file: string): string | undefined;
814
841
  /** The slug a content entry is most likely published under. */
815
- declare function slugFromContent(file: string): string | undefined;
842
+ export declare function slugFromContent(file: string): string | undefined;
816
843
  /**
817
844
  * Build a `serialize` function that stamps `lastmod` from git history.
818
845
  *
@@ -821,7 +848,7 @@ declare function slugFromContent(file: string): string | undefined;
821
848
  * is dropped rather than guessed at, since picking either one would be a coin
822
849
  * flip presented as a fact.
823
850
  */
824
- declare function gitLastmod(options?: GitLastmodOptions): Promise<(item: GetvitopsSitemapEntry) => GetvitopsSitemapEntry>;
851
+ export declare function gitLastmod(options?: GitLastmodOptions): Promise<(item: GetvitopsSitemapEntry) => GetvitopsSitemapEntry>;
825
852
  //#endregion
826
- export { type AnalyticsStrategy, type ClarityOptions, type ConsentCategory, type GatedTag, type GetvitopsAdProperty, type GetvitopsAdsOptions, type GetvitopsAnalyticsOptions, type GetvitopsConsentOptions, type GetvitopsCssOptions, type GetvitopsFaviconOptions, type GetvitopsMediaOptions, type GetvitopsOptions, type GetvitopsSeoOptions, type GetvitopsSitemapChangeFreq, type GetvitopsSitemapEntry, type GetvitopsSitemapOptions, type GetvitopsTrackingOptions, type GitLastmodOptions, type GoogleAnalyticsOptions, type MatomoOptions, type OptionalConsentCategory, type PlausibleOptions, type ResolveAdsContext, type ResolveAnalyticsContext, type ResolveTrackingContext, type ResolvedAdTag, type ResolvedAds, type ResolvedAnalytics, type ResolvedSeo, type ResolvedTag, type ResolvedTracking, type SeoContext, type SeoNameTag, type SeoOgType, type SeoPropertyTag, type SeoProps, type SeoTwitterCard, TRACKING_COOKIE, type TrackingCategory, consentCategories, vitops as default, gitLastmod, resolveAds, resolveAnalytics, resolveSeo, resolveTracking, routeFromPage, slugFromContent };
853
+ export { type AnalyticsStrategy, type ClarityOptions, type ConsentCategory, type GatedTag, type GetvitopsAdProperty, type GetvitopsAdsOptions, type GetvitopsAnalyticsOptions, type GetvitopsConsentOptions, type GetvitopsCssOptions, type GetvitopsFaviconOptions, type GetvitopsMediaOptions, type GetvitopsOptions, type GetvitopsSeoOptions, type GetvitopsSitemapChangeFreq, type GetvitopsSitemapEntry, type GetvitopsSitemapOptions, type GetvitopsTrackingOptions, type GitLastmodOptions, type GoogleAnalyticsOptions, type MatomoOptions, type OptionalConsentCategory, type PlausibleOptions, type ResolveAdsContext, type ResolveAnalyticsContext, type ResolveTrackingContext, type ResolvedAdTag, type ResolvedAds, type ResolvedAnalytics, type ResolvedSeo, type ResolvedTag, type ResolvedTracking, type SeoContext, type SeoNameTag, type SeoOgType, type SeoPropertyTag, type SeoProps, type SeoTwitterCard, TRACKING_COOKIE, type TrackingCategory, resolveTracking };
827
854
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/analytics.ts","../src/seo.ts","../src/integration.ts","../src/ads.ts","../src/lastmod.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":["MediaOutputKind"],"sources":["../src/analytics.ts","../src/seo.ts","../src/integration.ts","../src/ads.ts","../src/lastmod.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
- import { n as TRACKING_ENDPOINT, r as resolveTracking, t as TRACKING_COOKIE } from "./tracking-CmSfxWC3.mjs";
1
+ import { n as TRACKING_ENDPOINT, r as resolveTracking, t as TRACKING_COOKIE } from "./tracking-M2PqlMpw.mjs";
2
2
  import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { dirname, join, relative, resolve } from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { isConfig, renderRobotsTxt, resolveConfig } from "@getvitops/generator";
6
6
  import { collectIconRefs, faviconLinks, generateFavicons, generateIconInclude, resolveIcon, scanFiles, writeFaviconManifest } from "@getvitops/utils";
7
7
  import vitopsPlugin from "@getvitops/vite";
8
- import { AD_PLATFORMS, AD_PROVIDER_KEYS } from "@getvitops/utils/ads";
8
+ import { AD_PLATFORMS, AD_PROVIDER_KEYS, normalizeConversionLabels } from "@getvitops/utils/ads";
9
9
  import { fontProviders } from "astro/config";
10
10
  import { execFile } from "node:child_process";
11
11
  import { promisify } from "node:util";
@@ -14,6 +14,20 @@ export * from "@getvitops/utils"
14
14
 
15
15
  //#region src/analytics.ts
16
16
  /**
17
+ * A small inline snippet reading each experiment's cookie into a plain
18
+ * object, skipping any experiment whose cookie isn't set (visitor not yet
19
+ * assigned, or the experiment disabled after this page was cached). Shared
20
+ * across providers so the cookie-parsing logic — and its `SAFE_ID` guard on
21
+ * both the experiment key and cookie name, the same discipline `bad()`
22
+ * already applies to provider IDs landing in an inline `<script>` — exists
23
+ * exactly once.
24
+ */
25
+ function experimentCookieReads(experiments) {
26
+ const entries = Object.entries(experiments).filter(([key, e]) => !bad(key) && !bad(e.cookieName));
27
+ if (!entries.length) return "";
28
+ return "function _abrc(n){var m=document.cookie.match(new RegExp('(?:^|; )'+n+'=([^;]*)'));return m?decodeURIComponent(m[1]):null;}var _ab={};" + entries.map(([key, e]) => `_ab[${JSON.stringify(key)}]=_abrc(${JSON.stringify(e.cookieName)});`).join("") + `for(var _k in _ab){if(_ab[_k]==null)delete _ab[_k];}`;
29
+ }
30
+ /**
17
31
  * IDs land inside an inline `<script>`, so they are checked rather than trusted.
18
32
  * A `<\/script>` in a measurement ID is not a plausible accident, but the cost of
19
33
  * the guard is one regex and the cost of not having it is arbitrary script
@@ -27,10 +41,12 @@ function bad(value) {
27
41
  function literal(value) {
28
42
  return JSON.stringify(value).replaceAll("<", "\\u003c");
29
43
  }
30
- function googleAnalytics(input) {
44
+ function googleAnalytics(input, experiments = {}) {
31
45
  const opts = typeof input === "string" ? { id: input } : input;
32
46
  if (bad(opts.id)) return `googleAnalytics: "${opts.id}" is not a valid measurement ID. Skipped.`;
33
- const config = opts.config ? `,${literal(opts.config)}` : "";
47
+ const staticConfig = opts.config ?? {};
48
+ const reads = experimentCookieReads(experiments);
49
+ const gtagConfig = reads ? `${reads}gtag('config','${opts.id}',Object.assign(${literal(staticConfig)},_ab));` : `gtag('config','${opts.id}'${opts.config ? `,${literal(staticConfig)}` : ""});`;
34
50
  return {
35
51
  key: "googleAnalytics",
36
52
  provider: "Google Analytics",
@@ -45,7 +61,7 @@ function googleAnalytics(input) {
45
61
  ],
46
62
  strategy: "idle",
47
63
  gated: true,
48
- inline: `window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('js',new Date());gtag('config','${opts.id}'${config});`,
64
+ inline: "window.dataLayer=window.dataLayer||[];function gtag(){dataLayer.push(arguments)}gtag('js',new Date());" + gtagConfig,
49
65
  src: `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(opts.id)}`,
50
66
  attrs: {}
51
67
  };
@@ -74,7 +90,7 @@ function clarity(input) {
74
90
  attrs: {}
75
91
  };
76
92
  }
77
- function matomo(opts) {
93
+ function matomo(opts, experiments = {}, dimensions = {}) {
78
94
  const siteId = String(opts.siteId);
79
95
  if (bad(siteId)) return `matomo: "${siteId}" is not a valid site ID. Skipped.`;
80
96
  let base;
@@ -85,6 +101,9 @@ function matomo(opts) {
85
101
  }
86
102
  const url = `${base.origin}${base.pathname.replace(/\/*$/, "")}/`;
87
103
  const cookies = opts.cookies === true;
104
+ const dimensionEntries = Object.entries(experiments).filter(([key]) => dimensions[key] != null);
105
+ const reads = experimentCookieReads(Object.fromEntries(dimensionEntries));
106
+ const dimensionPushes = reads ? reads + dimensionEntries.map(([key]) => `if(_ab[${JSON.stringify(key)}]!=null)_paq.push(['setCustomDimension',${dimensions[key]},_ab[${JSON.stringify(key)}]]);`).join("") : "";
88
107
  return {
89
108
  key: "matomo",
90
109
  provider: "Matomo",
@@ -99,14 +118,15 @@ function matomo(opts) {
99
118
  ] : [],
100
119
  strategy: "idle",
101
120
  gated: true,
102
- inline: `var _paq=window._paq=window._paq||[];` + (cookies ? "" : `_paq.push(['disableCookies']);`) + `_paq.push(['trackPageView']);_paq.push(['enableLinkTracking']);(function(){var u="${url}";_paq.push(['setTrackerUrl',u+'matomo.php']);_paq.push(['setSiteId','${siteId}']);var d=document,g=d.createElement('script'),s=d.getElementsByTagName('script')[0];g.async=true;g.src=u+'matomo.js';s.parentNode.insertBefore(g,s)})();`,
121
+ inline: `var _paq=window._paq=window._paq||[];` + (cookies ? "" : `_paq.push(['disableCookies']);`) + dimensionPushes + `_paq.push(['trackPageView']);_paq.push(['enableLinkTracking']);(function(){var u="${url}";_paq.push(['setTrackerUrl',u+'matomo.php']);_paq.push(['setSiteId','${siteId}']);var d=document,g=d.createElement('script'),s=d.getElementsByTagName('script')[0];g.async=true;g.src=u+'matomo.js';s.parentNode.insertBefore(g,s)})();`,
103
122
  src: null,
104
123
  attrs: {}
105
124
  };
106
125
  }
107
- function plausible(input) {
126
+ function plausible(input, experiments = {}) {
108
127
  const opts = typeof input === "string" ? { domain: input } : input;
109
128
  if (bad(opts.domain)) return `plausible: "${opts.domain}" is not a valid domain. Skipped.`;
129
+ const reads = experimentCookieReads(experiments);
110
130
  return {
111
131
  key: "plausible",
112
132
  provider: "Plausible Analytics",
@@ -115,7 +135,7 @@ function plausible(input) {
115
135
  cookies: [],
116
136
  strategy: "idle",
117
137
  gated: true,
118
- inline: "",
138
+ inline: reads ? `window.plausible=window.plausible||function(){(window.plausible.q=window.plausible.q||[]).push(arguments)};plausible.init({customProperties:function(){${reads}return _ab;}});` : "",
119
139
  src: opts.src ?? "https://plausible.io/js/script.js",
120
140
  attrs: { "data-domain": opts.domain }
121
141
  };
@@ -136,11 +156,12 @@ function resolveAnalytics(opts, ctx = {}) {
136
156
  warnings
137
157
  };
138
158
  const strategy = opts.strategy ?? "idle";
159
+ const experiments = ctx.experiments ?? {};
139
160
  const results = [
140
- opts.googleAnalytics === void 0 ? null : googleAnalytics(opts.googleAnalytics),
161
+ opts.googleAnalytics === void 0 ? null : googleAnalytics(opts.googleAnalytics, experiments),
141
162
  opts.clarity === void 0 ? null : clarity(opts.clarity),
142
- opts.matomo === void 0 ? null : matomo(opts.matomo),
143
- opts.plausible === void 0 ? null : plausible(opts.plausible)
163
+ opts.matomo === void 0 ? null : matomo(opts.matomo, experiments, ctx.matomoAbVariantDimensions ?? {}),
164
+ opts.plausible === void 0 ? null : plausible(opts.plausible, experiments)
144
165
  ];
145
166
  for (const result of results) {
146
167
  if (result === null) continue;
@@ -239,6 +260,18 @@ function resolveAds(ads, ctx = {}) {
239
260
  warnings.push(`ads: ${provider} ${platform.tag.needs} "${id}" is not a valid ID. Skipped.`);
240
261
  continue;
241
262
  }
263
+ let safeEntry = entry;
264
+ const labels = normalizeConversionLabels(entry.conversionLabel);
265
+ const invalidActions = Object.entries(labels).filter(([, label]) => label && !SAFE_ID.test(label)).map(([action]) => action);
266
+ if (invalidActions.length) {
267
+ for (const action of invalidActions) warnings.push(`ads: ${provider} conversionLabel.${action} "${labels[action]}" is not a valid label. Conversion firing skipped for that action.`);
268
+ const cleanLabels = Object.fromEntries(Object.entries(labels).filter(([action]) => !invalidActions.includes(action)));
269
+ const { conversionLabel: _drop, ...rest } = entry;
270
+ safeEntry = Object.keys(cleanLabels).length ? {
271
+ ...rest,
272
+ conversionLabel: cleanLabels
273
+ } : rest;
274
+ }
242
275
  const category = entry.category ?? "marketing";
243
276
  tags.push({
244
277
  key: provider,
@@ -250,7 +283,7 @@ function resolveAds(ads, ctx = {}) {
250
283
  gated: ctx.consent === true,
251
284
  inline: platform.tag.bootstrap(id, {
252
285
  provider,
253
- ...entry
286
+ ...safeEntry
254
287
  }),
255
288
  src: platform.tag.src ? platform.tag.src(id) : null,
256
289
  attrs: {}
@@ -291,7 +324,7 @@ const PROVIDERS = new Set(Object.keys(fontProviders));
291
324
  * key into a committed file or inventing an interpolation syntax, so Adobe families are
292
325
  * declared in `astro.config` directly — see the error thrown below.
293
326
  */
294
- const NEEDS_OPTIONS = new Set(["adobe"]);
327
+ const NEEDS_OPTIONS = /* @__PURE__ */ new Set(["adobe"]);
295
328
  /**
296
329
  * Map declarations onto Astro's `fonts:` entries.
297
330
  *
@@ -326,6 +359,31 @@ function resolveFonts(decls) {
326
359
  };
327
360
  }
328
361
 
362
+ //#endregion
363
+ //#region src/i18n.ts
364
+ function codesOf(entry) {
365
+ return typeof entry === "string" ? [entry] : entry.codes;
366
+ }
367
+ /** Every BCP-47 code Astro's `i18n` block actually recognises. */
368
+ function astroLocaleCodes(i18n) {
369
+ return new Set(i18n.locales.flatMap(codesOf));
370
+ }
371
+ /**
372
+ * Warning strings for a mismatch — `[]` when either side is absent (nothing
373
+ * to reconcile) or the two agree.
374
+ */
375
+ function localeConsistencyWarnings(site, i18n) {
376
+ if (!site || !i18n) return [];
377
+ const warnings = [];
378
+ const astroCodes = astroLocaleCodes(i18n);
379
+ const siteCodes = new Set(Object.keys(site.locales));
380
+ const onlyInSite = [...siteCodes].filter((c) => !astroCodes.has(c));
381
+ const onlyInAstro = [...astroCodes].filter((c) => !siteCodes.has(c));
382
+ if (onlyInSite.length || onlyInAstro.length) warnings.push(`i18n: site.locales and astro.config.mjs's i18n.locales disagree` + (onlyInSite.length ? ` — site.locales has ${onlyInSite.map((c) => `"${c}"`).join(", ")} that astro's i18n doesn't` : "") + (onlyInAstro.length ? `${onlyInSite.length ? ", and" : " —"} astro's i18n has ${onlyInAstro.map((c) => `"${c}"`).join(", ")} that site.locales doesn't` : "") + ". EmDash reads locales from astro.config.mjs directly, so a locale missing there never reaches its admin regardless of what site.locales says.");
383
+ if (i18n.defaultLocale !== site.defaultLocale) warnings.push(`i18n: astro.config.mjs's i18n.defaultLocale ("${i18n.defaultLocale}") does not match site.defaultLocale ("${site.defaultLocale}") — SEO/legal prose renders in one locale by default while EmDash's own routing defaults to the other.`);
384
+ return warnings;
385
+ }
386
+
329
387
  //#endregion
330
388
  //#region src/integration.ts
331
389
  /**
@@ -367,6 +425,25 @@ function undisclosedProviders(configPath, analytics) {
367
425
  return Object.keys(SITE_CONFIG_KEYS).filter((key) => analytics[key] !== void 0 && !declared[SITE_CONFIG_KEYS[key]]);
368
426
  }
369
427
  /**
428
+ * Read `site.locales`/`site.defaultLocale` out of the site config, for
429
+ * `localeConsistencyWarnings` to check against Astro's own `i18n` block.
430
+ *
431
+ * Best-effort, matching `readSiteAds`'s shape exactly: a malformed or missing
432
+ * config is silence here, since every other consumer of this file already
433
+ * validates it loudly elsewhere in the same hook.
434
+ */
435
+ function readSiteLocales(configPath, siteEnv) {
436
+ try {
437
+ const cfg = resolveConfig(JSON.parse(readFileSync(configPath, "utf8")), siteEnv);
438
+ return {
439
+ locales: cfg.site.locales,
440
+ defaultLocale: cfg.site.defaultLocale
441
+ };
442
+ } catch {
443
+ return null;
444
+ }
445
+ }
446
+ /**
370
447
  * Read `site.ads` and this environment's ads switch out of the site config.
371
448
  *
372
449
  * Best-effort, like `undisclosedProviders`: every other consumer of this file
@@ -395,6 +472,55 @@ function readSiteAds(configPath, siteEnv) {
395
472
  }
396
473
  }
397
474
  /**
475
+ * Read `site.experiments` + `site.analytics.matomo.abVariantDimensions` out of
476
+ * the site config, resolving each experiment's cookie name (default
477
+ * `_ab_<key>`, matching `vitopsAbTesting()`'s own default) so `<Analytics />`
478
+ * doesn't need to know the fallback rule itself.
479
+ *
480
+ * Mirrors `readSiteAds`'s shape: best-effort, only enabled experiments
481
+ * (a disabled one has no cookie to read and shouldn't cost every provider's
482
+ * bootstrap a no-op check), `null` when nothing's configured.
483
+ */
484
+ function readSiteExperiments(configPath, siteEnv) {
485
+ try {
486
+ const cfg = resolveConfig(JSON.parse(readFileSync(configPath, "utf8")), siteEnv);
487
+ const configured = cfg.site.experiments;
488
+ if (!configured || Object.keys(configured).length === 0) return null;
489
+ const experiments = {};
490
+ let primaryCookie = null;
491
+ for (const [key, experiment] of Object.entries(configured)) {
492
+ if (!experiment.enabled) continue;
493
+ const cookieName = experiment.cookieName ?? `_ab_${key}`;
494
+ experiments[key] = { cookieName };
495
+ primaryCookie ??= cookieName;
496
+ }
497
+ if (Object.keys(experiments).length === 0) return null;
498
+ return {
499
+ experiments,
500
+ matomoAbVariantDimensions: cfg.site.analytics?.matomo?.abVariantDimensions ?? {},
501
+ primaryCookie
502
+ };
503
+ } catch {
504
+ return null;
505
+ }
506
+ }
507
+ /**
508
+ * Is `site.crm` configured and not explicitly disabled?
509
+ *
510
+ * Mirrors `readSiteAds`'s "read the config, decide nothing else" shape — this
511
+ * only answers the one question `hasConversionRoute`'s warning needs, not the
512
+ * whole `CrmConfig`, which the consumer's own route file builds from the same
513
+ * config however it likes.
514
+ */
515
+ function siteCrmEnabled(configPath, siteEnv) {
516
+ try {
517
+ const cfg = resolveConfig(JSON.parse(readFileSync(configPath, "utf8")), siteEnv);
518
+ return Boolean(cfg.site.crm && cfg.site.crm.enabled !== false);
519
+ } catch {
520
+ return false;
521
+ }
522
+ }
523
+ /**
398
524
  * The consent + tracking facts a site config already states.
399
525
  *
400
526
  * These were the one pair the integration did not read. `site.tracking` and
@@ -547,6 +673,10 @@ function vitops(opts = {}) {
547
673
  if (opts.site && !opts.css) logger.warn("site: needs a `css` config — it is read during generation, which runs in the Vite plugin that `css` registers. Without it the site config changes nothing here.");
548
674
  const legalInput = opts.legal?.input ?? siteInput;
549
675
  if (opts.legal && !legalInput) throw new Error("[vitops] legal: needs `legal.input` (a site config path), the top-level `site` option, or a `css.input` that is itself a site config.");
676
+ if (siteInput) {
677
+ const siteLocales = readSiteLocales(resolve(root, siteInput), opts.site?.siteEnv ?? "production");
678
+ for (const warning of localeConsistencyWarnings(siteLocales, config.i18n)) logger.warn(warning);
679
+ }
550
680
  if (opts.seo) {
551
681
  if (!config.site) logger.warn("seo: needs the `site` astro.config option (your deployed URL) — without it <Seo /> omits canonical, og:url and any relative og:image rather than guessing an origin.");
552
682
  if (config.integrations.some((i) => i.name === "emdash")) logger.warn("seo: emdash() is registered, and its <EmDashHead> emits the same title/description/canonical/og tags from the CMS. Use one or the other — rendering both duplicates every tag.");
@@ -561,6 +691,7 @@ function vitops(opts = {}) {
561
691
  const analytics = resolveAnalytics(opts.analytics, { consent: consentEnabled });
562
692
  const trackingOpts = opts.tracking === true ? { enabled: true } : opts.tracking || (siteConsent?.tracking?.enabled ? siteConsent.tracking : void 0);
563
693
  const adsConfig = siteInput ? readSiteAds(resolve(root, siteInput), opts.site?.siteEnv ?? "production") : null;
694
+ const experimentsConfig = siteInput ? readSiteExperiments(resolve(root, siteInput), opts.site?.siteEnv ?? "production") : null;
564
695
  const detectedCategories = consentCategories([...analytics.tags, ...resolveAds(adsConfig?.properties, {
565
696
  consent: consentEnabled,
566
697
  enabled: true
@@ -576,6 +707,7 @@ function vitops(opts = {}) {
576
707
  });
577
708
  for (const warning of tracking.warnings) logger.warn(warning);
578
709
  if (tracking.enabled && !hasConversionRoute(root)) logger.warn(`tracking is on, so the capture script beacons \`tel:\` conversions to ${TRACKING_ENDPOINT}, but no route answers that path. Add \`src/pages/api/track.ts\` exporting \`createConversionRoute(...)\` from \`@getvitops/astro/routes\` — otherwise every call conversion is silently lost.`);
710
+ if (siteInput && siteCrmEnabled(resolve(root, siteInput), opts.site?.siteEnv ?? "production") && !hasConversionRoute(root)) logger.warn(`site.crm is configured, but no route answers ${TRACKING_ENDPOINT}. Add \`src/pages/api/track.ts\` exporting \`createConversionRoute({ crm: {...}, ... })\` from \`@getvitops/astro/routes\` — otherwise submissions never reach the CRM.`);
579
711
  const ads = resolveAds(adsConfig?.properties, {
580
712
  consent: consentEnabled,
581
713
  consentCategories: offeredCategories,
@@ -633,7 +765,8 @@ function vitops(opts = {}) {
633
765
  }
634
766
  const consentRuntime = consentEnabled || analytics.needsRuntime;
635
767
  if (webComponents || consentRuntime) {
636
- const coreDist = join(dirname(fileURLToPath(import.meta.resolve("@getvitops/core/package.json"))), "dist");
768
+ const corePkg = fileURLToPath(import.meta.resolve("@getvitops/core/package.json"));
769
+ const coreDist = join(dirname(corePkg), "dist");
637
770
  const dest = join(publicDir, "vitops");
638
771
  if (existsSync(coreDist)) {
639
772
  const bundles = webComponents ? [
@@ -790,8 +923,11 @@ function vitops(opts = {}) {
790
923
  tracking: tracking.enabled ? {
791
924
  enabled: true,
792
925
  category: tracking.category,
793
- cookies: tracking.cookies
926
+ cookies: tracking.cookies,
927
+ abVariantCookie: experimentsConfig?.primaryCookie ?? null
794
928
  } : null,
929
+ experiments: experimentsConfig?.experiments ?? {},
930
+ matomoAbVariantDimensions: experimentsConfig?.matomoAbVariantDimensions ?? {},
795
931
  ads: adsConfig
796
932
  })] } });
797
933
  if (opts.css) {
@@ -924,7 +1060,7 @@ async function commitDates(dirs, cwd) {
924
1060
  ...dirs
925
1061
  ], {
926
1062
  cwd,
927
- maxBuffer: 64 * 1024 * 1024
1063
+ maxBuffer: 67108864
928
1064
  });
929
1065
  let current;
930
1066
  for (const line of stdout.split("\n")) {