@getvitops/utils 8.3.0-alpha.0 → 8.3.0-alpha.1

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 CHANGED
@@ -1,5 +1,36 @@
1
1
  # @getvitops/utils
2
2
 
3
+ ## 8.3.0-alpha.1
4
+
5
+ ### Minor Changes
6
+
7
+ - 24c568b: Escape hatches on every block, a section theme, widget areas, a badges block, Tailwind scanning of package renderers, and a sturdier `vitops audit`.
8
+
9
+ **@getvitops/emdash**
10
+
11
+ - Every `vitops.*` block and `vitops_section` gains three fields: `anchor` (rendered as the `id`, so `#contact` works), `className` (`class_name` on the section type: block-type slugs are lowercase) and `style`. `style` takes CSS custom properties only (`--p-card: 2rem`); any other declaration, and any value containing `url(`, `@`, `{`, `}` or a backslash, is dropped. With the `tailwind` format a class typed in the CMS only works if the site already uses it. They are applied to the block's own outermost element (the grid `<ul>` for `vitops.cards`), never an extra wrapper, so a block's markup is the same whether or not an editor fills them in.
12
+ - `vitops_section` gains a `theme` select: a role, or `<role>:solid`, with readable text chosen by the generator's contrast rules. It overrides `tone`/`color`/`solid`, which stay for existing content. Other site roles still go through `tone`.
13
+ - New `vitops.badges` block (a row of `.badge` or `.tag` labels, each with an optional tone, icon and link) and `vitops.widgetArea` block, plus `<VitopsWidgetArea name level />` from `@getvitops/emdash/astro`. A widget area is content defined once under Widgets and shown wherever it is placed. An area cannot hold another area, and an area containing a `core:*` component widget is rendered by EmDash's own renderer.
14
+ - Existing sites: re-run the block-type seed only on a new database; on an existing one add the `theme`, `anchor`, `className` and `style` fields to the `vitops_section` block type in the admin.
15
+
16
+ **@getvitops/create**: the emdash template seeds `announcement` and `pre-footer` widget areas, renders them in `Layout.astro`, and documents when to use a Section, a block or a widget area. Sites scaffolded earlier add the two `<VitopsWidgetArea>` lines and the `widgetAreas` seed entries by hand.
17
+
18
+ **@getvitops/generator / astro / vite** (`tailwind` format only): the Astro integration now adds `@source` lines for `@getvitops/astro` and `@getvitops/emdash`, because Tailwind v4 does not scan `node_modules`; utilities used only by their renderers (`font-display`, `font-caption`, `font-footnote`) were never emitted and the page silently rendered at body size. New `sources` option on `generate()` and the Vite plugin. A site's `src/package-classes.txt` workaround can be deleted. css and bricks: no change.
19
+
20
+ **@getvitops/cli**: `vitops emdash check-prerender` now also warns when a page uses a type-role class (`font-display`, …) the built CSS has no rule for; `--strict` makes it fail. `vitops audit` checks for its optional packages and a Chromium binary before starting the preview server (so a failed run no longer leaves port 4398 busy), stops the server on Ctrl+C and termination, and takes its page list from the built sitemap, then the menus; the guessed defaults (`/privacy`, `/contact`, …) are used only when neither exists.
21
+
22
+ **@getvitops/utils**: the audit helpers behind the above (`preflight`, `fetchSitemapPaths`, `sitemapLocs`, `pathsFromSitemapLocs`).
23
+
24
+ **@getvitops/core** (all formats): new `.block-anchor` rule gives an anchored block `scroll-margin-top: var(--header-offset, 5rem)`; set `--header-offset` to a sticky header's height. New `widget-area.css` partial. No other consumer-facing change.
25
+
26
+ The other fixed-group packages: no consumer-facing change beyond the above.
27
+
28
+ **Also fixed in the emdash template** (`site.json`): it defined only the `display`, `heading` and `body` type roles while the blocks use `title`, `eyebrow`, `lead`, `caption` and `footnote`, so those rendered at body size with no rule behind them. The roles are added with the reference design system's specs. A site scaffolded earlier adds the same five entries under `designSystem.themes.default.typography.roles`; `vitops emdash check-prerender` now names any that are missing.
29
+
30
+ ### Patch Changes
31
+
32
+ - @getvitops/config@8.3.0-alpha.1
33
+
3
34
  ## 8.3.0-alpha.0
4
35
 
5
36
  ### Minor Changes
@@ -100,12 +100,25 @@ export declare function isAuditMenu(name: string | undefined): boolean;
100
100
  export declare function pathsFromMenus(menus: MenuLike[]): string[];
101
101
  /** Same, from an EmDash seed file's parsed JSON (`{ menus: [...] }`). */
102
102
  export declare function pathsFromSeed(seed: unknown): string[];
103
+ /** `<loc>` values in a sitemap or sitemap index, in document order. */
104
+ export declare function sitemapLocs(xml: string): string[];
105
+ /**
106
+ * The page paths in a sitemap's `<loc>`s. The origin is dropped: a built sitemap names the
107
+ * production host, and the audit reads the same paths from whatever server it was pointed at.
108
+ */
109
+ export declare function pathsFromSitemapLocs(locs: string[]): string[];
103
110
  interface PagePlanInput {
111
+ /** Paths from the built sitemap. */
112
+ sitemapPaths?: string[];
104
113
  /** Paths found in menus. */
105
114
  menuPaths?: string[];
106
115
  /** Paths from `--pages`. */
107
116
  extra?: string[];
108
- /** Default true. `--no-defaults` for a site that wants an exact list. */
117
+ /**
118
+ * Default true, but only used when the sitemap and the menus found nothing: guessing
119
+ * `/privacy`, `/contact`… is the fallback, not an addition to a list the site already gave.
120
+ * `--no-defaults` for a site that wants an exact list.
121
+ */
109
122
  includeDefaults?: boolean;
110
123
  }
111
124
  /** The pages to try, `/` always first, unique, in a stable order. */
@@ -146,6 +159,17 @@ export declare function renderMarkdown(report: AuditReport): string;
146
159
  export declare function reportFiles(report: AuditReport): Record<string, string>;
147
160
  /** One-line-per-failure console summary. */
148
161
  export declare function formatSummary(report: AuditReport): string;
162
+ /**
163
+ * Check, before any server starts, that everything the audit needs is there: the three optional
164
+ * packages and a Chromium binary. A missing browser used to surface only after the preview
165
+ * server was up, and the failed launch left it running.
166
+ */
167
+ export declare function preflight(): Promise<void>;
168
+ /**
169
+ * Page paths from the served site's sitemap (`/sitemap-index.xml`, then `/sitemap.xml`), following
170
+ * an index one level to its child sitemaps. Empty when the site serves none.
171
+ */
172
+ export declare function fetchSitemapPaths(baseUrl: string): Promise<string[]>;
149
173
  /** A free TCP port, or `true` for "this specific one is free". */
150
174
  export declare function isPortFree(port: number): Promise<boolean>;
