@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
@@ -0,0 +1,71 @@
1
+ import { t } from "@getvitops/utils/content";
2
+
3
+ // src/routes/config-adapters.ts
4
+ /**
5
+ * Resolves the one display name for a site's organization — `organization.name`
6
+ * (localizable; `locale` picks which one, defaulting to `site.defaultLocale`,
7
+ * which is also the fallback if the pick has no entry), falling back to
8
+ * `legalName` when `name` isn't set at all, and to `undefined` (never `''`)
9
+ * when neither resolves to anything — `t()` returns `''` for a genuinely
10
+ * missing translation, and an empty name is worse than none.
11
+ *
12
+ * This is the one place that fallback chain is written. An EmDash site's own
13
+ * `getSiteSettings().title` is a separate, runtime-only fact (EmDash's
14
+ * settings aren't readable at Astro build/config time at all — see
15
+ * AGENTS.md's "EmDash plugin as the design-system editing surface") and stays
16
+ * authoritative for what it drives (the rendered page chrome); this helper is
17
+ * for the build-time-only consumers that can't reach EmDash at all — favicon
18
+ * generation, generated legal documents, notification emails — so a scaffold
19
+ * needing a name in `astro.config.mjs` has one real source instead of a third
20
+ * hardcoded string.
21
+ */
22
+ function resolveOrganizationName(config, locale) {
23
+ const org = config.organization;
24
+ const resolvedLocale = locale ?? config.site.defaultLocale;
25
+ return (org?.name != null ? t(org.name, resolvedLocale, config.site.defaultLocale) || void 0 : void 0) ?? org?.legalName;
26
+ }
27
+ /**
28
+ * Builds the `context` option `createConversionRoute()` expects, straight from a
29
+ * resolved `Config` — instead of every consumer hand-writing a `NotifyContext`
30
+ * literal.
31
+ *
32
+ * `locationEmails` is primary-first, then every other location with an `email`,
33
+ * in `organization.locations`' own key order — `planNotifications` picks the
34
+ * first that's set, so order here is the whole precedence.
35
+ */
36
+ function toNotifyContext(config, locale) {
37
+ const org = config.organization;
38
+ const primaryKey = org?.primaryLocation;
39
+ const locationEmails = Object.entries(org?.locations ?? {}).sort(([a], [b]) => a === primaryKey ? -1 : b === primaryKey ? 1 : 0).map(([, loc]) => loc.email).filter((email) => !!email);
40
+ return {
41
+ notifications: config.site.notifications,
42
+ canonical: config.site.domains?.canonical,
43
+ organizationName: resolveOrganizationName(config, locale),
44
+ locationEmails
45
+ };
46
+ }
47
+ /**
48
+ * `site.crm` IS `CrmConfig` — both are the schema's inferred type from `@getvitops/config` — so
49
+ * this returns its argument's field unchanged.
50
+ *
51
+ * @deprecated Pass `config.site.crm` directly. Kept so existing callers keep compiling; it
52
+ * stopped being an adapter when the type stopped being a copy.
53
+ */
54
+ function toCrmConfig(config) {
55
+ return config.site.crm;
56
+ }
57
+ /**
58
+ * `site.ads.google`'s three server-side-relevant fields, for
59
+ * `createConversionRoute`'s `googleAdsOffline.config`. Undefined when the site has no Google Ads
60
+ * property configured at all, which `createConversionRoute` reads the same way `crm` being
61
+ * omitted does: the upload never runs.
62
+ *
63
+ * @deprecated Pass `config.site.ads?.google` directly — it is already assignable. Kept so existing
64
+ * callers keep compiling.
65
+ */
66
+ function toGoogleAdsConfig(config) {
67
+ return config.site.ads?.google;
68
+ }
69
+
70
+ export { toNotifyContext as i, toCrmConfig as n, toGoogleAdsConfig as r, resolveOrganizationName as t };
71
+ //# sourceMappingURL=config-adapters-CbTywgEY.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config-adapters-CbTywgEY.mjs","names":[],"sources":["../src/routes/config-adapters.ts"],"sourcesContent":["/**\n * Adapts a resolved `Config` (`@getvitops/generator`) into the structural shapes\n * `createConversionRoute()` expects — `context: NotifyContext` and `crm.config:\n * CrmConfig`. Neither of those types can import `Config` themselves (they live in\n * `@getvitops/utils`, which the generator already depends on — importing back\n * would be a cycle), which is why `conversion.ts`'s own docblock used to describe\n * `toNotifyContext(config)` as a helper that \"does not exist.\" This package\n * already depends on both the generator and utils, so it's the one place the\n * adapter can live.\n *\n * `import type` only — erased at build, so pulling in `Config`'s type costs\n * nothing at runtime and doesn't drag the generator's `node:fs`/lightningcss\n * entry point into a Worker bundle.\n */\nimport type { Config } from '@getvitops/generator';\n// The `/content` subpath, not the root barrel: the root re-exports `favicon` (sharp), and a\n// Worker route should not depend on tree-shaking to keep it out.\nimport { t, type Localizable } from '@getvitops/utils/content';\nimport type { AdPropertySetup } from '@getvitops/utils/ads';\nimport type { CrmConfig } from '@getvitops/utils/crm';\nimport type { NotifyContext } from '@getvitops/utils/notify';\n\n/**\n * The slice of a `Config` that `resolveOrganizationName` reads. Structural on\n * purpose: `import config from './site.json'` types its literals widened\n * (`colors.utilities` becomes `string[]`, not the schema's union), so it is not\n * assignable to the full `Config`, and every scaffolded site calls this with\n * exactly such an import. A resolved `Config` still satisfies it.\n */\nexport interface OrganizationNameSource {\n organization?: { name?: Localizable | undefined; legalName?: string | undefined } | undefined;\n site: { defaultLocale: string };\n}\n\n/**\n * Resolves the one display name for a site's organization — `organization.name`\n * (localizable; `locale` picks which one, defaulting to `site.defaultLocale`,\n * which is also the fallback if the pick has no entry), falling back to\n * `legalName` when `name` isn't set at all, and to `undefined` (never `''`)\n * when neither resolves to anything — `t()` returns `''` for a genuinely\n * missing translation, and an empty name is worse than none.\n *\n * This is the one place that fallback chain is written. An EmDash site's own\n * `getSiteSettings().title` is a separate, runtime-only fact (EmDash's\n * settings aren't readable at Astro build/config time at all — see\n * AGENTS.md's \"EmDash plugin as the design-system editing surface\") and stays\n * authoritative for what it drives (the rendered page chrome); this helper is\n * for the build-time-only consumers that can't reach EmDash at all — favicon\n * generation, generated legal documents, notification emails — so a scaffold\n * needing a name in `astro.config.mjs` has one real source instead of a third\n * hardcoded string.\n */\nexport function resolveOrganizationName(\n config: OrganizationNameSource,\n locale?: string,\n): string | undefined {\n const org = config.organization;\n const resolvedLocale = locale ?? config.site.defaultLocale;\n const name =\n org?.name != null\n ? t(org.name, resolvedLocale, config.site.defaultLocale) || undefined\n : undefined;\n return name ?? org?.legalName;\n}\n\n/**\n * Builds the `context` option `createConversionRoute()` expects, straight from a\n * resolved `Config` — instead of every consumer hand-writing a `NotifyContext`\n * literal.\n *\n * `locationEmails` is primary-first, then every other location with an `email`,\n * in `organization.locations`' own key order — `planNotifications` picks the\n * first that's set, so order here is the whole precedence.\n */\nexport function toNotifyContext(config: Config, locale?: string): NotifyContext {\n const org = config.organization;\n const primaryKey = org?.primaryLocation;\n const locationEmails = Object.entries(org?.locations ?? {})\n .sort(([a], [b]) => (a === primaryKey ? -1 : b === primaryKey ? 1 : 0))\n .map(([, loc]) => loc.email)\n .filter((email): email is string => !!email);\n\n return {\n notifications: config.site.notifications,\n canonical: config.site.domains?.canonical,\n organizationName: resolveOrganizationName(config, locale),\n locationEmails,\n };\n}\n\n/**\n * `site.crm` IS `CrmConfig` — both are the schema's inferred type from `@getvitops/config` — so\n * this returns its argument's field unchanged.\n *\n * @deprecated Pass `config.site.crm` directly. Kept so existing callers keep compiling; it\n * stopped being an adapter when the type stopped being a copy.\n */\nexport function toCrmConfig(config: Config): CrmConfig | undefined {\n return config.site.crm;\n}\n\n/**\n * `site.ads.google`'s three server-side-relevant fields, for\n * `createConversionRoute`'s `googleAdsOffline.config`. Undefined when the site has no Google Ads\n * property configured at all, which `createConversionRoute` reads the same way `crm` being\n * omitted does: the upload never runs.\n *\n * @deprecated Pass `config.site.ads?.google` directly — it is already assignable. Kept so existing\n * callers keep compiling.\n */\nexport function toGoogleAdsConfig(\n config: Config,\n): Pick<AdPropertySetup, 'accountId' | 'conversionActionId' | 'reporting'> | undefined {\n return config.site.ads?.google;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAoDA,SAAgB,wBACd,QACA,QACoB;CACpB,MAAM,MAAM,OAAO;CACnB,MAAM,iBAAiB,UAAU,OAAO,KAAK;CAK7C,QAHE,KAAK,QAAQ,OACT,EAAE,IAAI,MAAM,gBAAgB,OAAO,KAAK,aAAa,KAAK,SAC1D,WACS,KAAK;AACtB;;;;;;;;;;AAWA,SAAgB,gBAAgB,QAAgB,QAAgC;CAC9E,MAAM,MAAM,OAAO;CACnB,MAAM,aAAa,KAAK;CACxB,MAAM,iBAAiB,OAAO,QAAQ,KAAK,aAAa,CAAC,CAAC,CAAC,CACxD,MAAM,CAAC,IAAI,CAAC,OAAQ,MAAM,aAAa,KAAK,MAAM,aAAa,IAAI,CAAE,CAAC,CACtE,KAAK,GAAG,SAAS,IAAI,KAAK,CAAC,CAC3B,QAAQ,UAA2B,CAAC,CAAC,KAAK;CAE7C,OAAO;EACL,eAAe,OAAO,KAAK;EAC3B,WAAW,OAAO,KAAK,SAAS;EAChC,kBAAkB,wBAAwB,QAAQ,MAAM;EACxD;CACF;AACF;;;;;;;;AASA,SAAgB,YAAY,QAAuC;CACjE,OAAO,OAAO,KAAK;AACrB;;;;;;;;;;AAWA,SAAgB,kBACd,QACqF;CACrF,OAAO,OAAO,KAAK,KAAK;AAC1B"}
@@ -1,3 +1,3 @@
1
- import { a as TRACKING_ENDPOINT } from "../tracking-BNKKBqOe.mjs";
1
+ import { a as TRACKING_ENDPOINT } from "../tracking-D2xOzjFD.mjs";
2
2
  export * from "@getvitops/utils/tracking";
