@avi2dg/checks 0.1.0 → 0.3.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 +80 -15
- package/package.json +14 -2
- package/scripts/backtest.ts +0 -0
- package/scripts/comment-gate.ts +0 -0
- package/scripts/mutation-compare.ts +173 -0
- package/scripts/test-layout.ts +3 -2
- package/stryker.preset.js +13 -0
package/README.md
CHANGED
|
@@ -5,8 +5,9 @@ Deterministic checks shared across my TypeScript repos. One package,
|
|
|
5
5
|
Effect language-service block, the shared commitlint config, the shared
|
|
6
6
|
dependency-cruiser base, the test-layout check with its bunfig preset,
|
|
7
7
|
the commit-identity check with its workflow, the comment gate with its
|
|
8
|
-
workflow and backtest,
|
|
9
|
-
|
|
8
|
+
workflow and backtest, the Stryker mutation-testing preset with its
|
|
9
|
+
no-regression comparator, and the Effect error-channel plugin compiled
|
|
10
|
+
to JavaScript.
|
|
10
11
|
|
|
11
12
|
Published as `@avi2dg/checks` on the public npm registry.
|
|
12
13
|
|
|
@@ -50,7 +51,7 @@ cp node_modules/@avi2dg/checks/bunfig.toml bunfig.toml
|
|
|
50
51
|
`package.json` gains three scripts:
|
|
51
52
|
|
|
52
53
|
```json
|
|
53
|
-
"lint": "oxlint --type-aware &&
|
|
54
|
+
"lint": "oxlint --type-aware && checks-lint-coverage && checks-test-layout && checks-commit-identity HEAD",
|
|
54
55
|
"typecheck": "tsc --noEmit && effect-tsgo diagnostics --project tsconfig.json --format text --strict",
|
|
55
56
|
"test": "bun test --randomize"
|
|
56
57
|
```
|
|
@@ -64,14 +65,25 @@ cp node_modules/@avi2dg/checks/bunfig.toml bunfig.toml
|
|
|
64
65
|
`commit-identity.ts` refuses a commit with an author other than the
|
|
65
66
|
repository owner; see "Commit identity" below.
|
|
66
67
|
|
|
68
|
+
A repo that runs mutation testing installs `@stryker-mutator/core` and
|
|
69
|
+
`@hughescr/stryker-bun-runner`, then spreads the shipped preset in
|
|
70
|
+
`stryker.conf.mjs`:
|
|
71
|
+
|
|
72
|
+
```js
|
|
73
|
+
import preset from "@avi2dg/checks/stryker.preset.js";
|
|
74
|
+
|
|
75
|
+
export default {
|
|
76
|
+
...preset,
|
|
77
|
+
};
|
|
78
|
+
```
|
|
79
|
+
|
|
67
80
|
The registry version is pinned by the consumer's lockfile; bump
|
|
68
81
|
`@avi2dg/checks` to adopt a new release.
|
|
69
82
|
|
|
70
83
|
## Test layout
|
|
71
84
|
|
|
72
|
-
`
|
|
73
|
-
|
|
74
|
-
it does not:
|
|
85
|
+
`checks-test-layout` fails unless the repo holds this shape, and names the
|
|
86
|
+
file and the path to move it to when it does not:
|
|
75
87
|
|
|
76
88
|
- Every test file is `tests/**/*.test.ts` or `.tsx`. A `*.test.ts`, `*.spec.ts` or
|
|
77
89
|
`*_test.ts` under `src/`, `test/`, `__tests__/` or the repo root fails.
|
|
@@ -192,8 +204,8 @@ commit-identity check below is the enforcement.
|
|
|
192
204
|
one carries an identity other than the repository owner's:
|
|
193
205
|
|
|
194
206
|
```sh
|
|
195
|
-
bun
|
|
196
|
-
bun
|
|
207
|
+
bun run checks-commit-identity <base-ref> <head-ref>
|
|
208
|
+
bun run checks-commit-identity <ref>
|
|
197
209
|
```
|
|
198
210
|
|
|
199
211
|
With one argument it checks that commit alone, which is the form the
|
|
@@ -235,8 +247,8 @@ leaves stale once the base branch advances after the pull request opens.
|
|
|
235
247
|
when an added line carries a banned comment:
|
|
236
248
|
|
|
237
249
|
```sh
|
|
238
|
-
bun
|
|
239
|
-
bun
|
|
250
|
+
bun run checks-comment-gate <base-ref> <head-ref>
|
|
251
|
+
bun run checks-comment-gate <ref>
|
|
240
252
|
```
|
|
241
253
|
|
|
242
254
|
With two arguments it diffs the base against the head. With one it diffs
|
|
@@ -268,7 +280,7 @@ jobs:
|
|
|
268
280
|
at each recent commit, so a repository can measure its own history:
|
|
269
281
|
|
|
270
282
|
```sh
|
|
271
|
-
bun
|
|
283
|
+
bun run checks-backtest [commit-count]
|
|
272
284
|
```
|
|
273
285
|
|
|
274
286
|
It walks the last `commit-count` first-parent commits, 60 by default,
|
|
@@ -278,6 +290,52 @@ comment text as a share of added lines.
|
|
|
278
290
|
`generated/`, `vendor/`, `repos/`, `node_modules/` and `dist/` are out
|
|
279
291
|
of reach, so the figures are authored code.
|
|
280
292
|
|
|
293
|
+
## Mutation compare
|
|
294
|
+
|
|
295
|
+
`checks-mutation-compare` gates a pull request on no-regression rather
|
|
296
|
+
than an absolute threshold: the head mutation score may not fall below
|
|
297
|
+
the score at the merge-base.
|
|
298
|
+
|
|
299
|
+
```sh
|
|
300
|
+
checks-mutation-compare <base-report> <head-report> [--advisory]
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
Both reports are Stryker `mutation.json` files. It prints the overall
|
|
304
|
+
score of each report and the per-file scores that differ, and exits 1
|
|
305
|
+
when the head score is below the base score. The score is Stryker's:
|
|
306
|
+
`Killed` and `Timeout` over those plus `Survived` and `NoCoverage`, so
|
|
307
|
+
`CompileError`, `RuntimeError`, `Ignored` and `Pending` mutants leave
|
|
308
|
+
it. Every file in a report counts toward that report's score, including
|
|
309
|
+
files present in only one of the two.
|
|
310
|
+
|
|
311
|
+
`--advisory` prints the same verdict and always exits 0. That is how
|
|
312
|
+
consumers run it for the first month; after that they drop the flag and
|
|
313
|
+
it blocks.
|
|
314
|
+
|
|
315
|
+
Enforcement runs on pull requests, comparing the head report against a
|
|
316
|
+
report built at the merge-base:
|
|
317
|
+
|
|
318
|
+
```yaml
|
|
319
|
+
jobs:
|
|
320
|
+
mutation-compare:
|
|
321
|
+
runs-on: ubuntu-latest
|
|
322
|
+
steps:
|
|
323
|
+
- uses: actions/checkout@v5
|
|
324
|
+
with:
|
|
325
|
+
fetch-depth: 0
|
|
326
|
+
- uses: oven-sh/setup-bun@v2
|
|
327
|
+
- run: bun install --frozen-lockfile
|
|
328
|
+
- run: bunx stryker run
|
|
329
|
+
- run: |
|
|
330
|
+
base="$(git merge-base HEAD origin/main)"
|
|
331
|
+
git worktree add /tmp/mutation-base "$base"
|
|
332
|
+
(cd /tmp/mutation-base && bun install --frozen-lockfile && bunx stryker run)
|
|
333
|
+
- run: bun run checks-mutation-compare --advisory /tmp/mutation-base/reports/mutation/mutation.json reports/mutation/mutation.json
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
The shared Stryker preset's `json` reporter writes
|
|
337
|
+
`reports/mutation/mutation.json` in each worktree.
|
|
338
|
+
|
|
281
339
|
## Why it is shaped this way
|
|
282
340
|
|
|
283
341
|
- `plugins` does not inherit through oxlint `extends`. `rules`,
|
|
@@ -308,9 +366,16 @@ of reach, so the figures are authored code.
|
|
|
308
366
|
consumer's copy is compared key by key against the installed one
|
|
309
367
|
instead. `[test] pathIgnorePatterns` is a real bunfig key, and an empty
|
|
310
368
|
`--path-ignore-patterns` flag overrides the file's own list.
|
|
311
|
-
- The
|
|
312
|
-
|
|
313
|
-
|
|
369
|
+
- The Stryker preset is a JavaScript module, not JSON: Stryker 10 does
|
|
370
|
+
not resolve `extends` in a JSON config, but a `.mjs` config that
|
|
371
|
+
spreads an imported object consumes it. Keys the consumer sets after
|
|
372
|
+
the spread win.
|
|
373
|
+
- Each runnable script ships a `checks-` bin entry, so consumer
|
|
374
|
+
`package.json` scripts call the short name, which the package manager
|
|
375
|
+
puts on `PATH` only there; a shell runs it through `bun run`, which
|
|
376
|
+
never falls back to the registry the way `bunx` does. The `.ts` checks
|
|
377
|
+
keep a `bun` shebang, which needs no build step and no `dist/`
|
|
378
|
+
entry, unlike the oxlint plugin that node loads.
|
|
314
379
|
- `bun` counts as a built-in module. Nothing installed resolves it except
|
|
315
380
|
`@types/bun`, which would otherwise make every runtime `bun` import look
|
|
316
381
|
like a dev-only dependency.
|
|
@@ -344,7 +409,7 @@ a tag off `main` and reruns the build, `dist/` check, lint, typecheck
|
|
|
344
409
|
and tests before it publishes:
|
|
345
410
|
|
|
346
411
|
```sh
|
|
347
|
-
git tag v0.
|
|
412
|
+
git tag v0.3.0 && git push origin v0.3.0
|
|
348
413
|
```
|
|
349
414
|
|
|
350
415
|
The `release` workflow publishes the tagged version through npm
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@avi2dg/checks",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Deterministic checks shared across the captain's TypeScript repos",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -18,10 +18,12 @@
|
|
|
18
18
|
"scripts/lint-coverage.sh",
|
|
19
19
|
"scripts/test-layout.ts",
|
|
20
20
|
"scripts/commit-identity.ts",
|
|
21
|
+
"scripts/mutation-compare.ts",
|
|
21
22
|
"scripts/comments.ts",
|
|
22
23
|
"scripts/comment-gate.ts",
|
|
23
24
|
"scripts/backtest.ts",
|
|
24
25
|
"oxlintrc.json",
|
|
26
|
+
"stryker.preset.js",
|
|
25
27
|
"tsconfig.effect.json",
|
|
26
28
|
"dist/index.js"
|
|
27
29
|
],
|
|
@@ -32,16 +34,26 @@
|
|
|
32
34
|
"./scripts/lint-coverage.sh": "./scripts/lint-coverage.sh",
|
|
33
35
|
"./scripts/test-layout.ts": "./scripts/test-layout.ts",
|
|
34
36
|
"./scripts/commit-identity.ts": "./scripts/commit-identity.ts",
|
|
37
|
+
"./scripts/mutation-compare.ts": "./scripts/mutation-compare.ts",
|
|
35
38
|
"./scripts/comments.ts": "./scripts/comments.ts",
|
|
36
39
|
"./scripts/comment-gate.ts": "./scripts/comment-gate.ts",
|
|
37
40
|
"./scripts/backtest.ts": "./scripts/backtest.ts",
|
|
38
41
|
"./oxlintrc.json": "./oxlintrc.json",
|
|
42
|
+
"./stryker.preset.js": "./stryker.preset.js",
|
|
39
43
|
"./tsconfig.effect.json": "./tsconfig.effect.json",
|
|
40
44
|
"./dist/index.js": "./dist/index.js"
|
|
41
45
|
},
|
|
46
|
+
"bin": {
|
|
47
|
+
"checks-lint-coverage": "scripts/lint-coverage.sh",
|
|
48
|
+
"checks-test-layout": "scripts/test-layout.ts",
|
|
49
|
+
"checks-commit-identity": "scripts/commit-identity.ts",
|
|
50
|
+
"checks-mutation-compare": "scripts/mutation-compare.ts",
|
|
51
|
+
"checks-comment-gate": "scripts/comment-gate.ts",
|
|
52
|
+
"checks-backtest": "scripts/backtest.ts"
|
|
53
|
+
},
|
|
42
54
|
"scripts": {
|
|
43
55
|
"build": "bun build effect-channel/index.ts --outdir dist --target node --format esm",
|
|
44
|
-
"lint": "oxlint --type-aware && ./scripts/lint-coverage.sh && bun scripts/test-layout.ts && bun scripts/commit-identity.ts HEAD && bun scripts/comment-gate.ts HEAD && depcruise --config .dependency-cruiser.cjs effect-channel scripts tests commitlint.config.js dependency-cruiser.config.js .dependency-cruiser.cjs",
|
|
56
|
+
"lint": "oxlint --type-aware && ./scripts/lint-coverage.sh && bun scripts/test-layout.ts && bun scripts/commit-identity.ts HEAD && bun scripts/comment-gate.ts HEAD && depcruise --config .dependency-cruiser.cjs effect-channel scripts tests commitlint.config.js dependency-cruiser.config.js stryker.preset.js .dependency-cruiser.cjs",
|
|
45
57
|
"typecheck": "tsc --noEmit && effect-tsgo diagnostics --project tsconfig.json --format text --strict",
|
|
46
58
|
"test": "bun test --randomize"
|
|
47
59
|
},
|
package/scripts/backtest.ts
CHANGED
|
File without changes
|
package/scripts/comment-gate.ts
CHANGED
|
File without changes
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
|
|
4
|
+
export type Tally = {
|
|
5
|
+
readonly killed: number;
|
|
6
|
+
readonly total: number;
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
export type FileComparison = {
|
|
10
|
+
readonly path: string;
|
|
11
|
+
readonly base: Tally;
|
|
12
|
+
readonly head: Tally;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type Comparison = {
|
|
16
|
+
readonly base: Tally;
|
|
17
|
+
readonly head: Tally;
|
|
18
|
+
readonly files: readonly FileComparison[];
|
|
19
|
+
readonly regression: boolean;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export type Options = {
|
|
23
|
+
readonly basePath: string;
|
|
24
|
+
readonly headPath: string;
|
|
25
|
+
readonly advisory: boolean;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export class ReportError extends Error {}
|
|
29
|
+
|
|
30
|
+
const DETECTED = new Set(["Killed", "Timeout"]);
|
|
31
|
+
const UNDETECTED = new Set(["Survived", "NoCoverage"]);
|
|
32
|
+
const USAGE = "usage: mutation-compare.ts [--advisory] <base-report> <head-report>";
|
|
33
|
+
|
|
34
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
35
|
+
return typeof value === "object" && value !== null;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function parseReport(text: string): Map<string, readonly string[]> {
|
|
39
|
+
let parsed: unknown;
|
|
40
|
+
try {
|
|
41
|
+
parsed = JSON.parse(text);
|
|
42
|
+
} catch {
|
|
43
|
+
throw new ReportError("mutation-compare: report is not valid JSON");
|
|
44
|
+
}
|
|
45
|
+
if (!isRecord(parsed) || !isRecord(parsed["files"])) {
|
|
46
|
+
throw new ReportError("mutation-compare: report has no files table");
|
|
47
|
+
}
|
|
48
|
+
const files = new Map<string, readonly string[]>();
|
|
49
|
+
for (const [path, entry] of Object.entries(parsed["files"])) {
|
|
50
|
+
if (!isRecord(entry) || !Array.isArray(entry["mutants"])) {
|
|
51
|
+
throw new ReportError(`mutation-compare: ${path} has no mutants list`);
|
|
52
|
+
}
|
|
53
|
+
const statuses: string[] = [];
|
|
54
|
+
for (const mutant of entry["mutants"]) {
|
|
55
|
+
if (!isRecord(mutant) || typeof mutant["status"] !== "string") {
|
|
56
|
+
throw new ReportError(`mutation-compare: ${path} carries a mutant without a status`);
|
|
57
|
+
}
|
|
58
|
+
statuses.push(mutant["status"]);
|
|
59
|
+
}
|
|
60
|
+
files.set(path, statuses);
|
|
61
|
+
}
|
|
62
|
+
return files;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function tally(statuses: readonly string[]): Tally {
|
|
66
|
+
let killed = 0;
|
|
67
|
+
let total = 0;
|
|
68
|
+
for (const status of statuses) {
|
|
69
|
+
if (DETECTED.has(status)) killed += 1;
|
|
70
|
+
if (DETECTED.has(status) || UNDETECTED.has(status)) total += 1;
|
|
71
|
+
}
|
|
72
|
+
return { killed, total };
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const EMPTY: Tally = { killed: 0, total: 0 };
|
|
76
|
+
|
|
77
|
+
export function compareReports(
|
|
78
|
+
baseFiles: ReadonlyMap<string, readonly string[]>,
|
|
79
|
+
headFiles: ReadonlyMap<string, readonly string[]>,
|
|
80
|
+
): Comparison {
|
|
81
|
+
const paths = [...new Set([...baseFiles.keys(), ...headFiles.keys()])].sort();
|
|
82
|
+
const files = paths.map((path) => {
|
|
83
|
+
const baseStatuses = baseFiles.get(path);
|
|
84
|
+
const headStatuses = headFiles.get(path);
|
|
85
|
+
return {
|
|
86
|
+
path,
|
|
87
|
+
base: baseStatuses === undefined ? EMPTY : tally(baseStatuses),
|
|
88
|
+
head: headStatuses === undefined ? EMPTY : tally(headStatuses),
|
|
89
|
+
};
|
|
90
|
+
});
|
|
91
|
+
const base = tally([...baseFiles.values()].flat());
|
|
92
|
+
const head = tally([...headFiles.values()].flat());
|
|
93
|
+
return { base, head, files, regression: regressed(base, head) };
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function regressed(base: Tally, head: Tally): boolean {
|
|
97
|
+
if (base.total === 0) return head.total > 0 && head.killed < head.total;
|
|
98
|
+
if (head.total === 0) return false;
|
|
99
|
+
return head.killed * base.total < base.killed * head.total;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function points(count: Tally): number {
|
|
103
|
+
return count.total === 0 ? 100 : (100 * count.killed) / count.total;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function percent(count: Tally): string {
|
|
107
|
+
return count.total === 0 ? "n/a" : `${points(count).toFixed(2)}%`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function describe(count: Tally): string {
|
|
111
|
+
return `${percent(count)} (${count.killed}/${count.total})`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function deltaPoints(base: Tally, head: Tally): string {
|
|
115
|
+
const delta = points(head) - points(base);
|
|
116
|
+
return `${delta < 0 ? "-" : "+"}${Math.abs(delta).toFixed(2)}pp`;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function formatComparison(comparison: Comparison, advisory: boolean): string {
|
|
120
|
+
const changed = comparison.files.filter(
|
|
121
|
+
(file) => file.base.killed !== file.head.killed || file.base.total !== file.head.total,
|
|
122
|
+
);
|
|
123
|
+
const unchanged = comparison.files.length - changed.length;
|
|
124
|
+
const lines = [
|
|
125
|
+
`mutation-compare: base ${describe(comparison.base)} head ${describe(comparison.head)} delta ${deltaPoints(comparison.base, comparison.head)}`,
|
|
126
|
+
];
|
|
127
|
+
for (const file of changed) {
|
|
128
|
+
lines.push(` ${file.path}: ${describe(file.base)} -> ${describe(file.head)}`);
|
|
129
|
+
}
|
|
130
|
+
if (unchanged > 0) lines.push(` ${unchanged} unchanged file(s)`);
|
|
131
|
+
if (comparison.regression) {
|
|
132
|
+
lines.push(`mutation-compare: REGRESSION (${deltaPoints(comparison.base, comparison.head)})${advisory ? " in advisory mode, exit 0" : ""}`);
|
|
133
|
+
} else {
|
|
134
|
+
lines.push(`mutation-compare: no regression${advisory ? " (advisory mode, exit 0)" : ""}`);
|
|
135
|
+
}
|
|
136
|
+
return lines.join("\n");
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export function parseArgs(argv: readonly string[]): Options {
|
|
140
|
+
let advisory = false;
|
|
141
|
+
const paths: string[] = [];
|
|
142
|
+
for (const arg of argv) {
|
|
143
|
+
if (arg === "--advisory") advisory = true;
|
|
144
|
+
else if (arg.startsWith("--")) throw new ReportError(`${USAGE}: unknown flag ${arg}`);
|
|
145
|
+
else paths.push(arg);
|
|
146
|
+
}
|
|
147
|
+
if (paths.length !== 2 || paths[0] === undefined || paths[1] === undefined) throw new ReportError(USAGE);
|
|
148
|
+
return { basePath: paths[0], headPath: paths[1], advisory };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export function exitFor(comparison: Comparison, advisory: boolean): number {
|
|
152
|
+
return comparison.regression && !advisory ? 1 : 0;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
async function load(path: string): Promise<string> {
|
|
156
|
+
try {
|
|
157
|
+
return await readFile(path, "utf8");
|
|
158
|
+
} catch {
|
|
159
|
+
throw new ReportError(`mutation-compare: cannot read ${path}`);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (import.meta.main) {
|
|
164
|
+
try {
|
|
165
|
+
const options = parseArgs(process.argv.slice(2));
|
|
166
|
+
const comparison = compareReports(parseReport(await load(options.basePath)), parseReport(await load(options.headPath)));
|
|
167
|
+
console.log(formatComparison(comparison, options.advisory));
|
|
168
|
+
process.exit(exitFor(comparison, options.advisory));
|
|
169
|
+
} catch (error) {
|
|
170
|
+
console.error(error instanceof ReportError ? error.message : `mutation-compare: ${String(error)}`);
|
|
171
|
+
process.exit(2);
|
|
172
|
+
}
|
|
173
|
+
}
|
package/scripts/test-layout.ts
CHANGED
|
@@ -40,6 +40,7 @@ const BANNED_GLOBAL_CALLS: readonly string[] = ["fetch"];
|
|
|
40
40
|
|
|
41
41
|
export const REQUIRED_TEST_SCRIPT = "bun test --randomize";
|
|
42
42
|
export const LAYOUT_CHECK_MARK = "scripts/test-layout.ts";
|
|
43
|
+
export const LAYOUT_CHECK_BIN = "checks-test-layout";
|
|
43
44
|
|
|
44
45
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
45
46
|
return typeof value === "object" && value !== null;
|
|
@@ -215,11 +216,11 @@ export function scriptViolations(manifest: unknown): readonly Violation[] {
|
|
|
215
216
|
message: `scripts.test must be exactly "${REQUIRED_TEST_SCRIPT}", found ${JSON.stringify(test ?? null)}`,
|
|
216
217
|
});
|
|
217
218
|
}
|
|
218
|
-
if (typeof lint !== "string" || !lint.includes(LAYOUT_CHECK_MARK)) {
|
|
219
|
+
if (typeof lint !== "string" || (!lint.includes(LAYOUT_CHECK_MARK) && !lint.includes(LAYOUT_CHECK_BIN))) {
|
|
219
220
|
violations.push({
|
|
220
221
|
file,
|
|
221
222
|
line: undefined,
|
|
222
|
-
message: `scripts.lint must run the layout check: add "
|
|
223
|
+
message: `scripts.lint must run the layout check: add "${LAYOUT_CHECK_BIN}"`,
|
|
223
224
|
});
|
|
224
225
|
}
|
|
225
226
|
return violations;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
packageManager: "npm",
|
|
3
|
+
plugins: ["@stryker-mutator/*", "@hughescr/stryker-bun-runner"],
|
|
4
|
+
testRunner: "bun",
|
|
5
|
+
bun: { timeout: 60000 },
|
|
6
|
+
inPlace: true,
|
|
7
|
+
tempDirName: "../.stryker-tmp",
|
|
8
|
+
coverageAnalysis: "perTest",
|
|
9
|
+
reporters: ["html", "json", "clear-text", "progress"],
|
|
10
|
+
timeoutMS: 60000,
|
|
11
|
+
concurrency: 8,
|
|
12
|
+
thresholds: { high: 85, low: 70, break: null },
|
|
13
|
+
};
|