Skip to content

Commit 6cfc67c

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

File tree

4 files changed

+97
-0
lines changed

4 files changed

+97
-0
lines changed

.changeset/native-node-tty.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:tty` module when the `enable_nodejs_tty_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 ttyOverrides = getTtyOverrides(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+
...ttyOverrides.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+
...ttyOverrides.hybridModules,
125128
];
126129

127130
return {
@@ -752,3 +755,39 @@ function getStreamWrapOverrides({
752755
hybridModules: [],
753756
};
754757
}
758+
759+
/**
760+
* Returns the overrides for `node:tty` (unenv or workerd)
761+
*
762+
* The native tty implementation:
763+
* - is experimental and has no default enable date
764+
* - can be enabled with the "enable_nodejs_tty_module" flag
765+
* - can be disabled with the "disable_nodejs_tty_module" flag
766+
*/
767+
function getTtyOverrides({
768+
compatibilityFlags,
769+
}: {
770+
compatibilityDate: string;
771+
compatibilityFlags: string[];
772+
}): { nativeModules: string[]; hybridModules: string[] } {
773+
const disabledByFlag = compatibilityFlags.includes(
774+
"disable_nodejs_tty_module"
775+
);
776+
777+
const enabledByFlag =
778+
compatibilityFlags.includes("enable_nodejs_tty_module") &&
779+
compatibilityFlags.includes("experimental");
780+
781+
const enabled = enabledByFlag && !disabledByFlag;
782+
783+
// When enabled, use the native `tty` module from workerd
784+
return enabled
785+
? {
786+
nativeModules: ["tty"],
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:tty (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: "tty enabled by flag",
562+
compatibilityDate: "2024-09-23",
563+
compatibilityFlags: ["enable_nodejs_tty_module", "experimental"],
564+
expectRuntimeFlags: {
565+
enable_nodejs_tty_module: true,
566+
},
567+
},
568+
{
569+
name: "tty disabled by flag",
570+
compatibilityDate: "2024-09-23",
571+
compatibilityFlags: ["disable_nodejs_tty_module", "experimental"],
572+
expectRuntimeFlags: {
573+
enable_nodejs_tty_module: false,
574+
},
575+
},
576+
],
556577
].flat() as TestConfig[];
557578

558579
describe.each(localTestConfigs)(

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

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -859,6 +859,38 @@ 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 testTty() {
864+
const tty = await import("node:tty");
865+
866+
// Common exports (both unenv stub and native workerd)
867+
assertTypeOfProperties(tty, {
868+
isatty: "function",
869+
ReadStream: "function",
870+
WriteStream: "function",
871+
});
872+
873+
assertTypeOfProperties(tty.default, {
874+
isatty: "function",
875+
ReadStream: "function",
876+
WriteStream: "function",
877+
});
878+
879+
// isatty should return false (both unenv and workerd)
880+
assert.strictEqual(tty.isatty(0), false);
881+
assert.strictEqual(tty.isatty(1), false);
882+
assert.strictEqual(tty.isatty(2), false);
883+
884+
// Both implementations throw when calling ReadStream/WriteStream constructors
885+
assert.throws(
886+
() => new (tty.ReadStream as any)(0),
887+
/not implemented|ERR_METHOD_NOT_IMPLEMENTED/
888+
);
889+
assert.throws(
890+
() => new (tty.WriteStream as any)(1),
891+
/not implemented|ERR_METHOD_NOT_IMPLEMENTED/
892+
);
893+
},
862894
};
863895

864896
/**

0 commit comments

Comments
 (0)