3
3
  export { TRACKING_ENDPOINT };
@@ -1,4 +1,4 @@
1
- import { n as TRACKING_ENDPOINT } from "../tracking-M2PqlMpw.mjs";
1
+ import { n as TRACKING_ENDPOINT } from "../tracking-Cb3A0BrC.mjs";
2
2
 
3
3
  export * from "@getvitops/utils/tracking"
4
4
 
package/dist/index.d.mts CHANGED
@@ -1,11 +1,11 @@
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
- import { LegalOutput, SiteFont, StylesheetFormat } from "@getvitops/generator";
1
+ import { i as TRACKING_COOKIE, n as ResolveTrackingContext, o as TrackingCategory, r as ResolvedTracking, s as resolveTracking, t as GetvitopsTrackingOptions } from "./tracking-D2xOzjFD.mjs";
2
+ import { Config, LegalOutput, SiteFont, StylesheetFormat } from "@getvitops/generator";
3
3
  import { generateIconInclude } from "@getvitops/utils";
4
- import { AdProvider, ConversionLabelMap } from "@getvitops/utils/ads";
4
+ import { AdProvider } from "@getvitops/utils/ads";
5
+ import { AdPropertyConfig } from "@getvitops/config";
5
6
  import { OutputKind } from "@getvitops/utils/media";
