@coralreefventures/site-tools 0.4.0 → 0.4.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/README.md CHANGED
@@ -6,7 +6,7 @@ products' scripts go. The third locks a site behind the door.
6
6
 
7
7
  ```sh
8
8
  reef-serve [--root out] [--port 3140]
9
- reef-lighthouse [--root out] [--reports lighthouse] [--port 3160] [--threshold 90] [--chrome <path>] [path ...]
9
+ reef-lighthouse [--root out] [--reports lighthouse] [--port 3160] [--threshold 90] [--retries 2] [--chrome <path>] [path ...]
10
10
  reef-door-bundle --site <id> [--out out] --door-url <url> --hosts <h1,h2> --jwks-url <url> [--signout-chain id=host,...]
11
11
  [--name <display name>] [--mark <file>] [--mark-dark <file>] [--accent #rrggbb] [--accent-dark #rrggbb]
12
12
  ```
@@ -22,7 +22,10 @@ webServer: { command: `pnpm exec reef-serve --port ${port}`, port },
22
22
 
23
23
  `reef-lighthouse` is the merge gate: Lighthouse on every page in the export's sitemap (or the paths given), with its
24
24
  default mobile emulation, each of performance, accessibility, best practices and SEO at the threshold or above. A
25
- page under it fails the run and leaves its report in `--reports`. Lighthouse itself is not a dependency, because it
25
+ page under it is measured again, up to `--retries` more times (2 by default), and the median of its runs decides:
26
+ a performance score swings a few points between runs of the same page on a shared runner, and one low run should
27
+ not fail a gate three runs would pass, while a page every run finds low still fails. A page under it then fails the
28
+ run and leaves its last report in `--reports`. Lighthouse itself is not a dependency, because it
26
29
  bundles axe-core (MPL-2.0), which is outside the family's licence allowlist; it runs through `pnpm dlx`, pinned
27
30
  (`--lighthouse-version`). Chrome is Playwright's Chromium when `@playwright/test` is installed beside this package,
28
31
  or `--chrome`.
@@ -2,11 +2,13 @@
2
2
  import { parseArgs } from "node:util";
3
3
  import { runLighthouse } from "../lighthouse.js";
4
4
  /**
5
- * reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--chrome <path>] [path ...]
5
+ * reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--retries 2] [--chrome <path>] [path ...]
6
6
  *
7
7
  * Lighthouse on every page in the export's sitemap, or the paths given, each category at the threshold or above. The
8
- * export is served on a free port unless --port pins one (Streamlane's run met EADDRINUSE on a fixed 3160).
9
- * Exits 1 with the failing pages named when one is under, and leaves their reports in --reports.
8
+ * export is served on a free port unless --port pins one (Streamlane's run met EADDRINUSE on a fixed 3160). A page
9
+ * under the threshold is measured again, up to --retries more times, and the median of its runs decides, since a
10
+ * performance score swings a few points between runs on a shared runner. Exits 1 with the failing pages named when
11
+ * one is under, and leaves their reports in --reports.
10
12
  */
