@coralreefventures/site-tools 0.4.1 → 0.4.2

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
@@ -25,7 +25,9 @@ default mobile emulation, each of performance, accessibility, best practices and
25
25
  page under it is measured again, up to `--retries` more times (2 by default), and the median of its runs decides:
26
26
  a performance score swings a few points between runs of the same page on a shared runner, and one low run should
27
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
28
+ run and leaves its last report in `--reports`. A category's own floor (`--performance 85`) stands in for the
29
+ threshold for that category alone, where a site's shell puts every page a few points under 90 on a shared runner
30
+ while the other three categories hold at 90. Lighthouse itself is not a dependency, because it
29
31
  bundles axe-core (MPL-2.0), which is outside the family's licence allowlist; it runs through `pnpm dlx`, pinned
30
32
  (`--lighthouse-version`). Chrome is Playwright's Chromium when `@playwright/test` is installed beside this package,
31
33
  or `--chrome`.
@@ -1,14 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
  import { parseArgs } from "node:util";
3
- import { runLighthouse } from "../lighthouse.js";
3
+ import { categories, runLighthouse } from "../lighthouse.js";
4
4
  /**
5
- * reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--retries 2] [--chrome <path>] [path ...]
5
+ * reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--retries 2] [--performance N]
6
+ * [--accessibility N] [--best-practices N] [--seo N] [--chrome <path>] [path ...]
6
7
  *
7
8
  * Lighthouse on every page in the export's sitemap, or the paths given, each category at the threshold or above. The
8
9
  * export is served on a free port unless --port pins one (Streamlane's run met EADDRINUSE on a fixed 3160). A page
9
10
  * under the threshold is measured again, up to --retries more times, and the median of its runs decides, since a
10
11
  * 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.
12
+ * one is under, and leaves their reports in --reports. A category's own floor (--performance 85) stands in for the
13
+ * threshold for that category alone.
12
14
  */
