diff --git a/src/cli.ts b/src/cli.ts index 7f2254c29..e75cfea04 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,5 +1,5 @@ import { constants as osConstants } from 'os'; -import type { ChildProcess } from 'child_process'; +import type { ChildProcess, Serializable } from 'child_process'; import type { Server } from 'net'; import { cli } from 'cleye'; import { @@ -212,6 +212,18 @@ cli({ relaySignals(childProcess, ipc); + if (process.send) { + childProcess.on('message', (message) => { + process.send!(message); + }); + } + + if (childProcess.send) { + process.on('message', (message) => { + childProcess.send(message as Serializable); + }); + } + childProcess.on( 'close', (exitCode) => { diff --git a/src/run.ts b/src/run.ts index 958ff5cee..f8fef0c7f 100644 --- a/src/run.ts +++ b/src/run.ts @@ -16,9 +16,13 @@ export const run = ( 'inherit', // stdin 'inherit', // stdout 'inherit', // stderr - 'ipc', // parent-child communication ]; + // If parent process spawns tsx with ipc, spawn child with ipc + if (process.send) { + stdio.push('ipc'); + } + if (options) { if (options.noCache) { environment.TSX_DISABLE_CACHE = '1'; diff --git a/tests/specs/cli.ts b/tests/specs/cli.ts index 351b30bcf..21f17311e 100644 --- a/tests/specs/cli.ts +++ b/tests/specs/cli.ts @@ -3,10 +3,9 @@ import { setTimeout } from 'timers/promises'; import { testSuite, expect } from 'manten'; import { createFixture } from 'fs-fixture'; import packageJson from '../../package.json'; -import { tsxPath } from '../utils/tsx.js'; import { ptyShell, isWindows } from '../utils/pty-shell/index'; import { expectMatchInOrder } from '../utils/expect-match-in-order.js'; -import type { NodeApis } from '../utils/tsx.js'; +import { tsxPath, type NodeApis } from '../utils/tsx.js'; import { isProcessAlive } from '../utils/is-process-alive.js'; export default testSuite(({ describe }, node: NodeApis) => { @@ -344,5 +343,32 @@ export default testSuite(({ describe }, node: NodeApis) => { }, 10_000); }); }); + + test('relays ipc message to child and back', async ({ onTestFinish }) => { + const fixture = await createFixture({ + 'file.js': ` + process.on('message', (received) => { + process.send('goodbye'); + process.exit(); + }); + `, + }); + + onTestFinish(async () => await fixture.rm()); + + const tsxProcess = tsx(['file.js'], { + cwd: fixture.path, + stdio: ['ipc'], + reject: false, + }); + + tsxProcess.send('hello'); + const received = await new Promise((resolve) => { + tsxProcess.once('message', resolve); + }); + expect(received).toBe('goodbye'); + + await tsxProcess; + }); }); }); diff --git a/tests/utils/tsx.ts b/tests/utils/tsx.ts index 7722b69b2..35dd8508a 100644 --- a/tests/utils/tsx.ts +++ b/tests/utils/tsx.ts @@ -1,5 +1,5 @@ import { fileURLToPath } from 'url'; -import { execaNode } from 'execa'; +import { execaNode, type NodeOptions } from 'execa'; import getNode from 'get-node'; import { compareNodeVersion, type Version } from './node-features.js'; @@ -62,12 +62,11 @@ export const createNode = async ( tsx: ( args: string[], - cwd?: string, + cwdOrOptions?: string | NodeOptions, ) => execaNode( tsxPath, args, { - cwd, env: { TSX_DISABLE_CACHE: '1', DEBUG: '1', @@ -76,6 +75,11 @@ export const createNode = async ( nodeOptions: [], reject: false, all: true, + ...( + typeof cwdOrOptions === 'string' + ? { cwd: cwdOrOptions } + : cwdOrOptions + ), }, ),