151
175
  /** Run a command to completion, inheriting stdio. Rejects on a non-zero exit. */
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/audit/types.ts","../../src/audit/plan.ts","../../src/audit/run.ts"],"mappings":";KAEY;UAEK;;EAEf;;EAEA;;EAEA;;EAEA,iBAAiB;;UAGF;EACf;;EAEA,QAAQ;EACR;EACA;;EAEA;;EAEA;;UAGe;;EAEf;EACA;EACA;;EAEA;IACE;IACA;IACA;;;UAIa;EACf;EACA;;KAGU;UAEK;EACf;EACA;EACA,QAAQ;EACR;;EAEA;EACA,UAAU;EACV,aAAa;EACb,UAAU;;KAGA;UAEK;EACf;EACA;EACA,YAAY;EACZ,SAAS;EACT;EACA,OAAO;;EAEP;;EAEA;EACA;;qBCrDW,iBAAiB;;;;;;qBAYjB;;qBAGA;qBAEA;qBAKA;;;;;UAgBI;EACf;EACA,QAAQ;;UAEO;EACf;EACA,WAAW;EACX,QAAQ;;;;;;;;wBASM,cAAc;;wBAsBd,YAAY;;wBAKZ,eAAe,OAAO;;wBAOtB,cAAc;UAKb;;EAEf;;EAEA;;EAEA;;;wBAIc,UAAU,QAAO;;;;;;wBAqBjB,eACd,cACA;EACG;EAAyC;;;wBAgB9B,gBAAgB,oCAAoC;;wBAKpD,aAAa,eAAe;;wBAe5B,aACd,SAAS,yBACT,YAAY,4BACZ,SAAS,eACR;;wBAyCa,cACd,cACA,aACA,gCACA;EAAW;EAA6B;IACvC;wBAmBa,YACd,cACA,aACA,oBACA,SAAS,aACT,YAAY,gBACZ,SAAS,eACR;wBAYa,YAAY;EAC1B;EACA;EACA,YAAY;EACZ,SAAS;EACT,OAAO;IACL;;wBAiBY,SAAS;wBAOT,mBAAmB,MAAM,YAAY,SAAS;wBAyC9C,eAAe,QAAQ;;wBAiCvB,YAAY,QAAQ,cAAc;;wBAgBlC,cAAc,QAAQ;;wBC3VtB,WAAW,eAAe;;wBAoB1B,WAAW,aAAa,gBAAgB,cAAc;UAUrD;EACf;EACA;;;;;;;;;;wBAWoB,aAAa;EACjC;EACA;EACA;IACE,QAAQ;UA+FK;EACf;EACA;EACA,SAAS;EACT,YAAY;EACZ,cAAc;;;wBAIM,SAAS,MAAM,aAAa,QAAQ"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../../src/audit/types.ts","../../src/audit/plan.ts","../../src/audit/run.ts"],"mappings":";KAEY;UAEK;;EAEf;;EAEA;;EAEA;;EAEA,iBAAiB;;UAGF;EACf;;EAEA,QAAQ;EACR;EACA;;EAEA;;EAEA;;UAGe;;EAEf;EACA;EACA;;EAEA;IACE;IACA;IACA;;;UAIa;EACf;EACA;;KAGU;UAEK;EACf;EACA;EACA,QAAQ;EACR;;EAEA;EACA,UAAU;EACV,aAAa;EACb,UAAU;;KAGA;UAEK;EACf;EACA;EACA,YAAY;EACZ,SAAS;EACT;EACA,OAAO;;EAEP;;EAEA;EACA;;qBCrDW,iBAAiB;;;;;;qBAYjB;;qBAGA;qBAEA;qBAKA;;;;;UAgBI;EACf;EACA,QAAQ;;UAEO;EACf;EACA,WAAW;EACX,QAAQ;;;;;;;;wBASM,cAAc;;wBAsBd,YAAY;;wBAKZ,eAAe,OAAO;;wBAOtB,cAAc;;wBAMd,YAAY;;;;;wBAaZ,qBAAqB;UAcpB;;EAEf;;EAEA;;EAEA;;;;;;EAMA;;;wBAIc,UAAU,QAAO;;;;;;wBAuBjB,eACd,cACA;EACG;EAAyC;;;wBAgB9B,gBAAgB,oCAAoC;;wBAKpD,aAAa,eAAe;;wBAe5B,aACd,SAAS,yBACT,YAAY,4BACZ,SAAS,eACR;;wBAyCa,cACd,cACA,aACA,gCACA;EAAW;EAA6B;IACvC;wBAmBa,YACd,cACA,aACA,oBACA,SAAS,aACT,YAAY,gBACZ,SAAS,eACR;wBAYa,YAAY;EAC1B;EACA;EACA,YAAY;EACZ,SAAS;EACT,OAAO;IACL;;wBAiBY,SAAS;wBAOT,mBAAmB,MAAM,YAAY,SAAS;wBAyC9C,eAAe,QAAQ;;wBAiCvB,YAAY,QAAQ,cAAc;;wBAgBlC,cAAc,QAAQ;;;;;;wBCjXhB,aAAa;;;;;wBAoBb,kBAAkB,kBAAkB;;wBAyB1C,WAAW,eAAe;;wBAoB1B,WAAW,aAAa,gBAAgB,cAAc;UAUrD;EACf;EACA;;;;;;;;;;wBAWoB,aAAa;EACjC;EACA;EACA;IACE,QAAQ;UA+FK;EACf;EACA;EACA,SAAS;EACT,YAAY;EACZ,cAAc;;;wBAIM,SAAS,MAAM,aAAa,QAAQ"}
@@ -1,3 +1,4 @@
1
+ import { existsSync } from "node:fs";
1
2
  import { spawn } from "node:child_process";
2
3
  import { createServer } from "node:net";
3
4
 
@@ -77,6 +78,25 @@ function pathsFromSeed(seed) {
77
78
  const menus = seed?.menus;
78
79
  return Array.isArray(menus) ? pathsFromMenus(menus) : [];
79
80
  }
81
+ /** `<loc>` values in a sitemap or sitemap index, in document order. */
82
+ function sitemapLocs(xml) {
83
+ return [...xml.matchAll(/<loc>\s*([^<\s][^<]*?)\s*<\/loc>/gi)].map((m) => m[1].replace(/&amp;/g, "&"));
84
+ }
85
+ /** A `<sitemapindex>` lists other sitemaps, not pages. */
86
+ const isSitemapIndex = (xml) => /<sitemapindex[\s>]/i.test(xml);
87
+ /**
88
+ * The page paths in a sitemap's `<loc>`s. The origin is dropped: a built sitemap names the
89
+ * production host, and the audit reads the same paths from whatever server it was pointed at.
90
+ */
91
+ function pathsFromSitemapLocs(locs) {
92
+ const out = [];
93
+ for (const loc of locs) try {
94
+ const { pathname } = new URL(loc);
95
+ const path = normalizePath(pathname);
96
+ if (path && !/\.(xml|txt)$/i.test(pathname)) out.push(path);
97
+ } catch {}
98
+ return out;
99
+ }
80
100
  /** The pages to try, `/` always first, unique, in a stable order. */
81
101
  function planPages(input = {}) {
82
102
  const seen = /* @__PURE__ */ new Set();
@@ -88,7 +108,9 @@ function planPages(input = {}) {
88
108
  }
89
109
  };
90
110
  add("/");
91
- if (input.includeDefaults !== false) for (const p of DEFAULT_PAGES) add(p);
111
+ const discovered = (input.sitemapPaths?.length ?? 0) + (input.menuPaths?.length ?? 0) > 0;
112
+ if (input.includeDefaults !== false && !discovered) for (const p of DEFAULT_PAGES) add(p);
113
+ for (const p of input.sitemapPaths ?? []) add(p);
92
114
  for (const p of input.menuPaths ?? []) add(p);
93
115
  for (const p of input.extra ?? []) add(normalizePath(p));
94
116
  return out;