6
7
  import { AstroIntegration } from "astro";
7
8
  export * from "@getvitops/utils";
8
- //#region src/analytics.d.ts
9
9
  /**
10
10
  * The `<Analytics />` resolution layer — provider config in, the exact tags to
11
11
  * emit out.
@@ -175,8 +175,6 @@ interface ResolveAnalyticsContext {
175
175
  export declare function resolveAnalytics(opts: GetvitopsAnalyticsOptions | undefined, ctx?: ResolveAnalyticsContext): ResolvedAnalytics;
176
176
  /** Which optional categories a set of tags actually needs a visitor to decide. */
177
177
  export declare function consentCategories(tags: GatedTag[]): OptionalConsentCategory[];
178
- //#endregion
179
- //#region src/seo.d.ts
180
178
  /**
181
179
  * The `<Seo />` resolution layer — site-level defaults merged with per-page props
182
180
  * into the exact tags to emit.
@@ -196,11 +194,12 @@ type SeoOgType = 'website' | 'article' | 'profile';
196
194
  * Site-level SEO defaults, passed to `vitops({ seo })` and baked into the
197
195
  * `virtual:getvitops/head` module. Per-page `<Seo />` props override these.
198
196
  *
199
- * The vocabulary mirrors the site-config schema's `seo` block
200
- * (`@getvitops/generator`, `src/config.ts`) so a `Config` → these adapter would
201
- * be a flat field map — but it is not imported from there. This is the
202
- * integration's own option surface, and it must stay JSON-serialisable: it is
203
- * `JSON.stringify`d into the virtual module, so no functions, no `URL`s, no dates.
197
+ * `site.seo` in the site config is the source (`@getvitops/config`'s `SeoSchema`);
198
+ * `resolveSeoDefaults()` turns it into this shape for a build locale, and `vitops({ seo })` is merged
199
+ * over the result as an override. This interface is therefore the *resolved* shape — plain strings,
200
+ * `Localizable` already picked — and must stay JSON-serialisable: it is `JSON.stringify`d into the
201
+ * virtual module, so no functions, no `URL`s, no dates. `seo-defaults.test.ts` fails when a
202
+ * `site.seo` field is added to the schema and not read here (ADR-0002).
204
203
  */
