Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
14 commits
Select commit Hold shift + click to select a range
80476f8
fix(repo): honour per-project passWithNoTests in the vm harness
systemfsoftware-maker Sep 25, 2026
0b54bfd
fix(repo): serve the harness vitest to every module a vm run loads
systemfsoftware-maker Sep 25, 2026
19cea13
fix(repo): keep import.meta.vitest text out of the vm runner rewrite
systemfsoftware-maker Sep 25, 2026
0cff2e6
fix(repo): locate the vm harness from its own module, not the project…
systemfsoftware-maker Sep 25, 2026
9eee93c
fix(repo): restore defineConfig and mergeConfig on the config subpath
systemfsoftware-maker Sep 25, 2026
bbc77cd
fix(repo): report failing test names and messages on a failed dry run
systemfsoftware-maker Sep 25, 2026
36ccd17
test(repo): record the vm parity baseline once per fixture
systemfsoftware-maker Sep 25, 2026
337a2f9
refactor(repo): declare the TypeScript compiler service as a class
systemfsoftware-maker Sep 25, 2026
5ff5f1a
test(repo): select concurrency mutators over the fixtures that hold them
systemfsoftware-maker Sep 25, 2026
d548671
test(repo): draw test-contribution commands from the fields the judge…
systemfsoftware-maker Sep 25, 2026
da0259d
test(repo): prove concurrent vm runners overlap with a rendezvous
systemfsoftware-maker Sep 25, 2026
e700b9f
test(repo): keep worker-options law keys clear of the V8 JSON key-cac…
systemfsoftware-maker Sep 25, 2026
4fc29f1
test(repo): resolve the pass-with-no-tests sandbox to its real path
systemfsoftware-maker Sep 25, 2026
3fd6adf
build(repo): declare effect a required peer of stryker-js
systemfsoftware-maker Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/dry-run-failure-names-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@systemfsoftware/stryker-js': patch
---

A failed dry run now says which tests broke and why. The refusal names every failing test with its failure message in the error output and in the log at the default level, instead of reporting only how many failed.
10 changes: 10 additions & 0 deletions .changeset/restore-config-authoring-helpers.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
---
'@systemfsoftware/stryker-js': major
---

Restore `defineConfig` and `mergeConfig` on `@systemfsoftware/stryker-js/config`. `defineConfig` accepts an options object, a promise of one, or a `(env: ConfigEnv) => …` factory, as in 10.1.1, and `mergeConfig(defaults, overrides)` composes presets again. `StrykerConfig` is again the partial-options type, so `const config: StrykerConfig = defineConfig({ … })` typechecks. Config files written against the 10.1.1 `./config` surface load and typecheck without edits.

This removes the 11.0.0 `StrykerConfig` class value. If you adopted it:

- Replace `StrykerConfig.define(…)` with `defineConfig(…)` and `StrykerConfig.merge(…)` with `mergeConfig(…)`, imported from `@systemfsoftware/stryker-js/config`.
- Replace `StrykerConfig.createDefaultOptions()` and `StrykerConfig.defaultOptions` with `Configuration.createDefaultOptions()` and `Configuration.defaultOptions` from the package entry point.
5 changes: 5 additions & 0 deletions .changeset/stryker-js-effect-required-peer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@systemfsoftware/stryker-js": patch
---

Installing `@systemfsoftware/stryker-js` on its own now works. `effect` was declared an optional peer even though Stryker imports it at runtime. Package managers never install optional peers, so a project without `effect` failed with `Failed to read config` before the run started. `effect` is now a required peer: pnpm and npm install it automatically, and a project that already uses Effect shares its own copy with Stryker.
5 changes: 5 additions & 0 deletions .changeset/test-contribution-property-input-schema.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@systemfsoftware/stryker-test-contribution': none
---

Tests only, no release: the `judgeTestContribution` laws now generate their commands from a purpose-built schema of the fields the judgement reads instead of deriving them from the command's whole mutation-test report schema, which keeps every property inside its CI run budget.
5 changes: 5 additions & 0 deletions .changeset/vm-harness-pass-with-no-tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@systemfsoftware/stryker-vm-harness': patch
---

