smartshell/ts/classes.smartshell.ts

206 lines
6.2 KiB
TypeScript
Raw Normal View History

import * as plugins from './plugins.js';
import { ShellEnv } from './classes.shellenv.js';
import type { IShellEnvContructorOptions, TExecutor } from './classes.shellenv.js';
import { ShellLog } from './classes.shelllog.js';
2017-03-08 15:51:02 +00:00
2018-07-30 14:08:14 +00:00
import * as cp from 'child_process';
2017-03-08 15:51:02 +00:00
// -- interfaces --
export interface IExecResult {
exitCode: number;
stdout: string;
2017-03-08 15:51:02 +00:00
}
export interface IExecResultStreaming {
childProcess: cp.ChildProcess;
finalPromise: Promise<IExecResult>;
2024-04-18 11:42:51 +00:00
kill: () => Promise<void>;
terminate: () => Promise<void>;
keyboardInterrupt: () => Promise<void>;
customSignal: (signalArg: plugins.smartexit.TProcessSignal) => Promise<void>;
}
2017-03-08 15:51:02 +00:00
export class Smartshell {
2019-05-19 20:41:20 +00:00
public shellEnv: ShellEnv;
public smartexit = new plugins.smartexit.SmartExit();
constructor(optionsArg: IShellEnvContructorOptions) {
this.shellEnv = new ShellEnv(optionsArg);
2018-11-26 16:55:15 +00:00
}
2017-03-10 19:14:40 +00:00
/**
* executes a given command async
*/
private async _exec(options: {
2023-06-22 12:16:16 +00:00
commandString: string;
silent?: boolean;
strict?: boolean;
streaming?: boolean;
interactive?: boolean;
}): Promise<IExecResult | IExecResultStreaming | void> {
if (options.interactive) {
return await this._execInteractive(options);
}
2023-06-22 12:16:16 +00:00
return await this._execCommand(options);
}
2023-06-22 12:16:16 +00:00
private async _execInteractive(options: {
commandString: string;
interactive?: boolean;
}): Promise<void> {
if (process.env.CI) {
return;
}
2023-06-22 12:16:16 +00:00
const done = plugins.smartpromise.defer();
2023-06-22 12:16:16 +00:00
const shell = cp.spawn(options.commandString, {
stdio: 'inherit',
shell: true,
detached: true
});
2023-06-22 12:16:16 +00:00
this.smartexit.addProcess(shell);
shell.on('close', (code) => {
console.log(`interactive shell terminated with code ${code}`);
this.smartexit.removeProcess(shell);
done.resolve();
});
await done.promise;
}
private async _execCommand(options: {
commandString: string;
silent?: boolean;
strict?: boolean;
streaming?: boolean;
}): Promise<IExecResult | IExecResultStreaming> {
const done = plugins.smartpromise.defer<IExecResult | IExecResultStreaming>();
const childProcessEnded = plugins.smartpromise.defer<IExecResult>();
const commandToExecute = this.shellEnv.createEnvExecString(options.commandString);
2023-06-22 12:16:16 +00:00
const shellLogInstance = new ShellLog();
const execChildProcess = cp.spawn(commandToExecute, [], {
shell: true,
2021-11-26 14:17:52 +00:00
cwd: process.cwd(),
2018-10-28 18:12:15 +00:00
env: process.env,
2020-05-22 01:23:27 +00:00
detached: false,
});
2019-05-19 20:41:20 +00:00
this.smartexit.addProcess(execChildProcess);
2020-05-22 01:23:27 +00:00
execChildProcess.stdout.on('data', (data) => {
if (!options.silent) {
shellLogInstance.writeToConsole(data);
}
shellLogInstance.addToBuffer(data);
});
2020-05-22 01:23:27 +00:00
execChildProcess.stderr.on('data', (data) => {
if (!options.silent) {
shellLogInstance.writeToConsole(data);
}
shellLogInstance.addToBuffer(data);
});
2018-07-30 14:08:14 +00:00
execChildProcess.on('exit', (code, signal) => {
2019-05-19 20:41:20 +00:00
this.smartexit.removeProcess(execChildProcess);
if (options.strict && code === 1) {
done.reject();
}
const execResult = {
exitCode: code,
stdout: shellLogInstance.logStore.toString(),
2018-07-30 14:08:14 +00:00
};
if (!options.streaming) {
done.resolve(execResult);
}
childProcessEnded.resolve(execResult);
});
if (options.streaming) {
2021-07-26 19:24:13 +00:00
done.resolve({
childProcess: execChildProcess,
finalPromise: childProcessEnded.promise,
2024-04-18 11:42:51 +00:00
kill: async () => {
2021-08-17 16:19:52 +00:00
console.log(`running tree kill with SIGKILL on process ${execChildProcess.pid}`);
2024-04-18 11:42:51 +00:00
await plugins.smartexit.SmartExit.killTreeByPid(execChildProcess.pid, 'SIGKILL');
2021-08-17 16:19:52 +00:00
},
2024-04-18 11:42:51 +00:00
terminate: async () => {
2021-08-17 16:19:52 +00:00
console.log(`running tree kill with SIGTERM on process ${execChildProcess.pid}`);
2024-04-18 11:42:51 +00:00
await plugins.smartexit.SmartExit.killTreeByPid(execChildProcess.pid, 'SIGTERM');
},
keyboardInterrupt: async () => {
console.log(`running tree kill with SIGINT on process ${execChildProcess.pid}`);
await plugins.smartexit.SmartExit.killTreeByPid(execChildProcess.pid, 'SIGINT');
},
customSignal: async (signalArg: plugins.smartexit.TProcessSignal) => {
console.log(`running tree kill with custom signal ${signalArg} on process ${execChildProcess.pid}`);
await plugins.smartexit.SmartExit.killTreeByPid(execChildProcess.pid, signalArg);
2021-07-26 19:24:13 +00:00
},
});
}
return await done.promise;
2018-07-30 14:08:14 +00:00
}
public async exec(commandString: string): Promise<IExecResult> {
return (await this._exec({ commandString })) as IExecResult;
2018-07-30 14:08:14 +00:00
}
public async execSilent(commandString: string): Promise<IExecResult> {
return (await this._exec({ commandString, silent: true })) as IExecResult;
2018-07-30 14:08:14 +00:00
}
2017-03-10 21:08:04 +00:00
public async execStrict(commandString: string): Promise<IExecResult> {
return (await this._exec({ commandString, strict: true })) as IExecResult;
2019-05-29 08:56:45 +00:00
}
public async execStrictSilent(commandString: string): Promise<IExecResult> {
return (await this._exec({ commandString, silent: true, strict: true })) as IExecResult;
2018-07-30 14:08:14 +00:00
}
2017-03-11 01:36:27 +00:00
2023-06-22 12:16:16 +00:00
public async execStreaming(
commandString: string,
silent: boolean = false
): Promise<IExecResultStreaming> {
return (await this._exec({ commandString, silent, streaming: true })) as IExecResultStreaming;
2018-07-30 14:08:14 +00:00
}
public async execStreamingSilent(commandString: string): Promise<IExecResultStreaming> {
2023-06-22 12:16:16 +00:00
return (await this._exec({
commandString,
silent: true,
streaming: true,
})) as IExecResultStreaming;
2018-07-30 14:08:14 +00:00
}
public async execInteractive(commandString: string) {
await this._exec({ commandString, interactive: true });
}
2023-06-22 12:16:16 +00:00
public async execAndWaitForLine(
commandString: string,
regexArg: RegExp,
silentArg: boolean = false
) {
let done = plugins.smartpromise.defer();
let execStreamingResult = await this.execStreaming(commandString, silentArg);
2018-07-30 14:08:14 +00:00
execStreamingResult.childProcess.stdout.on('data', (stdOutChunk: string) => {
if (regexArg.test(stdOutChunk)) {
done.resolve();
2017-03-10 21:08:04 +00:00
}
});
return done.promise;
2018-07-30 14:08:14 +00:00
}
public async execAndWaitForLineSilent(commandString: string, regexArg: RegExp) {
return this.execAndWaitForLine(commandString, regexArg, true);
2019-05-28 08:43:54 +00:00
}
}