205
204
  interface GetvitopsSeoOptions {
206
205
  /** `og:site_name`, and the `<title>` when a page passes none. */
@@ -361,8 +360,6 @@ interface SeoContext {
361
360
  * package free of the consumer-global coupling that sank the previous site model.
362
361
  */
363
362
  export declare function resolveSeo(defaults: GetvitopsSeoOptions, props: SeoProps, ctx: SeoContext): ResolvedSeo;
364
- //#endregion
365
- //#region src/integration.d.ts
366
363
  interface GetvitopsFaviconOptions {
367
364
  /** Source SVG or PNG. */
368
365
  source: string;
@@ -643,8 +640,11 @@ interface GetvitopsOptions {
643
640
  sitemap?: boolean | GetvitopsSitemapOptions;
644
641
  /**
645
642
  * Site-level defaults for `<Seo />` (`@getvitops/astro/Seo.astro`) — site name,
646
- * title template, Open Graph + Twitter defaults, verification tokens. Off unless
647
- * provided; per-page props override every field.
643
+ * title template, Open Graph + Twitter defaults, verification tokens.
644
+ *
645
+ * These come from `site.seo` in the site config (with `organization.name` as the site
646
+ * name); this option is an **override** merged over that, field by field, for a value
647
+ * an app wants to set in code. Per-page props override both.
648
648
  *
649
649
  * Needs the `site` astro.config option for canonical and `og:url`; without it
650
650
  * `<Seo />` omits every absolute-URL tag rather than deriving one from the
@@ -770,32 +770,13 @@ interface GetvitopsIconsOptions {
770
770
  overrides?: Record<string, string>;
771
771
  }
772
772
  export default function vitops(opts?: GetvitopsOptions): AstroIntegration;
773
- //#endregion
774
- //#region src/ads.d.ts
775
773
  /**
776
- * One ad property, mirroring a `site.ads` entry field for field.
777
- *
778
- * Structural rather than `SiteAdProperty` imported from the generator, matching
779
- * how `GetvitopsSeoOptions` and `GetvitopsTrackingOptions` already work: the
780
- * component surface must stay usable by a consumer who passes a plain object.
774
+ * One ad property, as `<Ads />` reads it: the fields of a `site.ads` entry that decide what is
775
+ * emitted. A subset of `AdPropertyConfig` from `@getvitops/config` — the schema's own inferred
776
+ * type, not a copy — so a plain object is still enough for a consumer to pass, and a field the
777
+ * schema renames fails here instead of silently reading `undefined`.
781
778
  */
782
- interface GetvitopsAdProperty {
783
- accountId?: string;
784
- pixelId?: 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;
794
- /** Default `marketing`. `analytics` for a property used purely for measurement. */
795
- category?: 'marketing' | 'analytics';
796
- /** False keeps the property on record without emitting its tag. */
797
- enabled?: boolean;
798
- }
779
+ type GetvitopsAdProperty = Pick<AdPropertyConfig, 'accountId' | 'pixelId' | 'conversionLabel' | 'conversionActionId' | 'reporting' | 'category' | 'enabled'>;
799
780
  type GetvitopsAdsOptions = Partial<Record<AdProvider, GetvitopsAdProperty>>;
800
781
  /** One ad tag. `key` is the platform, so a caller can find one without matching prose. */
801
782
  interface ResolvedAdTag extends GatedTag {
@@ -820,8 +801,6 @@ interface ResolveAdsContext {
820
801
  }
821
802
  /** Resolve the configured ad properties into the tags `<Ads />` emits. */
822
803
  export declare function resolveAds(ads: GetvitopsAdsOptions | undefined, ctx?: ResolveAdsContext): ResolvedAds;
823
- //#endregion
824
- //#region src/lastmod.d.ts
825
804
  interface GitLastmodOptions {
826
805
  /** Directories to read history from (default `src/pages`, `src/content`, `src/data`). */
827
806
  dirs?: string[];
@@ -849,6 +828,17 @@ export declare function slugFromContent(file: string): string | undefined;
849
828
  * flip presented as a fact.
850
829
  */
851
830
  export declare function gitLastmod(options?: GitLastmodOptions): Promise<(item: GetvitopsSitemapEntry) => GetvitopsSitemapEntry>;
852
- //#endregion
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 };
831
+ interface ResolveSeoDefaultsOptions {
832
+ /** Build locale. Default `site.defaultLocale`; a value with no entry falls back to the default. */
833
+ locale?: string;
834
+ /** Which `site.environments` entry to read `robots` from. Default `'production'`. */
835
+ env?: string;
836
+ }
837
+ export declare function resolveSeoDefaults(config: Config, options?: ResolveSeoDefaultsOptions): GetvitopsSeoOptions;
838
+ /**
839
+ * Merge `vitops({ seo })` over config-derived defaults. The two nested objects merge key by key, so
840
+ * setting `openGraph.locale` in code doesn't discard the config's fallback image.
841
+ */
842
+ export declare function mergeSeoDefaults(fromConfig: GetvitopsSeoOptions, override: GetvitopsSeoOptions | undefined): GetvitopsSeoOptions;
843
+ 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 ResolveSeoDefaultsOptions, 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 };
854
844
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
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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"}
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","../src/seo-defaults.ts"],"mappings":"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"}
package/dist/index.mjs CHANGED
@@ -1,4 +1,5 @@
1
- import { n as TRACKING_ENDPOINT, r as resolveTracking, t as TRACKING_COOKIE } from "./tracking-M2PqlMpw.mjs";
1
+ import { t as resolveOrganizationName } from "./config-adapters-CbTywgEY.mjs";
2
+ import { n as TRACKING_ENDPOINT, r as resolveTracking, t as TRACKING_COOKIE } from "./tracking-Cb3A0BrC.mjs";
2
3
  import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
4
  import { dirname, join, relative, resolve } from "node:path";
4
5
  import { fileURLToPath } from "node:url";
@@ -7,12 +8,13 @@ import { collectIconRefs, faviconLinks, generateFavicons, generateIconInclude, r
7
8
  import vitopsPlugin from "@getvitops/vite";
8
9
  import { AD_PLATFORMS, AD_PROVIDER_KEYS, normalizeConversionLabels } from "@getvitops/utils/ads";
9
10
  import { fontProviders } from "astro/config";
11
+ import { t } from "@getvitops/utils/content";
10
12
  import { execFile } from "node:child_process";
11
13
  import { promisify } from "node:util";
12
14
 
13
15
  export * from "@getvitops/utils"
14
16
 
15
- //#region src/analytics.ts
17
+ // src/analytics.ts
16
18
  /**
17
19
  * A small inline snippet reading each experiment's cookie into a plain
18
20
  * object, skipping any experiment whose cookie isn't set (visitor not yet
@@ -195,8 +197,7 @@ function consentCategories(tags) {
195
197
  ].filter((c) => used.has(c));
196
198
  }
197
199
 
198
- //#endregion
199
- //#region src/ads.ts
200
+ // src/ads.ts
200
201
  /**
201
202
  * The `<Ads />` resolution layer — the site's ad properties in, the exact tags to
202
203
  * emit out.
@@ -306,8 +307,7 @@ function resolveAds(ads, ctx = {}) {
306
307
  };
307
308
  }
308
309
 
309
- //#endregion
310
- //#region src/fonts.ts
310
+ // src/fonts.ts
311
311
  /**
312
312
  * Provider names, matching `fontProviders`' keys 1:1 (verified against astro@7.1.2).
313
313
  * The generator's `SiteFontSchema` enumerates the same set, so a mismatch is a
@@ -359,8 +359,7 @@ function resolveFonts(decls) {
359
359
  };
360
360
  }
361
361
 
362
- //#endregion
363
- //#region src/i18n.ts
362
+ // src/i18n.ts
364
363
  function codesOf(entry) {
365
364
  return typeof entry === "string" ? [entry] : entry.codes;
366
365
  }
@@ -384,8 +383,69 @@ function localeConsistencyWarnings(site, i18n) {
384
383
  return warnings;
385
384
  }
386
385
 
387
- //#endregion
388
- //#region src/integration.ts
386
+ // src/seo-defaults.ts
387
+ /** Drop `undefined`/empty-string keys so the result carries only what the config actually states. */
388
+ function compact(value) {
389
+ return Object.fromEntries(Object.entries(value).filter(([, v]) => v !== void 0 && v !== ""));
390
+ }
391
+ function resolveSeoDefaults(config, options = {}) {
392
+ const seo = config.site.seo;
393
+ const defaultLocale = config.site.defaultLocale;
394
+ const locale = options.locale ?? defaultLocale;
395
+ const text = (value) => t(value, locale, defaultLocale) || void 0;
396
+ const robots = config.site.environments?.[options.env ?? "production"]?.robots ?? seo?.robots;
397
+ const image = seo?.openGraph?.image;
398
+ const openGraph = compact({
399
+ type: seo?.openGraph?.type,
400
+ locale: seo?.openGraph?.locale,
401
+ localeAlternates: seo?.openGraph?.localeAlternates,
402
+ image: image === void 0 ? void 0 : typeof image === "string" ? { url: image } : compact({
403
+ url: image.src,
404
+ alt: image.alt,
405
+ width: image.width,
406
+ height: image.height,
407
+ type: image.type
408
+ })
409
+ });
410
+ const twitter = compact({
411
+ card: seo?.twitter?.card,
412
+ site: seo?.twitter?.site,
413
+ creator: seo?.twitter?.creator
414
+ });
415
+ return compact({
416
+ siteName: text(seo?.siteName) ?? resolveOrganizationName(config, locale),
417
+ titleTemplate: text(seo?.titleTemplate),
418
+ defaultTitle: text(seo?.defaultTitle),
419
+ defaultDescription: text(seo?.defaultDescription),
420
+ robots,
421
+ googleSiteVerification: seo?.googleSiteVerification,
422
+ bingSiteVerification: seo?.bingSiteVerification,
423
+ ...Object.keys(openGraph).length ? { openGraph } : {},
424
+ ...Object.keys(twitter).length ? { twitter } : {}
425
+ });
426
+ }
427
+ /**
428
+ * Merge `vitops({ seo })` over config-derived defaults. The two nested objects merge key by key, so
429
+ * setting `openGraph.locale` in code doesn't discard the config's fallback image.
430
+ */
431
+ function mergeSeoDefaults(fromConfig, override) {
432
+ if (!override) return fromConfig;
433
+ const merged = {
434
+ ...fromConfig,
435
+ ...override
436
+ };
437
+ if (fromConfig.openGraph || override.openGraph) merged.openGraph = {
438
+ ...fromConfig.openGraph,
439
+ ...override.openGraph
440
+ };
441
+ if (fromConfig.twitter || override.twitter) merged.twitter = {
442
+ ...fromConfig.twitter,
443
+ ...override.twitter
444
+ };
445
+ return merged;
446
+ }
447
+
448
+ // src/integration.ts
389
449
  /**
390
450
  * @getvitops/astro — the Astro integration. One entry wires the design system's
391
451
  * `<head>` contributions into a consumer site:
@@ -444,6 +504,20 @@ function readSiteLocales(configPath, siteEnv) {
444
504
  }
445
505
  }
446
506
  /**
507
+ * Resolve `site.seo` (plus `organization.name` as the site-name fallback) out of the site config.
508
+ *
509
+ * Best-effort, like `readSiteLocales`: a malformed or missing config is silence here, since every
510
+ * other consumer of the file validates it loudly in the same hook. An empty object means "the
511
+ * config states no SEO defaults", which is also what no site config at all means.
512
+ */
513
+ function readSiteSeo(configPath, siteEnv) {
514
+ try {
515
+ return resolveSeoDefaults(resolveConfig(JSON.parse(readFileSync(configPath, "utf8")), siteEnv), { env: siteEnv });
516
+ } catch {
517
+ return {};
518
+ }
519
+ }
520
+ /**
447
521
  * Read `site.ads` and this environment's ads switch out of the site config.
448
522
  *
449
523
  * Best-effort, like `undisclosedProviders`: every other consumer of this file
@@ -677,7 +751,8 @@ function vitops(opts = {}) {
677
751
  const siteLocales = readSiteLocales(resolve(root, siteInput), opts.site?.siteEnv ?? "production");
678
752
  for (const warning of localeConsistencyWarnings(siteLocales, config.i18n)) logger.warn(warning);
679
753
  }
680
- if (opts.seo) {
754
+ const seoDefaults = mergeSeoDefaults(siteInput ? readSiteSeo(resolve(root, siteInput), opts.site?.siteEnv ?? "production") : {}, opts.seo);
755
+ if (opts.seo || Object.keys(seoDefaults).some((k) => k !== "siteName")) {
681
756
  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.");
682
757
  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.");
683
758
  }
@@ -914,7 +989,7 @@ function vitops(opts = {}) {
914
989
  sitemap: sitemapHref,
915
990
  fonts: headFonts,
916
991
  site: config.site ?? null,
917
- seo: opts.seo ?? {},
992
+ seo: seoDefaults,
918
993
  analytics: opts.analytics ?? {},
919
994
  consent: consentEnabled,
920
995
  consentCategories: offeredCategories,
@@ -973,8 +1048,7 @@ function vitops(opts = {}) {
973
1048
  };
974
1049
  }
975
1050
 
976
- //#endregion
977
- //#region src/lastmod.ts
1051
+ // src/lastmod.ts
978
1052
  /**
979
1053
  * Real per-page `<lastmod>` for the sitemap, derived from git.
980
1054
  *
@@ -1133,8 +1207,7 @@ async function gitLastmod(options = {}) {
1133
1207
  };
1134
1208
  }
1135
1209
 
1136
- //#endregion
1137
- //#region src/seo.ts
1210
+ // src/seo.ts
1138
1211
  /** `en-CA` → `en_CA`; Open Graph wants the underscore form. */
1139
1212
  const ogLocale = (locale) => locale.replace("-", "_");
1140
1213
  /**
@@ -1255,6 +1328,5 @@ function resolveSeo(defaults, props, ctx) {
1255
1328
  };
1256
1329
  }
1257
1330
 
1258
- //#endregion
1259
- export { TRACKING_COOKIE, consentCategories, vitops as default, gitLastmod, resolveAds, resolveAnalytics, resolveSeo, resolveTracking, routeFromPage, slugFromContent };
1331
+ export { TRACKING_COOKIE, consentCategories, vitops as default, gitLastmod, mergeSeoDefaults, resolveAds, resolveAnalytics, resolveSeo, resolveSeoDefaults, resolveTracking, routeFromPage, slugFromContent };
1260
1332
  //# sourceMappingURL=index.mjs.map