@push.rocks/smartshell 3.2.2 → 3.2.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,6 +3,6 @@
3
3
  */
4
4
  export const commitinfo = {
5
5
  name: '@push.rocks/smartshell',
6
- version: '3.2.2',
6
+ version: '3.2.4',
7
7
  description: 'A library for executing shell commands using promises.'
8
8
  }
@@ -2,7 +2,6 @@ import * as plugins from './plugins.js';
2
2
  import { ShellEnv } from './classes.shellenv.js';
3
3
  import type { IShellEnvContructorOptions, TExecutor } from './classes.shellenv.js';
4
4
  import { ShellLog } from './classes.shelllog.js';
5
-
6
5
  import * as cp from 'child_process';
7
6
 
8
7
  // -- interfaces --
@@ -17,7 +16,15 @@ export interface IExecResultStreaming {
17
16
  kill: () => Promise<void>;
18
17
  terminate: () => Promise<void>;
19
18
  keyboardInterrupt: () => Promise<void>;
20
- customSignal: (signalArg: plugins.smartexit.TProcessSignal) => Promise<void>;
19
+ customSignal: (signal: plugins.smartexit.TProcessSignal) => Promise<void>;
20
+ }
21
+
22
+ interface IExecOptions {
23
+ commandString: string;
24
+ silent?: boolean;
25
+ strict?: boolean;
26
+ streaming?: boolean;
27
+ interactive?: boolean;
21
28
  }
22
29
 
