1 | /**
|
2 | * @module index
|
3 | * @author Toru Nagashima
|
4 | * @copyright 2015 Toru Nagashima. All rights reserved.
|
5 | * See LICENSE file in root directory for full license.
|
6 | */
|
7 |
|
8 |
|
9 | //------------------------------------------------------------------------------
|
10 | // Requirements
|
11 | //------------------------------------------------------------------------------
|
12 |
|
13 | const shellQuote = require("shell-quote")
|
14 | const matchTasks = require("./match-tasks")
|
15 | const readPackageJson = require("./read-package-json")
|
16 | const runTasks = require("./run-tasks")
|
17 |
|
18 | //------------------------------------------------------------------------------
|
19 | // Helpers
|
20 | //------------------------------------------------------------------------------
|
21 |
|
22 | const ARGS_PATTERN = /\{(!)?([*@]|\d+)([^}]+)?}/g
|
23 |
|
24 | /**
|
25 | * Converts a given value to an array.
|
26 | *
|
27 | * @param {string|string[]|null|undefined} x - A value to convert.
|
28 | * @returns {string[]} An array.
|
29 | */
|
30 | function toArray(x) {
|
31 | if (x == null) {
|
32 | return []
|
33 | }
|
34 | return Array.isArray(x) ? x : [x]
|
35 | }
|
36 |
|
37 | /**
|
38 | * Replaces argument placeholders (such as `{1}`) by arguments.
|
39 | *
|
40 | * @param {string[]} patterns - Patterns to replace.
|
41 | * @param {string[]} args - Arguments to replace.
|
42 | * @returns {string[]} replaced
|
43 | */
|
44 | function applyArguments(patterns, args) {
|
45 | const defaults = Object.create(null)
|
46 |
|
47 | return patterns.map(pattern => pattern.replace(ARGS_PATTERN, (whole, indirectionMark, id, options) => {
|
48 | if (indirectionMark != null) {
|
49 | throw Error(`Invalid Placeholder: ${whole}`)
|
50 | }
|
51 | if (id === "@") {
|
52 | return shellQuote.quote(args)
|
53 | }
|
54 | if (id === "*") {
|
55 | return shellQuote.quote([args.join(" ")])
|
56 | }
|
57 |
|
58 | const position = parseInt(id, 10)
|
59 | if (position >= 1 && position <= args.length) {
|
60 | return shellQuote.quote([args[position - 1]])
|
61 | }
|
62 |
|
63 | // Address default values
|
64 | if (options != null) {
|
65 | const prefix = options.slice(0, 2)
|
66 |
|
67 | if (prefix === ":=") {
|
68 | defaults[id] = shellQuote.quote([options.slice(2)])
|
69 | return defaults[id]
|
70 | }
|
71 | if (prefix === ":-") {
|
72 | return shellQuote.quote([options.slice(2)])
|
73 | }
|
74 |
|
75 | throw Error(`Invalid Placeholder: ${whole}`)
|
76 | }
|
77 | if (defaults[id] != null) {
|
78 | return defaults[id]
|
79 | }
|
80 |
|
81 | return ""
|
82 | }))
|
83 | }
|
84 |
|
85 | /**
|
86 | * Parse patterns.
|
87 | * In parsing process, it replaces argument placeholders (such as `{1}`) by arguments.
|
88 | *
|
89 | * @param {string|string[]} patternOrPatterns - Patterns to run.
|
90 | * A pattern is a npm-script name or a Glob-like pattern.
|
91 | * @param {string[]} args - Arguments to replace placeholders.
|
92 | * @returns {string[]} Parsed patterns.
|
93 | */
|
94 | function parsePatterns(patternOrPatterns, args) {
|
95 | const patterns = toArray(patternOrPatterns)
|
96 | const hasPlaceholder = patterns.some(pattern => ARGS_PATTERN.test(pattern))
|
97 |
|
98 | return hasPlaceholder ? applyArguments(patterns, args) : patterns
|
99 | }
|
100 |
|
101 | /**
|
102 | * Converts a given config object to an `--:=` style option array.
|
103 | *
|
104 | * @param {object|null} config -
|
105 | * A map-like object to overwrite package configs.
|
106 | * Keys are package names.
|
107 | * Every value is a map-like object (Pairs of variable name and value).
|
108 | * @returns {string[]} `--:=` style options.
|
109 | */
|
110 | function toOverwriteOptions(config) {
|
111 | const options = []
|
112 |
|
113 | for (const packageName of Object.keys(config)) {
|
114 | const packageConfig = config[packageName]
|
115 |
|
116 | for (const variableName of Object.keys(packageConfig)) {
|
117 | const value = packageConfig[variableName]
|
118 |
|
119 | options.push(`--${packageName}:${variableName}=${value}`)
|
120 | }
|
121 | }
|
122 |
|
123 | return options
|
124 | }
|
125 |
|
126 | /**
|
127 | * Converts a given config object to an `--a=b` style option array.
|
128 | *
|
129 | * @param {object|null} config -
|
130 | * A map-like object to set configs.
|
131 | * @returns {string[]} `--a=b` style options.
|
132 | */
|
133 | function toConfigOptions(config) {
|
134 | return Object.keys(config).map(key => `--${key}=${config[key]}`)
|
135 | }
|
136 |
|
137 | /**
|
138 | * Gets the maximum length.
|
139 | *
|
140 | * @param {number} length - The current maximum length.
|
141 | * @param {string} name - A name.
|
142 | * @returns {number} The maximum length.
|
143 | */
|
144 | function maxLength(length, name) {
|
145 | return Math.max(name.length, length)
|
146 | }
|
147 |
|
148 | //------------------------------------------------------------------------------
|
149 | // Public Interface
|
150 | //------------------------------------------------------------------------------
|
151 |
|
152 | /**
|
153 | * Runs npm-scripts which are matched with given patterns.
|
154 | *
|
155 | * @param {string|string[]} patternOrPatterns - Patterns to run.
|
156 | * A pattern is a npm-script name or a Glob-like pattern.
|
157 | * @param {object|undefined} [options] Optional.
|
158 | * @param {boolean} options.parallel -
|
159 | * If this is `true`, run scripts in parallel.
|
160 | * Otherwise, run scripts in sequencial.
|
161 | * Default is `false`.
|
162 | * @param {stream.Readable|null} options.stdin -
|
163 | * A readable stream to send messages to stdin of child process.
|
164 | * If this is `null`, ignores it.
|
165 | * If this is `process.stdin`, inherits it.
|
166 | * Otherwise, makes a pipe.
|
167 | * Default is `null`.
|
168 | * @param {stream.Writable|null} options.stdout -
|
169 | * A writable stream to receive messages from stdout of child process.
|
170 | * If this is `null`, cannot send.
|
171 | * If this is `process.stdout`, inherits it.
|
172 | * Otherwise, makes a pipe.
|
173 | * Default is `null`.
|
174 | * @param {stream.Writable|null} options.stderr -
|
175 | * A writable stream to receive messages from stderr of child process.
|
176 | * If this is `null`, cannot send.
|
177 | * If this is `process.stderr`, inherits it.
|
178 | * Otherwise, makes a pipe.
|
179 | * Default is `null`.
|
180 | * @param {string[]} options.taskList -
|
181 | * Actual name list of npm-scripts.
|
182 | * This function search npm-script names in this list.
|
183 | * If this is `null`, this function reads `package.json` of current directly.
|
184 | * @param {object|null} options.packageConfig -
|
185 | * A map-like object to overwrite package configs.
|
186 | * Keys are package names.
|
187 | * Every value is a map-like object (Pairs of variable name and value).
|
188 | * e.g. `{"npm-run-all": {"test": 777}}`
|
189 | * Default is `null`.
|
190 | * @param {boolean} options.silent -
|
191 | * The flag to set `silent` to the log level of npm.
|
192 | * Default is `false`.
|
193 | * @param {boolean} options.continueOnError -
|
194 | * The flag to ignore errors.
|
195 | * Default is `false`.
|
196 | * @param {boolean} options.printLabel -
|
197 | * The flag to print task names at the head of each line.
|
198 | * Default is `false`.
|
199 | * @param {boolean} options.printName -
|
200 | * The flag to print task names before running each task.
|
201 | * Default is `false`.
|
202 | * @param {number} options.maxParallel -
|
203 | * The maximum number of parallelism.
|
204 | * Default is unlimited.
|
205 | * @param {string} options.npmPath -
|
206 | * The path to npm.
|
207 | * Default is `process.env.npm_execpath`.
|
208 | * @returns {Promise}
|
209 | * A promise object which becomes fullfilled when all npm-scripts are completed.
|
210 | */
|
211 | module.exports = function npmRunAll(patternOrPatterns, options) { //eslint-disable-line complexity
|
212 | const stdin = (options && options.stdin) || null
|
213 | const stdout = (options && options.stdout) || null
|
214 | const stderr = (options && options.stderr) || null
|
215 | const taskList = (options && options.taskList) || null
|
216 | const config = (options && options.config) || null
|
217 | const packageConfig = (options && options.packageConfig) || null
|
218 | const args = (options && options.arguments) || []
|
219 | const parallel = Boolean(options && options.parallel)
|
220 | const silent = Boolean(options && options.silent)
|
221 | const continueOnError = Boolean(options && options.continueOnError)
|
222 | const printLabel = Boolean(options && options.printLabel)
|
223 | const printName = Boolean(options && options.printName)
|
224 | const race = Boolean(options && options.race)
|
225 | const maxParallel = parallel ? ((options && options.maxParallel) || 0) : 1
|
226 | const aggregateOutput = Boolean(options && options.aggregateOutput)
|
227 | const npmPath = options && options.npmPath
|
228 | try {
|
229 | const patterns = parsePatterns(patternOrPatterns, args)
|
230 | if (patterns.length === 0) {
|
231 | return Promise.resolve(null)
|
232 | }
|
233 | if (taskList != null && Array.isArray(taskList) === false) {
|
234 | throw new Error("Invalid options.taskList")
|
235 | }
|
236 | if (typeof maxParallel !== "number" || !(maxParallel >= 0)) {
|
237 | throw new Error("Invalid options.maxParallel")
|
238 | }
|
239 | if (!parallel && race) {
|
240 | throw new Error("Invalid options.race")
|
241 | }
|
242 |
|
243 | const prefixOptions = [].concat(
|
244 | silent ? ["--silent"] : [],
|
245 | packageConfig ? toOverwriteOptions(packageConfig) : [],
|
246 | config ? toConfigOptions(config) : []
|
247 | )
|
248 |
|
249 | return Promise.resolve()
|
250 | .then(() => {
|
251 | if (taskList != null) {
|
252 | return { taskList, packageInfo: null }
|
253 | }
|
254 | return readPackageJson()
|
255 | })
|
256 | .then(x => {
|
257 | const tasks = matchTasks(x.taskList, patterns)
|
258 | const labelWidth = tasks.reduce(maxLength, 0)
|
259 |
|
260 | return runTasks(tasks, {
|
261 | stdin,
|
262 | stdout,
|
263 | stderr,
|
264 | prefixOptions,
|
265 | continueOnError,
|
266 | labelState: {
|
267 | enabled: printLabel,
|
268 | width: labelWidth,
|
269 | lastPrefix: null,
|
270 | lastIsLinebreak: true,
|
271 | },
|
272 | printName,
|
273 | packageInfo: x.packageInfo,
|
274 | race,
|
275 | maxParallel,
|
276 | npmPath,
|
277 | aggregateOutput,
|
278 | })
|
279 | })
|
280 | }
|
281 | catch (err) {
|
282 | return Promise.reject(new Error(err.message))
|
283 | }
|
284 | }
|