@@ -296,7 +318,7 @@ function formatSummary(report) {
296
318
  * over that port — so one browser binary serves both and nothing here needs Chrome
297
319
  * installed system-wide.
298
320
  */
299
- const INSTALL = "npm i -D playwright @axe-core/playwright lighthouse && npx playwright install chromium";
321
+ const INSTALL = "vp add -D playwright @axe-core/playwright lighthouse && vp exec playwright install chromium";
300
322
  /** Load an optional dependency or throw an error that says how to get it. */
301
323
  async function loadOptional(name) {
302
324
  try {
@@ -310,6 +332,47 @@ async function loadOptional(name) {
310
332
  throw err;
311
333
  }
312
334
  }
335
+ /**
336
+ * Check, before any server starts, that everything the audit needs is there: the three optional
337
+ * packages and a Chromium binary. A missing browser used to surface only after the preview
338
+ * server was up, and the failed launch left it running.
339
+ */
340
+ async function preflight() {
341
+ const pw = await loadOptional("playwright");
342
+ await loadOptional("@axe-core/playwright");
343
+ await loadOptional("lighthouse");
344
+ let exe = "";
345
+ try {
346
+ exe = pw.chromium.executablePath();
347
+ } catch {}
348
+ if (!exe || !existsSync(exe)) throw new Error("Chromium is not installed for Playwright.\n Install: vp exec playwright install chromium");
349
+ }
350
+ /**
351
+ * Page paths from the served site's sitemap (`/sitemap-index.xml`, then `/sitemap.xml`), following
352
+ * an index one level to its child sitemaps. Empty when the site serves none.
353
+ */
354
+ async function fetchSitemapPaths(baseUrl) {
355
+ const get = async (path) => {
356
+ try {
357
+ const res = await fetch(new URL(path, baseUrl), { redirect: "follow" });
358
+ return res.ok ? await res.text() : void 0;
359
+ } catch {
360
+ return;
361
+ }
362
+ };
363
+ for (const root of ["/sitemap-index.xml", "/sitemap.xml"]) {
364
+ const xml = await get(root);
365
+ if (!xml) continue;
366
+ if (!isSitemapIndex(xml)) return pathsFromSitemapLocs(sitemapLocs(xml));
367
+ const out = [];
368
+ for (const loc of sitemapLocs(xml)) {
369
+ const child = await get(new URL(loc).pathname);
370
+ if (child) out.push(...pathsFromSitemapLocs(sitemapLocs(child)));
371
+ }
372
+ return out;
373
+ }
374
+ return [];
375
+ }
313
376
  /** A free TCP port, or `true` for "this specific one is free". */
314
377
  function isPortFree(port) {
315
378
  return new Promise((resolve) => {
@@ -438,7 +501,7 @@ async function runAudit(opts) {
438
501
  args: [`--remote-debugging-port=${debugPort}`]
439
502
  });
440
503
  } catch (err) {
441
- throw new Error(`could not launch Chromium: ${err.message}\n Install a browser: npx playwright install chromium`, { cause: err });
504
+ throw new Error(`could not launch Chromium: ${err.message}\n Install a browser: vp exec playwright install chromium`, { cause: err });
442
505
  }
443
506
  const results = [];
444
507
  try {
@@ -505,5 +568,5 @@ async function runAudit(opts) {
505
568
  return results;
506
569
  }
507
570
 
508
- export { AXE_TAGS, DEFAULT_BUDGETS, DEFAULT_PAGES, DISCLAIMER, MANUAL_CHECKS, auditedPage, buildReport, classifyStatus, evaluatePage, formatSummary, isAuditMenu, isPortFree, normalizeImpact, normalizePath, pageSlug, parseImpacts, pathsFromMenus, pathsFromSeed, planPages, renderMarkdown, renderPageMarkdown, reportFiles, runAudit, runCommand, startPreview, unauditedPage };
571
+ export { AXE_TAGS, DEFAULT_BUDGETS, DEFAULT_PAGES, DISCLAIMER, MANUAL_CHECKS, auditedPage, buildReport, classifyStatus, evaluatePage, fetchSitemapPaths, formatSummary, isAuditMenu, isPortFree, normalizeImpact, normalizePath, pageSlug, parseImpacts, pathsFromMenus, pathsFromSeed, pathsFromSitemapLocs, planPages, preflight, renderMarkdown, renderPageMarkdown, reportFiles, runAudit, runCommand, sitemapLocs, startPreview, unauditedPage };
509
572
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.mjs","names":[],"sources":["../../src/audit/plan.ts","../../src/audit/run.ts"],"sourcesContent":["/**\n * `vitops audit` — the pure half. Decides which pages to audit, whether a response\n * status means \"skip\" or \"broken\", whether a result breaches a budget, and how it\n * reads. Touches no network, no browser, no filesystem and no clock (the caller\n * passes `generatedAt`) — the same split `indexing/plan.ts` and `ads/plan.ts` make,\n * for the same reason: `plan.test.ts` can assert every consequential case, and the\n * executor (`run.ts`) decides nothing.\n */\nimport type {\n AuditBudgets,\n AuditReport,\n AxeImpact,\n AxeViolation,\n BudgetFailure,\n FormFactor,\n PageMetrics,\n PageResult,\n} from './types.ts';\n\nexport const DEFAULT_BUDGETS: AuditBudgets = {\n lcpMs: 2500,\n cls: 0.1,\n tbtMs: 200,\n blockingImpacts: ['serious', 'critical'],\n};\n\n/**\n * `wcag2a`/`wcag2aa` are what AODA (WCAG 2.0 AA) asks for; `wcag21a`/`wcag21aa` add the\n * 2.1 criteria we target. `wcag21aa` alone covers only the *new AA* criteria, not the\n * new level-A ones, so both 2.1 tags are listed.\n */\nexport const AXE_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'];\n\n/** Pages tried on every run. `/` is required; the rest are skipped when they 404. */\nexport const DEFAULT_PAGES = ['/', '/privacy', '/terms', '/cookies', '/contact'];\n\nexport const DISCLAIMER =\n 'A pass means no automated violations were found by axe-core and the performance budgets held. ' +\n 'It is NOT an AODA / WCAG conformance certification: automated tools detect only a minority of ' +\n 'WCAG failures. The manual checks below are still required.';\n\nexport const MANUAL_CHECKS = [\n 'Keyboard order: tab through every page; order matches the visual and reading order, nothing is skipped or trapped.',\n 'Focus visibility: a clear focus indicator on every interactive element, including in forced-colors / high-contrast mode.',\n 'Screen reader pass: headings, landmarks, forms, dropdowns and the drawer menu read sensibly (NVDA or JAWS on Windows, VoiceOver on macOS/iOS).',\n 'Reduced motion: with prefers-reduced-motion on, no essential information depends on animation and nothing auto-plays.',\n '200% zoom (and 400% reflow): no loss of content or function, no horizontal scrolling for text.',\n 'Colour and contrast of imagery, gradients and text over images (axe cannot evaluate these).',\n 'Meaningful alternative text, captions and transcripts for media.',\n];\n\n// ── Pages ───────────────────────────────────────────────────────────────────\n\n/**\n * A menu-ish thing: EmDash seed (`items`), `getMenu()` (`items`, items with `children`),\n * or either shape nested. Only `name`, `url` and children are read.\n */\nexport interface MenuLike {\n name?: string;\n items?: MenuItemLike[];\n}\nexport interface MenuItemLike {\n url?: string | null;\n children?: MenuItemLike[];\n items?: MenuItemLike[];\n}\n\n/**\n * Normalise to a site-relative path, or `undefined` for anything that is not an\n * auditable page of this site: external URLs, hash-only links, `mailto:`/`tel:`,\n * protocol-relative URLs, file downloads (anything with an extension) and the CMS\n * admin. Query and hash are dropped; a trailing slash is too (except the root).\n */\nexport function normalizePath(url: string | null | undefined): string | undefined {\n if (!url) return undefined;\n const u = url.trim();\n if (!u.startsWith('/') || u.startsWith('//')) return undefined;\n const bare = u.split(/[?#]/)[0] ?? '';\n const path = bare.length > 1 ? bare.replace(/\\/+$/, '') : bare;\n if (!path) return undefined;\n if (/\\.[a-z0-9]{1,6}$/i.test(path)) return undefined;\n if (path === '/_emdash' || path.startsWith('/_emdash/')) return undefined;\n return path;\n}\n\nfunction walk(items: MenuItemLike[] | undefined, out: string[]): void {\n for (const item of items ?? []) {\n const p = normalizePath(item.url);\n if (p) out.push(p);\n walk(item.children, out);\n walk(item.items, out);\n }\n}\n\n/** Is this one of the menus whose links are pages worth auditing? */\nexport function isAuditMenu(name: string | undefined): boolean {\n return !!name && (name.startsWith('primary') || name.startsWith('footer'));\n}\n\n/** Paths from the `primary*` / `footer*` menus (`footer-legal`, `primary-mobile`, …). */\nexport function pathsFromMenus(menus: MenuLike[]): string[] {\n const out: string[] = [];\n for (const m of menus) if (isAuditMenu(m.name)) walk(m.items, out);\n return out;\n}\n\n/** Same, from an EmDash seed file's parsed JSON (`{ menus: [...] }`). */\nexport function pathsFromSeed(seed: unknown): string[] {\n const menus = (seed as { menus?: unknown } | null)?.menus;\n return Array.isArray(menus) ? pathsFromMenus(menus as MenuLike[]) : [];\n}\n\nexport interface PagePlanInput {\n /** Paths found in menus. */\n menuPaths?: string[];\n /** Paths from `--pages`. */\n extra?: string[];\n /** Default true. `--no-defaults` for a site that wants an exact list. */\n includeDefaults?: boolean;\n}\n\n/** The pages to try, `/` always first, unique, in a stable order. */\nexport function planPages(input: PagePlanInput = {}): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n const add = (p: string | undefined) => {\n if (p && !seen.has(p)) {\n seen.add(p);\n out.push(p);\n }\n };\n add('/');\n if (input.includeDefaults !== false) for (const p of DEFAULT_PAGES) add(p);\n for (const p of input.menuPaths ?? []) add(p);\n for (const p of input.extra ?? []) add(normalizePath(p));\n return out;\n}\n\n/**\n * What an HTTP status means for a candidate page. 404/410 → skipped (a single-page\n * site has no `/contact`, and that is fine) — except `/`, which must exist. Any other\n * non-2xx is an error: a 500 on `/privacy` is a defect, not an absent page.\n */\nexport function classifyStatus(\n path: string,\n httpStatus: number | undefined,\n): { status: 'audited' | 'skipped' | 'error'; reason?: string } {\n if (httpStatus === undefined) return { status: 'error', reason: 'no response' };\n if (httpStatus >= 200 && httpStatus < 300) return { status: 'audited' };\n if (httpStatus === 404 || httpStatus === 410) {\n return path === '/'\n ? { status: 'error', reason: `/ returned ${httpStatus} — the home page must exist` }\n : { status: 'skipped', reason: `${httpStatus} — page does not exist on this site` };\n }\n return { status: 'error', reason: `HTTP ${httpStatus}` };\n}\n\n// ── Budgets ─────────────────────────────────────────────────────────────────\n\nconst IMPACT_RANK: Record<AxeImpact, number> = { minor: 0, moderate: 1, serious: 2, critical: 3 };\n\n/** axe can report a null impact; treat it as `moderate`, never silently drop it. */\nexport function normalizeImpact(impact: string | null | undefined): AxeImpact {\n return impact && impact in IMPACT_RANK ? (impact as AxeImpact) : 'moderate';\n}\n\n/** `serious,critical` → impacts, rejecting typos so a budget cannot silently be empty. */\nexport function parseImpacts(list: string): AxeImpact[] {\n const parts = list\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n for (const p of parts)\n if (!(p in IMPACT_RANK))\n throw new Error(\n `unknown axe impact \"${p}\" (expected: minor | moderate | serious | critical)`,\n );\n if (parts.length === 0) throw new Error('at least one axe impact is required');\n return parts as AxeImpact[];\n}\n\n/** Everything that makes one audited page fail. Empty = the page passes. */\nexport function evaluatePage(\n metrics: PageMetrics | undefined,\n violations: AxeViolation[] | undefined,\n budgets: AuditBudgets,\n): BudgetFailure[] {\n const failures: BudgetFailure[] = [];\n\n const check = (\n kind: 'lcp' | 'cls' | 'tbt',\n value: number | undefined,\n limit: number,\n label: string,\n unit: string,\n ) => {\n if (value === undefined || Number.isNaN(value)) {\n // A metric Lighthouse could not compute must not pass by default.\n failures.push({ kind: 'metric-missing', message: `${label} could not be measured` });\n } else if (value > limit) {\n failures.push({\n kind,\n message: `${label} ${fmt(value, unit)} exceeds the budget of ${fmt(limit, unit)}`,\n });\n }\n };\n check('lcp', metrics?.lcpMs, budgets.lcpMs, 'LCP', 'ms');\n check('cls', metrics?.cls, budgets.cls, 'CLS', '');\n check('tbt', metrics?.tbtMs, budgets.tbtMs, 'TBT', 'ms');\n\n for (const v of violations ?? []) {\n if (budgets.blockingImpacts.includes(v.impact)) {\n failures.push({\n kind: 'axe',\n message: `axe ${v.impact}: ${v.id} — ${v.help} (${v.nodeCount} node${v.nodeCount === 1 ? '' : 's'})`,\n });\n }\n }\n return failures;\n}\n\nfunction fmt(n: number, unit: string): string {\n const v = unit === 'ms' ? Math.round(n) : Number(n.toFixed(3));\n return unit ? `${v} ${unit}` : String(v);\n}\n\n/** Build a `PageResult` for a page that was not audited (skipped or error). */\nexport function unauditedPage(\n path: string,\n url: string,\n httpStatus: number | undefined,\n verdict: { status: 'skipped' | 'error'; reason?: string },\n): PageResult {\n return {\n path,\n url,\n status: verdict.status,\n ...(httpStatus !== undefined ? { httpStatus } : {}),\n ...(verdict.reason ? { reason: verdict.reason } : {}),\n failures:\n verdict.status === 'error'\n ? [\n {\n kind: path === '/' ? 'required-page-missing' : 'page-error',\n message: verdict.reason ?? 'page error',\n },\n ]\n : [],\n };\n}\n\nexport function auditedPage(\n path: string,\n url: string,\n httpStatus: number,\n metrics: PageMetrics,\n violations: AxeViolation[],\n budgets: AuditBudgets,\n): PageResult {\n return {\n path,\n url,\n status: 'audited',\n httpStatus,\n metrics,\n violations,\n failures: evaluatePage(metrics, violations, budgets),\n };\n}\n\nexport function buildReport(input: {\n generatedAt: string;\n baseUrl: string;\n formFactor: FormFactor;\n budgets: AuditBudgets;\n pages: PageResult[];\n}): AuditReport {\n return {\n generatedAt: input.generatedAt,\n baseUrl: input.baseUrl,\n formFactor: input.formFactor,\n budgets: input.budgets,\n axeTags: AXE_TAGS,\n pages: input.pages,\n passed: input.pages.every((p) => p.failures.length === 0),\n disclaimer: DISCLAIMER,\n manualChecks: MANUAL_CHECKS,\n };\n}\n\n// ── Rendering ───────────────────────────────────────────────────────────────\n\n/** `/` → `home`, `/a/b` → `a-b`: a safe per-page filename. */\nexport function pageSlug(path: string): string {\n const s = path.replace(/^\\/+|\\/+$/g, '').replace(/[^a-zA-Z0-9]+/g, '-');\n return s || 'home';\n}\n\nconst dash = (v: number | undefined, f: (n: number) => string) => (v === undefined ? '—' : f(v));\n\nexport function renderPageMarkdown(page: PageResult, budgets: AuditBudgets): string {\n const lines: string[] = [];\n const verdict =\n page.status === 'skipped' ? 'SKIPPED' : page.failures.length === 0 ? 'PASS' : 'FAIL';\n lines.push(`### \\`${page.path}\\` — ${verdict}`);\n lines.push('');\n if (page.status !== 'audited') {\n lines.push(page.reason ?? page.status);\n for (const f of page.failures) lines.push(`- ✖ ${f.message}`);\n return lines.join('\\n');\n }\n const m = page.metrics;\n lines.push('| Metric | Value | Budget |', '| --- | --- | --- |');\n lines.push(\n `| LCP | ${dash(m?.lcpMs, (n) => `${Math.round(n)} ms`)} | ${budgets.lcpMs} ms |`,\n `| CLS | ${dash(m?.cls, (n) => n.toFixed(3))} | ${budgets.cls} |`,\n `| TBT | ${dash(m?.tbtMs, (n) => `${Math.round(n)} ms`)} | ${budgets.tbtMs} ms |`,\n `| Performance | ${dash(m?.scores.performance, String)} | — |`,\n `| SEO | ${dash(m?.scores.seo, String)} | — |`,\n `| Best practices | ${dash(m?.scores.bestPractices, String)} | — |`,\n );\n lines.push('');\n const v = page.violations ?? [];\n if (v.length === 0) lines.push('axe: no violations.');\n else {\n lines.push(`axe: ${v.length} violated rule${v.length === 1 ? '' : 's'}.`, '');\n for (const x of [...v].sort((a, b) => IMPACT_RANK[b.impact] - IMPACT_RANK[a.impact])) {\n const blocking = budgets.blockingImpacts.includes(x.impact) ? ' (blocking)' : '';\n lines.push(\n `- **${x.impact}**${blocking} \\`${x.id}\\` — ${x.help} · ${x.nodeCount} node${x.nodeCount === 1 ? '' : 's'} · [rule](${x.helpUrl})`,\n );\n for (const t of x.targets) lines.push(` - \\`${t}\\``);\n }\n }\n if (page.failures.length) {\n lines.push('', 'Failures:');\n for (const f of page.failures) lines.push(`- ✖ ${f.message}`);\n }\n return lines.join('\\n');\n}\n\nexport function renderMarkdown(report: AuditReport): string {\n const lines: string[] = [];\n lines.push('# Performance and accessibility audit', '');\n lines.push(\n `**${report.passed ? 'PASS' : 'FAIL'}** · ${report.baseUrl} · ${report.formFactor} · ${report.generatedAt}`,\n '',\n );\n lines.push(`> ${report.disclaimer}`, '');\n lines.push(\n `Budgets: LCP < ${report.budgets.lcpMs} ms, CLS < ${report.budgets.cls}, TBT < ${report.budgets.tbtMs} ms, ` +\n `no axe ${report.budgets.blockingImpacts.join(' or ')} violations. axe tags: ${report.axeTags.join(', ')}.`,\n '',\n );\n lines.push(\n '| Page | Result | LCP | CLS | TBT | Perf | SEO | BP |',\n '| --- | --- | --- | --- | --- | --- | --- | --- |',\n );\n for (const p of report.pages) {\n const m = p.metrics;\n const verdict = p.status === 'skipped' ? 'skipped' : p.failures.length ? 'FAIL' : 'pass';\n lines.push(\n `| \\`${p.path}\\` | ${verdict} | ${dash(m?.lcpMs, (n) => `${Math.round(n)} ms`)} | ${dash(m?.cls, (n) => n.toFixed(3))} | ${dash(m?.tbtMs, (n) => `${Math.round(n)} ms`)} | ${dash(m?.scores.performance, String)} | ${dash(m?.scores.seo, String)} | ${dash(m?.scores.bestPractices, String)} |`,\n );\n }\n lines.push('');\n for (const p of report.pages) lines.push(renderPageMarkdown(p, report.budgets), '');\n lines.push('## Manual checks still required', '');\n for (const c of report.manualChecks) lines.push(`- [ ] ${c}`);\n lines.push('');\n return lines.join('\\n');\n}\n\n/** Files to write, relative to the output dir: JSON + markdown overall and per page. */\nexport function reportFiles(report: AuditReport): Record<string, string> {\n const files: Record<string, string> = {\n 'report.json': `${JSON.stringify(report, null, 2)}\\n`,\n 'report.md': renderMarkdown(report),\n };\n for (const p of report.pages) {\n if (p.status === 'skipped') continue;\n const slug = pageSlug(p.path);\n files[`pages/${slug}.json`] = `${JSON.stringify(p, null, 2)}\\n`;\n files[`pages/${slug}.md`] =\n `${renderPageMarkdown(p, report.budgets)}\\n\\n> ${report.disclaimer}\\n`;\n }\n return files;\n}\n\n/** One-line-per-failure console summary. */\nexport function formatSummary(report: AuditReport): string {\n const lines: string[] = [];\n for (const p of report.pages) {\n if (p.status === 'skipped') lines.push(` – ${p.path} skipped (${p.reason})`);\n else if (p.failures.length === 0) lines.push(` ✓ ${p.path}`);\n else {\n lines.push(` ✖ ${p.path}`);\n for (const f of p.failures) lines.push(` ${f.message}`);\n }\n }\n lines.push('', report.passed ? '✓ no automated violations' : '✖ audit failed');\n lines.push('Note: a pass is NOT AODA certification — see the manual checks in report.md.');\n return lines.join('\\n');\n}\n","/**\n * `vitops audit` — the I/O half. Starts (or attaches to) a server, drives Chromium,\n * runs axe and Lighthouse, and hands the raw numbers to `plan.ts`. It decides nothing:\n * which pages, what a status means and whether a number breaches a budget are all\n * `plan.ts`'s.\n *\n * `playwright`, `@axe-core/playwright` and `lighthouse` are OPTIONAL peer dependencies,\n * loaded lazily. They are heavy (a browser driver and ~100 MB of Lighthouse) and a\n * consumer who never runs `vitops audit` must not pay for them. A missing one fails\n * with the install command, not a bare `Cannot find package`.\n *\n * Lighthouse runs on the SAME Chromium Playwright launched — Playwright opens it with\n * `--remote-debugging-port`, axe drives it through Playwright, and Lighthouse attaches\n * over that port — so one browser binary serves both and nothing here needs Chrome\n * installed system-wide.\n */\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { createServer } from 'node:net';\nimport { AXE_TAGS, auditedPage, classifyStatus, normalizeImpact, unauditedPage } from './plan.ts';\nimport type { AuditBudgets, AxeViolation, FormFactor, PageMetrics, PageResult } from './types.ts';\n\nconst INSTALL =\n 'npm i -D playwright @axe-core/playwright lighthouse && npx playwright install chromium';\n\n/** Load an optional dependency or throw an error that says how to get it. */\nasync function loadOptional<T>(name: string): Promise<T> {\n try {\n // A variable specifier keeps bundlers and the type checker from resolving it —\n // that is the whole point of \"optional\".\n return (await import(/* @vite-ignore */ name)) as T;\n } catch (err) {\n const code = (err as { code?: string }).code;\n if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND')\n throw new Error(\n `vitops audit needs \"${name}\", which is an optional dependency and is not installed.\\n` +\n ` Install: ${INSTALL}`,\n { cause: err },\n );\n throw err;\n }\n}\n\n/** A free TCP port, or `true` for \"this specific one is free\". */\nexport function isPortFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const s = createServer();\n s.once('error', () => resolve(false));\n s.listen(port, '127.0.0.1', () => s.close(() => resolve(true)));\n });\n}\n\nfunction freePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const s = createServer();\n s.once('error', reject);\n s.listen(0, '127.0.0.1', () => {\n const { port } = s.address() as { port: number };\n s.close(() => resolve(port));\n });\n });\n}\n\n/** Run a command to completion, inheriting stdio. Rejects on a non-zero exit. */\nexport function runCommand(cmd: string, args: string[], cwd: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { cwd, stdio: 'inherit', shell: process.platform === 'win32' });\n child.once('error', reject);\n child.once('exit', (code) =>\n code === 0 ? resolve() : reject(new Error(`${cmd} ${args.join(' ')} exited with ${code}`)),\n );\n });\n}\n\nexport interface PreviewServer {\n baseUrl: string;\n stop(): void;\n}\n\n/**\n * `astro preview` on a dedicated port.\n *\n * Astro's preview server silently moves to the next port when its own is taken, and\n * a readiness probe would then happily attach to whatever owns the original one — the\n * failure `site/playwright.config.ts` documents. So the port is checked free first and\n * the run aborts if it is not.\n */\nexport async function startPreview(opts: {\n cwd: string;\n port: number;\n timeoutMs?: number;\n}): Promise<PreviewServer> {\n if (!(await isPortFree(opts.port)))\n throw new Error(\n `port ${opts.port} is already in use — refusing to audit whatever is listening there. ` +\n `Pass --port <free port>, or --url to audit a server you started yourself.`,\n );\n const baseUrl = `http://127.0.0.1:${opts.port}`;\n const child: ChildProcess = spawn(\n 'npx',\n ['--no-install', 'astro', 'preview', '--host', '127.0.0.1', '--port', String(opts.port)],\n { cwd: opts.cwd, stdio: ['ignore', 'pipe', 'pipe'], detached: process.platform !== 'win32' },\n );\n let log = '';\n child.stdout?.on('data', (d) => (log += String(d)));\n child.stderr?.on('data', (d) => (log += String(d)));\n let exited: number | null | undefined;\n child.once('exit', (code) => (exited = code));\n\n const stop = () => {\n try {\n if (child.pid && process.platform !== 'win32') process.kill(-child.pid, 'SIGTERM');\n else child.kill('SIGTERM');\n } catch {\n /* already gone */\n }\n };\n\n const deadline = Date.now() + (opts.timeoutMs ?? 60_000);\n while (Date.now() < deadline) {\n if (exited !== undefined) {\n throw new Error(\n `astro preview exited early (code ${exited}). Did you build first? Pass --build.\\n${log.trim()}`,\n );\n }\n try {\n await fetch(baseUrl, { redirect: 'manual' });\n return { baseUrl, stop };\n } catch {\n await new Promise((r) => setTimeout(r, 250));\n }\n }\n stop();\n throw new Error(`astro preview did not answer on ${baseUrl} within the timeout.\\n${log.trim()}`);\n}\n\n// ── Browser tooling (structural types: these modules are optional) ───────────\n\ninterface AxeResult {\n violations: Array<{\n id: string;\n impact?: string | null;\n help: string;\n helpUrl: string;\n nodes: Array<{ target: unknown[] }>;\n }>;\n}\ninterface LighthouseAudits {\n [id: string]: { numericValue?: number } | undefined;\n}\ninterface LighthouseRun {\n lhr: {\n audits: LighthouseAudits;\n categories: Record<string, { score: number | null } | undefined>;\n };\n}\n\nfunction toViolations(res: AxeResult): AxeViolation[] {\n return res.violations.map((v) => ({\n id: v.id,\n impact: normalizeImpact(v.impact),\n help: v.help,\n helpUrl: v.helpUrl,\n nodeCount: v.nodes.length,\n targets: v.nodes.slice(0, 3).map((n) => String(n.target.flat().join(' '))),\n }));\n}\n\nfunction toMetrics(run: LighthouseRun): PageMetrics {\n const a = run.lhr.audits;\n const score = (id: string) => {\n const s = run.lhr.categories[id]?.score;\n return s == null ? undefined : Math.round(s * 100);\n };\n return {\n lcpMs: a['largest-contentful-paint']?.numericValue,\n cls: a['cumulative-layout-shift']?.numericValue,\n tbtMs: a['total-blocking-time']?.numericValue,\n scores: {\n performance: score('performance'),\n seo: score('seo'),\n bestPractices: score('best-practices'),\n },\n };\n}\n\nexport interface RunOptions {\n baseUrl: string;\n paths: string[];\n budgets: AuditBudgets;\n formFactor: FormFactor;\n onProgress?: (message: string) => void;\n}\n\n/** Audit each path. Never throws for a bad PAGE — only for missing tooling/browser. */\nexport async function runAudit(opts: RunOptions): Promise<PageResult[]> {\n const log = opts.onProgress ?? (() => {});\n const [pw, axe, lh] = await Promise.all([\n loadOptional<{\n chromium: {\n launch(o: { headless: boolean; args: string[] }): Promise<{\n newContext(): Promise<{\n newPage(): Promise<{\n goto(u: string, o: { waitUntil: string }): Promise<unknown>;\n }>;\n close(): Promise<void>;\n }>;\n close(): Promise<void>;\n }>;\n };\n }>('playwright'),\n loadOptional<{\n AxeBuilder: new (o: { page: unknown }) => {\n withTags(t: string[]): { analyze(): Promise<AxeResult> };\n };\n }>('@axe-core/playwright'),\n loadOptional<{\n default: (\n url: string,\n flags: Record<string, unknown>,\n config?: unknown,\n ) => Promise<LighthouseRun | undefined>;\n }>('lighthouse'),\n ]);\n\n const debugPort = await freePort();\n let browser;\n try {\n browser = await pw.chromium.launch({\n headless: true,\n args: [`--remote-debugging-port=${debugPort}`],\n });\n } catch (err) {\n throw new Error(\n `could not launch Chromium: ${(err as Error).message}\\n Install a browser: npx playwright install chromium`,\n { cause: err },\n );\n }\n\n const results: PageResult[] = [];\n try {\n for (const path of opts.paths) {\n const url = new URL(path, opts.baseUrl).href;\n let httpStatus: number | undefined;\n try {\n httpStatus = (await fetch(url, { redirect: 'follow' })).status;\n } catch {\n httpStatus = undefined;\n }\n const verdict = classifyStatus(path, httpStatus);\n if (verdict.status !== 'audited') {\n log(`${path}: ${verdict.status} (${verdict.reason})`);\n results.push(unauditedPage(path, url, httpStatus, verdict as never));\n continue;\n }\n\n log(`${path}: axe`);\n // axe-playwright refuses a page that did not come from `newContext()`.\n const context = await browser.newContext();\n const page = await context.newPage();\n let violations: AxeViolation[];\n try {\n await page.goto(url, { waitUntil: 'load' });\n violations = toViolations(await new axe.AxeBuilder({ page }).withTags(AXE_TAGS).analyze());\n } finally {\n await context.close();\n }\n\n log(`${path}: lighthouse`);\n const flags: Record<string, unknown> = {\n port: debugPort,\n output: 'json',\n logLevel: 'error',\n onlyCategories: ['performance', 'seo', 'best-practices'],\n formFactor: opts.formFactor,\n ...(opts.formFactor === 'desktop'\n ? {\n screenEmulation: {\n mobile: false,\n width: 1350,\n height: 940,\n deviceScaleFactor: 1,\n disabled: false,\n },\n throttling: {\n rttMs: 40,\n throughputKbps: 10 * 1024,\n cpuSlowdownMultiplier: 1,\n requestLatencyMs: 0,\n downloadThroughputKbps: 0,\n uploadThroughputKbps: 0,\n },\n }\n : {}),\n };\n const run = await lh.default(url, flags);\n const metrics: PageMetrics = run ? toMetrics(run) : { scores: {} };\n results.push(auditedPage(path, url, httpStatus!, metrics, violations, opts.budgets));\n }\n } finally {\n await browser.close();\n }\n return results;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../../src/audit/plan.ts","../../src/audit/run.ts"],"sourcesContent":["/**\n * `vitops audit` — the pure half. Decides which pages to audit, whether a response\n * status means \"skip\" or \"broken\", whether a result breaches a budget, and how it\n * reads. Touches no network, no browser, no filesystem and no clock (the caller\n * passes `generatedAt`) — the same split `indexing/plan.ts` and `ads/plan.ts` make,\n * for the same reason: `plan.test.ts` can assert every consequential case, and the\n * executor (`run.ts`) decides nothing.\n */\nimport type {\n AuditBudgets,\n AuditReport,\n AxeImpact,\n AxeViolation,\n BudgetFailure,\n FormFactor,\n PageMetrics,\n PageResult,\n} from './types.ts';\n\nexport const DEFAULT_BUDGETS: AuditBudgets = {\n lcpMs: 2500,\n cls: 0.1,\n tbtMs: 200,\n blockingImpacts: ['serious', 'critical'],\n};\n\n/**\n * `wcag2a`/`wcag2aa` are what AODA (WCAG 2.0 AA) asks for; `wcag21a`/`wcag21aa` add the\n * 2.1 criteria we target. `wcag21aa` alone covers only the *new AA* criteria, not the\n * new level-A ones, so both 2.1 tags are listed.\n */\nexport const AXE_TAGS = ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa'];\n\n/** Pages tried on every run. `/` is required; the rest are skipped when they 404. */\nexport const DEFAULT_PAGES = ['/', '/privacy', '/terms', '/cookies', '/contact'];\n\nexport const DISCLAIMER =\n 'A pass means no automated violations were found by axe-core and the performance budgets held. ' +\n 'It is NOT an AODA / WCAG conformance certification: automated tools detect only a minority of ' +\n 'WCAG failures. The manual checks below are still required.';\n\nexport const MANUAL_CHECKS = [\n 'Keyboard order: tab through every page; order matches the visual and reading order, nothing is skipped or trapped.',\n 'Focus visibility: a clear focus indicator on every interactive element, including in forced-colors / high-contrast mode.',\n 'Screen reader pass: headings, landmarks, forms, dropdowns and the drawer menu read sensibly (NVDA or JAWS on Windows, VoiceOver on macOS/iOS).',\n 'Reduced motion: with prefers-reduced-motion on, no essential information depends on animation and nothing auto-plays.',\n '200% zoom (and 400% reflow): no loss of content or function, no horizontal scrolling for text.',\n 'Colour and contrast of imagery, gradients and text over images (axe cannot evaluate these).',\n 'Meaningful alternative text, captions and transcripts for media.',\n];\n\n// ── Pages ───────────────────────────────────────────────────────────────────\n\n/**\n * A menu-ish thing: EmDash seed (`items`), `getMenu()` (`items`, items with `children`),\n * or either shape nested. Only `name`, `url` and children are read.\n */\nexport interface MenuLike {\n name?: string;\n items?: MenuItemLike[];\n}\nexport interface MenuItemLike {\n url?: string | null;\n children?: MenuItemLike[];\n items?: MenuItemLike[];\n}\n\n/**\n * Normalise to a site-relative path, or `undefined` for anything that is not an\n * auditable page of this site: external URLs, hash-only links, `mailto:`/`tel:`,\n * protocol-relative URLs, file downloads (anything with an extension) and the CMS\n * admin. Query and hash are dropped; a trailing slash is too (except the root).\n */\nexport function normalizePath(url: string | null | undefined): string | undefined {\n if (!url) return undefined;\n const u = url.trim();\n if (!u.startsWith('/') || u.startsWith('//')) return undefined;\n const bare = u.split(/[?#]/)[0] ?? '';\n const path = bare.length > 1 ? bare.replace(/\\/+$/, '') : bare;\n if (!path) return undefined;\n if (/\\.[a-z0-9]{1,6}$/i.test(path)) return undefined;\n if (path === '/_emdash' || path.startsWith('/_emdash/')) return undefined;\n return path;\n}\n\nfunction walk(items: MenuItemLike[] | undefined, out: string[]): void {\n for (const item of items ?? []) {\n const p = normalizePath(item.url);\n if (p) out.push(p);\n walk(item.children, out);\n walk(item.items, out);\n }\n}\n\n/** Is this one of the menus whose links are pages worth auditing? */\nexport function isAuditMenu(name: string | undefined): boolean {\n return !!name && (name.startsWith('primary') || name.startsWith('footer'));\n}\n\n/** Paths from the `primary*` / `footer*` menus (`footer-legal`, `primary-mobile`, …). */\nexport function pathsFromMenus(menus: MenuLike[]): string[] {\n const out: string[] = [];\n for (const m of menus) if (isAuditMenu(m.name)) walk(m.items, out);\n return out;\n}\n\n/** Same, from an EmDash seed file's parsed JSON (`{ menus: [...] }`). */\nexport function pathsFromSeed(seed: unknown): string[] {\n const menus = (seed as { menus?: unknown } | null)?.menus;\n return Array.isArray(menus) ? pathsFromMenus(menus as MenuLike[]) : [];\n}\n\n/** `<loc>` values in a sitemap or sitemap index, in document order. */\nexport function sitemapLocs(xml: string): string[] {\n return [...xml.matchAll(/<loc>\\s*([^<\\s][^<]*?)\\s*<\\/loc>/gi)].map((m) =>\n (m[1] as string).replace(/&amp;/g, '&'),\n );\n}\n\n/** A `<sitemapindex>` lists other sitemaps, not pages. */\nexport const isSitemapIndex = (xml: string) => /<sitemapindex[\\s>]/i.test(xml);\n\n/**\n * The page paths in a sitemap's `<loc>`s. The origin is dropped: a built sitemap names the\n * production host, and the audit reads the same paths from whatever server it was pointed at.\n */\nexport function pathsFromSitemapLocs(locs: string[]): string[] {\n const out: string[] = [];\n for (const loc of locs) {\n try {\n const { pathname } = new URL(loc);\n const path = normalizePath(pathname);\n if (path && !/\\.(xml|txt)$/i.test(pathname)) out.push(path);\n } catch {\n /* not a URL */\n }\n }\n return out;\n}\n\nexport interface PagePlanInput {\n /** Paths from the built sitemap. */\n sitemapPaths?: string[];\n /** Paths found in menus. */\n menuPaths?: string[];\n /** Paths from `--pages`. */\n extra?: string[];\n /**\n * Default true, but only used when the sitemap and the menus found nothing: guessing\n * `/privacy`, `/contact`… is the fallback, not an addition to a list the site already gave.\n * `--no-defaults` for a site that wants an exact list.\n */\n includeDefaults?: boolean;\n}\n\n/** The pages to try, `/` always first, unique, in a stable order. */\nexport function planPages(input: PagePlanInput = {}): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n const add = (p: string | undefined) => {\n if (p && !seen.has(p)) {\n seen.add(p);\n out.push(p);\n }\n };\n add('/');\n const discovered = (input.sitemapPaths?.length ?? 0) + (input.menuPaths?.length ?? 0) > 0;\n if (input.includeDefaults !== false && !discovered) for (const p of DEFAULT_PAGES) add(p);\n for (const p of input.sitemapPaths ?? []) add(p);\n for (const p of input.menuPaths ?? []) add(p);\n for (const p of input.extra ?? []) add(normalizePath(p));\n return out;\n}\n\n/**\n * What an HTTP status means for a candidate page. 404/410 → skipped (a single-page\n * site has no `/contact`, and that is fine) — except `/`, which must exist. Any other\n * non-2xx is an error: a 500 on `/privacy` is a defect, not an absent page.\n */\nexport function classifyStatus(\n path: string,\n httpStatus: number | undefined,\n): { status: 'audited' | 'skipped' | 'error'; reason?: string } {\n if (httpStatus === undefined) return { status: 'error', reason: 'no response' };\n if (httpStatus >= 200 && httpStatus < 300) return { status: 'audited' };\n if (httpStatus === 404 || httpStatus === 410) {\n return path === '/'\n ? { status: 'error', reason: `/ returned ${httpStatus} — the home page must exist` }\n : { status: 'skipped', reason: `${httpStatus} — page does not exist on this site` };\n }\n return { status: 'error', reason: `HTTP ${httpStatus}` };\n}\n\n// ── Budgets ─────────────────────────────────────────────────────────────────\n\nconst IMPACT_RANK: Record<AxeImpact, number> = { minor: 0, moderate: 1, serious: 2, critical: 3 };\n\n/** axe can report a null impact; treat it as `moderate`, never silently drop it. */\nexport function normalizeImpact(impact: string | null | undefined): AxeImpact {\n return impact && impact in IMPACT_RANK ? (impact as AxeImpact) : 'moderate';\n}\n\n/** `serious,critical` → impacts, rejecting typos so a budget cannot silently be empty. */\nexport function parseImpacts(list: string): AxeImpact[] {\n const parts = list\n .split(',')\n .map((s) => s.trim())\n .filter(Boolean);\n for (const p of parts)\n if (!(p in IMPACT_RANK))\n throw new Error(\n `unknown axe impact \"${p}\" (expected: minor | moderate | serious | critical)`,\n );\n if (parts.length === 0) throw new Error('at least one axe impact is required');\n return parts as AxeImpact[];\n}\n\n/** Everything that makes one audited page fail. Empty = the page passes. */\nexport function evaluatePage(\n metrics: PageMetrics | undefined,\n violations: AxeViolation[] | undefined,\n budgets: AuditBudgets,\n): BudgetFailure[] {\n const failures: BudgetFailure[] = [];\n\n const check = (\n kind: 'lcp' | 'cls' | 'tbt',\n value: number | undefined,\n limit: number,\n label: string,\n unit: string,\n ) => {\n if (value === undefined || Number.isNaN(value)) {\n // A metric Lighthouse could not compute must not pass by default.\n failures.push({ kind: 'metric-missing', message: `${label} could not be measured` });\n } else if (value > limit) {\n failures.push({\n kind,\n message: `${label} ${fmt(value, unit)} exceeds the budget of ${fmt(limit, unit)}`,\n });\n }\n };\n check('lcp', metrics?.lcpMs, budgets.lcpMs, 'LCP', 'ms');\n check('cls', metrics?.cls, budgets.cls, 'CLS', '');\n check('tbt', metrics?.tbtMs, budgets.tbtMs, 'TBT', 'ms');\n\n for (const v of violations ?? []) {\n if (budgets.blockingImpacts.includes(v.impact)) {\n failures.push({\n kind: 'axe',\n message: `axe ${v.impact}: ${v.id} — ${v.help} (${v.nodeCount} node${v.nodeCount === 1 ? '' : 's'})`,\n });\n }\n }\n return failures;\n}\n\nfunction fmt(n: number, unit: string): string {\n const v = unit === 'ms' ? Math.round(n) : Number(n.toFixed(3));\n return unit ? `${v} ${unit}` : String(v);\n}\n\n/** Build a `PageResult` for a page that was not audited (skipped or error). */\nexport function unauditedPage(\n path: string,\n url: string,\n httpStatus: number | undefined,\n verdict: { status: 'skipped' | 'error'; reason?: string },\n): PageResult {\n return {\n path,\n url,\n status: verdict.status,\n ...(httpStatus !== undefined ? { httpStatus } : {}),\n ...(verdict.reason ? { reason: verdict.reason } : {}),\n failures:\n verdict.status === 'error'\n ? [\n {\n kind: path === '/' ? 'required-page-missing' : 'page-error',\n message: verdict.reason ?? 'page error',\n },\n ]\n : [],\n };\n}\n\nexport function auditedPage(\n path: string,\n url: string,\n httpStatus: number,\n metrics: PageMetrics,\n violations: AxeViolation[],\n budgets: AuditBudgets,\n): PageResult {\n return {\n path,\n url,\n status: 'audited',\n httpStatus,\n metrics,\n violations,\n failures: evaluatePage(metrics, violations, budgets),\n };\n}\n\nexport function buildReport(input: {\n generatedAt: string;\n baseUrl: string;\n formFactor: FormFactor;\n budgets: AuditBudgets;\n pages: PageResult[];\n}): AuditReport {\n return {\n generatedAt: input.generatedAt,\n baseUrl: input.baseUrl,\n formFactor: input.formFactor,\n budgets: input.budgets,\n axeTags: AXE_TAGS,\n pages: input.pages,\n passed: input.pages.every((p) => p.failures.length === 0),\n disclaimer: DISCLAIMER,\n manualChecks: MANUAL_CHECKS,\n };\n}\n\n// ── Rendering ───────────────────────────────────────────────────────────────\n\n/** `/` → `home`, `/a/b` → `a-b`: a safe per-page filename. */\nexport function pageSlug(path: string): string {\n const s = path.replace(/^\\/+|\\/+$/g, '').replace(/[^a-zA-Z0-9]+/g, '-');\n return s || 'home';\n}\n\nconst dash = (v: number | undefined, f: (n: number) => string) => (v === undefined ? '—' : f(v));\n\nexport function renderPageMarkdown(page: PageResult, budgets: AuditBudgets): string {\n const lines: string[] = [];\n const verdict =\n page.status === 'skipped' ? 'SKIPPED' : page.failures.length === 0 ? 'PASS' : 'FAIL';\n lines.push(`### \\`${page.path}\\` — ${verdict}`);\n lines.push('');\n if (page.status !== 'audited') {\n lines.push(page.reason ?? page.status);\n for (const f of page.failures) lines.push(`- ✖ ${f.message}`);\n return lines.join('\\n');\n }\n const m = page.metrics;\n lines.push('| Metric | Value | Budget |', '| --- | --- | --- |');\n lines.push(\n `| LCP | ${dash(m?.lcpMs, (n) => `${Math.round(n)} ms`)} | ${budgets.lcpMs} ms |`,\n `| CLS | ${dash(m?.cls, (n) => n.toFixed(3))} | ${budgets.cls} |`,\n `| TBT | ${dash(m?.tbtMs, (n) => `${Math.round(n)} ms`)} | ${budgets.tbtMs} ms |`,\n `| Performance | ${dash(m?.scores.performance, String)} | — |`,\n `| SEO | ${dash(m?.scores.seo, String)} | — |`,\n `| Best practices | ${dash(m?.scores.bestPractices, String)} | — |`,\n );\n lines.push('');\n const v = page.violations ?? [];\n if (v.length === 0) lines.push('axe: no violations.');\n else {\n lines.push(`axe: ${v.length} violated rule${v.length === 1 ? '' : 's'}.`, '');\n for (const x of [...v].sort((a, b) => IMPACT_RANK[b.impact] - IMPACT_RANK[a.impact])) {\n const blocking = budgets.blockingImpacts.includes(x.impact) ? ' (blocking)' : '';\n lines.push(\n `- **${x.impact}**${blocking} \\`${x.id}\\` — ${x.help} · ${x.nodeCount} node${x.nodeCount === 1 ? '' : 's'} · [rule](${x.helpUrl})`,\n );\n for (const t of x.targets) lines.push(` - \\`${t}\\``);\n }\n }\n if (page.failures.length) {\n lines.push('', 'Failures:');\n for (const f of page.failures) lines.push(`- ✖ ${f.message}`);\n }\n return lines.join('\\n');\n}\n\nexport function renderMarkdown(report: AuditReport): string {\n const lines: string[] = [];\n lines.push('# Performance and accessibility audit', '');\n lines.push(\n `**${report.passed ? 'PASS' : 'FAIL'}** · ${report.baseUrl} · ${report.formFactor} · ${report.generatedAt}`,\n '',\n );\n lines.push(`> ${report.disclaimer}`, '');\n lines.push(\n `Budgets: LCP < ${report.budgets.lcpMs} ms, CLS < ${report.budgets.cls}, TBT < ${report.budgets.tbtMs} ms, ` +\n `no axe ${report.budgets.blockingImpacts.join(' or ')} violations. axe tags: ${report.axeTags.join(', ')}.`,\n '',\n );\n lines.push(\n '| Page | Result | LCP | CLS | TBT | Perf | SEO | BP |',\n '| --- | --- | --- | --- | --- | --- | --- | --- |',\n );\n for (const p of report.pages) {\n const m = p.metrics;\n const verdict = p.status === 'skipped' ? 'skipped' : p.failures.length ? 'FAIL' : 'pass';\n lines.push(\n `| \\`${p.path}\\` | ${verdict} | ${dash(m?.lcpMs, (n) => `${Math.round(n)} ms`)} | ${dash(m?.cls, (n) => n.toFixed(3))} | ${dash(m?.tbtMs, (n) => `${Math.round(n)} ms`)} | ${dash(m?.scores.performance, String)} | ${dash(m?.scores.seo, String)} | ${dash(m?.scores.bestPractices, String)} |`,\n );\n }\n lines.push('');\n for (const p of report.pages) lines.push(renderPageMarkdown(p, report.budgets), '');\n lines.push('## Manual checks still required', '');\n for (const c of report.manualChecks) lines.push(`- [ ] ${c}`);\n lines.push('');\n return lines.join('\\n');\n}\n\n/** Files to write, relative to the output dir: JSON + markdown overall and per page. */\nexport function reportFiles(report: AuditReport): Record<string, string> {\n const files: Record<string, string> = {\n 'report.json': `${JSON.stringify(report, null, 2)}\\n`,\n 'report.md': renderMarkdown(report),\n };\n for (const p of report.pages) {\n if (p.status === 'skipped') continue;\n const slug = pageSlug(p.path);\n files[`pages/${slug}.json`] = `${JSON.stringify(p, null, 2)}\\n`;\n files[`pages/${slug}.md`] =\n `${renderPageMarkdown(p, report.budgets)}\\n\\n> ${report.disclaimer}\\n`;\n }\n return files;\n}\n\n/** One-line-per-failure console summary. */\nexport function formatSummary(report: AuditReport): string {\n const lines: string[] = [];\n for (const p of report.pages) {\n if (p.status === 'skipped') lines.push(` – ${p.path} skipped (${p.reason})`);\n else if (p.failures.length === 0) lines.push(` ✓ ${p.path}`);\n else {\n lines.push(` ✖ ${p.path}`);\n for (const f of p.failures) lines.push(` ${f.message}`);\n }\n }\n lines.push('', report.passed ? '✓ no automated violations' : '✖ audit failed');\n lines.push('Note: a pass is NOT AODA certification — see the manual checks in report.md.');\n return lines.join('\\n');\n}\n","/**\n * `vitops audit` — the I/O half. Starts (or attaches to) a server, drives Chromium,\n * runs axe and Lighthouse, and hands the raw numbers to `plan.ts`. It decides nothing:\n * which pages, what a status means and whether a number breaches a budget are all\n * `plan.ts`'s.\n *\n * `playwright`, `@axe-core/playwright` and `lighthouse` are OPTIONAL peer dependencies,\n * loaded lazily. They are heavy (a browser driver and ~100 MB of Lighthouse) and a\n * consumer who never runs `vitops audit` must not pay for them. A missing one fails\n * with the install command, not a bare `Cannot find package`.\n *\n * Lighthouse runs on the SAME Chromium Playwright launched — Playwright opens it with\n * `--remote-debugging-port`, axe drives it through Playwright, and Lighthouse attaches\n * over that port — so one browser binary serves both and nothing here needs Chrome\n * installed system-wide.\n */\nimport { spawn, type ChildProcess } from 'node:child_process';\nimport { existsSync } from 'node:fs';\nimport { createServer } from 'node:net';\nimport {\n AXE_TAGS,\n auditedPage,\n classifyStatus,\n isSitemapIndex,\n normalizeImpact,\n pathsFromSitemapLocs,\n sitemapLocs,\n unauditedPage,\n} from './plan.ts';\nimport type { AuditBudgets, AxeViolation, FormFactor, PageMetrics, PageResult } from './types.ts';\n\nconst INSTALL =\n 'vp add -D playwright @axe-core/playwright lighthouse && vp exec playwright install chromium';\n\n/** Load an optional dependency or throw an error that says how to get it. */\nasync function loadOptional<T>(name: string): Promise<T> {\n try {\n // A variable specifier keeps bundlers and the type checker from resolving it —\n // that is the whole point of \"optional\".\n return (await import(/* @vite-ignore */ name)) as T;\n } catch (err) {\n const code = (err as { code?: string }).code;\n if (code === 'ERR_MODULE_NOT_FOUND' || code === 'MODULE_NOT_FOUND')\n throw new Error(\n `vitops audit needs \"${name}\", which is an optional dependency and is not installed.\\n` +\n ` Install: ${INSTALL}`,\n { cause: err },\n );\n throw err;\n }\n}\n\n/**\n * Check, before any server starts, that everything the audit needs is there: the three optional\n * packages and a Chromium binary. A missing browser used to surface only after the preview\n * server was up, and the failed launch left it running.\n */\nexport async function preflight(): Promise<void> {\n const pw = await loadOptional<{ chromium: { executablePath(): string } }>('playwright');\n await loadOptional('@axe-core/playwright');\n await loadOptional('lighthouse');\n let exe = '';\n try {\n exe = pw.chromium.executablePath();\n } catch {\n /* treated as missing below */\n }\n if (!exe || !existsSync(exe))\n throw new Error(\n 'Chromium is not installed for Playwright.\\n Install: vp exec playwright install chromium',\n );\n}\n\n/**\n * Page paths from the served site's sitemap (`/sitemap-index.xml`, then `/sitemap.xml`), following\n * an index one level to its child sitemaps. Empty when the site serves none.\n */\nexport async function fetchSitemapPaths(baseUrl: string): Promise<string[]> {\n const get = async (path: string): Promise<string | undefined> => {\n try {\n const res = await fetch(new URL(path, baseUrl), { redirect: 'follow' });\n return res.ok ? await res.text() : undefined;\n } catch {\n return undefined;\n }\n };\n for (const root of ['/sitemap-index.xml', '/sitemap.xml']) {\n const xml = await get(root);\n if (!xml) continue;\n if (!isSitemapIndex(xml)) return pathsFromSitemapLocs(sitemapLocs(xml));\n const out: string[] = [];\n for (const loc of sitemapLocs(xml)) {\n // The index names the production host; read the same file from the server being audited.\n const child = await get(new URL(loc).pathname);\n if (child) out.push(...pathsFromSitemapLocs(sitemapLocs(child)));\n }\n return out;\n }\n return [];\n}\n\n/** A free TCP port, or `true` for \"this specific one is free\". */\nexport function isPortFree(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const s = createServer();\n s.once('error', () => resolve(false));\n s.listen(port, '127.0.0.1', () => s.close(() => resolve(true)));\n });\n}\n\nfunction freePort(): Promise<number> {\n return new Promise((resolve, reject) => {\n const s = createServer();\n s.once('error', reject);\n s.listen(0, '127.0.0.1', () => {\n const { port } = s.address() as { port: number };\n s.close(() => resolve(port));\n });\n });\n}\n\n/** Run a command to completion, inheriting stdio. Rejects on a non-zero exit. */\nexport function runCommand(cmd: string, args: string[], cwd: string): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn(cmd, args, { cwd, stdio: 'inherit', shell: process.platform === 'win32' });\n child.once('error', reject);\n child.once('exit', (code) =>\n code === 0 ? resolve() : reject(new Error(`${cmd} ${args.join(' ')} exited with ${code}`)),\n );\n });\n}\n\nexport interface PreviewServer {\n baseUrl: string;\n stop(): void;\n}\n\n/**\n * `astro preview` on a dedicated port.\n *\n * Astro's preview server silently moves to the next port when its own is taken, and\n * a readiness probe would then happily attach to whatever owns the original one — the\n * failure `site/playwright.config.ts` documents. So the port is checked free first and\n * the run aborts if it is not.\n */\nexport async function startPreview(opts: {\n cwd: string;\n port: number;\n timeoutMs?: number;\n}): Promise<PreviewServer> {\n if (!(await isPortFree(opts.port)))\n throw new Error(\n `port ${opts.port} is already in use — refusing to audit whatever is listening there. ` +\n `Pass --port <free port>, or --url to audit a server you started yourself.`,\n );\n const baseUrl = `http://127.0.0.1:${opts.port}`;\n const child: ChildProcess = spawn(\n 'npx',\n ['--no-install', 'astro', 'preview', '--host', '127.0.0.1', '--port', String(opts.port)],\n { cwd: opts.cwd, stdio: ['ignore', 'pipe', 'pipe'], detached: process.platform !== 'win32' },\n );\n let log = '';\n child.stdout?.on('data', (d) => (log += String(d)));\n child.stderr?.on('data', (d) => (log += String(d)));\n let exited: number | null | undefined;\n child.once('exit', (code) => (exited = code));\n\n const stop = () => {\n try {\n if (child.pid && process.platform !== 'win32') process.kill(-child.pid, 'SIGTERM');\n else child.kill('SIGTERM');\n } catch {\n /* already gone */\n }\n };\n\n const deadline = Date.now() + (opts.timeoutMs ?? 60_000);\n while (Date.now() < deadline) {\n if (exited !== undefined) {\n throw new Error(\n `astro preview exited early (code ${exited}). Did you build first? Pass --build.\\n${log.trim()}`,\n );\n }\n try {\n await fetch(baseUrl, { redirect: 'manual' });\n return { baseUrl, stop };\n } catch {\n await new Promise((r) => setTimeout(r, 250));\n }\n }\n stop();\n throw new Error(`astro preview did not answer on ${baseUrl} within the timeout.\\n${log.trim()}`);\n}\n\n// ── Browser tooling (structural types: these modules are optional) ───────────\n\ninterface AxeResult {\n violations: Array<{\n id: string;\n impact?: string | null;\n help: string;\n helpUrl: string;\n nodes: Array<{ target: unknown[] }>;\n }>;\n}\ninterface LighthouseAudits {\n [id: string]: { numericValue?: number } | undefined;\n}\ninterface LighthouseRun {\n lhr: {\n audits: LighthouseAudits;\n categories: Record<string, { score: number | null } | undefined>;\n };\n}\n\nfunction toViolations(res: AxeResult): AxeViolation[] {\n return res.violations.map((v) => ({\n id: v.id,\n impact: normalizeImpact(v.impact),\n help: v.help,\n helpUrl: v.helpUrl,\n nodeCount: v.nodes.length,\n targets: v.nodes.slice(0, 3).map((n) => String(n.target.flat().join(' '))),\n }));\n}\n\nfunction toMetrics(run: LighthouseRun): PageMetrics {\n const a = run.lhr.audits;\n const score = (id: string) => {\n const s = run.lhr.categories[id]?.score;\n return s == null ? undefined : Math.round(s * 100);\n };\n return {\n lcpMs: a['largest-contentful-paint']?.numericValue,\n cls: a['cumulative-layout-shift']?.numericValue,\n tbtMs: a['total-blocking-time']?.numericValue,\n scores: {\n performance: score('performance'),\n seo: score('seo'),\n bestPractices: score('best-practices'),\n },\n };\n}\n\nexport interface RunOptions {\n baseUrl: string;\n paths: string[];\n budgets: AuditBudgets;\n formFactor: FormFactor;\n onProgress?: (message: string) => void;\n}\n\n/** Audit each path. Never throws for a bad PAGE — only for missing tooling/browser. */\nexport async function runAudit(opts: RunOptions): Promise<PageResult[]> {\n const log = opts.onProgress ?? (() => {});\n const [pw, axe, lh] = await Promise.all([\n loadOptional<{\n chromium: {\n launch(o: { headless: boolean; args: string[] }): Promise<{\n newContext(): Promise<{\n newPage(): Promise<{\n goto(u: string, o: { waitUntil: string }): Promise<unknown>;\n }>;\n close(): Promise<void>;\n }>;\n close(): Promise<void>;\n }>;\n };\n }>('playwright'),\n loadOptional<{\n AxeBuilder: new (o: { page: unknown }) => {\n withTags(t: string[]): { analyze(): Promise<AxeResult> };\n };\n }>('@axe-core/playwright'),\n loadOptional<{\n default: (\n url: string,\n flags: Record<string, unknown>,\n config?: unknown,\n ) => Promise<LighthouseRun | undefined>;\n }>('lighthouse'),\n ]);\n\n const debugPort = await freePort();\n let browser;\n try {\n browser = await pw.chromium.launch({\n headless: true,\n args: [`--remote-debugging-port=${debugPort}`],\n });\n } catch (err) {\n throw new Error(\n `could not launch Chromium: ${(err as Error).message}\\n Install a browser: vp exec playwright install chromium`,\n { cause: err },\n );\n }\n\n const results: PageResult[] = [];\n try {\n for (const path of opts.paths) {\n const url = new URL(path, opts.baseUrl).href;\n let httpStatus: number | undefined;\n try {\n httpStatus = (await fetch(url, { redirect: 'follow' })).status;\n } catch {\n httpStatus = undefined;\n }\n const verdict = classifyStatus(path, httpStatus);\n if (verdict.status !== 'audited') {\n log(`${path}: ${verdict.status} (${verdict.reason})`);\n results.push(unauditedPage(path, url, httpStatus, verdict as never));\n continue;\n }\n\n log(`${path}: axe`);\n // axe-playwright refuses a page that did not come from `newContext()`.\n const context = await browser.newContext();\n const page = await context.newPage();\n let violations: AxeViolation[];\n try {\n await page.goto(url, { waitUntil: 'load' });\n violations = toViolations(await new axe.AxeBuilder({ page }).withTags(AXE_TAGS).analyze());\n } finally {\n await context.close();\n }\n\n log(`${path}: lighthouse`);\n const flags: Record<string, unknown> = {\n port: debugPort,\n output: 'json',\n logLevel: 'error',\n onlyCategories: ['performance', 'seo', 'best-practices'],\n formFactor: opts.formFactor,\n ...(opts.formFactor === 'desktop'\n ? {\n screenEmulation: {\n mobile: false,\n width: 1350,\n height: 940,\n deviceScaleFactor: 1,\n disabled: false,\n },\n throttling: {\n rttMs: 40,\n throughputKbps: 10 * 1024,\n cpuSlowdownMultiplier: 1,\n requestLatencyMs: 0,\n downloadThroughputKbps: 0,\n uploadThroughputKbps: 0,\n },\n }\n : {}),\n };\n const run = await lh.default(url, flags);\n const metrics: PageMetrics = run ? toMetrics(run) : { scores: {} };\n results.push(auditedPage(path, url, httpStatus!, metrics, violations, opts.budgets));\n }\n } finally {\n await browser.close();\n }\n return results;\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getvitops/utils",
3
- "version": "8.3.0-alpha.0",
3
+ "version": "8.3.0-alpha.1",
4
4
  "description": "Shared build-time utilities for the Vitops toolchain — favicon generation and friends.",
5
5
  "homepage": "https://docs.vitops.ca",
6
6
  "bugs": "https://github.com/getvitops/design-system/issues",
@@ -107,7 +107,7 @@
107
107
  "access": "public"
108
108
  },
109
109
  "dependencies": {
110
- "@getvitops/config": "8.3.0-alpha.0",
110
+ "@getvitops/config": "8.3.0-alpha.1",
111
111
  "colorjs.io": "^0.5.2",
112
112
  "parse5": "^7.3.0",
113
113
  "png-to-ico": "^3.0.2",