feat(core): Add cwd option and child-process execution for custom working directory; implement signal-forwarding child runner; update docs and bump package version to 1.4.0
This commit is contained in:
@@ -3,6 +3,6 @@
|
||||
*/
|
||||
export const commitinfo = {
|
||||
name: '@git.zone/tsrun',
|
||||
version: '1.3.4',
|
||||
version: '1.5.0',
|
||||
description: 'run typescript programs efficiently'
|
||||
}
|
||||
|
75
ts/index.ts
75
ts/index.ts
@@ -1,14 +1,24 @@
|
||||
import * as plugins from './plugins.js';
|
||||
const __dirname = plugins.path.dirname(plugins.url.fileURLToPath(import.meta.url));
|
||||
|
||||
export const runPath = async (pathArg: string, fromFileUrl?: string) => {
|
||||
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);
|
||||
await runCli(pathArg, options);
|
||||
};
|
||||
|
||||
export const runCli = async (pathArg?: string) => {
|
||||
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
|
||||
@@ -26,3 +36,62 @@ export const runCli = async (pathArg?: string) => {
|
||||
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();
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
Reference in New Issue
Block a user