From 7c2a82009953648d1c2eb2d59b345854f696392c Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Tue, 20 Jan 2026 18:02:04 +0000 Subject: [PATCH 1/2] feat(unenv-preset): add native node:repl module support Co-Authored-By: pbacondarwin@cloudflare.com --- .changeset/native-node-repl.md | 5 +++ packages/unenv-preset/src/preset.ts | 39 +++++++++++++++++++ .../wrangler/e2e/unenv-preset/preset.test.ts | 21 ++++++++++ .../wrangler/e2e/unenv-preset/worker/index.ts | 39 +++++++++++++++++++ 4 files changed, 104 insertions(+) create mode 100644 .changeset/native-node-repl.md diff --git a/.changeset/native-node-repl.md b/.changeset/native-node-repl.md new file mode 100644 index 000000000000..293cb211ced0 --- /dev/null +++ b/.changeset/native-node-repl.md @@ -0,0 +1,5 @@ +--- +"@cloudflare/unenv-preset": minor +--- + +Add support for native `node:repl` module when the `enable_nodejs_repl_module` and `experimental` compatibility flags are enabled. diff --git a/packages/unenv-preset/src/preset.ts b/packages/unenv-preset/src/preset.ts index 38c10340fb86..2526102e1b35 100644 --- a/packages/unenv-preset/src/preset.ts +++ b/packages/unenv-preset/src/preset.ts @@ -83,6 +83,7 @@ export function getCloudflarePreset({ const sqliteOverrides = getSqliteOverrides(compat); const dgramOverrides = getDgramOverrides(compat); const streamWrapOverrides = getStreamWrapOverrides(compat); + const replOverrides = getReplOverrides(compat); // "dynamic" as they depend on the compatibility date and flags const dynamicNativeModules = [ @@ -102,6 +103,7 @@ export function getCloudflarePreset({ ...sqliteOverrides.nativeModules, ...dgramOverrides.nativeModules, ...streamWrapOverrides.nativeModules, + ...replOverrides.nativeModules, ]; // "dynamic" as they depend on the compatibility date and flags @@ -122,6 +124,7 @@ export function getCloudflarePreset({ ...sqliteOverrides.hybridModules, ...dgramOverrides.hybridModules, ...streamWrapOverrides.hybridModules, + ...replOverrides.hybridModules, ]; return { @@ -752,3 +755,39 @@ function getStreamWrapOverrides({ hybridModules: [], }; } + +/** + * Returns the overrides for `node:repl` (unenv or workerd) + * + * The native repl implementation: + * - is experimental and has no default enable date + * - can be enabled with the "enable_nodejs_repl_module" flag + * - can be disabled with the "disable_nodejs_repl_module" flag + */ +function getReplOverrides({ + compatibilityFlags, +}: { + compatibilityDate: string; + compatibilityFlags: string[]; +}): { nativeModules: string[]; hybridModules: string[] } { + const disabledByFlag = compatibilityFlags.includes( + "disable_nodejs_repl_module" + ); + + const enabledByFlag = + compatibilityFlags.includes("enable_nodejs_repl_module") && + compatibilityFlags.includes("experimental"); + + const enabled = enabledByFlag && !disabledByFlag; + + // When enabled, use the native `repl` module from workerd + return enabled + ? { + nativeModules: ["repl"], + hybridModules: [], + } + : { + nativeModules: [], + hybridModules: [], + }; +} diff --git a/packages/wrangler/e2e/unenv-preset/preset.test.ts b/packages/wrangler/e2e/unenv-preset/preset.test.ts index 4a8a4e3ae5f2..afa449a7ac50 100644 --- a/packages/wrangler/e2e/unenv-preset/preset.test.ts +++ b/packages/wrangler/e2e/unenv-preset/preset.test.ts @@ -553,6 +553,27 @@ const localTestConfigs: TestConfig[] = [ }, }, ], + // node:repl (experimental, no default enable date) + [ + // TODO: add test for disabled by date (no date defined yet) + // TODO: add test for enabled by date (no date defined yet) + { + name: "repl enabled by flag", + compatibilityDate: "2024-09-23", + compatibilityFlags: ["enable_nodejs_repl_module", "experimental"], + expectRuntimeFlags: { + enable_nodejs_repl_module: true, + }, + }, + { + name: "repl disabled by flag", + compatibilityDate: "2024-09-23", + compatibilityFlags: ["disable_nodejs_repl_module", "experimental"], + expectRuntimeFlags: { + enable_nodejs_repl_module: false, + }, + }, + ], ].flat() as TestConfig[]; describe.each(localTestConfigs)( diff --git a/packages/wrangler/e2e/unenv-preset/worker/index.ts b/packages/wrangler/e2e/unenv-preset/worker/index.ts index 377bec63eb5f..720f4ccc2de7 100644 --- a/packages/wrangler/e2e/unenv-preset/worker/index.ts +++ b/packages/wrangler/e2e/unenv-preset/worker/index.ts @@ -859,6 +859,45 @@ export const WorkerdTests: Record void> = { // `JSStreamSocket` is the default export of `node:_stream_wrap` assertTypeOf(streamWrap, "default", "function"); }, + + async testRepl() { + const repl = await import("node:repl"); + + // Common exports (both unenv stub and native workerd) + assertTypeOfProperties(repl, { + writer: "function", + start: "function", + Recoverable: "function", + REPLServer: "function", + builtinModules: "object", + _builtinLibs: "object", + }); + + assertTypeOfProperties(repl.default, { + writer: "function", + start: "function", + Recoverable: "function", + REPLServer: "function", + builtinModules: "object", + _builtinLibs: "object", + }); + + // REPL_MODE_SLOPPY and REPL_MODE_STRICT are symbols + assertTypeOf(repl, "REPL_MODE_SLOPPY", "symbol"); + assertTypeOf(repl, "REPL_MODE_STRICT", "symbol"); + assertTypeOf(repl.default, "REPL_MODE_SLOPPY", "symbol"); + assertTypeOf(repl.default, "REPL_MODE_STRICT", "symbol"); + + // builtinModules should be an array (not in TypeScript types but exported by both unenv and workerd) + assert.ok(Array.isArray((repl as any).builtinModules)); + assert.ok((repl as any).builtinModules.length > 0); + + // Both implementations throw when calling start() + assert.throws( + () => repl.start(), + /not implemented|ERR_METHOD_NOT_IMPLEMENTED/ + ); + }, }; /** From 8cad7400ed5727aaee1b68a03aa60ce90356a1ba Mon Sep 17 00:00:00 2001 From: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com> Date: Wed, 21 Jan 2026 10:21:18 +0000 Subject: [PATCH 2/2] refactor: use assertTypeOfProperties for symbol assertions Co-Authored-By: pbacondarwin@cloudflare.com --- packages/wrangler/e2e/unenv-preset/worker/index.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/wrangler/e2e/unenv-preset/worker/index.ts b/packages/wrangler/e2e/unenv-preset/worker/index.ts index 720f4ccc2de7..90d9e1aef05b 100644 --- a/packages/wrangler/e2e/unenv-preset/worker/index.ts +++ b/packages/wrangler/e2e/unenv-preset/worker/index.ts @@ -871,6 +871,8 @@ export const WorkerdTests: Record void> = { REPLServer: "function", builtinModules: "object", _builtinLibs: "object", + REPL_MODE_SLOPPY: "symbol", + REPL_MODE_STRICT: "symbol", }); assertTypeOfProperties(repl.default, { @@ -880,14 +882,10 @@ export const WorkerdTests: Record void> = { REPLServer: "function", builtinModules: "object", _builtinLibs: "object", + REPL_MODE_SLOPPY: "symbol", + REPL_MODE_STRICT: "symbol", }); - // REPL_MODE_SLOPPY and REPL_MODE_STRICT are symbols - assertTypeOf(repl, "REPL_MODE_SLOPPY", "symbol"); - assertTypeOf(repl, "REPL_MODE_STRICT", "symbol"); - assertTypeOf(repl.default, "REPL_MODE_SLOPPY", "symbol"); - assertTypeOf(repl.default, "REPL_MODE_STRICT", "symbol"); - // builtinModules should be an array (not in TypeScript types but exported by both unenv and workerd) assert.ok(Array.isArray((repl as any).builtinModules)); assert.ok((repl as any).builtinModules.length > 0);