Skip to content

Commit 7c2a820

Browse files
feat(unenv-preset): add native node:repl module support
Co-Authored-By: pbacondarwin@cloudflare.com <pete@bacondarwin.com>
1 parent c3407ad commit 7c2a820

File tree

4 files changed

+104
-0
lines changed

4 files changed

+104
-0
lines changed

.changeset/native-node-repl.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@cloudflare/unenv-preset": minor
3+
---
4+
5+
Add support for native `node:repl` module when the `enable_nodejs_repl_module` and `experimental` compatibility flags are enabled.

packages/unenv-preset/src/preset.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export function getCloudflarePreset({
8383
const sqliteOverrides = getSqliteOverrides(compat);
8484
const dgramOverrides = getDgramOverrides(compat);
8585
const streamWrapOverrides = getStreamWrapOverrides(compat);
86+
const replOverrides = getReplOverrides(compat);
8687

8788
// "dynamic" as they depend on the compatibility date and flags
8889
const dynamicNativeModules = [
@@ -102,6 +103,7 @@ export function getCloudflarePreset({
102103
...sqliteOverrides.nativeModules,
103104
...dgramOverrides.nativeModules,
104105
...streamWrapOverrides.nativeModules,
106+
...replOverrides.nativeModules,
105107
];
106108

107109
// "dynamic" as they depend on the compatibility date and flags
@@ -122,6 +124,7 @@ export function getCloudflarePreset({
122124
...sqliteOverrides.hybridModules,
123125
...dgramOverrides.hybridModules,
124126
...streamWrapOverrides.hybridModules,
127+
...replOverrides.hybridModules,
125128
];
126129

127130
return {
@@ -752,3 +755,39 @@ function getStreamWrapOverrides({
752755
hybridModules: [],
753756
};
754757
}
758+
759+
/**
760+
* Returns the overrides for `node:repl` (unenv or workerd)
761+
*
762+
* The native repl implementation:
763+
* - is experimental and has no default enable date
764+
* - can be enabled with the "enable_nodejs_repl_module" flag
765+
* - can be disabled with the "disable_nodejs_repl_module" flag
766+
*/
767+
function getReplOverrides({
768+
compatibilityFlags,
769+
}: {
770+
compatibilityDate: string;
771+
compatibilityFlags: string[];
772+
}): { nativeModules: string[]; hybridModules: string[] } {
773+
const disabledByFlag = compatibilityFlags.includes(
774+
"disable_nodejs_repl_module"
775+
);
776+
777+
const enabledByFlag =
778+
compatibilityFlags.includes("enable_nodejs_repl_module") &&
779+
compatibilityFlags.includes("experimental");
780+
781+
const enabled = enabledByFlag && !disabledByFlag;
782+
783+
// When enabled, use the native `repl` module from workerd
784+
return enabled
785+
? {
786+
nativeModules: ["repl"],
787+
hybridModules: [],
788+
}
789+
: {
790+
nativeModules: [],
791+
hybridModules: [],
792+
};
793+
}

packages/wrangler/e2e/unenv-preset/preset.test.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -553,6 +553,27 @@ const localTestConfigs: TestConfig[] = [
553553
},
554554
},
555555
],
556+
// node:repl (experimental, no default enable date)
557+
[
558+
// TODO: add test for disabled by date (no date defined yet)
559+
// TODO: add test for enabled by date (no date defined yet)
560+
{
561+
name: "repl enabled by flag",
562+
compatibilityDate: "2024-09-23",
563+
compatibilityFlags: ["enable_nodejs_repl_module", "experimental"],
564+
expectRuntimeFlags: {
565+
enable_nodejs_repl_module: true,
566+
},
567+
},
568+
{
569+
name: "repl disabled by flag",
570+
compatibilityDate: "2024-09-23",
571+
compatibilityFlags: ["disable_nodejs_repl_module", "experimental"],
572+
expectRuntimeFlags: {
573+
enable_nodejs_repl_module: false,
574+
},
575+
},
576+
],
556577
].flat() as TestConfig[];
557578

558579
describe.each(localTestConfigs)(

packages/wrangler/e2e/unenv-preset/worker/index.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,45 @@ export const WorkerdTests: Record<string, () => void> = {
859859
// `JSStreamSocket` is the default export of `node:_stream_wrap`
860860
assertTypeOf(streamWrap, "default", "function");
861861
},
862+
863+
async testRepl() {
864+
const repl = await import("node:repl");
865+
866+
// Common exports (both unenv stub and native workerd)
867+
assertTypeOfProperties(repl, {
868+
writer: "function",
869+
start: "function",
870+
Recoverable: "function",
871+
REPLServer: "function",
872+
builtinModules: "object",
873+
_builtinLibs: "object",
874+
});
875+
876+
assertTypeOfProperties(repl.default, {
877+
writer: "function",
878+
start: "function",
879+
Recoverable: "function",
880+
REPLServer: "function",
881+
builtinModules: "object",
882+
_builtinLibs: "object",
883+
});
884+
885+
// REPL_MODE_SLOPPY and REPL_MODE_STRICT are symbols
886+
assertTypeOf(repl, "REPL_MODE_SLOPPY", "symbol");
887+
assertTypeOf(repl, "REPL_MODE_STRICT", "symbol");
888+
assertTypeOf(repl.default, "REPL_MODE_SLOPPY", "symbol");
889+
assertTypeOf(repl.default, "REPL_MODE_STRICT", "symbol");
890+
891+
// builtinModules should be an array (not in TypeScript types but exported by both unenv and workerd)
892+
assert.ok(Array.isArray((repl as any).builtinModules));
893+
assert.ok((repl as any).builtinModules.length > 0);
894+
895+
// Both implementations throw when calling start()
896+
assert.throws(
897+
() => repl.start(),
898+
/not implemented|ERR_METHOD_NOT_IMPLEMENTED/
899+
);
900+
},
862901
};
863902

864903
/**

0 commit comments

Comments
 (0)