UNPKG

3.54 kBPlain TextView Raw
1import * as fsPath from 'path';
2import { Observable, Subject } from 'rxjs';
3import * as childProcess from 'child_process';
4import { log } from '../libs';
5
6export interface IExec {
7 code: number;
8 cmd: string;
9 stdout?: string;
10 stderr?: string;
11}
12
13export interface IExecOptions {
14 silent?: boolean;
15 onExit?: () => void;
16}
17
18export class ExecutionError extends Error {
19 public code: number = 1;
20
21 constructor(error: string = 'Error: Command failed', code?: number) {
22 super(error);
23 if (code) {
24 this.code = code;
25 }
26 }
27
28 public setCode(code: number): this {
29 this.code = code;
30 return this;
31 }
32}
33
34/**
35 * Executes the given shell command synchronously.
36 * @param cmd: The command to invoke.
37 * @param options: Options for the execution.
38 */
39export function exec(cmd: string, options: IExecOptions = {}): IExec {
40 const silent = options.silent || false;
41 let stdout: string = '';
42 let code = 0;
43 try {
44 stdout = childProcess.execSync(cmd).toString();
45 if (!silent) {
46 log.info(stdout);
47 }
48 } catch (err) {
49 const stderr = err.toString();
50 code = err.status || 1;
51 throw new ExecutionError(stderr).setCode(code);
52 }
53 return {
54 code,
55 cmd,
56 stdout,
57 };
58}
59
60/**
61 * Executes the given command within the given directory.
62 */
63export function execWithin(path: string, cmd: string, options?: IExecOptions) {
64 path = fsPath.resolve(path);
65 return exec(`cd ${path} && ${cmd}`, options);
66}
67
68/**
69 * Executes a process asynchronously
70 */
71export function execAsync(
72 cmd: string,
73 options: IExecOptions = {},
74): Promise<IExec> {
75 return new Promise((resolve, reject) => {
76 const silent = options.silent || false;
77 const child = childProcess.exec(
78 cmd,
79 (err: any, stdout: string, stderr: string) => {
80 if (err) {
81 return reject({
82 error: err,
83 code: err.status || 1,
84 });
85 }
86 resolve({
87 code: 0,
88 cmd,
89 stdout,
90 stderr: stderr === '' ? undefined : stderr,
91 });
92 },
93 );
94
95 // Stream output to the console unless silent is requested.
96 if (!silent) {
97 child.stdout.pipe(process.stdout);
98 child.stderr.pipe(process.stderr);
99 }
100
101 // Kill the child when the parent process exits.
102 process.on('exit', () => {
103 if (options.onExit) {
104 options.onExit();
105 }
106 child.kill();
107 });
108 });
109}
110
111/**
112 * Executes the given command with a given directory asynchronously
113 */
114export async function execWithinAsync(
115 path: string,
116 cmd: string,
117 options?: IExecOptions,
118) {
119 path = fsPath.resolve(path);
120 return execAsync(`cd ${path} && ${cmd}`, options);
121}
122
123/**
124 * Executes the given command in the background as a observable.
125 */
126export function exec$(cmd: string): Observable<IExec> {
127 let code = 0;
128 const result = new Subject<IExec>();
129
130 // Start the child-process.
131 const child = childProcess.exec(
132 cmd,
133 (err: any, stdout: string, stderr: string) => {
134 if (err) {
135 code = err.status || 1;
136 result.error(err);
137 } else {
138 result.complete();
139 }
140 },
141 );
142
143 // Listen for data.
144 const next = (stdout?: string, stderr?: string) => {
145 if (stdout) {
146 stdout = stdout.replace(/\n$/, '');
147 }
148 result.next({
149 code,
150 cmd,
151 stdout,
152 stderr,
153 });
154 };
155 child.stdout.on('data', chunk => next(chunk.toString()));
156 child.stderr.on('data', chunk => next(undefined, chunk.toString()));
157
158 // Finish up.
159 return result as Observable<IExec>;
160}