11
13
  const { values, positionals } = parseArgs({
12
14
  allowPositionals: true,
@@ -15,13 +17,14 @@ const { values, positionals } = parseArgs({
15
17
  reports: { type: "string", default: "lighthouse" },
16
18
  port: { type: "string" },
17
19
  threshold: { type: "string", default: "90" },
20
+ retries: { type: "string", default: "2" },
18
21
  chrome: { type: "string" },
19
22
  "lighthouse-version": { type: "string", default: "13.5.0" },
20
23
  help: { type: "boolean", short: "h" },
21
24
  },
22
25
  });
23
26
  if (values.help) {
24
- console.log("reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--chrome <path>] [path ...]");
27
+ console.log("reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--retries 2] [--chrome <path>] [path ...]");
25
28
  process.exit(0);
26
29
  }
27
30
  const threshold = Number(values.threshold) / 100;
@@ -30,6 +33,7 @@ const results = await runLighthouse({
30
33
  reportDir: values.reports,
31
34
  ...(values.port !== undefined && { port: Number(values.port) }),
32
35
  threshold,
36
+ retries: Number(values.retries),
33
37
  paths: positionals,
34
38
  lighthouseVersion: values["lighthouse-version"],
35
39
  ...(values.chrome !== undefined && { chrome: values.chrome }),
@@ -1 +1 @@
1
- {"version":3,"file":"reef-lighthouse.js","sourceRoot":"","sources":["../../src/bin/reef-lighthouse.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD;;;;;;GAMG;AACH,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;IACxC,gBAAgB,EAAE,IAAI;IACtB,OAAO,EAAE;QACP,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QACxC,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE;QAClD,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE;QAC5C,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC1B,oBAAoB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE;QAC3D,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;KACtC;CACF,CAAC,CAAC;AAEH,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IAChB,OAAO,CAAC,GAAG,CACT,8GAA8G,CAC/G,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC;AACjD,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC,IAAI;IACjB,SAAS,EAAE,MAAM,CAAC,OAAO;IACzB,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;IAC/D,SAAS;IACT,KAAK,EAAE,WAAW;IAClB,iBAAiB,EAAE,MAAM,CAAC,oBAAoB,CAAC;IAC/C,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;CAC9D,CAAC,CAAC;AAEH,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACnE,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,KAAK,CACX,SAAS,SAAS,GAAG,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,MAAM,CAAC,OAAO,GAAG,CAC/I,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"reef-lighthouse.js","sourceRoot":"","sources":["../../src/bin/reef-lighthouse.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AAEtC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD;;;;;;;;GAQG;AACH,MAAM,EAAE,MAAM,EAAE,WAAW,EAAE,GAAG,SAAS,CAAC;IACxC,gBAAgB,EAAE,IAAI;IACtB,OAAO,EAAE;QACP,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,KAAK,EAAE;QACxC,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE;QAClD,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QACxB,SAAS,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,IAAI,EAAE;QAC5C,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAE;QACzC,MAAM,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE;QAC1B,oBAAoB,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,QAAQ,EAAE;QAC3D,IAAI,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,GAAG,EAAE;KACtC;CACF,CAAC,CAAC;AAEH,IAAI,MAAM,CAAC,IAAI,EAAE,CAAC;IAChB,OAAO,CAAC,GAAG,CACT,4HAA4H,CAC7H,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC;AAED,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,CAAC,SAAS,CAAC,GAAG,GAAG,CAAC;AACjD,MAAM,OAAO,GAAG,MAAM,aAAa,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC,IAAI;IACjB,SAAS,EAAE,MAAM,CAAC,OAAO;IACzB,GAAG,CAAC,MAAM,CAAC,IAAI,KAAK,SAAS,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;IAC/D,SAAS;IACT,OAAO,EAAE,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC;IAC/B,KAAK,EAAE,WAAW;IAClB,iBAAiB,EAAE,MAAM,CAAC,oBAAoB,CAAC;IAC/C,GAAG,CAAC,MAAM,CAAC,MAAM,KAAK,SAAS,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;CAC9D,CAAC,CAAC;AAEH,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACnE,IAAI,QAAQ,CAAC,MAAM,EAAE,CAAC;IACpB,OAAO,CAAC,KAAK,CACX,SAAS,SAAS,GAAG,GAAG,MAAM,QAAQ,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,gBAAgB,MAAM,CAAC,OAAO,GAAG,CAC/I,CAAC;IACF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
@@ -11,6 +11,12 @@ export type LighthouseOptions = {
11
11
  threshold?: number;
12
12
  /** The pages to audit; every page in the export's sitemap when none are given. */
13
13
  paths?: readonly string[];
14
+ /**
15
+ * How many more times a page under the threshold is measured; 2 by default, so a page gets three runs and the
16
+ * median of each category's scores. Performance on a shared runner swings a few points between runs of the same
17
+ * page, and one low run fails a gate that three agree would pass; a page every run finds low still fails.
18
+ */
19
+ retries?: number;
14
20
  /** The Lighthouse release `pnpm dlx` runs. */
15
21
  lighthouseVersion?: string;
16
22
  /** The Chrome binary; Playwright's Chromium when it is installed and nothing is given. */
@@ -20,10 +26,19 @@ export type LighthouseOptions = {
20
26
  };
21
27
  export type PageResult = {
22
28
  path: string;
23
- /** Each category's score, 0 to 1. */
29
+ /** Each category's score, 0 to 1: the median over the page's runs. */
24
30
  scores: Record<Category, number>;
25
31
  /** The categories under the threshold. */
26
32
  low: Category[];
33
+ /** How many times the page was measured. */
34
+ runs: number;
35
+ };
36
+ /** The median of the scores, which three runs with one low outlier put at the typical run. */
37
+ export declare const median: (values: readonly number[]) => number;
38
+ /** The categories under the threshold, by the median of each one's scores over the runs so far. */
39
+ export declare const judge: (runs: readonly Record<Category, number>[], threshold: number) => {
40
+ scores: Record<Category, number>;
41
+ low: Category[];
27
42
  };
28
43
  /** Every page's path from the export's sitemap, whatever origin the sitemap names. */
29
44
  export declare const pagesFromSitemap: (root: string) => Promise<string[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"lighthouse.d.ts","sourceRoot":"","sources":["../src/lighthouse.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,UAAU,oEAAqE,CAAC;AAC7F,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kHAAkH;IAClH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,8CAA8C;IAC9C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACjC,0CAA0C;IAC1C,GAAG,EAAE,QAAQ,EAAE,CAAC;CACjB,CAAC;AAIF,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,MAAM,EAAE,CAGrE,CAAC;AAEF,yGAAyG;AACzG,eAAO,MAAM,UAAU,GAAI,WAAW,MAAM,EAAE,UAAU,MAAM,WACyB,CAAC;AA0CxF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAU,UAAS,iBAAsB,KAAG,OAAO,CAAC,UAAU,EAAE,CAiCzF,CAAC"}
1
+ {"version":3,"file":"lighthouse.d.ts","sourceRoot":"","sources":["../src/lighthouse.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,UAAU,oEAAqE,CAAC;AAC7F,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAC;AAEnD,MAAM,MAAM,iBAAiB,GAAG;IAC9B,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kHAAkH;IAClH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,kFAAkF;IAClF,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B;;;;OAIG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8CAA8C;IAC9C,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,GAAG,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;CAC9B,CAAC;AAEF,MAAM,MAAM,UAAU,GAAG;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,sEAAsE;IACtE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IACjC,0CAA0C;IAC1C,GAAG,EAAE,QAAQ,EAAE,CAAC;IAChB,4CAA4C;IAC5C,IAAI,EAAE,MAAM,CAAC;CACd,CAAC;AAEF,8FAA8F;AAC9F,eAAO,MAAM,MAAM,GAAI,QAAQ,SAAS,MAAM,EAAE,KAAG,MASlD,CAAC;AAEF,mGAAmG;AACnG,eAAO,MAAM,KAAK,GAChB,MAAM,SAAS,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,EAAE,EACzC,WAAW,MAAM,KAChB;IAAE,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;IAAC,GAAG,EAAE,QAAQ,EAAE,CAAA;CAKrD,CAAC;AAIF,sFAAsF;AACtF,eAAO,MAAM,gBAAgB,GAAU,MAAM,MAAM,KAAG,OAAO,CAAC,MAAM,EAAE,CAGrE,CAAC;AAEF,yGAAyG;AACzG,eAAO,MAAM,UAAU,GAAI,WAAW,MAAM,EAAE,UAAU,MAAM,WACyB,CAAC;AA2ExF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAU,UAAS,iBAAsB,KAAG,OAAO,CAAC,UAAU,EAAE,CA2BzF,CAAC"}
@@ -11,6 +11,22 @@ import { mkdir, readFile, rm } from "node:fs/promises";
11
11
  import path from "node:path";
12
12
  import { serve } from "./serve.js";
13
13
  export const categories = ["performance", "accessibility", "best-practices", "seo"];
14
+ /** The median of the scores, which three runs with one low outlier put at the typical run. */
15
+ export const median = (values) => {
16
+ const sorted = [...values].sort((left, right) => left - right);
17
+ const middle = Math.floor(sorted.length / 2);
18
+ if (sorted.length === 0) {
19
+ return 0;
20
+ }
21
+ return sorted.length % 2 === 1
22
+ ? sorted[middle]
23
+ : (sorted[middle - 1] + sorted[middle]) / 2;
24
+ };
25
+ /** The categories under the threshold, by the median of each one's scores over the runs so far. */
26
+ export const judge = (runs, threshold) => {
27
+ const scores = Object.fromEntries(categories.map((category) => [category, median(runs.map((run) => run[category]))]));
28
+ return { scores, low: categories.filter((category) => scores[category] < threshold) };
29
+ };
14
30
  /** Every page's path from the export's sitemap, whatever origin the sitemap names. */
15
31
  export const pagesFromSitemap = async (root) => {
16
32
  const sitemap = await readFile(path.join(root, "sitemap.xml"), "utf8");
@@ -52,6 +68,25 @@ const audit = (url, base, version, chrome) => new Promise((resolve, reject) => {
52
68
  run.on("error", reject);
53
69
  run.on("exit", (code) => (code === 0 ? resolve() : reject(new Error(`Lighthouse exited with ${code} on ${url}`))));
54
70
  });
71
+ const shown = (scores) => categories.map((category) => Math.round(scores[category] * 100)).join(" ");
72
+ /** One page's verdict: measured again while under the threshold, up to `retries` more times; the median decides. */
73
+ const measurePage = async (pagePath, base, measure) => {
74
+ const runs = [];
75
+ let verdict = judge(runs, measure.threshold);
76
+ while (runs.length === 0 || (verdict.low.length > 0 && runs.length <= measure.retries)) {
77
+ await audit(`${measure.origin}${pagePath}`, base, measure.version, measure.chrome);
78
+ const report = JSON.parse(await readFile(`${base}.report.json`, "utf8"));
79
+ const scores = Object.fromEntries(categories.map((category) => [category, report.categories[category]?.score ?? 0]));
80
+ runs.push(scores);
81
+ verdict = judge(runs, measure.threshold);
82
+ if (verdict.low.length > 0 && runs.length <= measure.retries) {
83
+ measure.log(`again ${pagePath.padEnd(28)} ${shown(scores)} (run ${runs.length} of ${measure.retries + 1})`);
84
+ }
85
+ }
86
+ const over = runs.length > 1 ? ` (median of ${runs.length} runs)` : "";
87
+ measure.log(`${verdict.low.length ? "FAIL" : "ok "} ${pagePath.padEnd(28)} ${shown(verdict.scores)}${over}`);
88
+ return { path: pagePath, scores: verdict.scores, low: verdict.low, runs: runs.length };
89
+ };
55
90
  /**
56
91
  * The merge gate: Lighthouse on every page, each category at the threshold or above, with Lighthouse's default
57
92
  * mobile emulation and throttling, against the export served as the host serves it. A page under the threshold
@@ -61,6 +96,7 @@ export const runLighthouse = async (options = {}) => {
61
96
  const root = options.root ?? "out";
62
97
  const reportDir = options.reportDir ?? "lighthouse";
63
98
  const threshold = options.threshold ?? 0.9;
99
+ const retries = Math.max(0, options.retries ?? 2);
64
100
  const version = options.lighthouseVersion ?? "13.5.0";
65
101
  const log = options.log ?? console.log;
66
102
  const paths = options.paths?.length ? options.paths : await pagesFromSitemap(root);
@@ -71,14 +107,9 @@ export const runLighthouse = async (options = {}) => {
71
107
  await mkdir(reportDir, { recursive: true });
72
108
  for (const pagePath of paths) {
73
109
  const base = reportBase(reportDir, pagePath);
74
- await audit(`${origin}${pagePath}`, base, version, chrome);
75
- const report = JSON.parse(await readFile(`${base}.report.json`, "utf8"));
76
- const scores = Object.fromEntries(categories.map((category) => [category, report.categories[category]?.score ?? 0]));
77
- const low = categories.filter((category) => scores[category] < threshold);
78
- results.push({ path: pagePath, scores, low });
79
- const shown = categories.map((category) => Math.round(scores[category] * 100)).join(" ");
80
- log(`${low.length ? "FAIL" : "ok "} ${pagePath.padEnd(28)} ${shown}`);
81
- if (low.length === 0) {
110
+ const result = await measurePage(pagePath, base, { origin, version, chrome, threshold, retries, log });
111
+ results.push(result);
112
+ if (result.low.length === 0) {
82
113
  await rm(`${base}.report.json`);
83
114
  await rm(`${base}.report.html`);
84
115
  }
@@ -1 +1 @@
1
- {"version":3,"file":"lighthouse.js","sourceRoot":"","sources":["../src/lighthouse.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"lighthouse.js","sourceRoot":"","sources":["../src/lighthouse.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coralreefventures/site-tools",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Command-line tools for a Coral Reef public site: serve the static export as the host does, gate it with Lighthouse, and lock it behind the door.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -4,11 +4,13 @@ import { parseArgs } from "node:util";
4
4
  import { runLighthouse } from "../lighthouse.ts";
5
5
 
6
6
  /**
7
- * reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--chrome <path>] [path ...]
7
+ * reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--retries 2] [--chrome <path>] [path ...]
8
8
  *
9
9
  * Lighthouse on every page in the export's sitemap, or the paths given, each category at the threshold or above. The
10
- * export is served on a free port unless --port pins one (Streamlane's run met EADDRINUSE on a fixed 3160).
11
- * Exits 1 with the failing pages named when one is under, and leaves their reports in --reports.
10
+ * export is served on a free port unless --port pins one (Streamlane's run met EADDRINUSE on a fixed 3160). A page
11
+ * under the threshold is measured again, up to --retries more times, and the median of its runs decides, since a
12
+ * performance score swings a few points between runs on a shared runner. Exits 1 with the failing pages named when
13
+ * one is under, and leaves their reports in --reports.
12
14
  */
13
15
  const { values, positionals } = parseArgs({
14
16
  allowPositionals: true,
@@ -17,6 +19,7 @@ const { values, positionals } = parseArgs({
17
19
  reports: { type: "string", default: "lighthouse" },
18
20
  port: { type: "string" },
19
21
  threshold: { type: "string", default: "90" },
22
+ retries: { type: "string", default: "2" },
20
23
  chrome: { type: "string" },
21
24
  "lighthouse-version": { type: "string", default: "13.5.0" },
22
25
  help: { type: "boolean", short: "h" },
@@ -25,7 +28,7 @@ const { values, positionals } = parseArgs({
25
28
 
26
29
  if (values.help) {
27
30
  console.log(
28
- "reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--chrome <path>] [path ...]",
31
+ "reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--retries 2] [--chrome <path>] [path ...]",
29
32
  );
30
33
  process.exit(0);
31
34
  }
@@ -36,6 +39,7 @@ const results = await runLighthouse({
36
39
  reportDir: values.reports,
37
40
  ...(values.port !== undefined && { port: Number(values.port) }),
38
41
  threshold,
42
+ retries: Number(values.retries),
39
43
  paths: positionals,
40
44
  lighthouseVersion: values["lighthouse-version"],
41
45
  ...(values.chrome !== undefined && { chrome: values.chrome }),
package/src/lighthouse.ts CHANGED
@@ -18,6 +18,12 @@ export type LighthouseOptions = {
18
18
  threshold?: number;
19
19
  /** The pages to audit; every page in the export's sitemap when none are given. */
20
20
  paths?: readonly string[];
21
+ /**
22
+ * How many more times a page under the threshold is measured; 2 by default, so a page gets three runs and the
23
+ * median of each category's scores. Performance on a shared runner swings a few points between runs of the same
24
+ * page, and one low run fails a gate that three agree would pass; a page every run finds low still fails.
25
+ */
26
+ retries?: number;
21
27
  /** The Lighthouse release `pnpm dlx` runs. */
22
28
  lighthouseVersion?: string;
23
29
  /** The Chrome binary; Playwright's Chromium when it is installed and nothing is given. */
@@ -28,10 +34,35 @@ export type LighthouseOptions = {
28
34
 
29
35
  export type PageResult = {
30
36
  path: string;
31
- /** Each category's score, 0 to 1. */
37
+ /** Each category's score, 0 to 1: the median over the page's runs. */
32
38
  scores: Record<Category, number>;
33
39
  /** The categories under the threshold. */
34
40
  low: Category[];
41
+ /** How many times the page was measured. */
42
+ runs: number;
43
+ };
44
+
45
+ /** The median of the scores, which three runs with one low outlier put at the typical run. */
46
+ export const median = (values: readonly number[]): number => {
47
+ const sorted = [...values].sort((left, right) => left - right);
48
+ const middle = Math.floor(sorted.length / 2);
49
+ if (sorted.length === 0) {
50
+ return 0;
51
+ }
52
+ return sorted.length % 2 === 1
53
+ ? (sorted[middle] as number)
54
+ : ((sorted[middle - 1] as number) + (sorted[middle] as number)) / 2;
55
+ };
56
+
57
+ /** The categories under the threshold, by the median of each one's scores over the runs so far. */
58
+ export const judge = (
59
+ runs: readonly Record<Category, number>[],
60
+ threshold: number,
61
+ ): { scores: Record<Category, number>; low: Category[] } => {
62
+ const scores = Object.fromEntries(
63
+ categories.map((category) => [category, median(runs.map((run) => run[category]))]),
64
+ ) as Record<Category, number>;
65
+ return { scores, low: categories.filter((category) => scores[category] < threshold) };
35
66
  };
36
67
 
37
68
  type Report = { categories: Record<string, { score: number | null } | undefined> };
@@ -86,6 +117,39 @@ const audit = (url: string, base: string, version: string, chrome: string | unde
86
117
  run.on("exit", (code) => (code === 0 ? resolve() : reject(new Error(`Lighthouse exited with ${code} on ${url}`))));
87
118
  });
88
119
 
120
+ type Measure = {
121
+ origin: string;
122
+ version: string;
123
+ chrome: string | undefined;
124
+ threshold: number;
125
+ retries: number;
126
+ log: (line: string) => void;
127
+ };
128
+
129
+ const shown = (scores: Record<Category, number>) =>
130
+ categories.map((category) => Math.round(scores[category] * 100)).join(" ");
131
+
132
+ /** One page's verdict: measured again while under the threshold, up to `retries` more times; the median decides. */
133
+ const measurePage = async (pagePath: string, base: string, measure: Measure): Promise<PageResult> => {
134
+ const runs: Record<Category, number>[] = [];
135
+ let verdict = judge(runs, measure.threshold);
136
+ while (runs.length === 0 || (verdict.low.length > 0 && runs.length <= measure.retries)) {
137
+ await audit(`${measure.origin}${pagePath}`, base, measure.version, measure.chrome);
138
+ const report = JSON.parse(await readFile(`${base}.report.json`, "utf8")) as Report;
139
+ const scores = Object.fromEntries(
140
+ categories.map((category) => [category, report.categories[category]?.score ?? 0]),
141
+ ) as Record<Category, number>;
142
+ runs.push(scores);
143
+ verdict = judge(runs, measure.threshold);
144
+ if (verdict.low.length > 0 && runs.length <= measure.retries) {
145
+ measure.log(`again ${pagePath.padEnd(28)} ${shown(scores)} (run ${runs.length} of ${measure.retries + 1})`);
146
+ }
147
+ }
148
+ const over = runs.length > 1 ? ` (median of ${runs.length} runs)` : "";
149
+ measure.log(`${verdict.low.length ? "FAIL" : "ok "} ${pagePath.padEnd(28)} ${shown(verdict.scores)}${over}`);
150
+ return { path: pagePath, scores: verdict.scores, low: verdict.low, runs: runs.length };
151
+ };
152
+
89
153
  /**
90
154
  * The merge gate: Lighthouse on every page, each category at the threshold or above, with Lighthouse's default
91
155
  * mobile emulation and throttling, against the export served as the host serves it. A page under the threshold
@@ -95,6 +159,7 @@ export const runLighthouse = async (options: LighthouseOptions = {}): Promise<Pa
95
159
  const root = options.root ?? "out";
96
160
  const reportDir = options.reportDir ?? "lighthouse";
97
161
  const threshold = options.threshold ?? 0.9;
162
+ const retries = Math.max(0, options.retries ?? 2);
98
163
  const version = options.lighthouseVersion ?? "13.5.0";
99
164
  const log = options.log ?? console.log;
100
165
  const paths = options.paths?.length ? options.paths : await pagesFromSitemap(root);
@@ -105,16 +170,9 @@ export const runLighthouse = async (options: LighthouseOptions = {}): Promise<Pa
105
170
  await mkdir(reportDir, { recursive: true });
106
171
  for (const pagePath of paths) {
107
172
  const base = reportBase(reportDir, pagePath);
108
- await audit(`${origin}${pagePath}`, base, version, chrome);
109
- const report = JSON.parse(await readFile(`${base}.report.json`, "utf8")) as Report;
110
- const scores = Object.fromEntries(
111
- categories.map((category) => [category, report.categories[category]?.score ?? 0]),
112
- ) as Record<Category, number>;
113
- const low = categories.filter((category) => scores[category] < threshold);
114
- results.push({ path: pagePath, scores, low });
115
- const shown = categories.map((category) => Math.round(scores[category] * 100)).join(" ");
116
- log(`${low.length ? "FAIL" : "ok "} ${pagePath.padEnd(28)} ${shown}`);
117
- if (low.length === 0) {
173
+ const result = await measurePage(pagePath, base, { origin, version, chrome, threshold, retries, log });
174
+ results.push(result);
175
+ if (result.low.length === 0) {
118
176
  await rm(`${base}.report.json`);
119
177
  await rm(`${base}.report.html`);
120
178
  }