@droneey/devkit-ts-stryker 1.29.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/LICENSE.md ADDED
@@ -0,0 +1,9 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025-PRESENT Dmytro Kurovskyi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,46 @@
1
+ # @droneey/devkit-ts-stryker
2
+
3
+ Shared [Stryker](https://stryker-mutator.io) configuration for Bun projects, and `mutation-check`, which mutates only the lines a change touches.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ bun add -d @droneey/devkit-ts-stryker @stryker-mutator/core
9
+ ```
10
+
11
+ Stryker has no `extends`, so `stryker.config.mjs` spreads the configuration and adds what the project mutates:
12
+
13
+ ```js
14
+ import { config } from '@droneey/devkit-ts-stryker';
15
+
16
+ export default {
17
+ ...config,
18
+ mutate: ['src/**/*.ts', '!src/**/__tests__/**'],
19
+ thresholds: { high: 100, low: 100, break: 100 },
20
+ };
21
+ ```
22
+
23
+ `config` runs the tests through Bun's command runner with `bunfig.mutation.toml` (copy `packages/typescript/templates/bun/bunfig.mutation.toml`: tests from `src`, no coverage). It works in `.stryker-tmp`, leaves the linked `.devkit` out of its sandbox and skips Stryker's tsconfig step, which TypeScript 7 does not support.
24
+
25
+ It keeps no incremental report: with the command runner Stryker cannot tell which tests a mutant meets, so a cached result can hide a survivor or report one that is gone. Mutating only the changed lines keeps a run short instead.
26
+
27
+ A Node project sets its own `testRunner` over `config`, such as `vitest` with its Stryker plugin.
28
+
29
+ ## The mutation check
30
+
31
+ ```json
32
+ {
33
+ "scripts": {
34
+ "mutation:check": "mutation-check",
35
+ "mutation:all": "mutation-check all"
36
+ }
37
+ }
38
+ ```
39
+
40
+ `mutation-check` compares the working tree with `origin/main` and mutates, among the files the configuration's `mutate` patterns name:
41
+
42
+ - the lines each change touches (`file:start-end`);
43
+ - a new file, whole;
44
+ - the file a changed spec proves, whole: `src/__tests__/order.utils.test.ts` proves `src/order.utils.ts`, so a weakened test cannot pass unseen.
45
+
46
+ A change that touches no mutated line runs no mutant. `mutation-check all` mutates everything the configuration names. The bin is built to JavaScript on publish, so it runs under Node and Bun alike.
package/dist/main.js ADDED
@@ -0,0 +1,113 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/main.ts
4
+ import { spawnSync } from "node:child_process";
5
+ import { existsSync, readFileSync } from "node:fs";
6
+ import { resolve } from "node:path";
7
+ import { pathToFileURL } from "node:url";
8
+
9
+ // src/changes.ts
10
+ import { matchesGlob } from "node:path";
11
+ var SECTION = /^diff --git /m;
12
+ var FILE = /\+\+\+ b\/(.+)/;
13
+ var HUNKS = /^@@ -\d+(?:,\d+)? \+(\d+)(?:,(\d+))? @@/gm;
14
+ var SPEC = /\/__tests__\/([^/]+?)(?:\.integration)?\.(?:test|spec)\.(tsx?)$/;
15
+ var linesOf = (section) => [
16
+ ...section.matchAll(HUNKS)
17
+ ].flatMap(([, first, length]) => {
18
+ const start = Number(first);
19
+ const count = Number(length ?? "1");
20
+ return count > 0 ? [
21
+ {
22
+ end: start + count - 1,
23
+ start
24
+ }
25
+ ] : [];
26
+ });
27
+ var changedLines = (diff) => new Map(diff.split(SECTION).flatMap((section) => {
28
+ const path = section.match(FILE)?.[1];
29
+ return path === undefined ? [] : [
30
+ [
31
+ path,
32
+ linesOf(section)
33
+ ]
34
+ ];
35
+ }));
36
+ var provenFiles = (paths) => paths.filter((path) => SPEC.test(path)).map((path) => path.replace(SPEC, "/$1.$2"));
37
+ var isMutated = (input) => input.mutate.some((pattern) => !pattern.startsWith("!") && matchesGlob(input.path, pattern)) && !input.mutate.some((pattern) => pattern.startsWith("!") && matchesGlob(input.path, pattern.slice(1)));
38
+ var mutateTargets = (changes) => {
39
+ const ranges = changedLines(changes.diff);
40
+ const whole = new Set([
41
+ ...changes.untracked,
42
+ ...provenFiles([
43
+ ...ranges.keys(),
44
+ ...changes.untracked
45
+ ])
46
+ ]);
47
+ const mutated = (path) => changes.exists(path) && isMutated({
48
+ mutate: changes.mutate,
49
+ path
50
+ });
51
+ return [
52
+ ...[
53
+ ...whole
54
+ ].filter(mutated),
55
+ ...[
56
+ ...ranges
57
+ ].filter(([path]) => !whole.has(path) && mutated(path)).flatMap(([path, lines]) => lines.map(({ end, start }) => `${path}:${start}-${end}`))
58
+ ];
59
+ };
60
+
61
+ // src/main.ts
62
+ var BASE = "origin/main";
63
+ var CONFIG_FILES = [
64
+ "stryker.config.mjs",
65
+ "stryker.config.js",
66
+ "stryker.config.json"
67
+ ];
68
+ var git = (args) => spawnSync("git", args, {
69
+ encoding: "utf8"
70
+ }).stdout;
71
+ var loadConfig = async () => {
72
+ const file = CONFIG_FILES.find((name) => existsSync(name));
73
+ if (file === undefined) {
74
+ throw new Error(`mutation-check: no Stryker configuration (${CONFIG_FILES.join(", ")})`);
75
+ }
76
+ return file.endsWith(".json") ? JSON.parse(readFileSync(file, "utf8")) : (await import(pathToFileURL(resolve(file)).href)).default;
77
+ };
78
+ var runStryker = (args) => spawnSync("stryker", [
79
+ "run",
80
+ ...args
81
+ ], {
82
+ stdio: "inherit"
83
+ }).status ?? 1;
84
+ if (process.argv.includes("all")) {
85
+ process.exitCode = runStryker([]);
86
+ } else {
87
+ const targets = mutateTargets({
88
+ diff: git([
89
+ "diff",
90
+ "-U0",
91
+ "--no-color",
92
+ "--diff-filter=ACMR",
93
+ BASE
94
+ ]),
95
+ exists: existsSync,
96
+ mutate: (await loadConfig()).mutate ?? [],
97
+ untracked: git([
98
+ "ls-files",
99
+ "--others",
100
+ "--exclude-standard"
101
+ ]).split(`
102
+ `).filter((path) => path !== "")
103
+ });
104
+ if (targets.length === 0) {
105
+ process.stdout.write(`mutation: no line to mutate changed
106
+ `);
107
+ } else {
108
+ process.exitCode = runStryker([
109
+ "--mutate",
110
+ targets.join(",")
111
+ ]);
112
+ }
113
+ }
package/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ export declare const config: Readonly<
2
+ Record<string, unknown> & {
3
+ ignorePatterns: readonly string[];
4
+ }
5
+ >;
package/index.mjs ADDED
@@ -0,0 +1,24 @@
1
+ const config = {
2
+ testRunner: 'command',
3
+ commandRunner: {
4
+ command: 'bun --config=./bunfig.mutation.toml test --bail',
5
+ },
6
+ coverageAnalysis: 'off',
7
+ ignorePatterns: [
8
+ '/.devkit',
9
+ ],
10
+ reporters: [
11
+ 'clear-text',
12
+ ],
13
+ clearTextReporter: {
14
+ allowColor: false,
15
+ logTests: false,
16
+ },
17
+ tempDirName: '.stryker-tmp',
18
+ // TypeScript 7 has no parseConfigFileTextToJson: a missing file keeps Stryker from rewriting tsconfig.
19
+ tsconfigFile: 'stryker-has-no-tsconfig.json',
20
+ // biome-ignore lint/style/useNamingConvention: Stryker names the option timeoutMS
21
+ timeoutMS: 30_000,
22
+ };
23
+
24
+ export { config };
package/package.json ADDED
@@ -0,0 +1,48 @@
1
+ {
2
+ "name": "@droneey/devkit-ts-stryker",
3
+ "version": "1.29.0",
4
+ "description": "Shared Stryker configuration and a mutation check that mutates only the lines a change touches.",
5
+ "keywords": [
6
+ "config",
7
+ "mutation-testing",
8
+ "shared",
9
+ "stryker"
10
+ ],
11
+ "homepage": "https://github.com/droneey/devkit/blob/main/packages/typescript/libs/stryker#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/droneey/devkit/issues"
14
+ },
15
+ "license": "MIT",
16
+ "author": {
17
+ "name": "Dmytro Kurovskyi",
18
+ "url": "https://kurovskyi.dev"
19
+ },
20
+ "repository": {
21
+ "type": "git",
22
+ "url": "https://github.com/droneey/devkit.git",
23
+ "directory": "packages/typescript/libs/stryker"
24
+ },
25
+ "type": "module",
26
+ "bin": {
27
+ "mutation-check": "dist/main.js"
28
+ },
29
+ "types": "./index.d.ts",
30
+ "exports": {
31
+ ".": {
32
+ "types": "./index.d.ts",
33
+ "default": "./index.mjs"
34
+ }
35
+ },
36
+ "files": [
37
+ "dist/*",
38
+ "index.d.ts",
39
+ "index.mjs"
40
+ ],
41
+ "scripts": {
42
+ "build": "rm -rf dist && bun build --target=node --packages=external ./src/main.ts --outdir dist",
43
+ "prepublishOnly": "bun run build"
44
+ },
45
+ "peerDependencies": {
46
+ "@stryker-mutator/core": ">=10.0.0"
47
+ }
48
+ }