Files
tsrun/ts/index.ts

98 lines
3.3 KiB
TypeScript

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<void> => {
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();
}
});
});
};