Repository navigation
Description
lib/crux-client.js freezes CRUX_FORM_FACTORS and DEFAULT_CRUX_FORM_FACTORS (#17), but four more exported constants have exactly the same shape and were left mutable. Split out of #17 rather than folded in, because it is a sweep across three modules and one of them changes a published type.
The hazard
Each of these is exported from a public subpath and used as a default parameter value or a shared argument inside the library, so a consumer mutating one changes behaviour for every later call in the process:
| Constant | Module | Exported via | Used as |
|---|---|---|---|
DEFAULT_PSI_STRATEGIES |
lib/psi.js:10 |
web-perf-cli/psi |
default strategies in runPsi, runPsiBatch, runPsiAuditBatch |
PSI_STRATEGIES |
lib/psi.js:9 |
web-perf-cli/psi |
validation list |
CHROME_FLAGS |
lib/lab.js:15 |
web-perf-cli/lab, root |
passed to every Chrome launch in lab.js and links.js |
DEFAULT_SKIP_AUDITS |
lib/lab.js:13 |
web-perf-cli/lab, root |
default in buildLighthouseConfig |
LAB_CATEGORIES |
lib/profiles.js:4 |
web-perf-cli/profiles |
category validation |
The PSI one is the closest analogue to what was fixed:
const { DEFAULT_PSI_STRATEGIES } = require('@hugoer/web-perf-cli/psi');
DEFAULT_PSI_STRATEGIES.push('desktop');
// every later runPsi / runPsiBatch / runPsiAuditBatch now issues an extra request per URL,
// against the 25,000/day PSI quotaCHROME_FLAGS is the sharpest: a consumer could append a flag that silently changes how every subsequent audit launches Chrome, which would quietly invalidate scores rather than just cost quota.
Do the same thing #17 did, including the typing
Freezing alone is not enough — that was the lesson from #17. DEFAULT_CRUX_FORM_FACTORS was declared string[], so it could never be passed back into the option it was the default for, and freezing it only changed the error code. Each constant here needs both halves:
Object.freeze, with an element type where the values are a closed set (DEFAULT_PSI_STRATEGIESshould bereadonly PsiStrategy[], notreadonly string[]).- The options that accept it widened to
readonly T[], so the exported constant can be passed to the function it is the default for. - A
type-tests/assertion that passes the constant into that option, plus a@ts-expect-erroron a mutation. A spread compiles against both readonly and mutable shapes, so a spread alone asserts nothing.
Published type change to expect
CHROME_FLAGS is currently string[] and becomes readonly string[]. lib/links.js and lib/lab.js both pass it straight to chromeLauncher.launch({ chromeFlags }); check whether chrome-launcher's own types accept a readonly array before committing to the freeze, and spread at the call site if not.
Verification
npm run lint, npm test, npm run generate-types, npm run check-types, and the new type-tests assertions mutation-checked by reverting each freeze in turn.
Activity