The vm runner now honours Vitest's `passWithNoTests`. A test file that registers no tests no longer fails the dry run when its Vitest project sets `passWithNoTests: true`, and still fails with `No test suite found in file <path>` when the option is false or unset. The option applies per project, as in Vitest: a root-level `test.passWithNoTests` covers every project, and one project in `test.projects` can opt in while another does not.
5 changes: 5 additions & 0 deletions .changeset/vm-harness-resolves-itself.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@systemfsoftware/stryker-vm-harness': patch
---

Projects that install `@systemfsoftware/stryker-js` and `vitest`, but not the vm runner package directly, now complete a `testRunner: 'vm'` dry run under pnpm's default isolated `node_modules` layout, instead of failing with a `Cannot find module` error for the vm runner's own package. The project's own `vitest` is still the one that runs your tests.
5 changes: 5 additions & 0 deletions .changeset/vm-runner-marker-text-preserved.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@systemfsoftware/stryker-vm-harness': patch
---

The vm runner no longer rewrites `import.meta.vitest` text that is not an expression. A string, template literal, comment or regex literal that mentions `import.meta.vitest` now reaches your code unchanged, so suites whose fixtures contain it, such as lint-rule tests, pass on the vm runner as they do under Vitest. In-source `if (import.meta.vitest)` tests still register and run, and `import.meta.vitest` inside a template `${…}` substitution still evaluates to the Vitest API.
5 changes: 5 additions & 0 deletions .changeset/vm-runner-serves-vitest-outside-root.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@systemfsoftware/stryker-vm-harness': patch
---

The vm runner now gives the Vitest API to every module a suite loads that imports `vitest`, not only to files inside the project directory. A Vitest `setupFiles` entry, helper module or workspace package outside the project used to receive a runner-less `vitest` and stop the whole run with "Vitest failed to find the runner"; its `beforeEach` and `it` calls now register against the current test file, as they do under Vitest. Dependencies under `node_modules` stay shared across test files, and `@effect/vitest` and `@systemfsoftware/effect-gherkin-spec` behave as before.
5 changes: 5 additions & 0 deletions .changeset/worker-options-law-domain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@systemfsoftware/stryker-js-plugin-runtime": none
---

`WorkerOptionsWire` gained an internal declaration so its round-trip law suite draws option keys that the JSON parser reads back unchanged on V8 13.6 (Node 24 and 26). Decoding and encoding worker options are unchanged, and every exported name, type and runtime behaviour is identical, so no release is warranted.
60 changes: 30 additions & 30 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@ A high mutation score guarantees test assertions catch regressions rather than j

## ⚡ Dual-Engine Workflow: In-Process `vm` vs Vitest Worker Sandbox

Mutation testing feedback latency slows down local development when every mutant forks new child processes. Stryker JS Effect provides a dual execution architecture configured through `StrykerConfig.define`:
Mutation testing feedback latency slows down local development when every mutant forks new child processes. Stryker JS Effect provides a dual execution architecture configured through `defineConfig`:

1. **Local Developer Loop (`testRunner: 'vm'`)**: The default runner. Runs your Vitest suites in-process in a worker thread per test runner, loading each test file as native ESM through Node's module hooks. No child process and no bundler step; the matchers, mocks, and snapshots come from the `vitest` installed in your project.
2. **CI Regression Gate (`testRunner: 'vitest'`)**: Executes integration suites in isolated Vitest worker threads with per-test coverage analysis.

```ts
import { StrykerConfig } from '@systemfsoftware/stryker-js/config'
import { defineConfig } from '@systemfsoftware/stryker-js/config'

export default StrykerConfig.define(({ isCi }) => ({
export default defineConfig(({ isCi }) => ({
// In-process vm locally for instant feedback; isolated Vitest workers in CI
testRunner: isCi ? 'vitest' : 'vm',
checkers: ['typescript'],
Expand Down Expand Up @@ -81,9 +81,9 @@ pnpm add -D @systemfsoftware/stryker-js \
Recommended for integration suites requiring full Vitest mocking APIs, DOM emulation, or browser runners:

```ts
import { StrykerConfig } from '@systemfsoftware/stryker-js/config'
import { defineConfig } from '@systemfsoftware/stryker-js/config'

