Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions .changeset/fix-wasm-double-extension-glob.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
"miniflare": patch
---

fix: glob patterns for module rules no longer match double-extension filenames like `foo.wasm.js`

Previously, the `globsToRegExps` helper compiled glob patterns without a trailing `$` anchor. This caused patterns like `**/*.wasm` to match any path containing `.wasm` as a substring — including filenames such as `foo.wasm.js` or `main.wasm.test.ts`.

When using `@cloudflare/vitest-pool-workers` with a `wrangler.configPath`, Wrangler's default `CompiledWasm` module rule (`**/*.wasm`) was silently applied to test files whose names contained `.wasm`, causing them to be loaded as WebAssembly binaries instead of JavaScript and failing at runtime.

The fix restores the `$` end anchor in the compiled regex so that `**/*.wasm` only matches paths that literally end in `.wasm`, while the leading `^` remains absent to allow matching anywhere within an absolute path.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// Regression test for https://github.com/cloudflare/workers-sdk/issues/8280
// A test file whose name contains ".wasm" (but whose actual extension is .ts)
// must NOT be treated as a WebAssembly module by the module rules.
import { it } from "vitest";

it("loads .wasm.test.ts files as JavaScript, not as WebAssembly", ({
expect,
}) => {
expect(true).toBe(true);
});
2 changes: 1 addition & 1 deletion packages/miniflare/src/plugins/core/modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export function compileModuleRules(rules: ModuleRule[]) {
if (finalisedTypes.has(rule.type)) continue;
compiledRules.push({
type: rule.type,
include: globsToRegExps(rule.include),
include: globsToRegExps(rule.include, { endAnchor: true }),
});
if (!rule.fallthrough) finalisedTypes.add(rule.type);
}
Expand Down
17 changes: 14 additions & 3 deletions packages/miniflare/src/shared/matcher.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,32 @@
import globToRegexp from "glob-to-regexp";
import { MatcherRegExps } from "../workers";

export function globsToRegExps(globs: string[] = []): MatcherRegExps {
export function globsToRegExps(
globs: string[] = [],
{ endAnchor }: { endAnchor?: boolean } = {}
): MatcherRegExps {
const include: RegExp[] = [];
const exclude: RegExp[] = [];
// Setting `flags: "g"` removes "^" and "$" from the generated regexp,
// allowing matches anywhere in the path...
// (https://github.com/fitzgen/glob-to-regexp/blob/2abf65a834259c6504ed3b80e85f893f8cd99127/index.js#L123-L127)
const opts: globToRegexp.Options = { globstar: true, flags: "g" };
// When `endAnchor` is true, we re-add the trailing "$" that was stripped.
// Without it, a pattern like `**/*.wasm` incorrectly matches `foo.wasm.js`
// since the regex matches `foo.wasm` anywhere inside the string. The leading
// "^" is intentionally kept absent so the pattern can match anywhere within
// an absolute path (e.g. `**/*.wasm` still matches `/abs/path/to/foo.wasm`).
const suffix = endAnchor ? "$" : "";
for (const glob of globs) {
// ...however, we don't actually want to include the "g" flag, since it will
// change `lastIndex` as paths are matched, and we want to reuse `RegExp`s.
// So, reconstruct each `RegExp` without any flags.
if (glob.startsWith("!")) {
exclude.push(new RegExp(globToRegexp(glob.slice(1), opts), ""));
exclude.push(
new RegExp(globToRegexp(glob.slice(1), opts).source + suffix)
);
} else {
include.push(new RegExp(globToRegexp(glob, opts), ""));
include.push(new RegExp(globToRegexp(glob, opts).source + suffix));
}
}
return { include, exclude };
Expand Down
27 changes: 27 additions & 0 deletions packages/miniflare/test/shared/matcher.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,30 @@ test("globsToRegExps/testRegExps: matches glob patterns", ({ expect }) => {
)
).toBe(true);
});

test("globsToRegExps/testRegExps: endAnchor prevents matching double-extension paths", ({
expect,
}) => {
// Regression test for https://github.com/cloudflare/workers-sdk/issues/8280
// With endAnchor, a pattern like **/*.wasm must NOT match foo.wasm.js — the
// extension must be anchored to the end of the path.
const wasmMatcher = globsToRegExps(["**/*.wasm"], { endAnchor: true });

expect(testRegExps(wasmMatcher, "foo.wasm")).toBe(true);
expect(testRegExps(wasmMatcher, "path/to/foo.wasm")).toBe(true);
expect(testRegExps(wasmMatcher, "/absolute/path/to/foo.wasm")).toBe(true);

// Must NOT match double-extension variants
expect(testRegExps(wasmMatcher, "foo.wasm.js")).toBe(false);
expect(testRegExps(wasmMatcher, "src/main.wasm.test.js")).toBe(false);
expect(testRegExps(wasmMatcher, "foo.wasm.map")).toBe(false);
});

test("globsToRegExps/testRegExps: without endAnchor, matches substring patterns", ({
expect,
}) => {
// KV Sites relies on patterns like "b" matching any path containing "b"
const matcher = globsToRegExps(["b"]);
expect(testRegExps(matcher, "b/b.txt")).toBe(true);
expect(testRegExps(matcher, "a.txt")).toBe(false);
});
Loading