@getvitops/astro 2.1.0 → 4.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 +337 -0
- package/README.md +2 -2
- package/dist/index.d.mts +75 -9
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +73 -26
- package/dist/index.mjs.map +1 -1
- package/dist/routes/conversion.d.mts +45 -0
- package/dist/routes/conversion.d.mts.map +1 -0
- package/dist/routes/conversion.mjs +95 -0
- package/dist/routes/conversion.mjs.map +1 -0
- package/package.json +23 -6
- package/src/analytics.ts +1 -1
- package/src/components/Analytics.astro +2 -2
- package/src/components/CookieConsent.astro +25 -17
- package/src/components/Details.astro +1 -1
- package/src/components/Drawer.astro +1 -1
- package/src/components/Head.astro +45 -7
- package/src/components/Icon.astro +3 -3
- package/src/components/NavShell.astro +123 -0
- package/src/components/NavShellToggle.astro +45 -0
- package/src/components/Popover.astro +1 -1
- package/src/components/Seo.astro +2 -2
- package/src/components/Tracking.astro +43 -0
- package/src/components/Tree.astro +82 -0
- package/src/components/TreeNode.astro +63 -0
- package/src/components/WebComponentLoader.astro +1 -1
- package/src/components/tree.ts +19 -0
- package/src/scripts/tracking.ts +147 -0
- package/src/seo.ts +2 -2
- package/src/tracking.ts +82 -0
- package/virtual.d.ts +5 -0
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { cpSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
2
|
import { dirname, join, relative, resolve } from "node:path";
|
|
3
3
|
import { fileURLToPath } from "node:url";
|
|
4
|
-
import {
|
|
4
|
+
import { isConfig, resolveConfig } from "@getvitops/generator";
|
|
5
5
|
import { collectIconRefs, faviconLinks, generateFavicons, generateIconInclude, resolveIcon, scanFiles, writeFaviconManifest } from "@getvitops/utils";
|
|
6
|
-
import
|
|
6
|
+
import vitopsPlugin from "@getvitops/vite";
|
|
7
7
|
import { fontProviders } from "astro/config";
|
|
8
8
|
import { execFile } from "node:child_process";
|
|
9
9
|
import { promisify } from "node:util";
|
|
@@ -204,10 +204,10 @@ function resolveFonts(decls) {
|
|
|
204
204
|
const head = [];
|
|
205
205
|
const seen = /* @__PURE__ */ new Set();
|
|
206
206
|
for (const decl of decls) {
|
|
207
|
-
if (!PROVIDERS.has(decl.provider)) throw new Error(`[
|
|
208
|
-
if (NEEDS_OPTIONS.has(decl.provider)) throw new Error(`[
|
|
209
|
-
if (!decl.cssVariable.startsWith("--")) throw new Error(`[
|
|
210
|
-
if (seen.has(decl.cssVariable)) throw new Error(`[
|
|
207
|
+
if (!PROVIDERS.has(decl.provider)) throw new Error(`[vitops] fonts: unknown provider "${decl.provider}" for "${decl.name}". Astro provides: ${[...PROVIDERS].sort().join(", ")}.`);
|
|
208
|
+
if (NEEDS_OPTIONS.has(decl.provider)) throw new Error(`[vitops] fonts: provider "${decl.provider}" needs an API id from your environment ("${decl.name}"), and a JSON declaration has nowhere to hold one. Declare this family in astro.config instead — fonts: [{ provider: fontProviders.${decl.provider}({ id: process.env.ADOBE_ID }), name: '${decl.name}', cssVariable: '${decl.cssVariable}' }] — and render its <Font /> yourself; the design-system token can still point at it.`);
|
|
209
|
+
if (!decl.cssVariable.startsWith("--")) throw new Error(`[vitops] fonts: cssVariable "${decl.cssVariable}" (${decl.name}) must start with "--".`);
|
|
210
|
+
if (seen.has(decl.cssVariable)) throw new Error(`[vitops] fonts: two families both declare ${decl.cssVariable} (the second is "${decl.name}"). Astro resolves one family per variable — give them separate variables, or drop one.`);
|
|
211
211
|
seen.add(decl.cssVariable);
|
|
212
212
|
const { provider, preload, ...rest } = decl;
|
|
213
213
|
fonts.push({
|
|
@@ -225,6 +225,39 @@ function resolveFonts(decls) {
|
|
|
225
225
|
};
|
|
226
226
|
}
|
|
227
227
|
|
|
228
|
+
//#endregion
|
|
229
|
+
//#region src/tracking.ts
|
|
230
|
+
/** The cookie the capture script writes. Mirrors `TRACKING_COOKIE` in utils. */
|
|
231
|
+
const TRACKING_COOKIE = "_ac";
|
|
232
|
+
/**
|
|
233
|
+
* Resolve the tracking config into what the component emits.
|
|
234
|
+
*
|
|
235
|
+
* Two warnings, both about the same failure — a persistent identifier written
|
|
236
|
+
* with no way for the visitor to decline, or a permission asked for with no way
|
|
237
|
+
* to answer. Returned as data rather than logged here so the check is a test
|
|
238
|
+
* rather than a line someone might scroll past, exactly as `resolveAnalytics`
|
|
239
|
+
* does.
|
|
240
|
+
*/
|
|
241
|
+
function resolveTracking(opts, ctx = {}) {
|
|
242
|
+
const warnings = [];
|
|
243
|
+
const category = opts?.category ?? "marketing";
|
|
244
|
+
const enabled = opts?.enabled === true;
|
|
245
|
+
if (!enabled) return {
|
|
246
|
+
enabled: false,
|
|
247
|
+
category,
|
|
248
|
+
cookies: [],
|
|
249
|
+
warnings
|
|
250
|
+
};
|
|
251
|
+
if (!ctx.consent) warnings.push(`tracking is on, so this site writes the \`${"_ac"}\` cookie — a 90-day identifier tying a visitor to the ad that brought them — and \`consent\` is off, so it is written for every visitor with no way to decline. Set \`consent: true\`.`);
|
|
252
|
+
else if (ctx.consentCategories && !ctx.consentCategories.includes(category)) warnings.push(`tracking demands the \`${category}\` consent category, but the banner offers ${ctx.consentCategories.join(", ") || "nothing"}. Add \`${category}\` to \`consent.categories\`, or the visitor is asked about something the banner never names.`);
|
|
253
|
+
return {
|
|
254
|
+
enabled,
|
|
255
|
+
category,
|
|
256
|
+
cookies: ["_ac"],
|
|
257
|
+
warnings
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
228
261
|
//#endregion
|
|
229
262
|
//#region src/integration.ts
|
|
230
263
|
/**
|
|
@@ -237,11 +270,11 @@ function resolveFonts(decls) {
|
|
|
237
270
|
* config from the `virtual:getvitops/head` module this integration provides.
|
|
238
271
|
*/
|
|
239
272
|
/**
|
|
240
|
-
* Which providers `
|
|
273
|
+
* Which providers `vitops({ analytics })` configures that the site config's
|
|
241
274
|
* own `analytics` block does not.
|
|
242
275
|
*
|
|
243
276
|
* The two are separate surfaces on purpose — the integration must not import
|
|
244
|
-
* `
|
|
277
|
+
* `Config` — but they describe the same site, and a disagreement between them
|
|
245
278
|
* is a compliance defect rather than a style issue: `vitops legal` derives the
|
|
246
279
|
* cookie notice from the site config, so a provider missing there is a tag the
|
|
247
280
|
* site runs and its own notice never mentions. That is precisely what the
|
|
@@ -259,7 +292,7 @@ const SITE_CONFIG_KEYS = {
|
|
|
259
292
|
function undisclosedProviders(configPath, analytics) {
|
|
260
293
|
let declared;
|
|
261
294
|
try {
|
|
262
|
-
declared = JSON.parse(readFileSync(configPath, "utf8")).analytics ?? {};
|
|
295
|
+
declared = JSON.parse(readFileSync(configPath, "utf8")).site?.analytics ?? {};
|
|
263
296
|
} catch {
|
|
264
297
|
return [];
|
|
265
298
|
}
|
|
@@ -328,14 +361,14 @@ async function probeIconPackage(name) {
|
|
|
328
361
|
* the build here would turn "your JSON has a trailing comma" into an error about
|
|
329
362
|
* an option the consumer never set.
|
|
330
363
|
*/
|
|
331
|
-
function
|
|
364
|
+
function readsAsConfig(path) {
|
|
332
365
|
try {
|
|
333
|
-
return
|
|
366
|
+
return isConfig(JSON.parse(readFileSync(path, "utf8")));
|
|
334
367
|
} catch {
|
|
335
368
|
return false;
|
|
336
369
|
}
|
|
337
370
|
}
|
|
338
|
-
function
|
|
371
|
+
function vitops(opts = {}) {
|
|
339
372
|
return {
|
|
340
373
|
name: "@getvitops/astro",
|
|
341
374
|
hooks: { "astro:config:setup": async ({ config, updateConfig, injectScript, logger }) => {
|
|
@@ -347,10 +380,10 @@ function getvitops(opts = {}) {
|
|
|
347
380
|
if (opts.legal && !opts.css) logger.warn("legal: needs a `css` config — the generation runs in the Vite plugin that `css` registers. Run `npx vitops legal` instead if you do not use `css`.");
|
|
348
381
|
if (opts.media && !opts.css) logger.warn("media: needs a `css` config — the encoding runs in the Vite plugin that `css` registers. Run `npx vitops media` instead if you do not use `css`.");
|
|
349
382
|
const cssInput = opts.css ? opts.css.input ?? "design-system.json" : void 0;
|
|
350
|
-
const siteInput = opts.site?.input ?? (cssInput != null &&
|
|
383
|
+
const siteInput = opts.site?.input ?? (cssInput != null && readsAsConfig(resolve(root, cssInput)) ? cssInput : void 0);
|
|
351
384
|
if (opts.site && !opts.css) logger.warn("site: needs a `css` config — it is read during generation, which runs in the Vite plugin that `css` registers. Without it the site config changes nothing here.");
|
|
352
385
|
const legalInput = opts.legal?.input ?? siteInput;
|
|
353
|
-
if (opts.legal && !legalInput) throw new Error("[
|
|
386
|
+
if (opts.legal && !legalInput) throw new Error("[vitops] legal: needs `legal.input` (a site config path), the top-level `site` option, or a `css.input` that is itself a site config.");
|
|
354
387
|
if (opts.seo) {
|
|
355
388
|
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.");
|
|
356
389
|
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.");
|
|
@@ -358,9 +391,18 @@ function getvitops(opts = {}) {
|
|
|
358
391
|
const consentEnabled = !!opts.consent;
|
|
359
392
|
const consentOpts = typeof opts.consent === "object" ? opts.consent : {};
|
|
360
393
|
const analytics = resolveAnalytics(opts.analytics, { consent: consentEnabled });
|
|
394
|
+
const trackingOpts = opts.tracking === true ? { enabled: true } : opts.tracking || void 0;
|
|
361
395
|
const detectedCategories = consentCategories(analytics.tags);
|
|
362
|
-
const
|
|
396
|
+
const defaultCategories = ["analytics", "preferences"];
|
|
397
|
+
const trackingCategory = trackingOpts?.enabled ? trackingOpts.category ?? "marketing" : null;
|
|
398
|
+
if (detectedCategories.includes("marketing") || trackingCategory === "marketing") defaultCategories.push("marketing");
|
|
399
|
+
const offeredCategories = consentOpts.categories ?? defaultCategories;
|
|
363
400
|
for (const warning of analytics.warnings) logger.warn(warning);
|
|
401
|
+
const tracking = resolveTracking(trackingOpts, {
|
|
402
|
+
consent: consentEnabled,
|
|
403
|
+
consentCategories: offeredCategories
|
|
404
|
+
});
|
|
405
|
+
for (const warning of tracking.warnings) logger.warn(warning);
|
|
364
406
|
if (opts.analytics && legalInput) {
|
|
365
407
|
const undisclosed = undisclosedProviders(resolve(root, legalInput), opts.analytics);
|
|
366
408
|
if (undisclosed.length) logger.warn(`analytics: ${undisclosed.join(", ")} ${undisclosed.length === 1 ? "is" : "are"} configured here but absent from your site config's \`analytics\` block, so the cookie notice \`legal\` generates will not disclose ${undisclosed.length === 1 ? "it" : "them"}. Add ${undisclosed.map((k) => SITE_CONFIG_KEYS[k]).join(", ")} to ${legalInput}.`);
|
|
@@ -395,8 +437,8 @@ function getvitops(opts = {}) {
|
|
|
395
437
|
};
|
|
396
438
|
const declared = [];
|
|
397
439
|
if (o.input) {
|
|
398
|
-
const
|
|
399
|
-
declared.push(...site.fonts ?? []);
|
|
440
|
+
const cfg = resolveConfig(JSON.parse(readFileSync(resolve(root, o.input), "utf8")), o.siteEnv ?? "production");
|
|
441
|
+
declared.push(...cfg.site.fonts ?? []);
|
|
400
442
|
}
|
|
401
443
|
declared.push(...o.families ?? []);
|
|
402
444
|
if (declared.length === 0) logger.warn("fonts: nothing declared — no webfonts will load. Add families, or drop the option.");
|
|
@@ -441,7 +483,7 @@ function getvitops(opts = {}) {
|
|
|
441
483
|
try {
|
|
442
484
|
({default: sitemap} = await import("@astrojs/sitemap"));
|
|
443
485
|
} catch {
|
|
444
|
-
throw new Error("[
|
|
486
|
+
throw new Error("[vitops] sitemap requires `@astrojs/sitemap` in your devDependencies — install it, or drop the `sitemap` option.");
|
|
445
487
|
}
|
|
446
488
|
if (config.output === "server") logger.warn("sitemap: output: 'server' — @astrojs/sitemap lists prerendered routes only, so on-demand pages are left out. Add `export const prerender = true` to the pages you want indexed, or list them in `sitemap.customPages`.");
|
|
447
489
|
const sitemapOpts = opts.sitemap === true ? void 0 : opts.sitemap;
|
|
@@ -451,7 +493,7 @@ function getvitops(opts = {}) {
|
|
|
451
493
|
}
|
|
452
494
|
}
|
|
453
495
|
if (siteInput) try {
|
|
454
|
-
const key =
|
|
496
|
+
const key = resolveConfig(JSON.parse(readFileSync(resolve(root, siteInput), "utf8")), opts.site?.siteEnv ?? "production").site.seo?.indexing?.indexNow?.key;
|
|
455
497
|
if (key) {
|
|
456
498
|
mkdirSync(publicDir, { recursive: true });
|
|
457
499
|
writeFileSync(join(publicDir, `${key}.txt`), `${key}\n`);
|
|
@@ -558,13 +600,18 @@ function getvitops(opts = {}) {
|
|
|
558
600
|
consent: consentEnabled,
|
|
559
601
|
consentCategories: offeredCategories,
|
|
560
602
|
consentPolicyUrl: consentOpts.policyUrl ?? null,
|
|
561
|
-
consentRuntime
|
|
603
|
+
consentRuntime,
|
|
604
|
+
tracking: tracking.enabled ? {
|
|
605
|
+
enabled: true,
|
|
606
|
+
category: tracking.category,
|
|
607
|
+
cookies: tracking.cookies
|
|
608
|
+
} : null
|
|
562
609
|
})] } });
|
|
563
610
|
if (opts.css) {
|
|
564
611
|
const format = opts.css.format ?? "tailwind";
|
|
565
|
-
if (format === "design") throw new Error("[
|
|
612
|
+
if (format === "design") throw new Error("[vitops] css.format: 'design' emits only DESIGN.md, not a stylesheet. Run `npx vitops generate --format design --out .` for that file, and set css.format to 'tailwind' | 'css' | 'bricks' here.");
|
|
566
613
|
const out = opts.css.out ?? "src/styles";
|
|
567
|
-
const plugins = [
|
|
614
|
+
const plugins = [vitopsPlugin({
|
|
568
615
|
input: opts.css.input ?? "design-system.json",
|
|
569
616
|
...opts.css.theme != null ? { theme: opts.css.theme } : {},
|
|
570
617
|
format,
|
|
@@ -587,7 +634,7 @@ function getvitops(opts = {}) {
|
|
|
587
634
|
TAILWIND_VITE
|
|
588
635
|
));
|
|
589
636
|
} catch {
|
|
590
|
-
throw new Error("[
|
|
637
|
+
throw new Error("[vitops] css.format: 'tailwind' requires `tailwindcss` and `@tailwindcss/vite` in your devDependencies — install them, or switch to css.format: 'css' for the standalone bundle.");
|
|
591
638
|
}
|
|
592
639
|
plugins.push(...tailwindcss());
|
|
593
640
|
}
|
|
@@ -609,7 +656,7 @@ function getvitops(opts = {}) {
|
|
|
609
656
|
* Real per-page `<lastmod>` for the sitemap, derived from git.
|
|
610
657
|
*
|
|
611
658
|
* This is the one lever that reliably moves Google. It is also load-bearing for
|
|
612
|
-
* `vitops
|
|
659
|
+
* `vitops search notify`, which decides what to submit by diffing lastmod against the
|
|
613
660
|
* previous build — with no lastmod, that diff can see pages appear and disappear
|
|
614
661
|
* but never see one *change*, so an edited page is never resubmitted.
|
|
615
662
|
*
|
|
@@ -714,7 +761,7 @@ async function commitDates(dirs, cwd) {
|
|
|
714
761
|
*/
|
|
715
762
|
async function gitLastmod(options = {}) {
|
|
716
763
|
const cwd = options.cwd ?? process.cwd();
|
|
717
|
-
const warn = options.onWarn ?? ((m) => console.warn(`[
|
|
764
|
+
const warn = options.onWarn ?? ((m) => console.warn(`[vitops] gitLastmod: ${m}`));
|
|
718
765
|
const dirs = (options.dirs ?? DEFAULT_DIRS).filter((d) => existsSync(`${cwd}/${d}`));
|
|
719
766
|
const identity = (item) => item;
|
|
720
767
|
if (dirs.length === 0) {
|
|
@@ -886,5 +933,5 @@ function resolveSeo(defaults, props, ctx) {
|
|
|
886
933
|
}
|
|
887
934
|
|
|
888
935
|
//#endregion
|
|
889
|
-
export { consentCategories,
|
|
936
|
+
export { TRACKING_COOKIE, consentCategories, vitops as default, gitLastmod, resolveAnalytics, resolveSeo, resolveTracking, routeFromPage, slugFromContent };
|
|
890
937
|
//# sourceMappingURL=index.mjs.map
|