-
Notifications
You must be signed in to change notification settings - Fork 33
/
cli.js
163 lines (143 loc) · 4.71 KB
/
cli.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/* eslint no-console:0 */
const execa = require('execa')
const {default: Queue} = require('p-queue')
const path = require('path')
const logUpdate = require('./log-update.js')
const colors = require('./colors.js')
const CODE_OK = 0
/**
* Spawn several commands in children processes, in series
* @param {Array} commands Binary with array of args, like ['npm', ['run', 'test']]
* @param {Object} options Default options for given commands
* @return {Promise<Number>} Resolved with exit code, when all commands where executed on one failed.
*/
function serialSpawn(commands, options = {}) {
return commands.reduce(
(promise, args) => promise.then(() => getSpawnPromise(...args, options)),
Promise.resolve()
)
}
/**
* Spawn several commands in children processes, in parallel
* @param {Array} commands Binary with array of args, like ['npm', ['run', 'test']]
* @param {Object} options Default options for given commands
* @return {Promise<Number>} Resolved with exit code, when all commands where executed on one failed.
*/
function parallelSpawn(commands, options = {}) {
const {chunks = 15, title} = options
commands = commands.map(([bin, args, opts]) => [
bin,
args,
{...opts, ...options}
])
const commandsTitle = title || 'commands'
console.log(
colors.cyan(`›› Running ${commands.length} ${commandsTitle} in parallel.`)
)
return spawnList(commands, {chunks, title})
.then(() => {
logUpdate.done(
colors.green(`✔ ${commands.length} ${commandsTitle} run successfully.`)
)
return CODE_OK
})
.catch(showError)
}
/**
* Executes n commands as an updating list in the command line
* @param {Array} commands Binary with array of args, like ['npm', ['run', 'test']]
* @param {Object} options Options for the spawn list
* @param {Number=} options.chunks Number of chunks of tasks to split by to avoid too long output
* @param {string=} options.title Title to be used as command
*/
function spawnList(commands, {chunks = 15, title = ''} = {}) {
const concurrency = Number(chunks)
const queue = new Queue({concurrency})
const logUpdateProgress = (title, pending) => {
const pendingMessage = colors.cyan(
`${pending} of ${commands.length} pending`
)
logUpdate(`› ${title} ─ ${pendingMessage}`)
}
if (title) logUpdateProgress(title, commands.length)
commands.map(([bin, args, opts, titleFromCommand]) =>
queue
.add(() =>
execa(bin, args, opts).catch(e => {
console.error(bin, args, opts)
console.error(e)
})
)
.then(() => {
const titleToUse =
title || titleFromCommand || getCommandCallMessage(bin, args, opts)
const {size, pending} = queue
const totalPending = size + pending
logUpdateProgress(titleToUse, totalPending)
})
)
return queue.onIdle()
}
/**
* Spawn given command and return a promise of the exit code value
* @param {String} bin Binary path or alias
* @param {Array} args Array of args, like ['npm', ['run', 'test']]
* @param {Object} options Options to pass to child_process.spawn call
* @return {Promise<any>} Process exit code
*/
function getSpawnPromise(bin, args, options = {}) {
if (options.stdio !== 'ignore') {
console.log('')
console.log(getCommandCallMessage(bin, args, options))
}
const execaOptions = {
shell: true,
stdio: 'inherit',
cwd: process.cwd(),
...options
}
return execa(bin, args, execaOptions).then(() => CODE_OK)
}
/**
* Get caption presenting comman execution in a folder
* @param {String} bin Binary path or alias
* @param {Array} args Array of args, like ['npm', ['run', 'test']]
* @param {Object} options to pass to child_process.spawn call
* @return {String}
*/
function getCommandCallMessage(bin, args, options = {}) {
const folder = options.cwd
? options.cwd
.split(path.sep)
.slice(-2)
.join(path.sep)
: ''
const command = bin.split(path.sep).pop() + ' ' + args.join(' ')
return `${colors.bold(command)} ${colors.cyan(folder)}`
}
/**
* Shows an error in the command line and exits process
* It also outputs help content of the command
* The program param will have commander instance to output the help command
* @param {String} msg
* @param {Object} foreignProgram
*/
const showError = (msg, foreignProgram) => {
console.error(colors.red(`✖ Error: ${msg}\n`))
foreignProgram && foreignProgram.outputHelp(txt => txt)
process.exit(1)
}
/**
* Shows a visible warning message the command line.
* @param {String} msg
*/
const showWarning = msg => {
console.warn(colors.yellow(`⚠ ${msg}\n`))
}
module.exports = {
getSpawnPromise,
parallelSpawn,
serialSpawn,
showError,
showWarning
}