1 | import * as fsPath from 'path';
|
2 | import { Observable, Subject } from 'rxjs';
|
3 | import * as childProcess from 'child_process';
|
4 | import { log } from './log';
|
5 |
|
6 | export interface IExec {
|
7 | code: number;
|
8 | cmd: string;
|
9 | stdout?: string;
|
10 | stderr?: string;
|
11 | }
|
12 |
|
13 | export interface IExecOptions {
|
14 | silent?: boolean;
|
15 | onExit?: () => void;
|
16 | }
|
17 |
|
18 | export 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 |
|
36 |
|
37 |
|
38 |
|
39 | export 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 |
|
62 |
|
63 | export function execWithin(path: string, cmd: string, options?: IExecOptions) {
|
64 | path = fsPath.resolve(path);
|
65 | return exec(`cd ${path} && ${cmd}`, options);
|
66 | }
|
67 |
|
68 |
|
69 |
|
70 |
|
71 | export 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 |
|
96 | if (!silent) {
|
97 | child.stdout.pipe(process.stdout);
|
98 | child.stderr.pipe(process.stderr);
|
99 | }
|
100 |
|
101 |
|
102 | process.on('exit', () => {
|
103 | if (options.onExit) {
|
104 | options.onExit();
|
105 | }
|
106 | child.kill();
|
107 | });
|
108 | });
|
109 | }
|
110 |
|
111 |
|
112 |
|
113 |
|
114 | export async function execWithinAsync(
|
115 | path: string,
|
116 | cmd: string,
|
117 | options?: IExecOptions,
|
118 | ) {
|
119 | path = fsPath.resolve(path);
|
120 | return await execAsync(`cd ${path} && ${cmd}`, options);
|
121 | }
|
122 |
|
123 |
|
124 |
|
125 |
|
126 | export function exec$(cmd: string): Observable<IExec> {
|
127 | let code = 0;
|
128 | const result = new Subject<IExec>();
|
129 |
|
130 |
|
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 |
|
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 |
|
159 | return result as Observable<IExec>;
|
160 | }
|