Problem Statement
When you run stryker run, the CLI tells the engine where to find the built-in reporters (html, json, progress) by turning package names into file:// URLs, which the engine then imports at runtime. So the CLI only works when @systemfsoftware/stryker-js-html-reporter and @systemfsoftware/stryker-js-engine happen to be installed beside it under those exact names. Package the CLI so it carries its own code and those lookups fail. Built-in reporters are part of the product, not user extensions, so they should be imported in TypeScript and handed to the engine as values.
Goal
stryker run loads its built-in reporters through static imports: no reporter package is resolved by name at runtime, HostOptions carries no module-specifier channel for them, and plugins the user configures in stryker.config.json still load by name from the project.
Evidence: Current Behavior
packages/stryker-js/stryker-js-cli/src/Cli.ts:1119-1121 — the CLI builds the descriptors:
reporterPluginModules: [
import.meta.resolve('@systemfsoftware/stryker-js-html-reporter'),
import.meta.resolve('@systemfsoftware/stryker-js-engine/builtin-reporters'),
],
packages/stryker-js/stryker-js-engine/src/Run.ts:399-400 — the engine merges them with user plugins and dynamically imports every descriptor:
const descriptors: readonly string[] = [...pluginsList, ...appendPluginsList, ...env.reporterPluginModules]
const loaded = yield* loadPlugins(descriptors, env.basePath).pipe(
Both at 7f11497e8b35da696e9f975df80f9714b91f79fa.
Orientation
- The built-ins already exist as values:
packages/stryker-js/stryker-js-engine/src/builtin-reporters.ts exports strykerPlugins, and packages/stryker-js/stryker-js-html-reporter/src/index.ts does the same. Nothing needs designing — the CLI just needs to import them instead of naming them.
- The descriptor path stays as it is for user plugins:
packages/stryker-js/stryker-js-engine/src/Config.ts:352-364 resolves plugins: [...] from stryker.config.json against the project directory.
- Gold paths that must keep passing:
pnpm --filter @systemfsoftware/stryker-js-cli test:contract and pnpm --filter @systemfsoftware/stryker-js-engine test. The contract lane packs the CLI and installs the tarball alone (global-setup.ts), which is the environment where a runtime lookup has nothing to find.
- No test currently asserts the html report artifact is written; add that assertion so the html reporter's registration is observable and cannot silently vanish.
Non-Counting Outcomes
- Replacing
import.meta.resolve with hard-coded relative paths into sibling dist/ output — same cross-package knowledge, just harder to see.
- Removing the field but leaving the reporters unregistered, so runs succeed while the html report silently stops being produced.
- Deleting or stubbing consumer-plugin loading to make the types work.
- Verifying with exit codes only — a run that writes no report still exits 0.
Acceptance Criteria
Problem Statement
When you run
stryker run, the CLI tells the engine where to find the built-in reporters (html, json, progress) by turning package names intofile://URLs, which the engine then imports at runtime. So the CLI only works when@systemfsoftware/stryker-js-html-reporterand@systemfsoftware/stryker-js-enginehappen to be installed beside it under those exact names. Package the CLI so it carries its own code and those lookups fail. Built-in reporters are part of the product, not user extensions, so they should be imported in TypeScript and handed to the engine as values.Goal
stryker runloads its built-in reporters through static imports: no reporter package is resolved by name at runtime,HostOptionscarries no module-specifier channel for them, and plugins the user configures instryker.config.jsonstill load by name from the project.Evidence: Current Behavior
packages/stryker-js/stryker-js-cli/src/Cli.ts:1119-1121— the CLI builds the descriptors:packages/stryker-js/stryker-js-engine/src/Run.ts:399-400— the engine merges them with user plugins and dynamically imports every descriptor:Both at
7f11497e8b35da696e9f975df80f9714b91f79fa.Orientation
packages/stryker-js/stryker-js-engine/src/builtin-reporters.tsexportsstrykerPlugins, andpackages/stryker-js/stryker-js-html-reporter/src/index.tsdoes the same. Nothing needs designing — the CLI just needs to import them instead of naming them.packages/stryker-js/stryker-js-engine/src/Config.ts:352-364resolvesplugins: [...]fromstryker.config.jsonagainst the project directory.pnpm --filter @systemfsoftware/stryker-js-cli test:contractandpnpm --filter @systemfsoftware/stryker-js-engine test. The contract lane packs the CLI and installs the tarball alone (global-setup.ts), which is the environment where a runtime lookup has nothing to find.Non-Counting Outcomes
import.meta.resolvewith hard-coded relative paths into siblingdist/output — same cross-package knowledge, just harder to see.Acceptance Criteria
HostOptionshas noreporterPluginModulesmember, so reintroducing the CLI-side descriptor failspnpm --filter @systemfsoftware/stryker-js-cli typecheck.grep -rn "import.meta.resolve" packages/stryker-js/stryker-js-cli/srcreturns no reporter package specifier.pnpm --filter @systemfsoftware/stryker-js-cli test:contractexits 0 and includes a new assertion that an html-reporter run writes the report artifact to the configured output path.stryker.config.jsoncompletes a run with a terminal stream event.pnpm --filter @systemfsoftware/stryker-js-engine testexits 0.