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 dfe8a35927b8..c9cbe5f08e65 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 { @@ -760,3 +763,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 ed3bdc835352..300f4b8f1bb7 100644 --- a/packages/wrangler/e2e/unenv-preset/preset.test.ts +++ b/packages/wrangler/e2e/unenv-preset/preset.test.ts @@ -598,6 +598,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..90d9e1aef05b 100644 --- a/packages/wrangler/e2e/unenv-preset/worker/index.ts +++ b/packages/wrangler/e2e/unenv-preset/worker/index.ts @@ -859,6 +859,43 @@ 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", + REPL_MODE_SLOPPY: "symbol", + REPL_MODE_STRICT: "symbol", + }); + + assertTypeOfProperties(repl.default, { + writer: "function", + start: "function", + Recoverable: "function", + REPLServer: "function", + builtinModules: "object", + _builtinLibs: "object", + REPL_MODE_SLOPPY: "symbol", + 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/ + ); + }, }; /**