1 |
|
2 |
|
3 | const child_process = require('child_process')
|
4 | const debug = require('debug')('observable-process')
|
5 | const extend = require('extend')
|
6 | const mergeStream = require('merge-stream')
|
7 | const stringArgv = require('string-argv')
|
8 | const TextStreamSearch = require('text-stream-search')
|
9 |
|
10 |
|
11 |
|
12 |
|
13 |
|
14 |
|
15 |
|
16 |
|
17 |
|
18 |
|
19 |
|
20 |
|
21 |
|
22 |
|
23 |
|
24 |
|
25 |
|
26 |
|
27 |
|
28 |
|
29 |
|
30 | class ObservableProcess {
|
31 |
|
32 |
|
33 |
|
34 |
|
35 |
|
36 |
|
37 |
|
38 |
|
39 |
|
40 |
|
41 |
|
42 |
|
43 |
|
44 |
|
45 |
|
46 |
|
47 |
|
48 |
|
49 |
|
50 | constructor (args
|
51 |
|
52 |
|
53 |
|
54 |
|
55 |
|
56 |
|
57 |
|
58 | ) {
|
59 | if (args.env != null) this.env = args.env
|
60 | this.verbose = args.verbose || false
|
61 | this.cwd = args.cwd != null ? args.cwd : process.cwd()
|
62 | this.stdout = args.stdout || process.stdout
|
63 | this.stderr = args.stderr || process.stderr
|
64 | this.ended = false
|
65 | this.endedListeners = []
|
66 |
|
67 |
|
68 |
|
69 | const options = {
|
70 | env: {},
|
71 | cwd: this.cwd
|
72 | }
|
73 | extend(options.env, process.env, this.env)
|
74 | var runnable = ''
|
75 | var params = []
|
76 | if (args.command != null) {
|
77 | ;[runnable, ...params] = this._splitCommand(args.command)
|
78 | }
|
79 | if (args.commands != null) {
|
80 | runnable = args.commands[0]
|
81 | params = args.commands.splice(1)
|
82 | }
|
83 | debug(`starting '${runnable}' with arguments [${params.join(',')}]`)
|
84 | this.process = child_process.spawn(runnable, params, options)
|
85 | this.process.on('close', this._onClose.bind(this))
|
86 |
|
87 | this.textStreamSearch = new TextStreamSearch(
|
88 | mergeStream(this.process.stdout, this.process.stderr)
|
89 | )
|
90 |
|
91 | if (this.stdout) {
|
92 | this.process.stdout.on('data', data => {
|
93 | this.stdout.write(data.toString())
|
94 | })
|
95 | }
|
96 | if (this.stderr) {
|
97 | this.process.stderr.on('data', data => {
|
98 | this.stderr.write(data.toString())
|
99 | })
|
100 | }
|
101 |
|
102 |
|
103 |
|
104 | this.killed = false
|
105 |
|
106 | this.stdin = this.process.stdin
|
107 | }
|
108 |
|
109 |
|
110 |
|
111 | enter (text ) {
|
112 | this.stdin.write(`${text}\n`)
|
113 | }
|
114 |
|
115 | fullOutput () {
|
116 | return this.textStreamSearch.fullText()
|
117 | }
|
118 |
|
119 | kill () {
|
120 | debug('killing the process')
|
121 | this.killed = true
|
122 | this.process.kill()
|
123 | }
|
124 |
|
125 |
|
126 | notifyEnded () {
|
127 | for (let resolver of this.endedListeners) {
|
128 | resolver({ exitCode: this.exitCode, killed: this.killed })
|
129 | }
|
130 | }
|
131 |
|
132 | _onClose (exitCode ) {
|
133 | debug(`process has ended with code ${exitCode}`)
|
134 | this.exitCode = exitCode
|
135 | this.ended = true
|
136 | if (this.verbose) {
|
137 | if (this.stderr) this.stderr.write('PROCESS ENDED\n')
|
138 | if (this.stderr) this.stderr.write(`\nEXIT CODE: ${this.exitCode}`)
|
139 | }
|
140 | this.notifyEnded()
|
141 | }
|
142 |
|
143 | pid () {
|
144 | if (this.process) return this.process.pid
|
145 | }
|
146 |
|
147 | waitForEnd () {
|
148 | return new Promise(resolve => {
|
149 | this.endedListeners.push(resolve)
|
150 | })
|
151 | }
|
152 |
|
153 |
|
154 | async waitForText (text , timeout ) {
|
155 | await this.textStreamSearch.waitForText(text, timeout)
|
156 | }
|
157 |
|
158 | resetOutputStreams () {
|
159 | this.textStreamSearch.reset()
|
160 | }
|
161 |
|
162 | _splitCommand (command ) {
|
163 | if (Array.isArray(command)) {
|
164 | return command
|
165 | } else {
|
166 | return stringArgv(command)
|
167 | }
|
168 | }
|
169 | }
|
170 |
|
171 | module.exports = ObservableProcess
|