@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 +5 -2
- package/dist/bin/reef-lighthouse.js +8 -4
- package/dist/bin/reef-lighthouse.js.map +1 -1
- package/dist/lighthouse.d.ts +16 -1
- package/dist/lighthouse.d.ts.map +1 -1
- package/dist/lighthouse.js +39 -8
- package/dist/lighthouse.js.map +1 -1
- package/package.json +1 -1
- package/src/bin/reef-lighthouse.ts +8 -4
- package/src/lighthouse.ts +69 -11
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
|
|
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
|
-
*
|
|
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
|
|
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"}
|
package/dist/lighthouse.d.ts
CHANGED
|
@@ -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[]>;
|
package/dist/lighthouse.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/dist/lighthouse.js
CHANGED
|
@@ -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
|
|
75
|
-
|
|
76
|
-
|
|
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
|
}
|
package/dist/lighthouse.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lighthouse.js","sourceRoot":"","sources":["../src/lighthouse.ts"],"names":[],"mappings":";;;;;;;;AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAC3C,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAEnC,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,aAAa,EAAE,eAAe,EAAE,gBAAgB,EAAE,KAAK,CAAU,CAAC;
|
|
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.
|
|
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
|
-
*
|
|
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
|
|
109
|
-
|
|
110
|
-
|
|
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
|
}
|