13
15
  const { values, positionals } = parseArgs({
14
16
  allowPositionals: true,
@@ -18,29 +20,35 @@ const { values, positionals } = parseArgs({
18
20
  port: { type: "string" },
19
21
  threshold: { type: "string", default: "90" },
20
22
  retries: { type: "string", default: "2" },
23
+ performance: { type: "string" },
24
+ accessibility: { type: "string" },
25
+ "best-practices": { type: "string" },
26
+ seo: { type: "string" },
21
27
  chrome: { type: "string" },
22
28
  "lighthouse-version": { type: "string", default: "13.5.0" },
23
29
  help: { type: "boolean", short: "h" },
24
30
  },
25
31
  });
26
32
  if (values.help) {
27
- console.log("reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--retries 2] [--chrome <path>] [path ...]");
33
+ console.log("reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--retries 2] [--performance N] [--accessibility N] [--best-practices N] [--seo N] [--chrome <path>] [path ...]");
28
34
  process.exit(0);
29
35
  }
30
36
  const threshold = Number(values.threshold) / 100;
37
+ const thresholds = Object.fromEntries(categories.flatMap((category) => values[category] === undefined ? [] : [[category, Number(values[category]) / 100]]));
31
38
  const results = await runLighthouse({
32
39
  root: values.root,
33
40
  reportDir: values.reports,
34
41
  ...(values.port !== undefined && { port: Number(values.port) }),
35
42
  threshold,
36
43
  retries: Number(values.retries),
44
+ thresholds,
37
45
  paths: positionals,
38
46
  lighthouseVersion: values["lighthouse-version"],
39
47
  ...(values.chrome !== undefined && { chrome: values.chrome }),
40
48
  });
41
49
  const failures = results.filter((result) => result.low.length > 0);
42
50
  if (failures.length) {
43
- console.error(`Under ${threshold * 100}:\n${failures.map((result) => `${result.path}: ${result.low.join(", ")}`).join("\n")}\nReports in ${values.reports}/`);
51
+ console.error(`Under the threshold:\n${failures.map((result) => `${result.path}: ${result.low.join(", ")}`).join("\n")}\nReports in ${values.reports}/`);
44
52
  process.exit(1);
45
53
  }
46
54
  //# sourceMappingURL=reef-lighthouse.js.map
@@ -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;;;;;;;;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"}
1
+ {"version":3,"file":"reef-lighthouse.js","sourceRoot":"","sources":["../../src/bin/reef-lighthouse.ts"],"names":[],"mappings":"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"}
@@ -9,6 +9,11 @@ export type LighthouseOptions = {
9
9
  port?: number;
10
10
  /** The least score, 0 to 1, every category must reach on every page; 0.9 by default. */
11
11
  threshold?: number;
12
+ /**
13
+ * A category's own floor, 0 to 1, in place of the threshold: a site holds performance at 0.85, say, where the
14
+ * shell's scripts and stylesheets on a shared runner put every page a few points under 90, and the rest at 0.9.
15
+ */
16
+ thresholds?: Partial<Record<Category, number>>;
12
17
  /** The pages to audit; every page in the export's sitemap when none are given. */
13
18
  paths?: readonly string[];
14
19
  /**
@@ -36,7 +41,7 @@ export type PageResult = {
36
41
  /** The median of the scores, which three runs with one low outlier put at the typical run. */
37
42
  export declare const median: (values: readonly number[]) => number;
38
43
  /** 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) => {
44
+ export declare const judge: (runs: readonly Record<Category, number>[], threshold: number, thresholds?: Partial<Record<Category, number>>) => {
40
45
  scores: Record<Category, number>;
41
46
  low: Category[];
42
47
  };
@@ -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;;;;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"}
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;;;OAGG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC;IAC/C,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,EACjB,aAAY,OAAO,CAAC,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAM,KACjD;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;AA4ExF;;;;GAIG;AACH,eAAO,MAAM,aAAa,GAAU,UAAS,iBAAsB,KAAG,OAAO,CAAC,UAAU,EAAE,CAwCzF,CAAC"}
@@ -23,9 +23,9 @@ export const median = (values) => {
23
23
  : (sorted[middle - 1] + sorted[middle]) / 2;
24
24
  };
25
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) => {
26
+ export const judge = (runs, threshold, thresholds = {}) => {
27
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) };
28
+ return { scores, low: categories.filter((category) => scores[category] < (thresholds[category] ?? threshold)) };
29
29
  };
30
30
  /** Every page's path from the export's sitemap, whatever origin the sitemap names. */
31
31
  export const pagesFromSitemap = async (root) => {
@@ -72,13 +72,13 @@ const shown = (scores) => categories.map((category) => Math.round(scores[categor
72
72
  /** One page's verdict: measured again while under the threshold, up to `retries` more times; the median decides. */
73
73
  const measurePage = async (pagePath, base, measure) => {
74
74
  const runs = [];
75
- let verdict = judge(runs, measure.threshold);
75
+ let verdict = judge(runs, measure.threshold, measure.thresholds);
76
76
  while (runs.length === 0 || (verdict.low.length > 0 && runs.length <= measure.retries)) {
77
77
  await audit(`${measure.origin}${pagePath}`, base, measure.version, measure.chrome);
78
78
  const report = JSON.parse(await readFile(`${base}.report.json`, "utf8"));
79
79
  const scores = Object.fromEntries(categories.map((category) => [category, report.categories[category]?.score ?? 0]));
80
80
  runs.push(scores);
81
- verdict = judge(runs, measure.threshold);
81
+ verdict = judge(runs, measure.threshold, measure.thresholds);
82
82
  if (verdict.low.length > 0 && runs.length <= measure.retries) {
83
83
  measure.log(`again ${pagePath.padEnd(28)} ${shown(scores)} (run ${runs.length} of ${measure.retries + 1})`);
84
84
  }
@@ -97,6 +97,7 @@ export const runLighthouse = async (options = {}) => {
97
97
  const reportDir = options.reportDir ?? "lighthouse";
98
98
  const threshold = options.threshold ?? 0.9;
99
99
  const retries = Math.max(0, options.retries ?? 2);
100
+ const thresholds = options.thresholds ?? {};
100
101
  const version = options.lighthouseVersion ?? "13.5.0";
101
102
  const log = options.log ?? console.log;
102
103
  const paths = options.paths?.length ? options.paths : await pagesFromSitemap(root);
@@ -107,7 +108,15 @@ export const runLighthouse = async (options = {}) => {
107
108
  await mkdir(reportDir, { recursive: true });
108
109
  for (const pagePath of paths) {
109
110
  const base = reportBase(reportDir, pagePath);
110
- const result = await measurePage(pagePath, base, { origin, version, chrome, threshold, retries, log });
111
+ const result = await measurePage(pagePath, base, {
112
+ origin,
113
+ version,
114
+ chrome,
115
+ threshold,
116
+ thresholds,
117
+ retries,
118
+ log,
119
+ });
111
120
  results.push(result);
112
121
  if (result.low.length === 0) {
113
122
  await rm(`${base}.report.json`);
@@ -118,7 +127,11 @@ export const runLighthouse = async (options = {}) => {
118
127
  finally {
119
128
  server.close();
120
129
  }
121
- log(`\n${categories.join(", ")}; ${paths.length} pages, threshold ${threshold * 100}`);
130
+ const floors = categories
131
+ .filter((category) => thresholds[category] !== undefined)
132
+ .map((category) => `, ${category} ${(thresholds[category] ?? 0) * 100}`)
133
+ .join("");
134
+ log(`\n${categories.join(", ")}; ${paths.length} pages, threshold ${threshold * 100}${floors}`);
122
135
  return results;
123
136
  };
124
137
  //# sourceMappingURL=lighthouse.js.map
@@ -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.1",
3
+ "version": "0.4.2",
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",
@@ -1,16 +1,18 @@
1
1
  #!/usr/bin/env node
2
2
  import { parseArgs } from "node:util";
3
3
 
4
- import { runLighthouse } from "../lighthouse.ts";
4
+ import { categories, runLighthouse } from "../lighthouse.ts";
5
5
 
6
6
  /**
7
- * reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--retries 2] [--chrome <path>] [path ...]
7
+ * reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--retries 2] [--performance N]
8
+ * [--accessibility N] [--best-practices N] [--seo N] [--chrome <path>] [path ...]
8
9
  *
9
10
  * Lighthouse on every page in the export's sitemap, or the paths given, each category at the threshold or above. The
10
11
  * export is served on a free port unless --port pins one (Streamlane's run met EADDRINUSE on a fixed 3160). A page
11
12
  * under the threshold is measured again, up to --retries more times, and the median of its runs decides, since a
12
13
  * 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.
14
+ * one is under, and leaves their reports in --reports. A category's own floor (--performance 85) stands in for the
15
+ * threshold for that category alone.
14
16
  */
15
17
  const { values, positionals } = parseArgs({
16
18
  allowPositionals: true,
@@ -20,6 +22,10 @@ const { values, positionals } = parseArgs({
20
22
  port: { type: "string" },
21
23
  threshold: { type: "string", default: "90" },
22
24
  retries: { type: "string", default: "2" },
25
+ performance: { type: "string" },
26
+ accessibility: { type: "string" },
27
+ "best-practices": { type: "string" },
28
+ seo: { type: "string" },
23
29
  chrome: { type: "string" },
24
30
  "lighthouse-version": { type: "string", default: "13.5.0" },
25
31
  help: { type: "boolean", short: "h" },
@@ -28,18 +34,24 @@ const { values, positionals } = parseArgs({
28
34
 
29
35
  if (values.help) {
30
36
  console.log(
31
- "reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--retries 2] [--chrome <path>] [path ...]",
37
+ "reef-lighthouse [--root out] [--reports lighthouse] [--port N] [--threshold 90] [--retries 2] [--performance N] [--accessibility N] [--best-practices N] [--seo N] [--chrome <path>] [path ...]",
32
38
  );
33
39
  process.exit(0);
34
40
  }
35
41
 
36
42
  const threshold = Number(values.threshold) / 100;
43
+ const thresholds = Object.fromEntries(
44
+ categories.flatMap((category) =>
45
+ values[category] === undefined ? [] : [[category, Number(values[category]) / 100]],
46
+ ),
47
+ );
37
48
  const results = await runLighthouse({
38
49
  root: values.root,
39
50
  reportDir: values.reports,
40
51
  ...(values.port !== undefined && { port: Number(values.port) }),
41
52
  threshold,
42
53
  retries: Number(values.retries),
54
+ thresholds,
43
55
  paths: positionals,
44
56
  lighthouseVersion: values["lighthouse-version"],
45
57
  ...(values.chrome !== undefined && { chrome: values.chrome }),
@@ -48,7 +60,7 @@ const results = await runLighthouse({
48
60
  const failures = results.filter((result) => result.low.length > 0);
49
61
  if (failures.length) {
50
62
  console.error(
51
- `Under ${threshold * 100}:\n${failures.map((result) => `${result.path}: ${result.low.join(", ")}`).join("\n")}\nReports in ${values.reports}/`,
63
+ `Under the threshold:\n${failures.map((result) => `${result.path}: ${result.low.join(", ")}`).join("\n")}\nReports in ${values.reports}/`,
52
64
  );
53
65
  process.exit(1);
54
66
  }
package/src/lighthouse.ts CHANGED
@@ -16,6 +16,11 @@ export type LighthouseOptions = {
16
16
  port?: number;
17
17
  /** The least score, 0 to 1, every category must reach on every page; 0.9 by default. */
18
18
  threshold?: number;
19
+ /**
20
+ * A category's own floor, 0 to 1, in place of the threshold: a site holds performance at 0.85, say, where the
21
+ * shell's scripts and stylesheets on a shared runner put every page a few points under 90, and the rest at 0.9.
22
+ */
23
+ thresholds?: Partial<Record<Category, number>>;
19
24
  /** The pages to audit; every page in the export's sitemap when none are given. */
20
25
  paths?: readonly string[];
21
26
  /**
@@ -58,11 +63,12 @@ export const median = (values: readonly number[]): number => {
58
63
  export const judge = (
59
64
  runs: readonly Record<Category, number>[],
60
65
  threshold: number,
66
+ thresholds: Partial<Record<Category, number>> = {},
61
67
  ): { scores: Record<Category, number>; low: Category[] } => {
62
68
  const scores = Object.fromEntries(
63
69
  categories.map((category) => [category, median(runs.map((run) => run[category]))]),
64
70
  ) as Record<Category, number>;
65
- return { scores, low: categories.filter((category) => scores[category] < threshold) };
71
+ return { scores, low: categories.filter((category) => scores[category] < (thresholds[category] ?? threshold)) };
66
72
  };
67
73
 
68
74
  type Report = { categories: Record<string, { score: number | null } | undefined> };
@@ -122,6 +128,7 @@ type Measure = {
122
128
  version: string;
123
129
  chrome: string | undefined;
124
130
  threshold: number;
131
+ thresholds: Partial<Record<Category, number>>;
125
132
  retries: number;
126
133
  log: (line: string) => void;
127
134
  };
@@ -132,7 +139,7 @@ const shown = (scores: Record<Category, number>) =>
132
139
  /** One page's verdict: measured again while under the threshold, up to `retries` more times; the median decides. */
133
140
  const measurePage = async (pagePath: string, base: string, measure: Measure): Promise<PageResult> => {
134
141
  const runs: Record<Category, number>[] = [];
135
- let verdict = judge(runs, measure.threshold);
142
+ let verdict = judge(runs, measure.threshold, measure.thresholds);
136
143
  while (runs.length === 0 || (verdict.low.length > 0 && runs.length <= measure.retries)) {
137
144
  await audit(`${measure.origin}${pagePath}`, base, measure.version, measure.chrome);
138
145
  const report = JSON.parse(await readFile(`${base}.report.json`, "utf8")) as Report;
@@ -140,7 +147,7 @@ const measurePage = async (pagePath: string, base: string, measure: Measure): Pr
140
147
  categories.map((category) => [category, report.categories[category]?.score ?? 0]),
141
148
  ) as Record<Category, number>;
142
149
  runs.push(scores);
143
- verdict = judge(runs, measure.threshold);
150
+ verdict = judge(runs, measure.threshold, measure.thresholds);
144
151
  if (verdict.low.length > 0 && runs.length <= measure.retries) {
145
152
  measure.log(`again ${pagePath.padEnd(28)} ${shown(scores)} (run ${runs.length} of ${measure.retries + 1})`);
146
153
  }
@@ -160,6 +167,7 @@ export const runLighthouse = async (options: LighthouseOptions = {}): Promise<Pa
160
167
  const reportDir = options.reportDir ?? "lighthouse";
161
168
  const threshold = options.threshold ?? 0.9;
162
169
  const retries = Math.max(0, options.retries ?? 2);
170
+ const thresholds = options.thresholds ?? {};
163
171
  const version = options.lighthouseVersion ?? "13.5.0";
164
172
  const log = options.log ?? console.log;
165
173
  const paths = options.paths?.length ? options.paths : await pagesFromSitemap(root);
@@ -170,7 +178,15 @@ export const runLighthouse = async (options: LighthouseOptions = {}): Promise<Pa
170
178
  await mkdir(reportDir, { recursive: true });
171
179
  for (const pagePath of paths) {
172
180
  const base = reportBase(reportDir, pagePath);
173
- const result = await measurePage(pagePath, base, { origin, version, chrome, threshold, retries, log });
181
+ const result = await measurePage(pagePath, base, {
182
+ origin,
183
+ version,
184
+ chrome,
185
+ threshold,
186
+ thresholds,
187
+ retries,
188
+ log,
189
+ });
174
190
  results.push(result);
175
191
  if (result.low.length === 0) {
176
192
  await rm(`${base}.report.json`);
@@ -180,6 +196,10 @@ export const runLighthouse = async (options: LighthouseOptions = {}): Promise<Pa
180
196
  } finally {
181
197
  server.close();
182
198
  }
183
- log(`\n${categories.join(", ")}; ${paths.length} pages, threshold ${threshold * 100}`);
199
+ const floors = categories
200
+ .filter((category) => thresholds[category] !== undefined)
201
+ .map((category) => `, ${category} ${(thresholds[category] ?? 0) * 100}`)
202
+ .join("");
203
+ log(`\n${categories.join(", ")}; ${paths.length} pages, threshold ${threshold * 100}${floors}`);
184
204
  return results;
185
205
  };