#14742·meteor

rspack: .meteorignore negation patterns are dropped, so a sharded test run silently loads zero test files

Author: perberglandCreated Sep 12, 2026Updated Sep 14, 2026
Labelsmodern-build-stack

Summary

The Rspack integration builds its test-file filter from .meteorignore but discards every negation (!) pattern. A .meteorignore that says "ignore all test files, then re-include these specific ones" — the standard way to shard a suite across CI containers — therefore excludes every test file and the run loads nothing.

The damaging part is that this is silent. Mocha reports 0 passing, the process exits 0, and CI goes green. We only noticed because one unsharded job in the same workflow still ran and failed; the two sharded jobs had been reporting success while executing zero tests.

Reproduce

In any Rspack-enabled app with server tests, write a .meteorignore:

*.tests.ts
!/imports/localization/LanguageSections.tests.ts

then run the test command, e.g.

meteor test --once --driver-package meteortesting:mocha --port localhost:3055

Observed:

  0 passing (1ms)

exit code 0. Expected: the one re-included file's tests run.

.meteor/local/test/server-eager-tests.mjs shows why — the generated regex keeps the blanket ignore and contains no trace of the negation:

const MeteorIgnoreRegex = /…|(?:^|\/).*\/[^/]*\.tests\.ts/;
{
  const ctx = import.meta.webpackContext('…', { recursive: true, regExp: /\.(?:test|spec)s?\.[^.]+$/, … });
  await Promise.all(ctx.keys().filter((k) => {
    return !MeteorIgnoreRegex.test(k);
  }).map(ctx));
}

Cause

createIgnoreGlobConfig deliberately preserves negations, re-attaching the ! prefix:

https://github.com/meteor/meteor/blob/14dfaa2531f3496a89e5648a94b770d1868e7f07/npm-packages/meteor-rspack/lib/ignore.js#L55-L78

but createIgnoreRegex then drops them on the floor:

https://github.com/meteor/meteor/blob/14dfaa2531f3496a89e5648a94b770d1868e7f07/npm-packages/meteor-rspack/lib/ignore.js#L107-L112

const regexPatterns = globPatterns.map(pattern => {
  // Skip negation patterns for the regex
  if (pattern.startsWith('!')) {
    return null;
  }

The result is a single alternation of every positive pattern, consumed as a pure "does this path match anything ignored" test:

https://github.com/meteor/meteor/blob/14dfaa2531f3496a89e5648a94b770d1868e7f07/npm-packages/meteor-rspack/lib/test.js#L50-L53 https://github.com/meteor/meteor/blob/14dfaa2531f3496a89e5648a94b770d1868e7f07/npm-packages/meteor-rspack/lib/test.js#L78-L84

The degenerate all-negations case is handled (lib/ignore.js L133-136 returns a regex matching nothing), so the author clearly knew negations could appear — it is the mixed case, which is the useful one, that misbehaves.

Expected semantics

.meteorignore is documented as using gitignore syntax, where the last matching pattern wins and a later !pattern re-includes a path that an earlier pattern excluded. Meteor's own (non-Rspack) ignore handling honours that, which is why this shape of .meteorignore works on the classic bundler and breaks under Rspack.

Suggested fix

createIgnoreRegex returning a single RegExp cannot express last-match-wins, so the shape needs to change. Two options:

  1. Return a predicate rather than a regex — compile the patterns in order into { regex, negated } pairs and evaluate last-match-wins. lib/test.js would serialise that predicate into the generated eager-test module instead of a bare regex.
  2. Return a pair of regexes (ignored, re-included) and emit !ignored.test(k) || reincluded.test(k) in the generated module. Less general — it gets the ordering wrong for alternating patterns — but a much smaller change and enough for the common "ignore a glob, re-include specific files" case.

Happy to put up a PR for either; say which you'd prefer.

Secondary, possibly worth folding in

getMeteorIgnoreEntries reads only the .meteorignore file (https://github.com/meteor/meteor/blob/14dfaa2531f3496a89e5648a94b770d1868e7f07/npm-packages/meteor-rspack/lib/ignore.js#L11-L28), so the METEOR_IGNORE environment variable never reaches the eager-test filter. The Atmosphere rspack package also lists METEOR_IGNORE in RSPACK_UNSET_ENV, so it is cleared for the Rspack child process. An app that shards via METEOR_IGNORE rather than the file gets the opposite failure — no filtering at all, every test file loaded in every container — which is at least loud rather than silent, but is equally not what was asked for.

Versions