1
+
'use strict'
2
+
3
+
const { spawn } = require('child_process')
4
+
const os = require('os')
5
+
const which = require('which')
6
+
7
+
const escape = require('./escape.js')
8
+
9
+
// 'extra' object is for decorating the error a bit more
10
+
const promiseSpawn = (cmd, args, opts = {}, extra = {}) => {
11
+
if (opts.shell) {
12
+
return spawnWithShell(cmd, args, opts, extra)
13
+
}
14
+
15
+
let proc
16
+
17
+
const p = new Promise((res, rej) => {
18
+
proc = spawn(cmd, args, opts)
19
+
20
+
const stdout = []
21
+
const stderr = []
22
+
23
+
const reject = er => rej(Object.assign(er, {
24
+
cmd,
25
+
args,
26
+
...stdioResult(stdout, stderr, opts),
27
+
...extra,
28
+
}))
29
+
30
+
proc.on('error', reject)
31
+
32
+
if (proc.stdout) {
33
+
proc.stdout.on('data', c => stdout.push(c)).on('error', reject)
34
+
proc.stdout.on('error', er => reject(er))
35
+
}
36
+
37
+
if (proc.stderr) {
38
+
proc.stderr.on('data', c => stderr.push(c)).on('error', reject)
39
+
proc.stderr.on('error', er => reject(er))
40
+
}
41
+
42
+
proc.on('close', (code, signal) => {
43
+
const result = {
44
+
cmd,
45
+
args,
46
+
code,
47
+
signal,
48
+
...stdioResult(stdout, stderr, opts),
49
+
...extra,
50
+
}
51
+
52
+
if (code || signal) {
53
+
rej(Object.assign(new Error('command failed'), result))
54
+
} else {
55
+
res(result)
56
+
}
57
+
})
58
+
})
59
+
60
+
p.stdin = proc.stdin
61
+
p.process = proc
62
+
return p
63
+
}
64
+
65
+
const spawnWithShell = (cmd, args, opts, extra) => {
66
+
let command = opts.shell
67
+
// if shell is set to true, we use a platform default. we can't let the core
68
+
// spawn method decide this for us because we need to know what shell is in use
69
+
// ahead of time so that we can escape arguments properly. we don't need coverage here.
70
+
if (command === true) {
71
+
// istanbul ignore next
72
+
command = process.platform === 'win32' ? process.env.ComSpec : 'sh'
73
+
}
74
+
75
+
const options = { ...opts, shell: false }
76
+
const realArgs = []
77
+
let script = cmd
78
+
79
+
// first, determine if we're in windows because if we are we need to know if we're
80
+
// running an .exe or a .cmd/.bat since the latter requires extra escaping
81
+
const isCmd = /(?:^|\\)cmd(?:\.exe)?$/i.test(command)
82
+
if (isCmd) {
83
+
let doubleEscape = false
84
+
85
+
// find the actual command we're running
86
+
let initialCmd = ''
87
+
let insideQuotes = false
88
+
for (let i = 0; i < cmd.length; ++i) {
89
+
const char = cmd.charAt(i)
90
+
if (char === ' ' && !insideQuotes) {
91
+
break
92
+
}
93
+
94
+
initialCmd += char
95
+
if (char === '"' || char === "'") {
96
+
insideQuotes = !insideQuotes
97
+
}
98
+
}
99
+
100
+
let pathToInitial
101
+
try {
102
+
pathToInitial = which.sync(initialCmd, {
103
+
path: (options.env && options.env.PATH) || process.env.PATH,
104
+
pathext: (options.env && options.env.PATHEXT) || process.env.PATHEXT,
105
+
}).toLowerCase()
106
+
} catch (err) {
107
+
pathToInitial = initialCmd.toLowerCase()
108
+
}
109
+
110
+
doubleEscape = pathToInitial.endsWith('.cmd') || pathToInitial.endsWith('.bat')
111
+
for (const arg of args) {
112
+
script += ` ${escape.cmd(arg, doubleEscape)}`
113
+
}
114
+
realArgs.push('/d', '/s', '/c', script)
115
+
options.windowsVerbatimArguments = true
116
+
} else {
117
+
for (const arg of args) {
118
+
script += ` ${escape.sh(arg)}`
119
+
}
120
+
realArgs.push('-c', script)
121
+
}
122
+
123
+
return promiseSpawn(command, realArgs, options, extra)
124
+
}
125
+
126
+
// open a file with the default application as defined by the user's OS
127
+
const open = (_args, opts = {}, extra = {}) => {
128
+
const options = { ...opts, shell: true }
129
+
const args = [].concat(_args)
130
+
131
+
let platform = process.platform
132
+
// process.platform === 'linux' may actually indicate WSL, if that's the case
133
+
// we want to treat things as win32 anyway so the host can open the argument
134
+
if (platform === 'linux' && os.release().toLowerCase().includes('microsoft')) {
135
+
platform = 'win32'
136
+
}
137
+
138
+
let command = options.command
139
+
if (!command) {
140
+
if (platform === 'win32') {
141
+
// spawnWithShell does not do the additional os.release() check, so we
142
+
// have to force the shell here to make sure we treat WSL as windows.
143
+
options.shell = process.env.ComSpec
144
+
// also, the start command accepts a title so to make sure that we don't
145
+
// accidentally interpret the first arg as the title, we stick an empty
146
+
// string immediately after the start command
147
+
command = 'start ""'
148
+
} else if (platform === 'darwin') {
149
+
command = 'open'
150
+
} else {
151
+
command = 'xdg-open'
152
+
}
153
+
}
154
+
155
+
return spawnWithShell(command, args, options, extra)
156
+
}
157
+
promiseSpawn.open = open
158
+
159
+
const isPipe = (stdio = 'pipe', fd) => {
160
+
if (stdio === 'pipe' || stdio === null) {
161
+
return true
162
+
}
163
+
164
+
if (Array.isArray(stdio)) {
165
+
return isPipe(stdio[fd], fd)
166
+
}
167
+
168
+
return false
169
+
}
170
+
171
+
const stdioResult = (stdout, stderr, { stdioString = true, stdio }) => {
172
+
const result = {
173
+
stdout: null,
174
+
stderr: null,
175
+
}
176
+
177
+
// stdio is [stdin, stdout, stderr]
178
+
if (isPipe(stdio, 1)) {
179
+
result.stdout = Buffer.concat(stdout)
180
+
if (stdioString) {
181
+
result.stdout = result.stdout.toString().trim()
182
+
}
183
+
}
184
+
185
+
if (isPipe(stdio, 2)) {
186
+
result.stderr = Buffer.concat(stderr)
187
+
if (stdioString) {
188
+
result.stderr = result.stderr.toString().trim()
189
+
}
190
+
}
191
+
192
+
return result
193
+
}
194
+
195
+
module.exports = promiseSpawn
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4