mandrel-platform 0.16.0 → 0.17.0
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
|
@@ -87,6 +87,13 @@ every consumer.
|
|
|
87
87
|
A Biome base config with the recommended linter rule set, import organizer,
|
|
88
88
|
and standard formatter defaults (2-space indent, 100-char line width).
|
|
89
89
|
|
|
90
|
+
**Minimum Biome version: 2.0.** The base targets the **Biome v2 schema**
|
|
91
|
+
(`$schema: https://biomejs.dev/schemas/2.5.0/schema.json`, `assist.actions.
|
|
92
|
+
source.organizeImports`, `linter.rules.preset`). Biome v1 consumers cannot
|
|
93
|
+
`extends` this base — the v2 config shape is a hard configuration error on
|
|
94
|
+
Biome 1.x. There is no dual v1/v2 export; consumers on Biome 1.x must
|
|
95
|
+
upgrade to 2.x before adopting this base (refs #153).
|
|
96
|
+
|
|
90
97
|
**Consumer usage (`biome.json`):**
|
|
91
98
|
|
|
92
99
|
```jsonc
|
|
@@ -175,13 +182,28 @@ export default [
|
|
|
175
182
|
];
|
|
176
183
|
```
|
|
177
184
|
|
|
178
|
-
#### `lighthouse.base.json`
|
|
179
|
-
|
|
180
|
-
Lighthouse
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
+
#### `lighthouse.base.json` / `lighthouse-thresholds.base.json`
|
|
186
|
+
|
|
187
|
+
Lighthouse has **two runner mechanisms** in the fleet — `@lhci/cli`
|
|
188
|
+
(`lighthouserc`) and a bespoke puppeteer + baseline-drift script (collect a
|
|
189
|
+
Lighthouse result programmatically, diff category scores against a checked-in
|
|
190
|
+
baseline JSON). Neither can consume the other's config shape natively, so the
|
|
191
|
+
package ships **two bases**:
|
|
192
|
+
|
|
193
|
+
- **`lighthouse-thresholds.base.json`** — the **mechanism-neutral** score
|
|
194
|
+
floors (`categories.performance` / `.accessibility` / `.best-practices` /
|
|
195
|
+
`.seo`, each a bare `0.0`–`1.0` number). This is the shared source of
|
|
196
|
+
truth both mechanisms read. Runner-agnostic on purpose: it has no LHCI
|
|
197
|
+
`ci.assert` wrapper and no puppeteer-script wiring, just the floors.
|
|
198
|
+
- **`lighthouse.base.json`** — the **LHCI wrapper**. Ships the shared `ci`
|
|
199
|
+
block (collect settings + the four category assertions on the
|
|
200
|
+
`lighthouse:recommended` preset) for `@lhci/cli` consumers. Its
|
|
201
|
+
`categories:*` `minScore` values are sourced from
|
|
202
|
+
`lighthouse-thresholds.base.json` — keep the two in sync when a floor
|
|
203
|
+
changes.
|
|
204
|
+
|
|
205
|
+
**LHCI consumers** (`@lhci/cli`) deep-merge `lighthouse.base.json` and add
|
|
206
|
+
repo-specific `ci.collect.url` / `ci.collect.staticDistDir`:
|
|
185
207
|
|
|
186
208
|
```jsonc
|
|
187
209
|
// lighthouserc.js — deep-merge the base, add repo-specific collect targets
|
|
@@ -198,6 +220,23 @@ export default {
|
|
|
198
220
|
};
|
|
199
221
|
```
|
|
200
222
|
|
|
223
|
+
**Puppeteer / baseline-drift consumers** (no `@lhci/cli`, no `lighthouserc`)
|
|
224
|
+
extend `lighthouse-thresholds.base.json` directly — read the bare category
|
|
225
|
+
floors and gate the collected result against them, independent of any LHCI
|
|
226
|
+
config shape:
|
|
227
|
+
|
|
228
|
+
```jsonc
|
|
229
|
+
// scripts/lighthouse-baseline.mjs — read the shared floors, gate the collected result
|
|
230
|
+
import thresholds from "mandrel-platform/lighthouse-thresholds.base.json" with { type: "json" };
|
|
231
|
+
|
|
232
|
+
for (const [category, minScore] of Object.entries(thresholds.categories)) {
|
|
233
|
+
const score = lighthouseResult.categories[category].score;
|
|
234
|
+
if (score < minScore) {
|
|
235
|
+
throw new Error(`${category} score ${score} below floor ${minScore}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
```
|
|
239
|
+
|
|
201
240
|
> **Budgets stay consumer-tunable.** These bases standardize *which*
|
|
202
241
|
> tools run and their shared defaults — not *what each tool gates on*
|
|
203
242
|
> per consumer. Override any threshold, score floor, or budget locally;
|
|
@@ -465,6 +504,7 @@ pnpm run bootstrap
|
|
|
465
504
|
| `mandrel-platform/dependency-cruiser.base.json` | `config/dependency-cruiser.base.json` |
|
|
466
505
|
| `mandrel-platform/size-limit.base.json` | `config/size-limit.base.json` |
|
|
467
506
|
| `mandrel-platform/lighthouse.base.json` | `config/lighthouse.base.json` |
|
|
507
|
+
| `mandrel-platform/lighthouse-thresholds.base.json` | `config/lighthouse-thresholds.base.json` |
|
|
468
508
|
| `mandrel-platform/pnpm-workspace.supply-chain.yaml` | `config/pnpm-workspace.supply-chain.yaml` |
|
|
469
509
|
| `mandrel-platform/edge-security` | `config/edge-security/index.mjs` |
|
|
470
510
|
| `mandrel-platform/edge-security/*` | `config/edge-security/*` |
|
package/config/biome.base.json
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
{
|
|
2
|
-
"
|
|
3
|
-
"
|
|
4
|
-
|
|
5
|
-
},
|
|
2
|
+
"root": false,
|
|
3
|
+
"$schema": "https://biomejs.dev/schemas/2.5.0/schema.json",
|
|
4
|
+
"assist": { "actions": { "source": { "organizeImports": "on" } } },
|
|
6
5
|
"linter": {
|
|
7
6
|
"enabled": true,
|
|
8
7
|
"rules": {
|
|
9
|
-
"
|
|
8
|
+
"preset": "recommended"
|
|
10
9
|
}
|
|
11
10
|
},
|
|
12
11
|
"formatter": {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Mechanism-neutral Lighthouse score-floor thresholds for mandrel-platform consumers (refs #157). Runner-agnostic: both an LHCI `lighthouserc` (via lighthouse.base.json, which spreads this file's `categories` into its `ci.assert.assertions`) and a bespoke puppeteer + baseline-drift script (reading `categories` directly against each category's `lighthouse-result.categories.<key>.score`) can consume the same floors. Consumer-tunable: override any entry locally in either mechanism.",
|
|
3
|
+
"categories": {
|
|
4
|
+
"performance": 0.9,
|
|
5
|
+
"accessibility": 0.9,
|
|
6
|
+
"best-practices": 0.9,
|
|
7
|
+
"seo": 0.9
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"_comment": "Shared Lighthouse CI base for mandrel-platform consumers. Lighthouse's `lighthouserc.json` has no whole-file `extends`, so this ships the shared `ci` block (collect settings + category assertions on the recommended preset). Consumers deep-merge it and add repo-specific `ci.collect.url` / `ci.collect.staticDistDir` and any per-repo assertion overrides (see README). Category score floors stay consumer-tunable.",
|
|
2
|
+
"_comment": "Shared Lighthouse CI base for mandrel-platform LHCI (@lhci/cli) consumers. Lighthouse's `lighthouserc.json` has no whole-file `extends`, so this ships the shared `ci` block (collect settings + category assertions on the recommended preset). Consumers deep-merge it and add repo-specific `ci.collect.url` / `ci.collect.staticDistDir` and any per-repo assertion overrides (see README). The `categories:*` minScore floors below are sourced from the mechanism-neutral `mandrel-platform/lighthouse-thresholds.base.json` (refs #157) — keep the two files' scores in sync when a floor changes. Consumers who run Lighthouse via a bespoke puppeteer + baseline-drift script instead of @lhci/cli should extend `lighthouse-thresholds.base.json` directly rather than this file (see README). Category score floors stay consumer-tunable.",
|
|
3
3
|
"ci": {
|
|
4
4
|
"collect": {
|
|
5
5
|
"numberOfRuns": 3,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mandrel-platform",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"description": "Shared CI/deploy workflows, composite toolchain action, npm config package, Renovate preset, and operator runbook templates.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"./dependency-cruiser.base.json": "./config/dependency-cruiser.base.json",
|
|
25
25
|
"./size-limit.base.json": "./config/size-limit.base.json",
|
|
26
26
|
"./lighthouse.base.json": "./config/lighthouse.base.json",
|
|
27
|
+
"./lighthouse-thresholds.base.json": "./config/lighthouse-thresholds.base.json",
|
|
27
28
|
"./pnpm-workspace.supply-chain.yaml": "./config/pnpm-workspace.supply-chain.yaml",
|
|
28
29
|
"./edge-security": "./config/edge-security/index.mjs",
|
|
29
30
|
"./edge-security/*": "./config/edge-security/*",
|
|
@@ -40,7 +41,7 @@
|
|
|
40
41
|
"provenance": true
|
|
41
42
|
},
|
|
42
43
|
"dependencies": {
|
|
43
|
-
"mandrel": "^1.
|
|
44
|
+
"mandrel": "^1.81.0"
|
|
44
45
|
},
|
|
45
46
|
"scripts": {
|
|
46
47
|
"typecheck": "node --input-type=module --eval 'process.exit(0)'",
|
|
@@ -127,10 +127,19 @@ export function meetsThreshold(pct, threshold) {
|
|
|
127
127
|
}
|
|
128
128
|
|
|
129
129
|
/**
|
|
130
|
-
* Recursively find every `coverage-summary.json` under `
|
|
131
|
-
*
|
|
132
|
-
* pruned so the scan stays fast and
|
|
133
|
-
* `roots` (from `--coverage-dir`)
|
|
130
|
+
* Recursively find every `coverage-summary.json` under `root`, regardless of
|
|
131
|
+
* the name of the directory that directly contains it. `node_modules` and
|
|
132
|
+
* dotted dirs (e.g. `.git`, `.agents`) are pruned so the scan stays fast and
|
|
133
|
+
* never reads a vendored framework tree. `roots` (from `--coverage-dir`)
|
|
134
|
+
* overrides the auto-scan when provided.
|
|
135
|
+
*
|
|
136
|
+
* A directory literally named `coverage` (the common single-workspace shape)
|
|
137
|
+
* is still discovered, but so is a per-workspace fan-out layout where the
|
|
138
|
+
* top-level `coverage/` dir nests differently-named subdirectories per
|
|
139
|
+
* package (e.g. `coverage/web/coverage-summary.json`,
|
|
140
|
+
* `coverage/shared/coverage-summary.json`) — the match condition is "this
|
|
141
|
+
* directory contains a coverage-summary.json file", not "this directory is
|
|
142
|
+
* named coverage".
|
|
134
143
|
*/
|
|
135
144
|
export function findCoverageSummaries(root, roots = []) {
|
|
136
145
|
if (roots.length > 0) {
|
|
@@ -151,17 +160,13 @@ export function findCoverageSummaries(root, roots = []) {
|
|
|
151
160
|
} catch {
|
|
152
161
|
return;
|
|
153
162
|
}
|
|
163
|
+
const file = join(dir, "coverage-summary.json");
|
|
164
|
+
if (existsSync(file)) found.push(file);
|
|
154
165
|
for (const entry of entries) {
|
|
155
166
|
const name = entry.name;
|
|
156
167
|
if (!entry.isDirectory()) continue;
|
|
157
168
|
if (name === "node_modules" || name.startsWith(".")) continue;
|
|
158
|
-
|
|
159
|
-
if (name === "coverage") {
|
|
160
|
-
const file = join(full, "coverage-summary.json");
|
|
161
|
-
if (existsSync(file)) found.push(file);
|
|
162
|
-
// A coverage dir may still nest sub-package coverage; keep walking.
|
|
163
|
-
}
|
|
164
|
-
walk(full);
|
|
169
|
+
walk(join(dir, name));
|
|
165
170
|
}
|
|
166
171
|
};
|
|
167
172
|
walk(resolve(root));
|
|
@@ -243,6 +243,42 @@ test("findCoverageSummaries auto-scans **/coverage/, pruning node_modules + dott
|
|
|
243
243
|
}
|
|
244
244
|
});
|
|
245
245
|
|
|
246
|
+
test("findCoverageSummaries discovers a per-workspace fan-out layout (coverage/<workspace>/coverage-summary.json)", () => {
|
|
247
|
+
const root = mkdtempSync(join(tmpdir(), "cov-gate-"));
|
|
248
|
+
try {
|
|
249
|
+
// domio's shape: top-level coverage/ dir nests per-workspace subdirs that
|
|
250
|
+
// are NOT themselves named "coverage".
|
|
251
|
+
mkdirSync(join(root, "coverage", "web"), { recursive: true });
|
|
252
|
+
writeFileSync(
|
|
253
|
+
join(root, "coverage", "web", "coverage-summary.json"),
|
|
254
|
+
JSON.stringify(summary({ lines: 77 }))
|
|
255
|
+
);
|
|
256
|
+
mkdirSync(join(root, "coverage", "shared"), { recursive: true });
|
|
257
|
+
writeFileSync(
|
|
258
|
+
join(root, "coverage", "shared", "coverage-summary.json"),
|
|
259
|
+
JSON.stringify(summary({ lines: 93 }))
|
|
260
|
+
);
|
|
261
|
+
// Decoys that must still be pruned.
|
|
262
|
+
mkdirSync(join(root, "node_modules", "dep", "coverage"), { recursive: true });
|
|
263
|
+
writeFileSync(
|
|
264
|
+
join(root, "node_modules", "dep", "coverage", "coverage-summary.json"),
|
|
265
|
+
JSON.stringify(summary({ lines: 1 }))
|
|
266
|
+
);
|
|
267
|
+
mkdirSync(join(root, ".agents", "coverage"), { recursive: true });
|
|
268
|
+
writeFileSync(
|
|
269
|
+
join(root, ".agents", "coverage", "coverage-summary.json"),
|
|
270
|
+
JSON.stringify(summary({ lines: 2 }))
|
|
271
|
+
);
|
|
272
|
+
|
|
273
|
+
const files = findCoverageSummaries(root).sort();
|
|
274
|
+
assert.equal(files.length, 2);
|
|
275
|
+
assert.ok(files.some((f) => /coverage[/\\]shared[/\\]coverage-summary\.json$/.test(f)));
|
|
276
|
+
assert.ok(files.some((f) => /coverage[/\\]web[/\\]coverage-summary\.json$/.test(f)));
|
|
277
|
+
} finally {
|
|
278
|
+
rmSync(root, { recursive: true, force: true });
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
246
282
|
test("findCoverageSummaries honours explicit --coverage-dir roots", () => {
|
|
247
283
|
const root = mkdtempSync(join(tmpdir(), "cov-gate-"));
|
|
248
284
|
try {
|