23
30
  export class Smartshell {
@@ -29,61 +36,48 @@ export class Smartshell {
29
36
  }
30
37
 
31
38
  /**
32
- * executes a given command async
39
+ * Executes a given command asynchronously.
33
40
  */
34
- private async _exec(options: {
35
- commandString: string;
36
- silent?: boolean;
37
- strict?: boolean;
38
- streaming?: boolean;
39
- interactive?: boolean;
40
- }): Promise<IExecResult | IExecResultStreaming | void> {
41
+ private async _exec(options: IExecOptions): Promise<IExecResult | IExecResultStreaming | void> {
41
42
  if (options.interactive) {
42
- return await this._execInteractive(options);
43
+ return await this._execInteractive({ commandString: options.commandString });
43
44
  }
44
-
45
45
  return await this._execCommand(options);
46
46
  }
47
47
 
48
- private async _execInteractive(options: {
49
- commandString: string;
50
- interactive?: boolean;
51
- }): Promise<void> {
48
+ /**
49
+ * Executes an interactive command.
50
+ */
51
+ private async _execInteractive(options: Pick<IExecOptions, 'commandString'>): Promise<void> {
52
+ // Skip interactive execution in CI environments.
52
53
  if (process.env.CI) {
53
54
  return;
54
55
  }
55
56
 
56
- const done = plugins.smartpromise.defer();
57
-
58
- const shell = cp.spawn(options.commandString, {
59
- stdio: 'inherit',
60
- shell: true,
61
- detached: true
62
- });
57
+ return new Promise<void>((resolve) => {
58
+ const shell = cp.spawn(options.commandString, {
59
+ stdio: 'inherit',
60
+ shell: true,
61
+ detached: true,
62
+ });
63
63
 
64
- this.smartexit.addProcess(shell);
64
+ this.smartexit.addProcess(shell);
65
65
 
66
- shell.on('close', (code) => {
67
- console.log(`interactive shell terminated with code ${code}`);
68
- this.smartexit.removeProcess(shell);
69
- done.resolve();
66
+ shell.on('close', (code) => {
67
+ console.log(`Interactive shell terminated with code ${code}`);
68
+ this.smartexit.removeProcess(shell);
69
+ resolve();
70
+ });
70
71
  });
71
-
72
- await done.promise;
73
72
  }
74
73
 
75
- private async _execCommand(options: {
76
- commandString: string;
77
- silent?: boolean;
78
- strict?: boolean;
79
- streaming?: boolean;
80
- }): Promise<IExecResult | IExecResultStreaming> {
81
- const done = plugins.smartpromise.defer<IExecResult | IExecResultStreaming>();
82
- const childProcessEnded = plugins.smartpromise.defer<IExecResult>();
83
-
74
+ /**
75
+ * Executes a command and returns either a non-streaming result or a streaming interface.
76
+ */
77
+ private async _execCommand(options: IExecOptions): Promise<IExecResult | IExecResultStreaming> {
84
78
  const commandToExecute = this.shellEnv.createEnvExecString(options.commandString);
85
-
86
79
  const shellLogInstance = new ShellLog();
80
+
87
81
  const execChildProcess = cp.spawn(commandToExecute, [], {
88
82
  shell: true,
89
83
  cwd: process.cwd(),
@@ -93,6 +87,7 @@ export class Smartshell {
93
87
 
94
88
  this.smartexit.addProcess(execChildProcess);
95
89
 
90
+ // Capture stdout and stderr output.
96
91
  execChildProcess.stdout.on('data', (data) => {
97
92
  if (!options.silent) {
98
93
  shellLogInstance.writeToConsole(data);
@@ -107,47 +102,55 @@ export class Smartshell {
107
102
  shellLogInstance.addToBuffer(data);
108
103
  });
109
104
 
110
- execChildProcess.on('exit', (code, signal) => {
111
- this.smartexit.removeProcess(execChildProcess);
112
- if (options.strict && code === 1) {
113
- done.reject();
114
- }
115
-
116
- const execResult = {
117
- exitCode: code,
118
- stdout: shellLogInstance.logStore.toString(),
119
- };
105
+ // Wrap child process termination into a Promise.
106
+ const childProcessEnded: Promise<IExecResult> = new Promise((resolve, reject) => {
107
+ execChildProcess.on('exit', (code, signal) => {
108
+ this.smartexit.removeProcess(execChildProcess);
109
+
110
+ const execResult: IExecResult = {
111
+ exitCode: typeof code === 'number' ? code : (signal ? 1 : 0),
112
+ stdout: shellLogInstance.logStore.toString(),
113
+ };
114
+
115
+ if (options.strict && code !== 0) {
116
+ reject(new Error(`Command "${options.commandString}" exited with code ${code}`));
117
+ } else {
118
+ resolve(execResult);
119
+ }
120
+ });
120
121
 
121
- if (!options.streaming) {
122
- done.resolve(execResult);
123
- }
124
- childProcessEnded.resolve(execResult);
122
+ execChildProcess.on('error', (error) => {
123
+ this.smartexit.removeProcess(execChildProcess);
124
+ reject(error);
125
+ });
125
126
  });
126
127
 
128
+ // If streaming mode is enabled, return a streaming interface immediately.
127
129
  if (options.streaming) {
128
- done.resolve({
130
+ return {
129
131
  childProcess: execChildProcess,
130
- finalPromise: childProcessEnded.promise,
132
+ finalPromise: childProcessEnded,
131
133
  kill: async () => {
132
- console.log(`running tree kill with SIGKILL on process ${execChildProcess.pid}`);
134
+ console.log(`Running tree kill with SIGKILL on process ${execChildProcess.pid}`);
133
135
  await plugins.smartexit.SmartExit.killTreeByPid(execChildProcess.pid, 'SIGKILL');
134
136
  },
135
137
  terminate: async () => {
136
- console.log(`running tree kill with SIGTERM on process ${execChildProcess.pid}`);
138
+ console.log(`Running tree kill with SIGTERM on process ${execChildProcess.pid}`);
137
139
  await plugins.smartexit.SmartExit.killTreeByPid(execChildProcess.pid, 'SIGTERM');
138
140
  },
139
141
  keyboardInterrupt: async () => {
140
- console.log(`running tree kill with SIGINT on process ${execChildProcess.pid}`);
142
+ console.log(`Running tree kill with SIGINT on process ${execChildProcess.pid}`);
141
143
  await plugins.smartexit.SmartExit.killTreeByPid(execChildProcess.pid, 'SIGINT');
142
144
  },
143
- customSignal: async (signalArg: plugins.smartexit.TProcessSignal) => {
144
- console.log(`running tree kill with custom signal ${signalArg} on process ${execChildProcess.pid}`);
145
- await plugins.smartexit.SmartExit.killTreeByPid(execChildProcess.pid, signalArg);
145
+ customSignal: async (signal: plugins.smartexit.TProcessSignal) => {
146
+ console.log(`Running tree kill with custom signal ${signal} on process ${execChildProcess.pid}`);
147
+ await plugins.smartexit.SmartExit.killTreeByPid(execChildProcess.pid, signal);
146
148
  },
147
- });
149
+ } as IExecResultStreaming;
148
150
  }
149
151
 
150
- return await done.promise;
152
+ // For non-streaming mode, wait for the process to complete.
153
+ return await childProcessEnded;
151
154
  }
152
155
 
153
156
  public async exec(commandString: string): Promise<IExecResult> {
@@ -166,41 +169,35 @@ export class Smartshell {
166
169
  return (await this._exec({ commandString, silent: true, strict: true })) as IExecResult;
167
170
  }
168
171
 
169
- public async execStreaming(
170
- commandString: string,
171
- silent: boolean = false
172
- ): Promise<IExecResultStreaming> {
172
+ public async execStreaming(commandString: string, silent: boolean = false): Promise<IExecResultStreaming> {
173
173
  return (await this._exec({ commandString, silent, streaming: true })) as IExecResultStreaming;
174
174
  }
175
175
 
176
176
  public async execStreamingSilent(commandString: string): Promise<IExecResultStreaming> {
177
- return (await this._exec({
178
- commandString,
179
- silent: true,
180
- streaming: true,
181
- })) as IExecResultStreaming;
177
+ return (await this._exec({ commandString, silent: true, streaming: true })) as IExecResultStreaming;
182
178
  }
183
179
 
184
- public async execInteractive(commandString: string) {
180
+ public async execInteractive(commandString: string): Promise<void> {
185
181
  await this._exec({ commandString, interactive: true });
186
182
  }
187
183
 
188
184
  public async execAndWaitForLine(
189
185
  commandString: string,
190
- regexArg: RegExp,
191
- silentArg: boolean = false
192
- ) {
193
- let done = plugins.smartpromise.defer();
194
- let execStreamingResult = await this.execStreaming(commandString, silentArg);
195
- execStreamingResult.childProcess.stdout.on('data', (stdOutChunk: string) => {
196
- if (regexArg.test(stdOutChunk)) {
197
- done.resolve();
198
- }
186
+ regex: RegExp,
187
+ silent: boolean = false
188
+ ): Promise<void> {
189
+ const execStreamingResult = await this.execStreaming(commandString, silent);
190
+ return new Promise<void>((resolve) => {
191
+ execStreamingResult.childProcess.stdout.on('data', (chunk: Buffer | string) => {
192
+ const data = typeof chunk === 'string' ? chunk : chunk.toString();
193
+ if (regex.test(data)) {
194
+ resolve();
195
+ }
196
+ });
199
197
  });
200
- return done.promise;
201
198
  }
202
199
 
203
- public async execAndWaitForLineSilent(commandString: string, regexArg: RegExp) {
204
- return this.execAndWaitForLine(commandString, regexArg, true);
200
+ public async execAndWaitForLineSilent(commandString: string, regex: RegExp): Promise<void> {
201
+ return this.execAndWaitForLine(commandString, regex, true);
205
202
  }
206
203
  }