@getvitops/astro 0.9.0 → 2.0.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/CHANGELOG.md +694 -0
- package/README.md +239 -3
- package/dist/index.d.mts +622 -5
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +609 -12
- package/dist/index.mjs.map +1 -1
- package/package.json +20 -5
- package/src/analytics.ts +325 -0
- package/src/components/Analytics.astro +59 -0
- package/src/components/Cards.astro +1 -1
- package/src/components/CookieConsent.astro +106 -0
- package/src/components/Details.astro +10 -4
- package/src/components/Drawer.astro +9 -3
- package/src/components/Head.astro +49 -1
- package/src/components/Icon.astro +94 -0
- package/src/components/Popover.astro +9 -3
- package/src/components/Seo.astro +68 -0
- package/src/seo.ts +337 -0
- package/virtual.d.ts +59 -0
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/integration.ts"],"sourcesContent":["/**\n * @getvitops/astro — the Astro integration. One entry wires the design system's\n * `<head>` contributions into a consumer site:\n * - favicons + PWA manifest (generated into public/),\n * - the web-component runtime bundles (copied into public/vitops/),\n * - (opt-in) the design-system CSS, generated + auto-injected (no manual import).\n * The `<Head />` component (shipped alongside) renders the tags, reading resolved\n * config from the `virtual:getvitops/head` module this integration provides.\n */\nimport { cpSync, existsSync } from 'node:fs';\nimport { dirname, join, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { Format } from '@getvitops/generator';\nimport {\n type FaviconLink,\n faviconLinks,\n generateFavicons,\n writeFaviconManifest,\n} from '@getvitops/utils';\nimport vitops from '@getvitops/vite';\nimport type { AstroIntegration } from 'astro';\n\nexport interface GetvitopsFaviconOptions {\n /** Source SVG or PNG. */\n source: string;\n lowResSource?: string;\n /** App name → enables `site.webmanifest`. */\n name?: string;\n /** PWA theme color (also `<meta name=\"theme-color\">`). */\n themeColor?: string;\n backgroundColor?: string;\n}\n\nexport interface GetvitopsCssOptions {\n /** Path to design-system.json (default 'design-system.json'). */\n input?: string;\n /** Output format (default 'tailwind'). */\n format?: Format;\n /** Directory the generated CSS is written to (default 'src/styles'). */\n out?: string;\n /**\n * Auto-inject the generated stylesheet into every SSR page (default true).\n * Set false when other integrations add routes that must not inherit the\n * design system (e.g. EmDash's `/_emdash/admin`) — then import the generated\n * file (`<out>/tailwind.css` or `<out>/styles.css`) from your site layout so\n * only your own pages (and previews rendered through them) are styled.\n */\n inject?: boolean;\n}\n\nexport interface GetvitopsOptions {\n favicon?: GetvitopsFaviconOptions;\n /** Copy + link the web-component bundles (default true). */\n webComponents?: boolean;\n /** Generate + auto-inject the design-system CSS. Off unless provided. */\n css?: GetvitopsCssOptions;\n /**\n * Ship the live theme editor (default false).\n *\n * Copies `editor.js` into `public/vitops/`, loads it from `<Head />`, and mirrors\n * the generated `design-manifest.json` next to it so `<wc-theme-editor>` resolves\n * its default `/vitops/design-manifest.json`. Requires `css` (the manifest is a\n * `css`-format output) — place the `<wc-theme-editor>` tag in your layout yourself.\n *\n * Editing is live on any deploy; **saving back** to `design-system.json` needs the\n * dev server, so on a static build that button simply isn't rendered.\n */\n editor?: boolean;\n}\n\ninterface HeadData {\n favicons: boolean;\n faviconLinks: FaviconLink[];\n themeColor: string | null;\n webComponents: boolean;\n wcBase: string;\n editor: boolean;\n}\n\nconst VIRTUAL_ID = 'virtual:getvitops/head';\nconst RESOLVED_ID = `\\0${VIRTUAL_ID}`;\n\nfunction virtualHeadPlugin(data: HeadData) {\n return {\n name: '@getvitops/astro:virtual-head',\n resolveId(id: string) {\n return id === VIRTUAL_ID ? RESOLVED_ID : null;\n },\n load(id: string) {\n return id === RESOLVED_ID ? `export default ${JSON.stringify(data)};` : null;\n },\n };\n}\n\nexport default function getvitops(opts: GetvitopsOptions = {}): AstroIntegration {\n return {\n name: '@getvitops/astro',\n hooks: {\n 'astro:config:setup': async ({ config, updateConfig, injectScript, logger }) => {\n const root = fileURLToPath(config.root);\n const publicDir = fileURLToPath(config.publicDir);\n const webComponents = opts.webComponents !== false;\n const editor = opts.editor === true;\n if (editor && !opts.css)\n logger.warn('editor: true needs a `css` config — it reads design-manifest.json.');\n const hasManifest = !!(opts.favicon?.name && opts.favicon?.themeColor);\n let hasSvg = false;\n\n // 1. Favicons + manifest → public/\n if (opts.favicon) {\n const source = resolve(root, opts.favicon.source);\n hasSvg = source.endsWith('.svg');\n await generateFavicons({\n source,\n outputDir: publicDir,\n ...(opts.favicon.lowResSource\n ? { lowResSource: resolve(root, opts.favicon.lowResSource) }\n : {}),\n });\n if (opts.favicon.name && opts.favicon.themeColor) {\n await writeFaviconManifest(publicDir, {\n name: opts.favicon.name,\n themeColor: opts.favicon.themeColor,\n ...(opts.favicon.backgroundColor\n ? { backgroundColor: opts.favicon.backgroundColor }\n : {}),\n });\n }\n logger.info('generated favicons + manifest → public/');\n }\n\n // 2. Web-component bundles → public/vitops/\n if (webComponents) {\n const corePkg = fileURLToPath(import.meta.resolve('@getvitops/core/package.json'));\n const coreDist = join(dirname(corePkg), 'dist');\n const dest = join(publicDir, 'vitops');\n if (existsSync(coreDist)) {\n const bundles = ['polyfills.js', 'deferred.js', 'elements.js', 'polyfills'];\n if (editor) bundles.push('editor.js');\n for (const f of bundles) {\n const src = join(coreDist, f);\n if (existsSync(src)) cpSync(src, join(dest, f), { recursive: true });\n }\n logger.info('copied web-component bundles → public/vitops/');\n } else {\n logger.warn(\n `@getvitops/core dist not found (${coreDist}); run build:core. Skipping WC copy.`,\n );\n }\n }\n\n // 3. Virtual head module for <Head/>\n updateConfig({\n vite: {\n plugins: [\n virtualHeadPlugin({\n favicons: !!opts.favicon,\n faviconLinks: opts.favicon ? faviconLinks({ hasSvg, manifest: hasManifest }) : [],\n themeColor: opts.favicon?.themeColor ?? null,\n webComponents,\n wcBase: '/vitops',\n editor,\n }),\n ],\n },\n });\n\n // 4. CSS — generate + compile + auto-inject (consumer imports nothing).\n if (opts.css) {\n const format: Format = opts.css.format ?? 'tailwind';\n const out = opts.css.out ?? 'src/styles';\n const plugins = [\n vitops({\n input: opts.css.input ?? 'design-system.json',\n format,\n out,\n // Mirrored inside the generate pass so the served copy can never be\n // missing on a cold start or stale after a config edit.\n ...(editor ? { editorManifestDir: publicDir } : {}),\n }),\n ];\n // Tailwind is an optional peer — only the `tailwind` format needs it,\n // so it's loaded here rather than imported at module scope. (The\n // plugin is typed against a newer vite than @getvitops/vite's peer;\n // the Plugin shapes are structurally compatible at runtime — Astro\n // forwards them verbatim — so bridge the two type identities.)\n if (format === 'tailwind') {\n let tailwindcss: () => unknown[];\n try {\n ({ default: tailwindcss } = await import('@tailwindcss/vite'));\n } catch {\n throw new Error(\n \"[getvitops] css.format: 'tailwind' requires `tailwindcss` and `@tailwindcss/vite` \" +\n 'in your devDependencies — install them, or switch to css.format: ' +\n \"'css' for the standalone bundle.\",\n );\n }\n plugins.push(...(tailwindcss() as unknown as typeof plugins));\n }\n updateConfig({ vite: { plugins } });\n const cssFile = format === 'tailwind' ? 'tailwind.css' : 'styles.css';\n if (opts.css.inject !== false) {\n const cssPath = resolve(root, out, cssFile);\n injectScript('page-ssr', `import ${JSON.stringify(cssPath)};`);\n logger.info(`design-system CSS (${format}) auto-injected from ${out}/${cssFile}`);\n } else {\n logger.info(\n `design-system CSS (${format}) generated at ${out}/${cssFile} (inject: false — import it from your layout)`,\n );\n }\n }\n },\n },\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/analytics.ts","../src/fonts.ts","../src/integration.ts","../src/seo.ts"],"sourcesContent":["/**\n * The `<Analytics />` resolution layer — provider config in, the exact tags to\n * emit out.\n *\n * Kept separate from `Analytics.astro` for the same reason `seo.ts` is separate\n * from `Seo.astro`: everything decidable is a pure function and asserted in\n * `analytics.test.ts`, and the component only renders what this returns.\n *\n * Two properties here are the point of the whole file:\n *\n * - **Which consent category a provider needs is derived, never declared.** It\n * follows from whether that provider sets cookies, which follows from its own\n * configuration (Matomo with `disableCookies` is genuinely cookieless; Matomo\n * with `cookies: true` is not). A consumer cannot mark Google Analytics\n * `necessary` to skip a banner, because `setsCookies` isn't theirs to set.\n * - **The same fact drives the legal documents.** `@getvitops/generator`'s\n * `legal/providers.ts` maps the *site config's* `analytics` block onto named\n * processors with cookie lists. If a provider is configured here but not\n * there, the site runs a tag its own cookie notice doesn't disclose — so the\n * integration warns, and `warnings` is returned as data so that check is a\n * test rather than a log line someone might scroll past.\n */\n\n/**\n * When a tag is allowed to load. Third-party analytics is never worth a\n * millisecond of the critical path, so the default puts every tag after `load`.\n */\nexport type AnalyticsStrategy = 'idle' | 'async' | 'interaction';\n\n/**\n * The consent vocabulary, mirroring `@getvitops/core`'s `consent/store.ts`.\n * Mirrored rather than imported because this value is `JSON.stringify`d into the\n * virtual module and the component reads it at build time, while core's copy is a\n * browser runtime — `consent.test.ts` and `analytics.test.ts` pin both ends.\n */\nexport type ConsentCategory = 'necessary' | 'analytics' | 'marketing' | 'preferences';\n\nexport type OptionalConsentCategory = Exclude<ConsentCategory, 'necessary'>;\n\nexport interface GoogleAnalyticsOptions {\n /** Measurement ID, e.g. `G-XXXXXXXXXX`. */\n id: string;\n /**\n * Which category gates it (default `analytics`). Set `marketing` when the\n * property feeds Google Ads — the tag is the same, the permission isn't.\n */\n category?: OptionalConsentCategory;\n /** Extra `gtag('config', id, …)` parameters, e.g. `{ send_page_view: false }`. */\n config?: Record<string, string | number | boolean>;\n}\n\nexport interface ClarityOptions {\n /** Clarity project ID. */\n id: string;\n category?: OptionalConsentCategory;\n}\n\nexport interface MatomoOptions {\n /** Instance base URL — self-hosted, or `https://<you>.matomo.cloud`. */\n url: string;\n /** Site ID within that instance. */\n siteId: string | number;\n /**\n * Let Matomo set its cookies (default **false**).\n *\n * The default is what makes Matomo the cookieless option: with\n * `disableCookies` it stores no identifier on the device, needs no banner, and\n * the generated cookie notice can state positively that it sets none. Turning\n * this on gives you returning-visitor metrics and a consent obligation.\n */\n cookies?: boolean;\n}\n\nexport interface PlausibleOptions {\n /** The domain as registered in Plausible, e.g. `example.com`. */\n domain: string;\n /** Script URL — override for a self-hosted instance or a custom-events build. */\n src?: string;\n}\n\nexport interface GetvitopsAnalyticsOptions {\n googleAnalytics?: string | GoogleAnalyticsOptions;\n clarity?: string | ClarityOptions;\n matomo?: MatomoOptions;\n plausible?: string | PlausibleOptions;\n /** When tags load (default `'idle'`). */\n strategy?: AnalyticsStrategy;\n}\n\n/**\n * The consent gate, configured on the integration rather than inside `analytics`.\n *\n * That placement is the design, not an accident: analytics is one consumer of\n * consent, not its owner. A/B assignment, account personalisation and third-party\n * embeds gate on the same choice via `data-consent`, and a site can enable the\n * banner with no analytics configured at all.\n */\nexport interface GetvitopsConsentOptions {\n /**\n * Categories the banner offers. Defaults to the ones the configured providers\n * actually need — offering a choice nothing is waiting on is noise.\n */\n categories?: OptionalConsentCategory[];\n /** Link the banner to the cookie notice (`vitops legal` renders one). */\n policyUrl?: string;\n}\n\n/** One tag, fully resolved. */\nexport interface ResolvedTag {\n key: 'googleAnalytics' | 'clarity' | 'matomo' | 'plausible';\n /** The provider as a reader would recognise it — used in warnings. */\n provider: string;\n category: ConsentCategory;\n setsCookies: boolean;\n /** Names (or `prefix*` patterns) to clear when consent is withdrawn. */\n cookies: string[];\n strategy: AnalyticsStrategy;\n /**\n * Emitted inert (`type=\"text/plain\"`) for the runtime to activate, rather than\n * as a live `<script>`. False only for the one case that needs no runtime at\n * all: a cookieless provider with `strategy: 'async'`.\n */\n gated: boolean;\n /** Bootstrap JS. Runs before `src`, so a queue is populated before it drains. */\n inline: string;\n src: string | null;\n attrs: Record<string, string>;\n}\n\nexport interface ResolvedAnalytics {\n tags: ResolvedTag[];\n /** Does anything here need `@getvitops/core/consent` loaded? */\n needsRuntime: boolean;\n /** Configuration problems, as prose. The integration logs these. */\n warnings: string[];\n}\n\nexport interface ResolveAnalyticsContext {\n /** Is the consent gate enabled on this site (`getvitops({ consent })`)? */\n consent?: boolean;\n}\n\n/**\n * IDs land inside an inline `<script>`, so they are checked rather than trusted.\n * A `</script>` in a measurement ID is not a plausible accident, but the cost of\n * the guard is one regex and the cost of not having it is arbitrary script\n * injection from a config file.\n */\nconst SAFE_ID = /^[\\w.-]+$/;\n\nfunction bad(value: string): boolean {\n return !SAFE_ID.test(value);\n}\n\n/** JSON is a subset of JS object-literal syntax; `<` can't appear unescaped. */\nfunction literal(value: Record<string, string | number | boolean>): string {\n return JSON.stringify(value).replaceAll('<', '\\\\u003c');\n}\n\nfunction googleAnalytics(input: string | GoogleAnalyticsOptions): ResolvedTag | string {\n const opts = typeof input === 'string' ? { id: input } : input;\n if (bad(opts.id)) return `googleAnalytics: \"${opts.id}\" is not a valid measurement ID. Skipped.`;\n const config = opts.config ? `,${literal(opts.config)}` : '';\n return {\n key: 'googleAnalytics',\n provider: 'Google Analytics',\n category: opts.category ?? 'analytics',\n setsCookies: true,\n cookies: ['_ga', '_ga_*', '_gid', '_gat', '_gat_*'],\n strategy: 'idle',\n gated: true,\n inline:\n `window.dataLayer=window.dataLayer||[];` +\n `function gtag(){dataLayer.push(arguments)}` +\n `gtag('js',new Date());` +\n `gtag('config','${opts.id}'${config});`,\n src: `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(opts.id)}`,\n attrs: {},\n };\n}\n\nfunction clarity(input: string | ClarityOptions): ResolvedTag | string {\n const opts = typeof input === 'string' ? { id: input } : input;\n if (bad(opts.id)) return `clarity: \"${opts.id}\" is not a valid project ID. Skipped.`;\n const category = opts.category ?? 'analytics';\n // Clarity's snippet injects its own <script>, so there is no `src` for us to\n // schedule — activation *is* the load.\n //\n // The consentv2 call is not redundant with having gated the tag: Microsoft\n // enforces consent signals for EEA/UK/CH traffic from the project side, and\n // without one Clarity degrades to no-consent mode even though the visitor\n // agreed. `ad_Storage` follows the category, because that is the only thing\n // that tells us whether this tag was permitted for advertising.\n return {\n key: 'clarity',\n provider: 'Microsoft Clarity',\n category,\n setsCookies: true,\n cookies: ['_clck', '_clsk', 'MUID', 'CLID', 'ANONCHK', 'SM'],\n strategy: 'idle',\n gated: true,\n inline:\n `(function(c,l,a,r,i,t,y){c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};` +\n `t=l.createElement(r);t.async=1;t.src=\"https://www.clarity.ms/tag/\"+i;` +\n `y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y)})` +\n `(window,document,\"clarity\",\"script\",\"${opts.id}\");` +\n `window.clarity('consentv2',{ad_Storage:'${\n category === 'marketing' ? 'granted' : 'denied'\n }',analytics_Storage:'granted'});`,\n src: null,\n attrs: {},\n };\n}\n\nfunction matomo(opts: MatomoOptions): ResolvedTag | string {\n const siteId = String(opts.siteId);\n if (bad(siteId)) return `matomo: \"${siteId}\" is not a valid site ID. Skipped.`;\n let base: URL;\n try {\n base = new URL(opts.url);\n } catch {\n return `matomo: \"${opts.url}\" is not a valid URL. Skipped.`;\n }\n const url = `${base.origin}${base.pathname.replace(/\\/*$/, '')}/`;\n const cookies = opts.cookies === true;\n\n // `disableCookies` must be pushed *before* trackPageView, or the first request\n // of the visit sets the cookies this option exists to avoid.\n return {\n key: 'matomo',\n provider: 'Matomo',\n category: cookies ? 'analytics' : 'necessary',\n setsCookies: cookies,\n cookies: cookies ? ['_pk_id*', '_pk_ses*', '_pk_ref*', '_pk_cvar*', '_pk_hsr*'] : [],\n strategy: 'idle',\n gated: true,\n inline:\n `var _paq=window._paq=window._paq||[];` +\n (cookies ? '' : `_paq.push(['disableCookies']);`) +\n `_paq.push(['trackPageView']);_paq.push(['enableLinkTracking']);` +\n `(function(){var u=\"${url}\";` +\n `_paq.push(['setTrackerUrl',u+'matomo.php']);_paq.push(['setSiteId','${siteId}']);` +\n `var d=document,g=d.createElement('script'),s=d.getElementsByTagName('script')[0];` +\n `g.async=true;g.src=u+'matomo.js';s.parentNode.insertBefore(g,s)})();`,\n src: null,\n attrs: {},\n };\n}\n\nfunction plausible(input: string | PlausibleOptions): ResolvedTag | string {\n const opts = typeof input === 'string' ? { domain: input } : input;\n if (bad(opts.domain)) return `plausible: \"${opts.domain}\" is not a valid domain. Skipped.`;\n return {\n key: 'plausible',\n provider: 'Plausible Analytics',\n // Cookieless and storing no cross-site identifier, so there is nothing for a\n // banner to ask about. It still goes through the runtime for scheduling.\n category: 'necessary',\n setsCookies: false,\n cookies: [],\n strategy: 'idle',\n gated: true,\n inline: '',\n src: opts.src ?? 'https://plausible.io/js/script.js',\n attrs: { 'data-domain': opts.domain },\n };\n}\n\n/**\n * Resolve the configured providers into the tags to emit.\n *\n * An invalid provider is dropped with a warning rather than throwing: one\n * malformed ID should not take down a build, and a missing tag is visible in the\n * output while a failed build is just a red wall.\n */\nexport function resolveAnalytics(\n opts: GetvitopsAnalyticsOptions | undefined,\n ctx: ResolveAnalyticsContext = {},\n): ResolvedAnalytics {\n const warnings: string[] = [];\n const tags: ResolvedTag[] = [];\n if (!opts) return { tags, needsRuntime: false, warnings };\n\n const strategy = opts.strategy ?? 'idle';\n const results = [\n opts.googleAnalytics === undefined ? null : googleAnalytics(opts.googleAnalytics),\n opts.clarity === undefined ? null : clarity(opts.clarity),\n opts.matomo === undefined ? null : matomo(opts.matomo),\n opts.plausible === undefined ? null : plausible(opts.plausible),\n ];\n\n for (const result of results) {\n if (result === null) continue;\n if (typeof result === 'string') {\n warnings.push(result);\n continue;\n }\n // A cookieless tag on `async` needs nothing from the runtime, so it is emitted\n // as an ordinary <script async> and that site ships no consent bundle at all.\n const gated = result.category !== 'necessary' || strategy !== 'async';\n tags.push({ ...result, strategy, gated });\n }\n\n const setting = tags.filter((t) => t.setsCookies);\n if (setting.length && !ctx.consent) {\n warnings.push(\n `${setting.map((t) => t.provider).join(' and ')} ${setting.length === 1 ? 'sets' : 'set'} ` +\n `cookies (${setting.flatMap((t) => t.cookies).join(', ')}) and \\`consent\\` is off, so ` +\n `${setting.length === 1 ? 'it loads' : 'they load'} for every visitor with no way to ` +\n 'decline. Set `consent: true`, or switch to a cookieless provider (Plausible, or Matomo ' +\n 'with its default `cookies: false`).',\n );\n }\n\n return { tags, needsRuntime: tags.some((t) => t.gated), warnings };\n}\n\n/** Which optional categories a set of tags actually needs a visitor to decide. */\nexport function consentCategories(tags: ResolvedTag[]): OptionalConsentCategory[] {\n const used = new Set<OptionalConsentCategory>();\n for (const tag of tags) {\n if (tag.category !== 'necessary') used.add(tag.category);\n }\n return (['analytics', 'marketing', 'preferences'] as const).filter((c) => used.has(c));\n}\n","/**\n * Font LOADING — turning declarations into Astro's `fonts:` config.\n *\n * The design system NAMES fonts (`design-system.json`'s `fonts` block → `--font-<name>`\n * tokens) and loads none of them: a stack is a `font-family` list, with no `@font-face`,\n * no preload, and no metrics-matched fallback behind it. Something has to load them, and\n * the failure mode when nothing does is invisible — installing a `@fontsource*` package\n * and importing its CSS renders correctly while silently giving up subsetting, preload\n * and the `size-adjust`/`ascent-override` metrics that keep CLS down.\n *\n * So this module is the seam, in the same spirit as `vitopsHosting()`: declare the family\n * once and let the integration wire it, rather than hand-rolling the config.\n *\n * The declaration shape is `SiteFont` from the generator — deliberately not a new type.\n * The site config has carried a `fonts` array (provider / weights / subsets / preload)\n * since it was written; what was missing was anything that read it.\n */\nimport type { SiteFont } from '@getvitops/generator';\nimport { fontProviders } from 'astro/config';\n\n/**\n * Provider names, matching `fontProviders`' keys 1:1 (verified against astro@7.1.2).\n * The generator's `SiteFontSchema` enumerates the same set, so a mismatch is a\n * compile-time-invisible drift — `resolveFonts` throws on an unknown name rather than\n * quietly dropping the family and leaving the token resolving to a fallback stack.\n */\nconst PROVIDERS = new Set(Object.keys(fontProviders));\n\n/**\n * Providers a declaration can construct with no arguments.\n *\n * `adobe` is the one that can't: `fontProviders.adobe({ id })` takes a project API id,\n * and the point of that id being an argument is that it comes from the environment. A\n * JSON declaration has nowhere to put `process.env.ADOBE_ID` without either inlining the\n * key into a committed file or inventing an interpolation syntax, so Adobe families are\n * declared in `astro.config` directly — see the error thrown below.\n */\nconst NEEDS_OPTIONS = new Set(['adobe']);\n\n/**\n * What `<Head />` needs to emit `<Font />` for each family the integration registered.\n *\n * `preload` is passed to the component verbatim: Astro's `PreloadFilter` is\n * `boolean | Array<{weight?, style?, subset?}>`, and the declaration's per-face form is a\n * subset of that — so \"preload the 700 upright only\" survives instead of flattening to\n * \"preload everything\", which would preload the whole family.\n */\nexport interface HeadFont {\n cssVariable: string;\n preload: NonNullable<SiteFont['preload']> | false;\n}\n\nexport interface ResolvedFonts {\n /** Spread into `updateConfig({ fonts })`. Astro's own `FontFamily` shape. */\n fonts: Record<string, unknown>[];\n /** Passed through the virtual head module so `<Head />` can render `<Font />`. */\n head: HeadFont[];\n}\n\n/**\n * Map declarations onto Astro's `fonts:` entries.\n *\n * `preload` is dropped from the Astro entry on purpose — it is not part of the config\n * schema there, it is a prop on the `<Font />` component. It rides along in `head`\n * instead, which is what makes preload a property of the declaration rather than\n * something the consumer hand-picks in their layout.\n */\nexport function resolveFonts(decls: readonly SiteFont[]): ResolvedFonts {\n const fonts: Record<string, unknown>[] = [];\n const head: HeadFont[] = [];\n const seen = new Set<string>();\n\n for (const decl of decls) {\n if (!PROVIDERS.has(decl.provider))\n throw new Error(\n `[getvitops] fonts: unknown provider \"${decl.provider}\" for \"${decl.name}\". ` +\n `Astro provides: ${[...PROVIDERS].sort().join(', ')}.`,\n );\n if (NEEDS_OPTIONS.has(decl.provider))\n throw new Error(\n `[getvitops] fonts: provider \"${decl.provider}\" needs an API id from your environment ` +\n `(\"${decl.name}\"), and a JSON declaration has nowhere to hold one. Declare this family ` +\n `in astro.config instead — fonts: [{ provider: fontProviders.${decl.provider}` +\n `({ id: process.env.ADOBE_ID }), name: '${decl.name}', cssVariable: '${decl.cssVariable}' }] — ` +\n 'and render its <Font /> yourself; the design-system token can still point at it.',\n );\n if (!decl.cssVariable.startsWith('--'))\n throw new Error(\n `[getvitops] fonts: cssVariable \"${decl.cssVariable}\" (${decl.name}) must start with \"--\".`,\n );\n // Two families on one variable is the documented failure of Astro's Fonts API:\n // it warns and the last one wins, so the first family silently never loads.\n if (seen.has(decl.cssVariable))\n throw new Error(\n `[getvitops] fonts: two families both declare ${decl.cssVariable} ` +\n `(the second is \"${decl.name}\"). Astro resolves one family per variable — ` +\n `give them separate variables, or drop one.`,\n );\n seen.add(decl.cssVariable);\n\n const { provider, preload, ...rest } = decl;\n fonts.push({\n ...rest,\n provider: (fontProviders as unknown as Record<string, () => unknown>)[provider]!(),\n });\n head.push({ cssVariable: decl.cssVariable, preload: preload ?? false });\n }\n\n return { fonts, head };\n}\n","/**\n * @getvitops/astro — the Astro integration. One entry wires the design system's\n * `<head>` contributions into a consumer site:\n * - favicons + PWA manifest (generated into public/),\n * - the web-component runtime bundles (copied into public/vitops/),\n * - (opt-in) the design-system CSS, generated + auto-injected (no manual import).\n * The `<Head />` component (shipped alongside) renders the tags, reading resolved\n * config from the `virtual:getvitops/head` module this integration provides.\n */\nimport { cpSync, existsSync, readFileSync } from 'node:fs';\nimport { dirname, join, relative, resolve } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { LegalOutput, SiteFont, StylesheetFormat } from '@getvitops/generator';\nimport { isSiteConfig, resolveSiteConfig } from '@getvitops/generator';\nimport {\n type FaviconLink,\n collectIconRefs,\n faviconLinks,\n generateFavicons,\n generateIconInclude,\n resolveIcon,\n scanFiles,\n writeFaviconManifest,\n} from '@getvitops/utils';\nimport vitops from '@getvitops/vite';\nimport type { AstroIntegration } from 'astro';\nimport {\n consentCategories,\n type GetvitopsAnalyticsOptions,\n type GetvitopsConsentOptions,\n type OptionalConsentCategory,\n resolveAnalytics,\n} from './analytics.ts';\nimport { type HeadFont, resolveFonts } from './fonts.ts';\nimport type { GetvitopsSeoOptions } from './seo.ts';\n\nexport interface GetvitopsFaviconOptions {\n /** Source SVG or PNG. */\n source: string;\n lowResSource?: string;\n /** App name → enables `site.webmanifest`. */\n name?: string;\n /** PWA theme color (also `<meta name=\"theme-color\">`). */\n themeColor?: string;\n backgroundColor?: string;\n}\n\nexport interface GetvitopsCssOptions {\n /**\n * Path to the config (default `'design-system.json'`).\n *\n * May be a `design-system.json` **or** the larger site config that embeds one\n * (`company.json` / `site.json`) — they are told apart by shape. Pointing this\n * at a site config makes the top-level `site` option redundant and lets\n * `legal` and `fonts` default their own `input` to it, so the path is declared\n * once.\n */\n input?: string;\n /**\n * Which `designSystem.themes` entry to build, when `input` is a site config.\n * Default: the config's `defaultTheme`, else `default`.\n */\n theme?: string;\n /**\n * Output format (default 'tailwind'). Narrower than the generator's `Format`\n * on purpose: this option exists to produce a stylesheet to inject, and the\n * `design` format emits only `DESIGN.md`. Run that one via the CLI\n * (`vitops generate --format design --out .`).\n */\n format?: StylesheetFormat;\n /** Directory the generated CSS is written to (default 'src/styles'). */\n out?: string;\n /**\n * Auto-inject the generated stylesheet into every SSR page (default true).\n * Set false when other integrations add routes that must not inherit the\n * design system (e.g. EmDash's `/_emdash/admin`) — then import the generated\n * file (`<out>/tailwind.css` or `<out>/styles.css`) from your site layout so\n * only your own pages (and previews rendered through them) are styled.\n */\n inject?: boolean;\n /**\n * Also flip to dark when the visitor's OS asks and they have made no explicit\n * choice — `@media (prefers-color-scheme: dark)`.\n *\n * Off by default: turning it on flips the site dark for dark-OS visitors, which\n * is a visible change to an existing site. Turning it **on** is what makes\n * `<color-scheme-toggle>`'s \"System\" position do anything (System removes the\n * theme attribute, so without this block it falls through to light), and it is\n * the only way a no-JS page gets the OS appearance at all.\n *\n * The site config's `designSystem.defaultColorScheme: \"system\"` says the same thing and is the\n * better home for it when you have one — this option wins if both are set, and\n * exists because requiring a whole `SiteConfig` to set one boolean would be out\n * of proportion.\n */\n systemColorScheme?: boolean;\n}\n\nexport interface GetvitopsSiteOptions {\n /** Path to the site config (JSON). */\n input: string;\n /** Environment whose A/B variant applies (default 'production'). */\n siteEnv?: string;\n}\n\nexport interface GetvitopsLegalOptions {\n /**\n * Path to the site config (JSON) the documents are rendered from. Optional\n * when the top-level `site` option is set — it defaults to that file, since\n * they are almost always the same one.\n */\n input?: string;\n /**\n * Where to write them (default 'src/content/legal'). The default is a content\n * collection: the documents are markdown, and a collection is what lets a page\n * render one through your own layout rather than the integration injecting a\n * route (which nothing in this package does).\n */\n out?: string;\n /** Output format (default 'md'). */\n format?: LegalOutput;\n /** Environment whose A/B variant applies (default 'production'). */\n siteEnv?: string;\n}\n\n/** `changefreq`, mirroring @astrojs/sitemap's `ChangeFreq`. */\nexport type GetvitopsSitemapChangeFreq =\n | 'always'\n | 'hourly'\n | 'daily'\n | 'weekly'\n | 'monthly'\n | 'yearly'\n | 'never';\n\n/** One sitemap entry, mirroring @astrojs/sitemap's `SitemapItem`. */\nexport interface GetvitopsSitemapEntry {\n url: string;\n lastmod?: string;\n changefreq?: GetvitopsSitemapChangeFreq;\n priority?: number;\n /** Alternate-language variants. `lang` is required, as upstream requires it. */\n links?: { lang: string; hreflang?: string; url: string }[];\n}\n\n/**\n * Forwarded verbatim to `@astrojs/sitemap`. Deliberately a hand-written subset of\n * its `SitemapOptions` rather than a re-export: aliasing would put an\n * `import … from '@astrojs/sitemap'` — and, transitively via `SitemapItem`, from\n * `sitemap` — into this package's published .d.mts, which cannot resolve for the\n * consumers who don't install the optional peer. Worse, `skipLibCheck` (on by\n * default via astro's base tsconfig) *suppresses* that error, silently degrading\n * the option to `any` instead of failing loudly.\n *\n * Need something not listed here (e.g. `chunks`)? Add `sitemap()` to your own\n * `integrations` array — getvitops detects it and leaves yours in charge.\n */\nexport interface GetvitopsSitemapOptions {\n /** Keep a page out of the sitemap. Receives the page's full absolute URL. */\n filter?: (page: string) => boolean;\n /** Absolute URLs of pages Astro doesn't build but the site serves. */\n customPages?: string[];\n /** Absolute URLs of externally-generated sitemaps to list in the index. */\n customSitemaps?: string[];\n /** Max entries per file before it splits (default 45000). */\n entryLimit?: number;\n /** Filename prefix (default 'sitemap' → `sitemap-index.xml`). */\n filenameBase?: string;\n changefreq?: GetvitopsSitemapChangeFreq;\n lastmod?: Date;\n priority?: number;\n /** Per-entry rewrite; return `undefined` to drop the entry. */\n serialize?: (\n item: GetvitopsSitemapEntry,\n ) => GetvitopsSitemapEntry | Promise<GetvitopsSitemapEntry | undefined> | undefined;\n /** Emit `<xhtml:link rel=\"alternate\">` for localised routes. */\n i18n?: { defaultLocale: string; locales: Record<string, string> };\n /** XSL stylesheet to prettify the XML (path relative to `site`, or absolute URL). */\n xslURL?: string;\n /** XML namespaces to exclude (all included by default). */\n namespaces?: { news?: boolean; xhtml?: boolean; image?: boolean; video?: boolean };\n}\n\nexport interface GetvitopsOptions {\n /**\n * Your site config — the one place to name it.\n *\n * A `SiteConfig` records site-level facts, several of which other options here\n * need: `designSystem.defaultColorScheme` decides whether the generated colour layer\n * carries a `prefers-color-scheme` block, `fonts` can supply the webfont\n * declarations, and `legal` renders from it. Set this and each of those reads\n * it, rather than repeating the path per feature. Every one can still be given\n * explicitly, which wins.\n *\n * Needs `css`, since the generation runs in the Vite plugin `css` registers.\n */\n site?: GetvitopsSiteOptions;\n favicon?: GetvitopsFaviconOptions;\n /** Copy + link the web-component bundles (default true). */\n webComponents?: boolean;\n /** Generate + auto-inject the design-system CSS. Off unless provided. */\n css?: GetvitopsCssOptions;\n /**\n * Ship the live theme editor (default false).\n *\n * Copies `editor.js` into `public/vitops/`, loads it from `<Head />`, and mirrors\n * the generated `design-manifest.json` next to it so `<wc-theme-editor>` resolves\n * its default `/vitops/design-manifest.json`. Requires `css` (the manifest is a\n * `css`-format output) — place the `<wc-theme-editor>` tag in your layout yourself.\n *\n * Editing is live on any deploy; **saving back** to `design-system.json` needs the\n * dev server, so on a static build that button simply isn't rendered.\n */\n editor?: boolean;\n /**\n * Render the site's legal documents (privacy policy, terms, cookie notice)\n * from a site config, and re-render when it changes. Off unless provided.\n *\n * Requires `css`, which is what registers the Vite plugin that runs the\n * generation — without it the documents would be written once and never\n * refresh. Not using `css`? Run `vitops legal` from the CLI instead; it is the\n * same renderer and works in any stack.\n */\n legal?: GetvitopsLegalOptions;\n /**\n * Load webfonts through Astro's Fonts API and emit `<Font />` for each from\n * `<Head />`. Off unless provided.\n *\n * This is the seam the design system deliberately lacks. `design-system.json`'s\n * `fonts` block holds **stacks only** — `--font-<name>` tokens and nothing else: no\n * `@font-face`, no preload, no metrics-matched fallback. Declaring the family here\n * makes the token resolvable *and* loaded, so the token keeps pointing at a\n * `cssVariable` rather than at a literal stack the browser has no file for:\n *\n * ```js\n * getvitops({ fonts: [{ name: 'League Spartan', provider: 'fontsource',\n * cssVariable: '--font-league-spartan',\n * weights: ['100 900'], subsets: ['latin'], preload: true }] })\n * ```\n * ```jsonc\n * // design-system.json\n * \"fonts\": { \"display\": \"var(--font-league-spartan), sans-serif\" }\n * ```\n *\n * Pass a **string** instead to read the array from a site config's `fonts` block —\n * the same declarations, kept next to the rest of the site's facts. (`siteEnv`\n * picks the A/B variant, as in `legal`.) With the top-level `site` option set,\n * plain `true` does the same thing without repeating the path.\n *\n * Independent of `css`: the loading runs in `astro:config:setup`, not in the Vite\n * plugin. Both `<Head />` and the config entry are required — Astro's `fonts:`\n * config resolves the files, and `<Font />` is what puts the `@font-face` on the\n * page — so a declaration without `<Head />` in your layout loads nothing.\n */\n fonts?: boolean | string | SiteFont[] | GetvitopsFontsOptions;\n /**\n * Emit `sitemap-index.xml` + `sitemap-0.xml` via `@astrojs/sitemap`, and link it\n * from `<Head />`. Off unless provided; `true` uses its defaults.\n *\n * Needs `@astrojs/sitemap` (an optional peer — install it yourself) and the\n * `site` astro.config option, since a sitemap lists absolute URLs.\n *\n * Skipped with a warning when `emdash()` is registered — EmDash serves its own\n * database-driven `/sitemap.xml` — and deferred to when you already list\n * `@astrojs/sitemap` yourself. Note it enumerates **prerendered** routes only,\n * so on-demand pages of an `output: 'server'` site are not listed.\n */\n sitemap?: boolean | GetvitopsSitemapOptions;\n /**\n * Site-level defaults for `<Seo />` (`@getvitops/astro/Seo.astro`) — site name,\n * title template, Open Graph + Twitter defaults, verification tokens. Off unless\n * provided; per-page props override every field.\n *\n * Needs the `site` astro.config option for canonical and `og:url`; without it\n * `<Seo />` omits every absolute-URL tag rather than deriving one from the\n * request, and this warns at build time.\n *\n * On an EmDash site prefer `<EmDashHead>`, which covers the same tags from the\n * CMS — using both double-emits them.\n */\n seo?: GetvitopsSeoOptions;\n /**\n * Resolve semantic icon names centrally and, on a server build, derive which\n * icons to bundle by scanning your source. Off unless provided; `true` uses\n * the defaults below.\n *\n * Two separate jobs, and it is worth knowing which one you are buying:\n *\n * 1. **Naming.** `<Icon name=\"menu\" />` resolves through the semantic map, so\n * the icon set is a config value rather than something spelled out at every\n * call site. A name containing `:` passes through untouched — that is the\n * escape hatch for a set-specific glyph.\n * 2. **Bundle size.** astro-icon is zero-config on a static build, but under\n * `output: 'server'` it bundles EVERY icon in a set unless given an\n * `include` map. That is the list consumers end up hand-maintaining. Here it\n * is derived by scanning `scan` for icon references, merged with anything\n * you declare. On a static build no `include` is passed at all, because\n * there is nothing to trim.\n *\n * Names the scanner cannot read statically (`<Icon name={expr} />`) are\n * reported with file and line, not guessed at — declare them in `include`.\n */\n icons?: boolean | GetvitopsIconsOptions;\n /**\n * Analytics tags for `<Analytics />` — Google Analytics, Microsoft Clarity,\n * Matomo, Plausible. Off unless provided.\n *\n * Every tag loads off the critical path (`strategy`, default `'idle'`: after\n * `load`, on an idle callback) and, when it sets cookies, only after consent.\n *\n * Which consent category a provider needs is derived from whether it sets\n * cookies, not declared — so configuring a cookie-setting provider without\n * `consent` warns rather than silently tracking everyone.\n */\n analytics?: GetvitopsAnalyticsOptions;\n /**\n * The consent gate: `@getvitops/core/consent` + `<CookieConsent />`. Off unless\n * provided; `true` uses its defaults.\n *\n * A sibling of `analytics`, not part of it. The gate is general — anything\n * marked `data-consent=\"<category>\"` waits on the same choice, so A/B\n * assignment, personalisation and third-party embeds use it too, and a site can\n * enable it with no analytics configured at all.\n *\n * Pair it with `legal` so the cookie notice describes the categories this\n * actually offers.\n */\n consent?: boolean | GetvitopsConsentOptions;\n}\n\nexport interface GetvitopsFontsOptions {\n /** Path to a site config (JSON) whose `fonts` array holds the declarations. */\n input?: string;\n /** Declarations written inline; merged after anything `input` supplies. */\n families?: SiteFont[];\n /** Environment whose A/B variant applies to `input` (default 'production'). */\n siteEnv?: string;\n}\n\nexport interface GetvitopsIconsOptions {\n /** Icon set for UI chrome. Default `'fa7-solid'`. */\n ui?: string;\n /** Icon set for brand marks. Default `'simple-icons'`. */\n brand?: string;\n /**\n * Weight for suffix-weighted sets like Phosphor (`'bold'`, `'fill'`, …).\n * Ignored by sets that split weights across collections, e.g. Font Awesome.\n */\n weight?: string;\n /**\n * Icons to bundle, in `generateIconInclude`'s shape:\n * `{ semantic: ['menu'], 'simple-icons': ['zoho'] }`.\n *\n * Merged with what the scan finds. Declared names that don't resolve **throw**\n * (a config error); scanned names that don't resolve only **warn** (a source\n * typo shouldn't kill the dev server).\n */\n include?: Parameters<typeof generateIconInclude>[0];\n /**\n * How `<Icon />` draws the glyph.\n *\n * `'inline'` (default) reads it from the `@iconify-json/*` collection and\n * inlines the `<svg>` — no runtime request, no icon-integration peer.\n * `'sprite'` emits `<use href=\"…/icons.svg#id\">` instead, for pages served\n * where the sprite is already on the origin.\n *\n * Note this is separate from `register`: whether astro-icon is registered for\n * YOUR OWN `<Icon>` usage is a different question from how ours renders.\n */\n engine?: 'inline' | 'sprite';\n /**\n * Register the icon integration for you (default true). Set false when you\n * already list `icon()` in `integrations` yourself — the scan, the sprite and\n * the resolver still run either way.\n */\n register?: boolean;\n /** Directories to scan, relative to the project root. Default `['src']`; `false` disables. */\n scan?: string[] | false;\n /** Public href of the sprite, for the `'sprite'` engine. Default `'/vitops/icons.svg'`. */\n sprite?: string;\n /** Aliases applied before the semantic map — legacy names, or values stored in CMS content. */\n overrides?: Record<string, string>;\n}\n\n/**\n * Serialised into the virtual module with `JSON.stringify`, so every field here\n * must be JSON-representable — no functions. (That is why the sitemap's *href*\n * lives here and its options, which carry `filter`/`serialize`, do not.)\n */\ninterface HeadData {\n favicons: boolean;\n faviconLinks: FaviconLink[];\n themeColor: string | null;\n webComponents: boolean;\n wcBase: string;\n editor: boolean;\n /** `<link rel=\"sitemap\">` target, or null when no sitemap was registered. */\n sitemap: string | null;\n /**\n * Families registered by `getvitops({ fonts })`, for `<Font />`. Only ours: a\n * `cssVariable` Astro cannot resolve makes `<Font />` throw, so this must never\n * include a family declared elsewhere (a user-level `fonts:`, or EmDash's).\n */\n fonts: HeadFont[];\n /** `config.site`, so `<Seo />` has a canonical base even without `Astro.site`. */\n site: string | null;\n /** Site-level `<Seo />` defaults. Function-free, hence serialisable. */\n seo: GetvitopsSeoOptions;\n /** `<Analytics />` providers. IDs and flags only — function-free. */\n analytics: GetvitopsAnalyticsOptions;\n /** Is the consent gate active? Decides whether tags are gated at all. */\n consent: boolean;\n /** Categories `<CookieConsent />` offers unless a page overrides them. */\n consentCategories: OptionalConsentCategory[];\n /** Cookie-notice URL for the banner, or null. */\n consentPolicyUrl: string | null;\n /** Does `<Head />` need to load `consent.js`? (gate enabled, or a tag needs scheduling) */\n consentRuntime: boolean;\n}\n\n/**\n * Which providers `getvitops({ analytics })` configures that the site config's\n * own `analytics` block does not.\n *\n * The two are separate surfaces on purpose — the integration must not import\n * `SiteConfig` — but they describe the same site, and a disagreement between them\n * is a compliance defect rather than a style issue: `vitops legal` derives the\n * cookie notice from the site config, so a provider missing there is a tag the\n * site runs and its own notice never mentions. That is precisely what the\n * generator's processor table exists to prevent.\n *\n * Read is best-effort — the `legal` step already reports an unreadable config,\n * and a second copy of that error helps nobody.\n */\nconst SITE_CONFIG_KEYS: Record<string, string> = {\n googleAnalytics: 'googleAnalyticsId',\n clarity: 'clarityId',\n matomo: 'matomo',\n plausible: 'plausibleDomain',\n};\n\nfunction undisclosedProviders(configPath: string, analytics: GetvitopsAnalyticsOptions): string[] {\n let declared: Record<string, unknown>;\n try {\n const parsed = JSON.parse(readFileSync(configPath, 'utf8')) as { analytics?: unknown };\n declared = (parsed.analytics ?? {}) as Record<string, unknown>;\n } catch {\n return [];\n }\n return Object.keys(SITE_CONFIG_KEYS).filter(\n (key) =>\n analytics[key as keyof GetvitopsAnalyticsOptions] !== undefined &&\n !declared[SITE_CONFIG_KEYS[key] as string],\n );\n}\n\nconst VIRTUAL_ID = 'virtual:getvitops/head';\nconst RESOLVED_ID = `\\0${VIRTUAL_ID}`;\n\nfunction virtualHeadPlugin(data: HeadData) {\n return {\n name: '@getvitops/astro:virtual-head',\n resolveId(id: string) {\n return id === VIRTUAL_ID ? RESOLVED_ID : null;\n },\n load(id: string) {\n return id === RESOLVED_ID ? `export default ${JSON.stringify(data)};` : null;\n },\n };\n}\n\n/**\n * What `<Icon />` needs to resolve a name at render time.\n *\n * JSON only, same rule as `HeadData` — which is precisely why there is no\n * `resolver` field. `resolveIcon` is a pure function in `@getvitops/utils` that\n * the component imports statically; what varies per site is the *data* it takes.\n */\ninterface IconsData {\n engine: 'inline' | 'sprite' | 'none';\n /**\n * Absolute project root, for resolving `@iconify-json/*`.\n *\n * Explicit rather than `process.cwd()`: the collections are a dependency of\n * the SITE, and the cwd is wherever the process happened to start — a task\n * runner invoking astro from a monorepo root resolves nothing and every icon\n * renders as an empty box, with no error to explain it.\n */\n root: string;\n ui: string;\n brand: string;\n weight: string | null;\n overrides: Record<string, string>;\n sprite: string | null;\n}\n\nconst ICONS_ID = 'virtual:getvitops/icons';\nconst RESOLVED_ICONS_ID = `\\0${ICONS_ID}`;\n\nfunction virtualIconsPlugin(data: IconsData) {\n return {\n name: '@getvitops/astro:virtual-icons',\n resolveId(id: string) {\n return id === ICONS_ID ? RESOLVED_ICONS_ID : null;\n },\n load(id: string) {\n return id === RESOLVED_ICONS_ID ? `export default ${JSON.stringify(data)};` : null;\n },\n };\n}\n\n/** Defaults live here so the component and the integration can't drift. */\nconst ICON_DEFAULTS = {\n ui: 'fa7-solid',\n brand: 'simple-icons',\n sprite: '/vitops/icons.svg',\n scan: ['src'],\n} as const;\n\n/**\n * Probe an optional icon integration so it can be registered for the consumer's\n * OWN `<Icon>` usage. Nothing here decides how our `<Icon />` renders — that is\n * `engine`, and it never needs a peer.\n */\n/**\n * Indirection so the bundler can't resolve the specifier statically. See the\n * import site in the `tailwind` branch for why that matters.\n */\nconst TAILWIND_VITE = '@tailwindcss/vite';\n\nasync function probeIconPackage(name: 'astro-icon' | 'astro-iconset'): Promise<boolean> {\n try {\n await import(/* @vite-ignore */ name);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Does the file at `path` hold a site config rather than a bare design system?\n *\n * Swallows a missing or unparseable file and answers `false`: this only decides\n * whether the other options may default their paths to this one, and the real\n * read happens in the Vite plugin, which reports a parse error properly. Failing\n * the build here would turn \"your JSON has a trailing comma\" into an error about\n * an option the consumer never set.\n */\nfunction readsAsSiteConfig(path: string): boolean {\n try {\n return isSiteConfig(JSON.parse(readFileSync(path, 'utf8')));\n } catch {\n return false;\n }\n}\n\nexport default function getvitops(opts: GetvitopsOptions = {}): AstroIntegration {\n return {\n name: '@getvitops/astro',\n hooks: {\n 'astro:config:setup': async ({ config, updateConfig, injectScript, logger }) => {\n const root = fileURLToPath(config.root);\n const publicDir = fileURLToPath(config.publicDir);\n const webComponents = opts.webComponents !== false;\n const editor = opts.editor === true;\n if (editor && !opts.css)\n logger.warn('editor: true needs a `css` config — it reads design-manifest.json.');\n if (opts.legal && !opts.css)\n logger.warn(\n 'legal: needs a `css` config — the generation runs in the Vite plugin that `css` ' +\n 'registers. Run `npx vitops legal` instead if you do not use `css`.',\n );\n // Where the site config is, if there is one. `css.input` counts when it\n // is itself a site config — that is the common setup once a consumer\n // keeps their tokens inside `company.json`, and making every option\n // repeat the path is exactly the friction this is meant to remove.\n // The effective path, default included, so the answer matches what the\n // Vite plugin will actually read rather than only covering the case\n // where the option was written out.\n const cssInput = opts.css ? (opts.css.input ?? 'design-system.json') : undefined;\n const siteInput =\n opts.site?.input ??\n (cssInput != null && readsAsSiteConfig(resolve(root, cssInput)) ? cssInput : undefined);\n if (opts.site && !opts.css)\n logger.warn(\n 'site: needs a `css` config — it is read during generation, which runs in the Vite ' +\n 'plugin that `css` registers. Without it the site config changes nothing here.',\n );\n // `legal` only needs its own `input` when it points somewhere other than\n // the site config; the two are almost always the same file.\n const legalInput = opts.legal?.input ?? siteInput;\n if (opts.legal && !legalInput)\n throw new Error(\n '[getvitops] legal: needs `legal.input` (a site config path), the top-level `site` ' +\n 'option, or a `css.input` that is itself a site config.',\n );\n if (opts.seo) {\n // Warn once here rather than letting every page silently drop its\n // canonical — a missing absolute URL is invisible in the output.\n if (!config.site)\n logger.warn(\n 'seo: needs the `site` astro.config option (your deployed URL) — without it <Seo /> ' +\n 'omits canonical, og:url and any relative og:image rather than guessing an origin.',\n );\n if (config.integrations.some((i) => i.name === 'emdash'))\n logger.warn(\n 'seo: emdash() is registered, and its <EmDashHead> emits the same title/description/' +\n 'canonical/og tags from the CMS. Use one or the other — rendering both duplicates ' +\n 'every tag.',\n );\n }\n // Analytics + consent are resolved here rather than in the component:\n // resolveAnalytics is pure, its warnings belong beside the others, and\n // step 2 needs to know whether the consent bundle has to be copied.\n const consentEnabled = !!opts.consent;\n const consentOpts: GetvitopsConsentOptions =\n typeof opts.consent === 'object' ? opts.consent : {};\n const analytics = resolveAnalytics(opts.analytics, { consent: consentEnabled });\n // Fall back to `analytics` rather than an empty list: the gate is general,\n // so a site may be gating an A/B split or an embed we cannot see from\n // here, and a banner offering no choices is useless.\n const detectedCategories = consentCategories(analytics.tags);\n const offeredCategories: OptionalConsentCategory[] =\n consentOpts.categories ??\n (detectedCategories.length ? detectedCategories : ['analytics']);\n for (const warning of analytics.warnings) logger.warn(warning);\n\n // `legalInput`, not `opts.legal.input` — the path is now a cascade\n // (explicit legal input → `site.input` → a `css.input` that reads as a\n // site config), so a consumer who declares `legal: {}` and lets it fall\n // through still gets the disclosure check rather than skipping it.\n if (opts.analytics && legalInput) {\n const undisclosed = undisclosedProviders(resolve(root, legalInput), opts.analytics);\n if (undisclosed.length)\n logger.warn(\n `analytics: ${undisclosed.join(', ')} ${undisclosed.length === 1 ? 'is' : 'are'} ` +\n \"configured here but absent from your site config's `analytics` block, so the \" +\n 'cookie notice `legal` generates will not disclose ' +\n `${undisclosed.length === 1 ? 'it' : 'them'}. Add ` +\n `${undisclosed.map((k) => SITE_CONFIG_KEYS[k]).join(', ')} to ${legalInput}.`,\n );\n }\n\n const hasManifest = !!(opts.favicon?.name && opts.favicon?.themeColor);\n let hasSvg = false;\n\n // 1. Favicons + manifest → public/\n if (opts.favicon) {\n const source = resolve(root, opts.favicon.source);\n hasSvg = source.endsWith('.svg');\n await generateFavicons({\n source,\n outputDir: publicDir,\n ...(opts.favicon.lowResSource\n ? { lowResSource: resolve(root, opts.favicon.lowResSource) }\n : {}),\n // Also the manifest's `background_color`. The maskable outputs are\n // composited onto it, so the raster and the manifest agree.\n ...(opts.favicon.backgroundColor\n ? { backgroundColor: opts.favicon.backgroundColor }\n : {}),\n });\n if (opts.favicon.name && opts.favicon.themeColor) {\n await writeFaviconManifest(publicDir, {\n name: opts.favicon.name,\n themeColor: opts.favicon.themeColor,\n ...(opts.favicon.backgroundColor\n ? { backgroundColor: opts.favicon.backgroundColor }\n : {}),\n });\n }\n logger.info('generated favicons + manifest → public/');\n }\n\n // 1b. Webfonts → Astro's `fonts:` config (+ <Font /> data for <Head />)\n let headFonts: HeadFont[] = [];\n if (opts.fonts) {\n const given: GetvitopsFontsOptions = Array.isArray(opts.fonts)\n ? { families: opts.fonts }\n : typeof opts.fonts === 'string'\n ? { input: opts.fonts }\n : opts.fonts === true\n ? {}\n : opts.fonts;\n // `fonts: true` (or an options object with no `input`) reads the site\n // config's own `fonts` array — one place to declare a family, next to\n // the rest of the site's facts. That config is wherever it is: the\n // `site` option, or `css.input` when it is one.\n const o: GetvitopsFontsOptions = {\n ...given,\n ...(given.input == null && given.families == null && siteInput\n ? { input: siteInput, ...(opts.site?.siteEnv ? { siteEnv: opts.site.siteEnv } : {}) }\n : {}),\n };\n const declared: SiteFont[] = [];\n if (o.input) {\n // resolveSiteConfig validates and throws with field paths, so a typo'd\n // provider fails the build here rather than resolving to a fallback stack.\n const site = resolveSiteConfig(\n JSON.parse(readFileSync(resolve(root, o.input), 'utf8')),\n o.siteEnv ?? 'production',\n );\n declared.push(...(site.fonts ?? []));\n }\n declared.push(...(o.families ?? []));\n\n if (declared.length === 0) {\n logger.warn(\n 'fonts: nothing declared — no webfonts will load. Add families, or drop the option.',\n );\n } else {\n // A user-level `fonts:` is already on `config` here; another integration's\n // is not (its updateConfig may not have run yet). Astro concatenates the\n // arrays, and two entries on one variable means the last wins silently —\n // so flag the half we can actually see.\n const existing = new Set(\n ((config as { fonts?: { cssVariable?: string }[] }).fonts ?? []).map(\n (f) => f.cssVariable,\n ),\n );\n const { fonts, head } = resolveFonts(declared);\n for (const f of head)\n if (existing.has(f.cssVariable))\n logger.warn(\n `fonts: ${f.cssVariable} is already declared in your astro.config \\`fonts\\` ` +\n 'array. Astro resolves one family per variable, so one of the two will be ' +\n 'dropped — remove it from one place.',\n );\n updateConfig({ fonts });\n headFonts = head;\n const preloaded = head.filter((f) => f.preload !== false).length;\n logger.info(\n `fonts: ${fonts.length} famil${fonts.length === 1 ? 'y' : 'ies'} registered ` +\n `via Astro's Fonts API (${preloaded} preloaded) — <Head /> emits <Font /> for each`,\n );\n }\n }\n\n // 2. Web-component bundles → public/vitops/\n //\n // `consent.js` is copied independently of `webComponents`: the gate is\n // what decides whether third-party tags run, so a site that turned the\n // element runtime off and still asked for consent must not silently lose\n // it — that failure mode is tags loading for everyone.\n const consentRuntime = consentEnabled || analytics.needsRuntime;\n if (webComponents || consentRuntime) {\n const corePkg = fileURLToPath(import.meta.resolve('@getvitops/core/package.json'));\n const coreDist = join(dirname(corePkg), 'dist');\n const dest = join(publicDir, 'vitops');\n if (existsSync(coreDist)) {\n const bundles = webComponents\n ? ['polyfills.js', 'deferred.js', 'elements.js', 'polyfills']\n : [];\n if (consentRuntime) bundles.push('consent.js');\n if (editor) bundles.push('editor.js');\n for (const f of bundles) {\n const src = join(coreDist, f);\n if (existsSync(src)) cpSync(src, join(dest, f), { recursive: true });\n }\n logger.info('copied web-component bundles → public/vitops/');\n } else {\n logger.warn(\n `@getvitops/core dist not found (${coreDist}); run build:core. Skipping WC copy.`,\n );\n }\n }\n\n // 3. Sitemap — opt-in, delegated to the official @astrojs/sitemap.\n //\n // Registered through updateConfig rather than imported at module scope,\n // because the package is an optional peer. Astro core has no\n // addIntegration() (that is Starlight's plugin API), but appending here is\n // supported: runHookConfigSetup re-reads `integrations.length` each\n // iteration and assigns the merged array back onto the settings, and\n // @astrojs/sitemap declares no `astro:config:setup` hook at all — only\n // config:done / routes:resolved / build:done, which all run afterwards.\n //\n // Runs before the virtual head module so <Head /> can link the result.\n let sitemapHref: string | null = null;\n if (opts.sitemap) {\n const registered = config.integrations.map((i) => i.name);\n if (registered.includes('@astrojs/sitemap')) {\n logger.info(\n 'sitemap: @astrojs/sitemap is already in your integrations — leaving yours in charge.',\n );\n } else if (registered.includes('emdash')) {\n logger.warn(\n 'sitemap: emdash() already serves /sitemap.xml from the database, which also covers ' +\n 'on-demand pages a static sitemap cannot. Skipping — drop the `sitemap` option, or ' +\n 'add @astrojs/sitemap to `integrations` yourself to run both.',\n );\n } else if (!config.site) {\n logger.warn(\n 'sitemap: needs the `site` astro.config option (your deployed URL) — a sitemap lists ' +\n 'absolute URLs. Skipping.',\n );\n } else {\n // Optional peer, loaded the same way as @tailwindcss/vite below.\n //\n // The import is cast because the option surface here is this package's\n // narrowed mirror, not @astrojs/sitemap's own `SitemapOptions`: the two\n // disagree on `serialize`'s entry type (upstream's `changefreq` is the\n // `EnumChangefreq` enum, ours the string union a consumer actually\n // wants to write). The shapes are identical at runtime — the value is\n // forwarded verbatim. Cast the module rather than widening the local to\n // `(o?: unknown)`, which is the wrong variance: a target accepting\n // `unknown` promises to accept *any* argument, which upstream doesn't.\n type SitemapFactory = (o?: GetvitopsSitemapOptions) => AstroIntegration;\n let sitemap: SitemapFactory;\n try {\n ({ default: sitemap } = (await import('@astrojs/sitemap')) as unknown as {\n default: SitemapFactory;\n });\n } catch {\n throw new Error(\n '[getvitops] sitemap requires `@astrojs/sitemap` in your devDependencies — ' +\n 'install it, or drop the `sitemap` option.',\n );\n }\n if (config.output === 'server')\n logger.warn(\n \"sitemap: output: 'server' — @astrojs/sitemap lists prerendered routes only, so \" +\n 'on-demand pages are left out. Add `export const prerender = true` to the pages ' +\n 'you want indexed, or list them in `sitemap.customPages`.',\n );\n const sitemapOpts = opts.sitemap === true ? undefined : opts.sitemap;\n updateConfig({ integrations: [sitemap(sitemapOpts)] });\n sitemapHref = `/${sitemapOpts?.filenameBase ?? 'sitemap'}-index.xml`;\n logger.info(`sitemap registered → ${sitemapHref.slice(1)}`);\n }\n }\n\n // 4. Icons — resolve names centrally, and derive the bundle on a server build.\n //\n // Everything here has to finish BEFORE the updateConfig that appends the\n // icon integration, because an appended integration's own\n // astro:config:setup runs after this hook returns — it cannot be handed\n // options computed later. That is why the scan is synchronous rather than\n // deferred to the Vite plugin's buildStart, which runs far too late.\n let iconsData: IconsData | null = null;\n if (opts.icons) {\n const o: GetvitopsIconsOptions = opts.icons === true ? {} : opts.icons;\n const ui = o.ui ?? ICON_DEFAULTS.ui;\n const brand = o.brand ?? ICON_DEFAULTS.brand;\n const spriteHref = o.sprite ?? ICON_DEFAULTS.sprite;\n\n // Declared icons first. An unresolvable name here THROWS — it's a\n // config error, and generateIconInclude has always been loud about it.\n // `exactOptionalPropertyTypes` is on, so an optional key has to be\n // absent rather than explicitly undefined.\n const weightOpt = o.weight ? { weight: o.weight } : {};\n const include: Record<string, string[]> = o.include\n ? generateIconInclude({ ui, brand, ...weightOpt, ...o.include })\n : {};\n const add = (prefix: string, name: string) => {\n const list = (include[prefix] ??= []);\n if (!list.includes(name)) list.push(name);\n };\n\n // Then whatever the source actually references.\n const scanDirs = o.scan === false ? [] : (o.scan ?? [...ICON_DEFAULTS.scan]);\n const unresolved: string[] = [];\n const dynamic: { file: string; line: number; expr: string }[] = [];\n for (const dir of scanDirs) {\n const abs = resolve(root, dir);\n if (!existsSync(abs)) continue;\n const found = collectIconRefs(scanFiles(abs));\n dynamic.push(\n ...found.dynamic.map((d) => ({\n file: relative(root, d.file),\n line: d.line,\n expr: d.expr,\n })),\n );\n for (const name of found.names) {\n // Already qualified — group it under its own prefix verbatim.\n const colon = name.indexOf(':');\n if (colon > 0) {\n add(name.slice(0, colon), name.slice(colon + 1));\n continue;\n }\n // A scanned name that doesn't resolve only WARNS. It is far more\n // likely a local SVG (astro-icon reads src/icons/*.svg by bare\n // name) or a sprite id than a mistake, and a dev server that dies\n // on a typo in a template is worse than one that tells you.\n try {\n const q = resolveIcon(name, ui, weightOpt);\n add(q.slice(0, q.indexOf(':')), q.slice(q.indexOf(':') + 1));\n } catch {\n unresolved.push(name);\n }\n }\n }\n\n // `include` only matters where a bundle is at stake. astro-icon is\n // zero-config on a static build; passing a list there would trim\n // nothing and risk dropping a glyph the scan couldn't see.\n const wantsInclude = config.output !== 'static';\n if (dynamic.length && wantsInclude) {\n logger.warn(\n `icons: ${dynamic.length} icon name(s) are computed at runtime and cannot be ` +\n `bundled automatically:\\n` +\n dynamic.map((d) => ` ${d.file}:${d.line} ${d.expr}`).join('\\n') +\n `\\n Declare them in \\`icons.include\\` (or set \\`icons.scan: false\\` to silence this).`,\n );\n }\n if (unresolved.length)\n logger.warn(\n `icons: ${unresolved.length} name(s) are not in the semantic map for '${ui}': ` +\n `${unresolved.join(', ')}. Left as-is — fine for a local src/icons/*.svg or a ` +\n `sprite id, a typo otherwise.`,\n );\n\n const engine: IconsData['engine'] = o.engine ?? 'inline';\n if (o.sprite && !opts.css)\n logger.warn(\n 'icons: the sprite is written by the same pass that generates the CSS, so ' +\n '`sprite` without `css` produces no file. Add `css`, or run `vitops icons --sprite`.',\n );\n\n const registered = config.integrations.map((i) => i.name);\n const already = registered.find((n) => n === 'astro-icon' || n === 'astro-iconset');\n if (o.register === false) {\n logger.info('icons: register: false — resolving names only, not registering anything.');\n } else if (already) {\n // Only the registration is skipped. The resolver, the scan and the\n // sprite are not astro-icon's job, so they still run.\n logger.info(\n `icons: ${already} is already in your integrations — leaving yours in charge.`,\n );\n } else {\n // Registered for the consumer's OWN `<Icon>` (astro-icon's), and to\n // hand it the derived include. Independent of how ours renders —\n // `<Icon />` inlines from the collection and needs no integration —\n // so a missing package is silence, not an error.\n const pkg = (await probeIconPackage('astro-icon'))\n ? 'astro-icon'\n : (await probeIconPackage('astro-iconset'))\n ? 'astro-iconset'\n : null;\n if (pkg) {\n type IconFactory = (o?: Record<string, unknown>) => AstroIntegration;\n const { default: icon } = (await import(/* @vite-ignore */ pkg)) as unknown as {\n default: IconFactory;\n };\n updateConfig({ integrations: [icon(wantsInclude ? { include } : {})] });\n logger.info(\n wantsInclude\n ? `icons: ${pkg} registered — bundling ${Object.values(include).flat().length} icon(s)`\n : `icons: ${pkg} registered (output: 'static' — no include needed)`,\n );\n }\n }\n\n iconsData = {\n engine,\n root,\n ui,\n brand,\n weight: o.weight ?? null,\n overrides: o.overrides ?? {},\n sprite: engine === 'sprite' || o.sprite ? spriteHref : null,\n };\n }\n\n // 5. Virtual head module for <Head/>\n updateConfig({\n vite: {\n plugins: [\n virtualIconsPlugin(\n iconsData ?? {\n engine: 'none',\n root,\n ui: ICON_DEFAULTS.ui,\n brand: ICON_DEFAULTS.brand,\n weight: null,\n overrides: {},\n sprite: null,\n },\n ),\n virtualHeadPlugin({\n favicons: !!opts.favicon,\n faviconLinks: opts.favicon ? faviconLinks({ hasSvg, manifest: hasManifest }) : [],\n themeColor: opts.favicon?.themeColor ?? null,\n webComponents,\n wcBase: '/vitops',\n editor,\n sitemap: sitemapHref,\n fonts: headFonts,\n site: config.site ?? null,\n seo: opts.seo ?? {},\n analytics: opts.analytics ?? {},\n consent: consentEnabled,\n consentCategories: offeredCategories,\n consentPolicyUrl: consentOpts.policyUrl ?? null,\n consentRuntime,\n }),\n ],\n },\n });\n\n // 6. CSS — generate + compile + auto-inject (consumer imports nothing).\n if (opts.css) {\n const format: StylesheetFormat = opts.css.format ?? 'tailwind';\n // Typed out above, but a plain-JS config reaches here unchecked and\n // would otherwise fail late with an unresolvable `styles.css` import.\n if ((format as string) === 'design')\n throw new Error(\n \"[getvitops] css.format: 'design' emits only DESIGN.md, not a stylesheet. \" +\n 'Run `npx vitops generate --format design --out .` for that file, and set ' +\n \"css.format to 'tailwind' | 'css' | 'bricks' here.\",\n );\n const out = opts.css.out ?? 'src/styles';\n const plugins = [\n vitops({\n input: opts.css.input ?? 'design-system.json',\n ...(opts.css.theme != null ? { theme: opts.css.theme } : {}),\n format,\n out,\n // Mirrored inside the generate pass so the served copy can never be\n // missing on a cold start or stale after a config edit.\n ...(editor ? { editorManifestDir: publicDir } : {}),\n // Same mirroring rule as the manifest: the bytes are produced by\n // this pass, so the copy belongs inside it rather than in the hook.\n ...(iconsData?.sprite ? { spriteDir: publicDir } : {}),\n // Same reasoning: inside the pass, so the documents track the site\n // config in dev instead of going stale after the first build.\n ...(opts.legal ? { legal: { ...opts.legal, input: legalInput as string } } : {}),\n // Read during generation for the site-level facts the stylesheet\n // depends on — currently `designSystem.defaultColorScheme`, which decides\n // whether the colour layer carries a prefers-color-scheme block.\n ...(opts.site ? { site: opts.site } : {}),\n ...(opts.css.systemColorScheme != null\n ? { systemColorScheme: opts.css.systemColorScheme }\n : {}),\n }),\n ];\n // Tailwind is an optional peer — only the `tailwind` format needs it,\n // so it's loaded here rather than imported at module scope. (The\n // plugin is typed against a newer vite than @getvitops/vite's peer;\n // the Plugin shapes are structurally compatible at runtime — Astro\n // forwards them verbatim — so bridge the two type identities.)\n //\n // The specifier goes through a constant, with `@vite-ignore`, for the\n // same reason `probeIconPackage` does it: a statically analysable\n // `import('@tailwindcss/vite')` makes the bundler FOLLOW the module\n // even on a `css`-format site that never reaches this branch, and the\n // chain ends at @tailwindcss/oxide's native `.node` binding, which it\n // cannot parse. That failed the whole build with an opaque\n // \"stream did not contain valid UTF-8\" — for a site not using Tailwind\n // at all. `optional peer` has to mean optional to the bundler too.\n if (format === 'tailwind') {\n let tailwindcss: () => unknown[];\n try {\n ({ default: tailwindcss } = (await import(\n /* @vite-ignore */ TAILWIND_VITE\n )) as unknown as { default: () => unknown[] });\n } catch {\n throw new Error(\n \"[getvitops] css.format: 'tailwind' requires `tailwindcss` and `@tailwindcss/vite` \" +\n 'in your devDependencies — install them, or switch to css.format: ' +\n \"'css' for the standalone bundle.\",\n );\n }\n plugins.push(...(tailwindcss() as unknown as typeof plugins));\n }\n updateConfig({ vite: { plugins } });\n const cssFile = format === 'tailwind' ? 'tailwind.css' : 'styles.css';\n if (opts.css.inject !== false) {\n const cssPath = resolve(root, out, cssFile);\n injectScript('page-ssr', `import ${JSON.stringify(cssPath)};`);\n logger.info(`design-system CSS (${format}) auto-injected from ${out}/${cssFile}`);\n } else {\n logger.info(\n `design-system CSS (${format}) generated at ${out}/${cssFile} (inject: false — import it from your layout)`,\n );\n }\n }\n },\n },\n };\n}\n","/**\n * The `<Seo />` resolution layer — site-level defaults merged with per-page props\n * into the exact tags to emit.\n *\n * Kept separate from `Seo.astro` deliberately. The component this replaces\n * (`SEO.astro`, deleted in c949cae) fused resolution with markup, which is why it\n * was never unit-tested and why its title handling drifted: it computed a title\n * for `og:title` but left `<title>` to the layout, so the two could disagree.\n * Everything decidable lives here and is asserted in `seo.test.ts`; the component\n * only renders what this returns.\n */\n\n/** Twitter card types, per <https://developer.x.com/en/docs/twitter-for-websites/cards>. */\nexport type SeoTwitterCard = 'summary' | 'summary_large_image' | 'app' | 'player';\n\n/** Open Graph object types this component knows how to describe. */\nexport type SeoOgType = 'website' | 'article' | 'profile';\n\n/**\n * Site-level SEO defaults, passed to `getvitops({ seo })` and baked into the\n * `virtual:getvitops/head` module. Per-page `<Seo />` props override these.\n *\n * The vocabulary mirrors the site-config schema's `seo` block\n * (`@getvitops/generator`, `src/site.ts`) so a `SiteConfig` → these adapter would\n * be a flat field map — but it is not imported from there. This is the\n * integration's own option surface, and it must stay JSON-serialisable: it is\n * `JSON.stringify`d into the virtual module, so no functions, no `URL`s, no dates.\n */\nexport interface GetvitopsSeoOptions {\n /** `og:site_name`, and the `<title>` when a page passes none. */\n siteName?: string;\n /**\n * Page-title pattern; `%s` is the page's own title. e.g. `'%s · Acme'`.\n *\n * Applies to `<title>` only. `og:title`/`twitter:title` get the untemplated\n * title: a social card already shows `og:site_name` and the domain, so the\n * suffix would sit directly above the brand it repeats. Override per page with\n * `ogTitle`.\n *\n * Skipped when a page's title already equals `siteName` — a homepage titled\n * \"Acme\" emits `Acme`, not `Acme · Acme` — and never applied to `defaultTitle`.\n */\n titleTemplate?: string;\n /** `<title>` for pages that pass none. Used verbatim — the template is not applied. */\n defaultTitle?: string;\n /** `<meta name=\"description\">` for pages that pass none. */\n defaultDescription?: string;\n /** Site-wide robots directive, e.g. `'index,follow,max-image-preview:large'`. */\n robots?: string;\n /** Google Search Console verification token. */\n googleSiteVerification?: string;\n /** Bing Webmaster Tools verification token. */\n bingSiteVerification?: string;\n openGraph?: {\n /** Default `og:type` (default `'website'`). */\n type?: SeoOgType;\n /** `og:locale`. Written either way — `en-CA` and `en_CA` both emit `en_CA`. */\n locale?: string;\n /** Additional locales the same content exists in → `og:locale:alternate`. */\n localeAlternates?: string[];\n /** Fallback share image. Relative URLs resolve against `site`. */\n image?: { url: string; alt?: string; width?: number; height?: number; type?: string };\n };\n twitter?: {\n /** Card type when no image resolves (default `'summary'`). */\n card?: SeoTwitterCard;\n /** The site's own handle, `@acme`. */\n site?: string;\n /** Default author handle, `@ada`. */\n creator?: string;\n };\n}\n\n/** Per-page `<Seo />` props. Everything is optional; defaults fill the gaps. */\nexport interface SeoProps {\n /** Page title, before `titleTemplate` is applied. */\n title?: string;\n /**\n * `og:title`/`twitter:title`, when the social headline should differ from\n * `title`. Defaults to `title` **without** `titleTemplate` — see the note on\n * `titleTemplate` for why the suffix is dropped there.\n */\n ogTitle?: string;\n description?: string;\n /** Overrides the computed canonical. Relative values resolve against `site`. */\n canonical?: string;\n /** Overrides `og:site_name` and the title fallback. */\n siteName?: string;\n /** Share image for this page. Relative URLs resolve against `site`. */\n image?: string;\n imageAlt?: string;\n imageWidth?: number;\n imageHeight?: number;\n /** MIME type of the share image, e.g. `'image/png'`. */\n imageType?: string;\n noindex?: boolean;\n nofollow?: boolean;\n noarchive?: boolean;\n nocache?: boolean;\n /** Extra robots directives, e.g. `'max-snippet:-1'`. Appended to the composed value. */\n robotsExtras?: string;\n /** Full robots override. Wins over the flags above and the site default. */\n robots?: string;\n /** `og:type` for this page (default: the site default, else `'website'`). */\n type?: SeoOgType;\n /** Emitted only when `type` is `'article'`. */\n article?: {\n publishedTime?: string;\n modifiedTime?: string;\n expirationTime?: string;\n authors?: string[];\n section?: string;\n tags?: string[];\n };\n /** `og:locale` for this page. */\n locale?: string;\n /** Twitter author handle for this page, `@ada`. */\n twitterCreator?: string;\n /**\n * Translations of *this* page, as `<link rel=\"alternate\" hreflang>`.\n *\n * Explicit only, and it must include the current page. Nothing is inferred:\n * the deleted component derived alternates from the site's locale list and\n * pointed every one of them at that locale's homepage, so every page on the\n * site claimed the homepage as its translation. Only the page knows its own\n * translated URLs. Pass `hreflang: 'x-default'` for the fallback entry.\n */\n alternates?: { hreflang: string; href: string }[];\n}\n\n/** A `<meta property>` tag (Open Graph uses `property`, not `name`). */\nexport interface SeoPropertyTag {\n property: string;\n content: string;\n}\n\n/** A `<meta name>` tag. */\nexport interface SeoNameTag {\n name: string;\n content: string;\n}\n\n/** Everything `<Seo />` renders, already decided. `null` means \"emit nothing\". */\nexport interface ResolvedSeo {\n title: string | null;\n /** `og:title`/`twitter:title` — the page title without `titleTemplate`. */\n socialTitle: string | null;\n description: string | null;\n canonical: string | null;\n robots: string | null;\n openGraph: SeoPropertyTag[];\n twitter: SeoNameTag[];\n verification: SeoNameTag[];\n alternates: { hreflang: string; href: string }[];\n}\n\n/** Render context supplied by the component from `Astro`. */\nexport interface SeoContext {\n /**\n * The deployed origin — `Astro.site`, or `config.site` via the virtual module.\n *\n * `undefined` when neither is set, and then every absolute-URL tag is dropped\n * rather than being derived from the request. `astro-seo` falls back to\n * `Astro.url` here; on a static build that is `http://localhost:4321`, which\n * would bake a localhost canonical into production HTML.\n */\n site?: URL | undefined;\n /** `Astro.url.pathname` — already includes `base`. */\n pathname: string;\n /** `Astro.url.search`, if the canonical should keep the query string. */\n search?: string;\n}\n\n/** `en-CA` → `en_CA`; Open Graph wants the underscore form. */\nconst ogLocale = (locale: string) => locale.replace('-', '_');\n\n/**\n * Absolute URLs pass through; relative ones resolve against `site`, or are\n * dropped when there is no `site` to resolve them against. A relative `og:image`\n * or canonical is worse than none — crawlers resolve it against whatever origin\n * served the page.\n */\nfunction absolute(url: string | undefined, site: URL | undefined): string | null {\n if (!url) return null;\n try {\n return new URL(url).href;\n } catch {\n return site ? new URL(url, site).href : null;\n }\n}\n\n/** Composes the robots directive, or null when nothing meaningful was asked for. */\nfunction resolveRobots(props: SeoProps, defaults: GetvitopsSeoOptions): string | null {\n if (props.robots) return props.robots;\n const { noindex, nofollow, noarchive, nocache, robotsExtras } = props;\n if (noindex || nofollow || noarchive || nocache || robotsExtras) {\n return [\n noindex ? 'noindex' : 'index',\n nofollow ? 'nofollow' : 'follow',\n ...(noarchive ? ['noarchive'] : []),\n ...(nocache ? ['nocache'] : []),\n ...(robotsExtras ? [robotsExtras] : []),\n ].join(', ');\n }\n // No explicit default either: emit nothing. `index, follow` is what crawlers\n // already assume, so stating it adds bytes and no meaning.\n return defaults.robots ?? null;\n}\n\n/**\n * Merges site defaults with page props into the final tag set.\n *\n * Pure — no `Astro`, no filesystem, no config lookup. Everything it needs comes\n * through its three arguments, which is what makes it testable and what keeps the\n * package free of the consumer-global coupling that sank the previous site model.\n */\nexport function resolveSeo(\n defaults: GetvitopsSeoOptions,\n props: SeoProps,\n ctx: SeoContext,\n): ResolvedSeo {\n const { site } = ctx;\n const siteName = props.siteName ?? defaults.siteName ?? null;\n\n // The template applies to a page's own title only, and only when that title\n // isn't already the site name. Two ways to get \"Acme · Acme\" otherwise: running\n // it over `defaultTitle`, and a homepage whose own title happens to equal the\n // site name — which is the common case, not an edge case.\n const templated = props.title && defaults.titleTemplate && props.title !== siteName;\n const fallbackTitle = defaults.defaultTitle ?? siteName;\n const title = props.title\n ? templated\n ? String(defaults.titleTemplate).replaceAll('%s', props.title)\n : props.title\n : fallbackTitle;\n\n /*\n * `og:title`/`twitter:title` take the page's title *without* the template.\n *\n * The two are read in different places. `<title>` is navigational — a browser\n * tab or a search result, competing with a dozen others, so it needs the brand\n * suffix to disambiguate. A social card is self-contained and already shows\n * `og:site_name` and the domain, so repeating the brand there is furniture:\n * \"Installation · Acme\" sitting directly above \"Acme\".\n *\n * They still derive from one value, which is the part that matters — the\n * component this replaces computed them in separate places and they drifted.\n * Pass `ogTitle` when a page wants a genuinely different social headline.\n */\n const socialTitle = props.ogTitle ?? props.title ?? fallbackTitle;\n\n const description = props.description ?? defaults.defaultDescription ?? null;\n\n const canonical = props.canonical\n ? absolute(props.canonical, site)\n : site\n ? new URL(ctx.pathname + (ctx.search ?? ''), site).href\n : null;\n\n const image = absolute(props.image, site) ?? absolute(defaults.openGraph?.image?.url, site);\n // Only inherit the default image's metadata when the page didn't bring its own\n // image — otherwise a page's image would carry the default's alt text.\n const usingDefaultImage = !props.image;\n const defaultImage = defaults.openGraph?.image;\n const imageAlt = props.imageAlt ?? (usingDefaultImage ? defaultImage?.alt : undefined);\n const imageWidth = props.imageWidth ?? (usingDefaultImage ? defaultImage?.width : undefined);\n const imageHeight = props.imageHeight ?? (usingDefaultImage ? defaultImage?.height : undefined);\n const imageType = props.imageType ?? (usingDefaultImage ? defaultImage?.type : undefined);\n\n const type = props.type ?? defaults.openGraph?.type ?? 'website';\n const locale = props.locale ?? defaults.openGraph?.locale;\n\n const openGraph: SeoPropertyTag[] = [];\n const og = (property: string, content: string | number | null | undefined) => {\n if (content !== null && content !== undefined && content !== '')\n openGraph.push({ property, content: String(content) });\n };\n\n og('og:type', type);\n og('og:title', socialTitle);\n og('og:description', description);\n og('og:url', canonical);\n og('og:site_name', siteName);\n og('og:locale', locale ? ogLocale(locale) : null);\n for (const alt of defaults.openGraph?.localeAlternates ?? [])\n og('og:locale:alternate', ogLocale(alt));\n og('og:image', image);\n if (image) {\n og('og:image:alt', imageAlt);\n og('og:image:width', imageWidth);\n og('og:image:height', imageHeight);\n og('og:image:type', imageType);\n }\n if (type === 'article' && props.article) {\n const a = props.article;\n og('article:published_time', a.publishedTime);\n og('article:modified_time', a.modifiedTime);\n og('article:expiration_time', a.expirationTime);\n for (const author of a.authors ?? []) og('article:author', author);\n og('article:section', a.section);\n for (const tag of a.tags ?? []) og('article:tag', tag);\n }\n\n const twitter: SeoNameTag[] = [];\n const tw = (name: string, content: string | null | undefined) => {\n if (content) twitter.push({ name, content });\n };\n // A card with a large image is only honoured if there *is* an image.\n tw('twitter:card', image ? 'summary_large_image' : (defaults.twitter?.card ?? 'summary'));\n tw('twitter:site', defaults.twitter?.site);\n tw('twitter:creator', props.twitterCreator ?? defaults.twitter?.creator);\n tw('twitter:title', socialTitle);\n tw('twitter:description', description);\n tw('twitter:image', image);\n tw('twitter:image:alt', image ? imageAlt : null);\n\n const verification: SeoNameTag[] = [];\n if (defaults.googleSiteVerification)\n verification.push({\n name: 'google-site-verification',\n content: defaults.googleSiteVerification,\n });\n if (defaults.bingSiteVerification)\n verification.push({ name: 'msvalidate.01', content: defaults.bingSiteVerification });\n\n return {\n title,\n socialTitle,\n description,\n canonical,\n robots: resolveRobots(props, defaults),\n openGraph,\n twitter,\n verification,\n alternates: props.alternates ?? [],\n };\n}\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,15 +1,21 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getvitops/astro",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "2.0.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
|
"license": "MIT",
|
|
6
6
|
"files": [
|
|
7
7
|
"dist",
|
|
8
8
|
"src/components/Head.astro",
|
|
9
|
+
"src/components/Seo.astro",
|
|
10
|
+
"src/seo.ts",
|
|
11
|
+
"src/components/Analytics.astro",
|
|
12
|
+
"src/components/CookieConsent.astro",
|
|
13
|
+
"src/analytics.ts",
|
|
9
14
|
"src/components/Subgrid.astro",
|
|
10
15
|
"src/components/Cards.astro",
|
|
11
16
|
"src/components/NodeRenderer.astro",
|
|
12
17
|
"src/components/WebComponentLoader.astro",
|
|
18
|
+
"src/components/Icon.astro",
|
|
13
19
|
"src/components/Popover.astro",
|
|
14
20
|
"src/components/Details.astro",
|
|
15
21
|
"src/components/Drawer.astro",
|
|
@@ -25,10 +31,14 @@
|
|
|
25
31
|
"import": "./dist/index.mjs"
|
|
26
32
|
},
|
|
27
33
|
"./Head.astro": "./src/components/Head.astro",
|
|
34
|
+
"./Seo.astro": "./src/components/Seo.astro",
|
|
35
|
+
"./Analytics.astro": "./src/components/Analytics.astro",
|
|
36
|
+
"./CookieConsent.astro": "./src/components/CookieConsent.astro",
|
|
28
37
|
"./components/Subgrid.astro": "./src/components/Subgrid.astro",
|
|
29
38
|
"./components/Cards.astro": "./src/components/Cards.astro",
|
|
30
39
|
"./components/NodeRenderer.astro": "./src/components/NodeRenderer.astro",
|
|
31
40
|
"./components/WebComponentLoader.astro": "./src/components/WebComponentLoader.astro",
|
|
41
|
+
"./components/Icon.astro": "./src/components/Icon.astro",
|
|
32
42
|
"./components/Popover.astro": "./src/components/Popover.astro",
|
|
33
43
|
"./components/Details.astro": "./src/components/Details.astro",
|
|
34
44
|
"./components/Drawer.astro": "./src/components/Drawer.astro",
|
|
@@ -39,24 +49,29 @@
|
|
|
39
49
|
"access": "public"
|
|
40
50
|
},
|
|
41
51
|
"dependencies": {
|
|
42
|
-
"@getvitops/
|
|
43
|
-
"@getvitops/vite": "0.
|
|
44
|
-
"@getvitops/
|
|
45
|
-
"@getvitops/utils": "0.
|
|
52
|
+
"@getvitops/generator": "2.0.0",
|
|
53
|
+
"@getvitops/vite": "2.0.0",
|
|
54
|
+
"@getvitops/core": "2.0.0",
|
|
55
|
+
"@getvitops/utils": "2.0.0"
|
|
46
56
|
},
|
|
47
57
|
"devDependencies": {
|
|
58
|
+
"@astrojs/sitemap": "^3.7.3",
|
|
48
59
|
"@tailwindcss/vite": "^4.3.3",
|
|
49
60
|
"astro": "^7.1.2",
|
|
50
61
|
"tailwindcss": "^4.3.3",
|
|
51
62
|
"vitest": "^4.1.9"
|
|
52
63
|
},
|
|
53
64
|
"peerDependencies": {
|
|
65
|
+
"@astrojs/sitemap": ">=3.6",
|
|
54
66
|
"@tailwindcss/vite": ">=4",
|
|
55
67
|
"astro": ">=7",
|
|
56
68
|
"astro-icon": ">=1",
|
|
57
69
|
"tailwindcss": ">=4"
|
|
58
70
|
},
|
|
59
71
|
"peerDependenciesMeta": {
|
|
72
|
+
"@astrojs/sitemap": {
|
|
73
|
+
"optional": true
|
|
74
|
+
},
|
|
60
75
|
"@tailwindcss/vite": {
|
|
61
76
|
"optional": true
|
|
62
77
|
},
|