@getvitops/astro 8.1.0 → 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.
@@ -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")) {