Skip to content

Commit 976ff2d

Browse files
committed
feat: US-196 - Fix node -e stderr/errors not appearing in interactive shell
1 parent 47b969b commit 976ff2d

3 files changed

Lines changed: 114 additions & 1 deletion

File tree

packages/runtime/node/src/driver.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,13 @@ class NodeRuntimeDriver implements RuntimeDriver {
472472
},
473473
});
474474

475+
// Emit errorMessage as stderr (covers ReferenceError, SyntaxError, throw)
476+
if (result.errorMessage) {
477+
const errBytes = new TextEncoder().encode(result.errorMessage + '\n');
478+
ctx.onStderr?.(errBytes);
479+
proc.onStderr?.(errBytes);
480+
}
481+
475482
// Cleanup isolate
476483
executionDriver.dispose();
477484
this._activeDrivers.delete(ctx.pid);

packages/secure-exec-node/src/execution.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,10 @@ export async function executeWithRuntime<T = unknown>(
278278
};
279279
}
280280

281-
const errMessage = err instanceof Error ? err.message : String(err);
281+
// Include error class name (e.g. "SyntaxError: ...") to match Node.js output
282+
const errMessage = err instanceof Error
283+
? (err.name && err.name !== 'Error' ? `${err.name}: ${err.message}` : err.message)
284+
: String(err);
282285
const exitMatch = errMessage.match(/process\.exit\((\d+)\)/);
283286

284287
if (exitMatch) {

packages/secure-exec/tests/kernel/cross-runtime-terminal.test.ts

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,109 @@ describe.skipIf(wasmSkip)('cross-runtime exec: node', () => {
169169
}, 15_000);
170170
});
171171

172+
// ---------------------------------------------------------------------------
173+
// Node kernel.exec() stderr tests
174+
// ---------------------------------------------------------------------------
175+
176+
describe.skipIf(wasmSkip)('cross-runtime exec: node stderr', () => {
177+
let ctx: IntegrationKernelResult;
178+
179+
afterEach(async () => {
180+
await ctx?.dispose();
181+
});
182+
183+
it('kernel.exec node -e with undefined var returns ReferenceError on stderr', async () => {
184+
ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
185+
const result = await ctx.kernel.exec('node -e "lskdjf"');
186+
expect(result.exitCode).not.toBe(0);
187+
expect(result.stderr).toContain('ReferenceError');
188+
});
189+
190+
it('kernel.exec node -e throw Error returns message on stderr', async () => {
191+
ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
192+
const result = await ctx.kernel.exec('node -e "throw new Error(\'boom\')"');
193+
expect(result.exitCode).not.toBe(0);
194+
expect(result.stderr).toContain('boom');
195+
});
196+
197+
it('kernel.exec node -e with syntax error returns SyntaxError on stderr', async () => {
198+
ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
199+
const result = await ctx.kernel.exec('node -e "({"');
200+
expect(result.exitCode).not.toBe(0);
201+
expect(result.stderr).toContain('SyntaxError');
202+
});
203+
204+
it('kernel.exec node -e console.error returns stderr', async () => {
205+
ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
206+
const result = await ctx.kernel.exec('node -e "console.error(\'ERRMSG\')"');
207+
expect(result.stderr).toContain('ERRMSG');
208+
expect(result.exitCode).toBe(0);
209+
});
210+
});
211+
212+
// ---------------------------------------------------------------------------
213+
// Node cross-runtime terminal: stderr tests
214+
// ---------------------------------------------------------------------------
215+
216+
describe.skipIf(wasmSkip)('cross-runtime terminal: node stderr', () => {
217+
let harness: TerminalHarness;
218+
let ctx: IntegrationKernelResult;
219+
220+
afterEach(async () => {
221+
await harness?.dispose();
222+
await ctx?.dispose();
223+
});
224+
225+
it('node -e with undefined var shows ReferenceError on terminal', async () => {
226+
ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
227+
harness = new TerminalHarness(ctx.kernel);
228+
229+
await harness.waitFor(PROMPT);
230+
await harness.type('node -e "lskdjf"\n');
231+
await harness.waitFor(PROMPT, 2, 10_000);
232+
233+
const screen = harness.screenshotTrimmed();
234+
expect(screen).toContain('ReferenceError');
235+
}, 15_000);
236+
237+
it('node -e throw Error shows error message on terminal', async () => {
238+
ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
239+
harness = new TerminalHarness(ctx.kernel);
240+
241+
await harness.waitFor(PROMPT);
242+
await harness.type('node -e "throw new Error(\'boom\')"\n');
243+
await harness.waitFor(PROMPT, 2, 10_000);
244+
245+
const screen = harness.screenshotTrimmed();
246+
expect(screen).toContain('boom');
247+
}, 15_000);
248+
249+
it('node -e syntax error shows SyntaxError on terminal', async () => {
250+
ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
251+
harness = new TerminalHarness(ctx.kernel);
252+
253+
await harness.waitFor(PROMPT);
254+
await harness.type('node -e "({"\n');
255+
await harness.waitFor(PROMPT, 2, 10_000);
256+
257+
const screen = harness.screenshotTrimmed();
258+
expect(screen).toContain('SyntaxError');
259+
}, 15_000);
260+
261+
it('stderr callback chain: NodeRuntimeDriver → ctx.onStderr → PTY slave', async () => {
262+
ctx = await createIntegrationKernel({ runtimes: ['wasmvm', 'node'] });
263+
harness = new TerminalHarness(ctx.kernel);
264+
265+
await harness.waitFor(PROMPT);
266+
// console.error goes through onStdio → ctx.onStderr → PTY write
267+
await harness.type('node -e "console.error(\'STDERRTEST\')"\n');
268+
await harness.waitFor(PROMPT, 2, 10_000);
269+
270+
const screen = harness.screenshotTrimmed();
271+
expect(screen).toContain('STDERRTEST');
272+
}, 15_000);
273+
});
274+
172275
// ---------------------------------------------------------------------------
173276
// Python cross-runtime terminal tests
174277
// ---------------------------------------------------------------------------

0 commit comments

Comments
 (0)