Skip to content

Commit 473f473

Browse files
feat: add CLI feedback when waiting for stdin input
- Detects interactive TTY sessions and prints a helpful message to stderr when the command is waiting for input. - Prevents user confusion when running commands without arguments or pipes. - Adds test case to verify the behavior (mocking isTTY). - Handles platform-specific EOF instructions (Ctrl+Z for Windows, Ctrl+D for others). - Verifies macOS behavior via specific test case.
1 parent 6d88bd9 commit 473f473

File tree

2 files changed

+51
-8
lines changed

2 files changed

+51
-8
lines changed

lib/commands/unipept/unipept_subcommand.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -304,7 +304,8 @@ export abstract class UnipeptSubcommand {
304304
return this.streamInterface[Symbol.asyncIterator]();
305305
} else {
306306
if (process.stdin.isTTY) {
307-
process.stderr.write("Reading from standard input... (Press Ctrl+D to finish)\n");
307+
const eofKey = process.platform === "win32" ? "Ctrl+Z, Enter" : "Ctrl+D";
308+
process.stderr.write(`Reading from standard input... (Press ${eofKey} to finish)\n`);
308309
}
309310
this.streamInterface = createInterface({ input: process.stdin });
310311
return this.streamInterface[Symbol.asyncIterator]();

tests/commands/unipept/unipept_subcommand.test.ts

Lines changed: 49 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,13 @@ import { Pept2lca } from '../../../lib/commands/unipept/pept2lca.js';
22
import { vi, describe, test, expect, afterEach } from 'vitest';
33

44
describe('UnipeptSubcommand', () => {
5+
const originalIsTTY = process.stdin.isTTY;
6+
const originalPlatform = process.platform;
7+
58
afterEach(() => {
69
vi.restoreAllMocks();
10+
Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY, configurable: true });
11+
Object.defineProperty(process, 'platform', { value: originalPlatform, configurable: true });
712
});
813

914
test('test command setup', () => {
@@ -65,7 +70,6 @@ describe('UnipeptSubcommand', () => {
6570
const command = new Pept2lca();
6671

6772
// Mock process.stdin.isTTY
68-
const originalIsTTY = process.stdin.isTTY;
6973
Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true });
7074

7175
// Mock process.stderr.write
@@ -76,16 +80,57 @@ describe('UnipeptSubcommand', () => {
7680
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining("Reading from standard input..."));
7781

7882
command['streamInterface']?.close();
83+
});
84+
85+
test('test inputIterator prints correct EOF key for Windows', async () => {
86+
const command = new Pept2lca();
87+
88+
Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true });
89+
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true });
90+
91+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
92+
93+
const input = command["getInputIterator"]([]) as AsyncIterableIterator<string>;
7994

80-
// Restore
81-
Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY });
95+
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining("Ctrl+Z, Enter"));
96+
97+
command['streamInterface']?.close();
98+
});
99+
100+
test('test inputIterator prints correct EOF key for non-Windows', async () => {
101+
const command = new Pept2lca();
102+
103+
Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true });
104+
Object.defineProperty(process, 'platform', { value: 'linux', configurable: true });
105+
106+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
107+
108+
const input = command["getInputIterator"]([]) as AsyncIterableIterator<string>;
109+
110+
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining("Ctrl+D"));
111+
112+
command['streamInterface']?.close();
113+
});
114+
115+
test('test inputIterator prints correct EOF key for macOS', async () => {
116+
const command = new Pept2lca();
117+
118+
Object.defineProperty(process.stdin, 'isTTY', { value: true, configurable: true });
119+
Object.defineProperty(process, 'platform', { value: 'darwin', configurable: true });
120+
121+
const stderrSpy = vi.spyOn(process.stderr, 'write').mockImplementation(() => true);
122+
123+
const input = command["getInputIterator"]([]) as AsyncIterableIterator<string>;
124+
125+
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining("Ctrl+D"));
126+
127+
command['streamInterface']?.close();
82128
});
83129

84130
test('test inputIterator does NOT print warning when reading from piped stdin (not TTY)', async () => {
85131
const command = new Pept2lca();
86132

87133
// Mock process.stdin.isTTY
88-
const originalIsTTY = process.stdin.isTTY;
89134
Object.defineProperty(process.stdin, 'isTTY', { value: false, configurable: true });
90135

91136
// Mock process.stderr.write
@@ -96,8 +141,5 @@ describe('UnipeptSubcommand', () => {
96141
expect(stderrSpy).not.toHaveBeenCalled();
97142

98143
command['streamInterface']?.close();
99-
100-
// Restore
101-
Object.defineProperty(process.stdin, 'isTTY', { value: originalIsTTY });
102144
});
103145
});

0 commit comments

Comments
 (0)