diff --git a/src/node/internal/internal_readline.ts b/src/node/internal/internal_readline.ts index 9b51bc57b45..0fe42465473 100644 --- a/src/node/internal/internal_readline.ts +++ b/src/node/internal/internal_readline.ts @@ -24,33 +24,47 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. import { EventEmitter } from 'node-internal:events'; -import { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors'; +import type { Abortable } from 'node:events'; import type Readline from 'node:readline'; +// This class provides a no-op stub implementation that matches unenv's behavior. +// See: https://github.com/unjs/unenv/blob/main/src/runtime/node/internal/readline/interface.ts +// Methods are no-ops or return sensible defaults rather than throwing errors, +// which allows code that depends on readline to work without crashing. export class Interface extends EventEmitter implements Readline.Interface { - terminal: boolean; - line: string; - cursor: number; - - constructor() { - super(); - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface'); - } + terminal = false; + line = ''; + cursor = 0; getPrompt(): string { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.getPrompt'); + return ''; } setPrompt(_prompt: string): void { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.setPrompt'); + // No-op } prompt(_preserveCursor?: boolean): void { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.prompt'); + // No-op } - question(_query: unknown, _options: unknown, _callback?: unknown): void { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.question'); + question(query: string, callback: (answer: string) => void): void; + question( + query: string, + options: Abortable, + callback: (answer: string) => void + ): void; + question( + _query: string, + _optionsOrCallback: Abortable | ((answer: string) => void), + _callback?: (answer: string) => void + ): void { + // If callback is the second argument (no options) + if (typeof _optionsOrCallback === 'function') { + _optionsOrCallback(''); + } else if (typeof _callback === 'function') { + _callback(''); + } } pause(): this { @@ -62,15 +76,18 @@ export class Interface extends EventEmitter implements Readline.Interface { } close(): void { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.close'); + // No-op } write(_data: unknown, _key?: unknown): void { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.write'); + // No-op } getCursorPos(): Readline.CursorPos { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.getCursorPos'); + return { + rows: 0, + cols: 0, + }; } [Symbol.dispose](): void { @@ -82,8 +99,9 @@ export class Interface extends EventEmitter implements Readline.Interface { this.close(); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [Symbol.asyncIterator](): NodeJS.AsyncIterator { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface[Symbol.asyncIterator]'); + // Yield a single empty string so that `for await...of` loops complete + // immediately without blocking, consistent with no-op stub behavior. + async *[Symbol.asyncIterator](): NodeJS.AsyncIterator { + yield ''; } } diff --git a/src/node/internal/internal_readline_promises.ts b/src/node/internal/internal_readline_promises.ts index 64c635c04c4..194ed9bff16 100644 --- a/src/node/internal/internal_readline_promises.ts +++ b/src/node/internal/internal_readline_promises.ts @@ -24,36 +24,35 @@ // USE OR OTHER DEALINGS IN THE SOFTWARE. import { EventEmitter } from 'node-internal:events'; -import { ERR_METHOD_NOT_IMPLEMENTED } from 'node-internal:internal_errors'; +import type { Abortable } from 'node:events'; import type ReadlineType from 'node:readline/promises'; -import type { CursorPos } from 'node:readline'; -import type { Direction } from 'readline'; +import type { CursorPos, Direction } from 'node:readline'; +// This class provides a no-op stub implementation that matches unenv's behavior. +// See: https://github.com/unjs/unenv/blob/main/src/runtime/node/internal/readline/promises/interface.ts +// Methods are no-ops or return sensible defaults rather than throwing errors, +// which allows code that depends on readline to work without crashing. export class Interface extends EventEmitter implements ReadlineType.Interface { - terminal: boolean; - line: string; - cursor: number; - - constructor() { - super(); - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface'); - } + terminal = false; + line = ''; + cursor = 0; getPrompt(): string { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.getPrompt'); + return ''; } setPrompt(_prompt: string): void { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.setPrompt'); + // No-op } prompt(_preserveCursor?: boolean): void { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.prompt'); + // No-op } - // eslint-disable-next-line @typescript-eslint/require-await - async question(_query: unknown, _options?: unknown): Promise { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.question'); + question(query: string): Promise; + question(query: string, options: Abortable): Promise; + question(_query: string, _options?: Abortable): Promise { + return Promise.resolve(''); } pause(): this { @@ -65,15 +64,18 @@ export class Interface extends EventEmitter implements ReadlineType.Interface { } close(): void { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.close'); + // No-op } write(_data: unknown, _key?: unknown): void { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.write'); + // No-op } getCursorPos(): CursorPos { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.getCursorPos'); + return { + rows: 0, + cols: 0, + }; } [Symbol.dispose](): void { @@ -85,17 +87,16 @@ export class Interface extends EventEmitter implements ReadlineType.Interface { this.close(); } - // eslint-disable-next-line @typescript-eslint/no-explicit-any - [Symbol.asyncIterator](): NodeJS.AsyncIterator { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface[Symbol.asyncIterator]'); + // Yield a single empty string so that `for await...of` loops complete + // immediately without blocking, consistent with no-op stub behavior. + async *[Symbol.asyncIterator](): NodeJS.AsyncIterator { + yield ''; } } +// This class provides a no-op stub implementation that matches unenv's behavior. +// See: https://github.com/unjs/unenv/blob/main/src/runtime/node/internal/readline/promises/readline.ts export class Readline implements ReadlineType.Readline { - constructor() { - throw new ERR_METHOD_NOT_IMPLEMENTED('Readline'); - } - clearLine(_dir: Direction): this { return this; } @@ -104,9 +105,8 @@ export class Readline implements ReadlineType.Readline { return this; } - // eslint-disable-next-line @typescript-eslint/require-await - async commit(): Promise { - throw new ERR_METHOD_NOT_IMPLEMENTED('Interface.commit'); + commit(): Promise { + return Promise.resolve(); } cursorTo(_x: number, _y?: number): this { diff --git a/src/workerd/api/node/tests/readline-nodejs-test.js b/src/workerd/api/node/tests/readline-nodejs-test.js index 6283f67514e..8f3934a839d 100644 --- a/src/workerd/api/node/tests/readline-nodejs-test.js +++ b/src/workerd/api/node/tests/readline-nodejs-test.js @@ -41,20 +41,49 @@ export const readlineEmitKeypressEvents = { export const readlineCreateInterface = { test() { - assert.throws(() => readline.createInterface(), { - code: 'ERR_METHOD_NOT_IMPLEMENTED', - message: /Interface/, - }); + // createInterface returns a no-op stub that matches unenv behavior + const rl = readline.createInterface(); + assert.ok(rl instanceof readline.Interface); + assert.strictEqual(rl.terminal, false); + assert.strictEqual(rl.line, ''); + assert.strictEqual(rl.cursor, 0); assert.strictEqual(typeof readline.createInterface, 'function'); }, }; export const readlineInterface = { test() { - assert.throws(() => new readline.Interface(), { - code: 'ERR_METHOD_NOT_IMPLEMENTED', - message: /Interface/, + // Interface constructor returns a no-op stub that matches unenv behavior + const rl = new readline.Interface(); + assert.ok(rl instanceof readline.Interface); + assert.strictEqual(rl.terminal, false); + assert.strictEqual(rl.line, ''); + assert.strictEqual(rl.cursor, 0); + + // Test methods return sensible defaults instead of throwing + assert.strictEqual(rl.getPrompt(), ''); + rl.setPrompt('test'); // Should not throw + rl.prompt(); // Should not throw + rl.close(); // Should not throw + rl.write('test'); // Should not throw + assert.deepStrictEqual(rl.getCursorPos(), { rows: 0, cols: 0 }); + assert.strictEqual(rl.pause(), rl); + assert.strictEqual(rl.resume(), rl); + + // question calls callback with empty string + let questionAnswer = null; + rl.question('test?', (answer) => { + questionAnswer = answer; + }); + assert.strictEqual(questionAnswer, ''); + + // question with options also works + let questionAnswer2 = null; + rl.question('test?', {}, (answer) => { + questionAnswer2 = answer; }); + assert.strictEqual(questionAnswer2, ''); + assert.strictEqual(typeof readline.Interface, 'function'); }, }; @@ -99,29 +128,58 @@ export const readlinePromises = { }; export const readlinePromisesInterface = { - test() { - assert.throws(() => new readline.promises.Interface(), { - code: 'ERR_METHOD_NOT_IMPLEMENTED', - message: /Interface/, - }); + async test() { + // promises.Interface constructor returns a no-op stub that matches unenv behavior + const rl = new readline.promises.Interface(); + assert.ok(rl instanceof readline.promises.Interface); + assert.strictEqual(rl.terminal, false); + assert.strictEqual(rl.line, ''); + assert.strictEqual(rl.cursor, 0); + + // Test methods return sensible defaults instead of throwing + assert.strictEqual(rl.getPrompt(), ''); + rl.setPrompt('test'); // Should not throw + rl.prompt(); // Should not throw + rl.close(); // Should not throw + rl.write('test'); // Should not throw + assert.deepStrictEqual(rl.getCursorPos(), { rows: 0, cols: 0 }); + assert.strictEqual(rl.pause(), rl); + assert.strictEqual(rl.resume(), rl); + + // question returns a promise that resolves to empty string + const answer = await rl.question('test?'); + assert.strictEqual(answer, ''); }, }; export const readlinePromisesCreateInterface = { test() { - assert.throws(() => readline.promises.createInterface(), { - code: 'ERR_METHOD_NOT_IMPLEMENTED', - message: /Interface/, - }); + // promises.createInterface returns a no-op stub that matches unenv behavior + const rl = readline.promises.createInterface(); + assert.ok(rl instanceof readline.promises.Interface); + assert.strictEqual(rl.terminal, false); + assert.strictEqual(rl.line, ''); + assert.strictEqual(rl.cursor, 0); }, }; export const readlinePromisesReadline = { - test() { - assert.throws(() => new readline.promises.Readline(), { - code: 'ERR_METHOD_NOT_IMPLEMENTED', - message: /Readline/, - }); + async test() { + // Readline constructor returns a no-op stub that matches unenv behavior + const rl = new readline.promises.Readline(process.stdout); + assert.ok(rl instanceof readline.promises.Readline); + + // Test methods return sensible defaults instead of throwing + assert.strictEqual(rl.clearLine(0), rl); + assert.strictEqual(rl.clearScreenDown(), rl); + assert.strictEqual(rl.cursorTo(0), rl); + assert.strictEqual(rl.cursorTo(0, 0), rl); + assert.strictEqual(rl.moveCursor(1, 1), rl); + assert.strictEqual(rl.rollback(), rl); + + // commit returns a promise that resolves to undefined + const result = await rl.commit(); + assert.strictEqual(result, undefined); }, };