export default StrykerConfig.define({
export default defineConfig({
testRunner: 'vitest',
checkers: ['typescript'],
plugins: [
Expand All @@ -103,9 +103,9 @@ pnpm add -D @systemfsoftware/stryker-js @systemfsoftware/stryker-js-typescript-c
```

```ts
import { StrykerConfig } from '@systemfsoftware/stryker-js/config'
import { defineConfig } from '@systemfsoftware/stryker-js/config'

export default StrykerConfig.define({
export default defineConfig({
testRunner: 'vm',
checkers: ['typescript'],
plugins: [
Expand All @@ -121,9 +121,9 @@ export default StrykerConfig.define({
Execute any test framework (Jest, Mocha, Node test runner) via custom shell commands:

```ts
import { StrykerConfig } from '@systemfsoftware/stryker-js/config'
import { defineConfig } from '@systemfsoftware/stryker-js/config'

export default StrykerConfig.define({
export default defineConfig({
testRunner: 'command',
commandRunner: {
command: 'pnpm test',
Expand All @@ -147,9 +147,9 @@ pnpm add -D @systemfsoftware/stryker-js-svelte # .svelte (Svelte 5 only)
```

```ts
import { StrykerConfig } from '@systemfsoftware/stryker-js/config'
import { defineConfig } from '@systemfsoftware/stryker-js/config'

export default StrykerConfig.define({
export default defineConfig({
testRunner: 'vitest',
plugins: ['@systemfsoftware/stryker-js-angular'],
mutate: ['src/**/*.html', 'src/**/*.vue'],
Expand Down Expand Up @@ -186,9 +186,9 @@ pnpm add -D @systemfsoftware/stryker-js \
```

```ts
import { StrykerConfig } from '@systemfsoftware/stryker-js/config'
import { defineConfig } from '@systemfsoftware/stryker-js/config'

export default StrykerConfig.define(({ isCi }) => ({
export default defineConfig(({ isCi }) => ({
testRunner: 'vitest',
checkers: ['typescript'],
plugins: [
Expand Down Expand Up @@ -218,12 +218,12 @@ export default StrykerConfig.define(({ isCi }) => ({

### Environment-Aware Configuration

`StrykerConfig.define` accepts a callback instead of a static object. It receives a `ConfigEnv` of `{ command, isDryRun, mode, isCi }`, where `command` is `'run'` or `'merge-reports'` and `mode` is the resolved output mode:
`defineConfig` accepts a callback instead of a static object. It receives a `ConfigEnv` of `{ command, isDryRun, mode, isCi }`, where `command` is `'run'` or `'merge-reports'` and `mode` is the resolved output mode:

```ts
import { StrykerConfig } from '@systemfsoftware/stryker-js/config'
import { defineConfig } from '@systemfsoftware/stryker-js/config'

export default StrykerConfig.define(({ command, isCi, isDryRun }) => ({
export default defineConfig(({ command, isCi, isDryRun }) => ({
testRunner: 'vitest',
plugins: ['@systemfsoftware/stryker-js-vitest-runner'],
mutate: ['src/**/*.ts', '!src/**/*.test.ts'],
Expand Down Expand Up @@ -300,20 +300,20 @@ $ STRYKER_MODE=machine pnpm exec stryker run

## 📦 Workspace Packages

| Package | Purpose |
| ------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| [`@systemfsoftware/stryker-js`](packages/stryker-js) | Flagship CLI binary, run engine, and `./config` authoring surface (`StrykerConfig.define`, `StrykerConfig.merge`) |
| [`@systemfsoftware/stryker-js-vitest-runner`](packages/stryker-js-vitest-runner) | Vitest test runner plugin with sandbox isolation and per-test coverage analysis |
| [`@systemfsoftware/stryker-js-typescript-checker`](packages/stryker-js-typescript-checker) | TypeScript type checker plugin rejecting uncompilable mutants before running tests |
| [`@systemfsoftware/stryker-js-html-reporter`](packages/stryker-js-html-reporter) | Interactive HTML mutation report generator (`reports/mutation/index.html`) |
| [`@systemfsoftware/stryker-js-angular`](packages/frameworks/angular) | Framework plugin instrumenting `.html`, `.htm`, and `.vue` script regions |
| [`@systemfsoftware/stryker-js-svelte`](packages/frameworks/svelte) | Framework plugin instrumenting Svelte 5 `.svelte` scripts and template expressions |
| [`@systemfsoftware/stryker-framework-interface`](packages/frameworks/interface) | Types-only `Framework` contract a framework plugin implements |
| [`@systemfsoftware/stryker-ignorer-kit`](packages/ignorers/kit) | Authoring kit (`defineIgnorer`) and test harness (`testIgnorer`) for custom ignorers |
| [`@systemfsoftware/stryker-ignorer-interface`](packages/ignorers/interface) | AST node types and `Ignorer` contract |
| [`@systemfsoftware/stryker-ignorer-effect-schema-declarations`](packages/ignorers/effect-schema-declarations) | Ignorer filtering equivalent mutants on Effect Schema and Brand declarations |
| [`@systemfsoftware/stryker-ignorer-in-source-vitest-block`](packages/ignorers/in-source-vitest-block) | Ignorer removing unreachable mutants inside `if (import.meta.vitest)` blocks |
| [`@systemfsoftware/stryker-test-contribution`](packages/stryker-test-contribution) | Test suite hygiene plugin enforcing unique mutant kills per test file |
| Package | Purpose |
| ------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| [`@systemfsoftware/stryker-js`](packages/stryker-js) | Flagship CLI binary, run engine, and `./config` authoring surface (`defineConfig`, `mergeConfig`) |
| [`@systemfsoftware/stryker-js-vitest-runner`](packages/stryker-js-vitest-runner) | Vitest test runner plugin with sandbox isolation and per-test coverage analysis |
| [`@systemfsoftware/stryker-js-typescript-checker`](packages/stryker-js-typescript-checker) | TypeScript type checker plugin rejecting uncompilable mutants before running tests |
| [`@systemfsoftware/stryker-js-html-reporter`](packages/stryker-js-html-reporter) | Interactive HTML mutation report generator (`reports/mutation/index.html`) |
| [`@systemfsoftware/stryker-js-angular`](packages/frameworks/angular) | Framework plugin instrumenting `.html`, `.htm`, and `.vue` script regions |
| [`@systemfsoftware/stryker-js-svelte`](packages/frameworks/svelte) | Framework plugin instrumenting Svelte 5 `.svelte` scripts and template expressions |
| [`@systemfsoftware/stryker-framework-interface`](packages/frameworks/interface) | Types-only `Framework` contract a framework plugin implements |
| [`@systemfsoftware/stryker-ignorer-kit`](packages/ignorers/kit) | Authoring kit (`defineIgnorer`) and test harness (`testIgnorer`) for custom ignorers |
| [`@systemfsoftware/stryker-ignorer-interface`](packages/ignorers/interface) | AST node types and `Ignorer` contract |
| [`@systemfsoftware/stryker-ignorer-effect-schema-declarations`](packages/ignorers/effect-schema-declarations) | Ignorer filtering equivalent mutants on Effect Schema and Brand declarations |
| [`@systemfsoftware/stryker-ignorer-in-source-vitest-block`](packages/ignorers/in-source-vitest-block) | Ignorer removing unreachable mutants inside `if (import.meta.vitest)` blocks |
| [`@systemfsoftware/stryker-test-contribution`](packages/stryker-test-contribution) | Test suite hygiene plugin enforcing unique mutant kills per test file |

---

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,9 @@ $$\text{Runner}(E) = \begin{cases} \text{In-Process Worker-Thread `vm`}, & E \ne
- **CI (`testRunner: 'vitest'`)**: Executes within isolated worker processes, capturing comprehensive per-test coverage analysis and supporting complete mock/DOM environments.

```ts
import { StrykerConfig } from '@systemfsoftware/stryker-js/config'
import { defineConfig } from '@systemfsoftware/stryker-js/config'

export default StrykerConfig.define(({ isCi }) => ({
export default defineConfig(({ isCi }) => ({
testRunner: isCi ? 'vitest' : 'vm',
checkers: ['typescript'],
plugins: [
Expand All @@ -52,7 +52,7 @@ export default StrykerConfig.define(({ isCi }) => ({
Installed packages take the bare name, resolved from the project under test. A `file://` URL to a local build stays accepted:

```ts
export default StrykerConfig.define({
export default defineConfig({
plugins: ['@systemfsoftware/stryker-js-vitest-runner'],
})
```
Expand Down
4 changes: 2 additions & 2 deletions packages/frameworks/angular/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@ pnpm add -D @systemfsoftware/stryker-js-angular
Add the package name to `plugins` in your StrykerJS config:

```ts
import { StrykerConfig } from '@systemfsoftware/stryker-js/config'
import { defineConfig } from '@systemfsoftware/stryker-js/config'

export default StrykerConfig.define({
export default defineConfig({
testRunner: 'vitest',
plugins: ['@systemfsoftware/stryker-js-angular'],
mutate: [
Expand Down
4 changes: 2 additions & 2 deletions packages/frameworks/svelte/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@ pnpm add -D @systemfsoftware/stryker-js-svelte
Add the package name to `plugins` in your StrykerJS config:

```ts
import { StrykerConfig } from '@systemfsoftware/stryker-js/config'
import { defineConfig } from '@systemfsoftware/stryker-js/config'

export default StrykerConfig.define({
export default defineConfig({
testRunner: 'vitest',
plugins: ['@systemfsoftware/stryker-js-svelte'],
mutate: ['src/**/*.svelte'],
Expand Down
4 changes: 2 additions & 2 deletions packages/ignorers/effect-schema-declarations/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ pnpm add -D @systemfsoftware/stryker-ignorer-effect-schema-declarations
Add the package name to `ignorers`. Every ignorer the module exports is then active.

```ts
import { StrykerConfig } from '@systemfsoftware/stryker-js/config'
import { defineConfig } from '@systemfsoftware/stryker-js/config'

export default StrykerConfig.define({
export default defineConfig({
testRunner: {
plugin: '@systemfsoftware/stryker-js-vitest-runner',
},
Expand Down
4 changes: 2 additions & 2 deletions packages/ignorers/in-source-vitest-block/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@ pnpm add -D @systemfsoftware/stryker-ignorer-in-source-vitest-block
Add the package name to `ignorers`. Every ignorer the module exports is then active.

```ts
import { StrykerConfig } from '@systemfsoftware/stryker-js/config'
import { defineConfig } from '@systemfsoftware/stryker-js/config'

export default StrykerConfig.define({
export default defineConfig({
testRunner: {
plugin: '@systemfsoftware/stryker-js-vitest-runner',
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,19 @@ export const effectConcurrencyFixtureFiles = Effect.flatMap(
})),
)),
)

const CONCURRENCY_SELECTION_FIXTURE_NAMES: readonly string[] = [
'import-style-named.ts',
'import-style-aliased.ts',
'import-style-effect-namespace.ts',
'import-style-module-namespace.ts',
'import-style-bare-function.ts',
'import-style-bare-function-no-binding.ts',
'synchronization-removal.ts',
'refusals.ts',
]

export const effectConcurrencySelectionFixtureFiles = Effect.forEach(
CONCURRENCY_SELECTION_FIXTURE_NAMES,
(name) => Effect.map(effectConcurrencyFixtureContent(name), (content) => ({ name, content })),
)
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@ import { Gherkin, Given, it, makeFeature, Then, When } from '@systemfsoftware/ef
import { Instrument } from '@systemfsoftware/stryker-js-instrumenter'
import { Effect } from 'effect'

import { effectConcurrencyFixtureFiles, type FixtureFile } from './__fixtures__/effect-concurrency-files.js'
import {
effectConcurrencyFixtureFiles,
effectConcurrencySelectionFixtureFiles,
type FixtureFile,
} from './__fixtures__/effect-concurrency-files.js'
import { instrument } from './__fixtures__/instrument.js'

const ATOMIC_UPDATE_SPLIT = 'AtomicUpdateSplit'
Expand Down Expand Up @@ -165,9 +169,11 @@ Feature('Choosing extra concurrency mutations by name')
scenario(
'A run that names some concurrency mutators turns on exactly the ones it named and nothing else',
Gherkin.Do.pipe(
Given('every Effect concurrency fixture has been read from disk')(
Given(
'the fixtures holding a placement site for each concurrency mutator, and a bare update holding none, have been read from disk',
)(
'fixtures',
() => effectConcurrencyFixtureFiles,
() => effectConcurrencySelectionFixtureFiles,
),
When('the fixtures are instrumented once without extras and once for every choice of concurrency mutators')(
'runs',
Expand Down
Loading
Loading