import * as plugins from './plugins.js'; const __dirname = plugins.path.dirname(plugins.url.fileURLToPath(import.meta.url)); export interface IRunOptions { cwd?: string; } export const runPath = async (pathArg: string, fromFileUrl?: string, options?: IRunOptions) => { pathArg = fromFileUrl ? plugins.path.join(plugins.path.dirname(plugins.url.fileURLToPath(fromFileUrl)), pathArg) : pathArg; await runCli(pathArg, options); }; export const runCli = async (pathArg?: string, options?: IRunOptions) => { // CRITICAL: Branch BEFORE splicing argv to avoid corruption if (options?.cwd) { return runInChildProcess(pathArg, options.cwd); } // Existing in-process execution // contents of argv array // process.argv[0] -> node Executable // process.argv[1] -> tsrun executable const relativePathToTsFile = pathArg ? pathArg : process.argv[2]; const absolutePathToTsFile = plugins.path.isAbsolute(relativePathToTsFile) ? relativePathToTsFile : plugins.path.join(process.cwd(), relativePathToTsFile); // we want to have command line arguments available in the child process. // when we have a path sepcified through a function there is one argeument less to pay respect to. // thus when pathArg is specifed -> we only splice 2 pathArg ? process.argv.splice(0, 2) : process.argv.splice(0, 3); // this ensures transparent arguments for the child process const tsx = await import('tsx/esm/api'); const unregister = tsx.register(); await import(absolutePathToTsFile); }; const runInChildProcess = async (pathArg: string | undefined, cwd: string): Promise => { const { spawn } = await import('child_process'); // Resolve cli.child.js relative to this file const cliChildPath = plugins.path.join(__dirname, '../cli.child.js'); // Build args: [Node flags, entry point, script path, script args] const args = [ ...process.execArgv, // Preserve --inspect, etc. cliChildPath, ...process.argv.slice(2) // Original CLI args (not spliced) ]; return new Promise((resolve, reject) => { const child = spawn(process.execPath, args, { cwd: cwd, env: process.env, stdio: 'inherit', shell: false, windowsHide: false }); // Signal forwarding with cleanup const signalHandler = (signal: NodeJS.Signals) => { try { child.kill(signal); } catch {} }; const signals: NodeJS.Signals[] = ['SIGINT', 'SIGTERM', 'SIGHUP']; signals.forEach(sig => process.on(sig, signalHandler)); child.on('error', (err) => { signals.forEach(sig => process.off(sig, signalHandler)); reject(err); }); child.on('close', (code, signal) => { // Clean up signal handlers signals.forEach(sig => process.off(sig, signalHandler)); if (signal) { // Child was terminated by signal // On POSIX: try to exit with same signal // On Windows: exit with convention (128 + signal number) try { process.kill(process.pid, signal); } catch { // Fallback to exit code const signalExitCode = signal === 'SIGINT' ? 130 : 128; process.exit(signalExitCode); } } else if (code !== null && code !== 0) { process.exit(code); } else { resolve(); } }); }); };