Skip to content

build(repo): keep the site's test config inside the package - #73

Merged
kiro-systemf[bot] merged 1 commit into
mainfrom
fix/stryker-17-config
Oct 8, 2026
Merged

kiro-systemf[bot] merged 1 commit into
mainfrom
fix/stryker-17-config

Conversation

@systemfsoftware-maker

@systemfsoftware-maker systemfsoftware-maker commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

The Release gate's mutation job stopped in Stryker's dry run (run 37703593383, job "mutation · @endgame/site", exit 3). This makes the site's test config self-contained so the dry run can load it from Stryker's sandbox.

What failed

The job log shows a warning, then the failure:

WARN (#1): Possible causes: ... Stryker loaded plugins from: []
failed to load config from .../apps/site/.stryker-tmp/sandbox-8Do1Vh/vitest.config.ts
exit 3 (RuntimeError): ... Dry run failed: Failed to get test runner capabilities

Source references below are to stryker-js-effect at f06f244, the flake input the starter locks.

The dry-run failure: the site config imported a file outside the package

  • Stryker copies the package into apps/site/.stryker-tmp/sandbox-*/ (packages/stryker-js/src/Sandbox.service.ts:63-70) and links node_modules into the copy (Sandbox.blueprint.ts:590-649). The copy goes through two preprocessors (Sandbox.blueprint.ts:310-318). One disables type checks (:70). The other, makeTSConfigPreprocessor (:112-293), rewrites the extends, references and file-list paths in the package's tsconfig.json. No caller adds another (withPreprocessor, :689-692, has no call site), so nothing rewrites imports in other files.
  • The vitest runner passes the configured configFile to Vitest with root set to the sandbox (packages/stryker-js-vitest-runner/src/VitestRuntime.blueprint.ts:288,293), so Vitest loads the sandbox copy of apps/site/vitest.config.ts.
  • That copy imported ../../vitest.shared.ts, which came in with build(repo): move lint, test and mutation settings to the root #37 (be6d01e). From apps/site/.stryker-tmp/sandbox-*/, the path resolves to apps/site/vitest.shared.ts, and that file doesn't exist. Loading the kept sandbox's config with Vitest directly prints the error that Stryker swallows: [UNRESOLVED_IMPORT] Could not resolve '../../vitest.shared.ts' in vitest.config.ts.
  • tsconfig.json extends ../../tsconfig.base.json and was not affected, because the engine rewrote it to ../../../../tsconfig.base.json in the copy.

Nothing on this path depends on the 15.0.1 → 17.0.2 bump. The import is from #37.

The warning: a stryker-js-effect bug, not a plugin-loading failure

  • unknownOptionWarnings (packages/stryker-js/src/run/validate-options-admission.workflow.ts:192-200) appends the "Possible causes" text even when no option is unknown. warnings defaults to true (packages/stryker-js-plugin-interface/src/stryker-options.schema.ts:283), so every run prints it. That's why no Unknown stryker config option line comes before it in the log. Upstream StrykerJS prints it only when an unknown option exists (packages/core/src/config/options-validator.ts:267, if (excessPropertyNames.length)).

  • "Stryker loaded plugins from: []" prints the plugins option (validate-options-admission.workflow.ts:178-182), which defaults to [] (stryker-options.schema.ts:267). 17.x loads plugins from plugins, appendPlugins, ignorers, testRunner.plugin and checkers[].plugin (packages/stryker-js/src/plugin-loader.service.ts:511-521). stryker.shared.ts names its three plugins in the last three fields, as bare package names, which 17.x resolves from the project directory (resolveBareSpecifierOf, plugin-loader.service.ts:409-418).

  • All three load. In a scratch config, renaming any one of them stops the run at phase prepare with Failed to load plugin "<name>" (ImportFailed) and exit 4. The unchanged config exits 0:

    Renamed plugin Exit
    none 0
    @systemfsoftware/stryker-js-vitest-runner 4
    @systemfsoftware/stryker-js-typescript-checker 4
    @systemfsoftware/stryker-ignorer-effect-schema-declarations 4

The warning is harmless and stays. Fixing it belongs to stryker-js-effect. Turning off warnings.unknownOptions would also hide real unknown options, so this PR doesn't do that.

What changes

  • apps/site/vitest.config.ts now states its three test settings (globals, environment: 'node', and the src/** and tests/** test globs) itself. The values are unchanged, and the empty alias list is gone.
  • vitest.shared.ts is deleted, because it had no other consumer. Its entries in tsconfig.node.json and in the //#typecheck:node, test and mutation inputs in turbo.json go with it. git grep vitest.shared finds nothing.
  • stryker.shared.ts and oxlint.shared.ts stay. Stryker reads its config, and oxlint its own, from the package directory, never from the sandbox.

Not changed: break (100), the mutate globs, the stryker-js version, and every warning setting.

Gate

Clean checkout at b132a56, Linux, sandboxed:

$ pnpm bootstrap                    # exit 0
$ pnpm check:ci                     # exit 0
check-sfs-sources: all 25 @systemfsoftware/* packages resolve from our flakes (systemfsoftware, stryker-js-effect)
@endgame/site:test:       Tests  6 passed (6)
Tasks:    9 successful, 9 total     # lint, typecheck, typecheck:node, test, test:scripts
Tasks:    1 successful, 1 total     # dist
ok | 1 passed (9 steps) | 0 failed  # sandbox proofs
$ cd apps/site && sandbox -- pnpm exec stryker run --dryRunOnly   # exit 0
Found 2 of 34 file(s) to be mutated.
INFO (#1): Initial test run succeeded. Ran 6 tests in 442ms 135993ns (net 38.79786000000004 ms, overhead 403.33813299999997 ms).
INFO (#1): The dry-run has been completed successfully. No mutations have been executed.
$ pnpm journeys                     # exit 0
      Tests  5 passed (5)

The same stryker run --dryRunOnly on main (b2e193d) exits 3 with the CI message (failed to load config from .../.stryker-tmp/sandbox-*/vitest.config.ts).

Mutation itself wasn't run. The engine's guard allows it only on main CI. Done means the next Release gate on main produces the @endgame/site report at break 100.

Alternatives not taken

  • Point configFile at the package's own file by absolute path. Vitest would then read the config from outside the sandbox, so any path the config derives from its own location, such as an alias built from import.meta.url, would point at the unmutated sources.
  • Set tempDirName so the sandbox sits at the package's depth, e.g. ../../.stryker-tmp. This only works while every package sits exactly two levels deep. Every package's sandboxes would share one directory at the root.
  • Set inPlace. Stryker would write instrumented code over the checkout's own files during every run, local dry runs included.
  • Make vitest.shared.ts a private workspace package, as systemfsoftware and stryker-js-effect do with @systemfsoftware/vitest-config. That adds a package (manifest, lint, typecheck, lockfile importer) for a 13-line file with one consumer.

Conductor ruling, cycle 103.

Stryker copies apps/site into apps/site/.stryker-tmp/sandbox-*/ and its
vitest runner loads vitest.config.ts from that copy. The config imported
../../vitest.shared.ts, which from the copy resolves to
apps/site/vitest.shared.ts, a file that does not exist, so the Release
gate's dry run stopped at "failed to load config" and exited 3 (run
37703593383). The site's config now states its three test settings
itself, unchanged. vitest.shared.ts had no other consumer and is deleted
with its tsconfig.node.json and turbo input entries. stryker.shared.ts
and oxlint.shared.ts stay: Stryker and oxlint read those from the
package directory, never from the sandbox.

Conductor ruling, cycle 103

@kiro-systemf kiro-systemf Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified: 9/9 green on b132a56, root cause cited, dryRunOnly exit 0 vs 3 on main, 0 threads, hunt clean.

@kiro-systemf
kiro-systemf Bot merged commit bc991a4 into main Oct 8, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant