Skip to content

Commit 296bfac

Browse files
author
Kent C. Dodds
authored
fix(parallel): remove --parallel in favor of concurrently (#94)
**What**: This removes the `--parallel` capabities from `p-s` in favor of encouraging the use of the `concurrently` module. **Why**: DOTADIW. We want `p-s` to stay as small and focused as possible **How**: Refactoring and documenting BREAKING CHANGE: you must now use `concurrently` to have scripts run in parallel. Check the docs for an example of how to do this.
1 parent 3566a15 commit 296bfac

File tree

11 files changed

+207
-247
lines changed

11 files changed

+207
-247
lines changed

README.md

+12-33
Original file line numberDiff line numberDiff line change
@@ -44,18 +44,20 @@ module.exports = {
4444
default: 'node index.js',
4545
lint: 'eslint .',
4646
test: {
47-
// learn more about Jest here: https://kcd.im/egghead-jest
47+
// learn more about Jest here: https://facebook.github.io/jest
4848
default: 'jest',
4949
watch: {
5050
script: 'jest --watch',
5151
description: 'run in the amazingly intelligent Jest watch mode'
5252
}
5353
},
5454
build: {
55+
// learn more about Webpack here: https://webpack.js.org/
5556
default: 'webpack',
5657
prod: 'webpack -p',
5758
},
58-
validate: 'nps --parallel lint,test,build',
59+
// learn more about concurrently here: https://npm.im/concurrently
60+
validate: 'concurrently "nps lint" "nps test" "nps build"',
5961
},
6062
}
6163
```
@@ -75,7 +77,7 @@ scripts:
7577
build:
7678
default: webpack
7779
prod: webpack -p
78-
validate: nps --parallel lint,test,build
80+
validate: concurrently "nps lint" "nps test" "nps build"
7981
```
8082
8183
To use `p-s`, it's recommended that you either install it globally (`npm i -g p-s`) or add `./node_modules/bin` to your
@@ -84,7 +86,7 @@ To use `p-s`, it's recommended that you either install it globally (`npm i -g p-
8486
Then you can run:
8587

8688
```console
87-
p-s --help
89+
nps --help
8890
```
8991

9092
Which will output:
@@ -97,7 +99,6 @@ Which will output:
9799
-h, --help output usage information
98100
-V, --version output the version number
99101
-s, --silent Silent nps output
100-
-p, --parallel <script-name1,script-name2> Scripts to run in parallel (comma seprated)
101102
-c, --config <filepath> Config file to use (defaults to nearest package-scripts.yml or package-scripts.js)
102103
-l, --log-level <level> The log level to use (error, warn, info [default])
103104
-r, --require <module> Module to preload
@@ -109,7 +110,7 @@ test - jest
109110
test.watch - run in the amazingly intelligent Jest watch mode - jest --watch
110111
build - webpack
111112
build.prod - webpack -p
112-
validate - nps --parallel lint,test,build
113+
validate - concurrently "nps lint" "nps test" "nps build"
113114
```
114115

115116
**Because `p-s` is harder to type, it is recommended that you use the alias `nps` to interact with `p-s`, which is much
@@ -243,14 +244,6 @@ config).
243244
By default, `nps` will log out to the console before running the command. You can add `-s` to your command to silence
244245
this.
245246

246-
##### -p, --parallel
247-
248-
Run the given scripts in parallel. This enables handy workflows like this:
249-
250-
```console
251-
nps -p lint,build,cover && nps check-coverage && nps report-coverage
252-
```
253-
254247
##### -c, --config
255248

256249
Use a different config
@@ -283,7 +276,7 @@ nps lint --fix # --fix will be passed on to the lint script
283276

284277
##### scripts
285278

286-
If you don't use `-p` (because you don't need parallelism) then you can simply provide the name of the script like so:
279+
To run a script, you simply provide the name of the script like so:
287280

288281
```console
289282
nps cover
@@ -377,6 +370,10 @@ Log levels available:
377370

378371
## FAQ
379372

373+
### How do I do ___ ?
374+
375+
Have you looked at the examples in [other/EXAMPLES.md][examples]?
376+
380377
### Why `npm start`?
381378

382379
_Just to be clear:_ You do **not** have to use the `start` script. You can use whatever you like. But I recommend using
@@ -404,24 +401,6 @@ This was inspired by [a tweet][tweet] by [@sindresorhus][sindre].
404401
a line for every script (one of the pains I'm trying to solve) and a each script requires its own file (one of the
405402
benefits of npm scripts I wanted to keep).
406403

407-
## In the wild
408-
409-
- [react-component-template](https://github.com/nkbt/react-component-template) uses `p-s` to implement shareable npm
410-
scripts. See then how dependent [react-swap](https://github.com/nkbt/react-swap) can reuse them.
411-
412-
GOTCHAS:
413-
- use `process.cwd()` as the base for all paths
414-
415-
- [Hypercubed/EventsSpeedTests](https://github.com/Hypercubed/EventsSpeedTests) uses `p-s` to automate benchmark running
416-
and reporting in node and the browser. `package-scripts.js` enables us to keep our scripts DRY. Combined with
417-
[grunion](https://github.com/Hypercubed/grunion) allows benchmarks to be run, serially or concurrently, on glob
418-
patterns.
419-
420-
- [SmithersAssistant/Smithers](https://github.com/SmithersAssistant/smithers) is an [electron](https://electron.atom.io)
421-
based personal assistant. Smithers works on multiple platforms. Smithers uses `p-s` to dynamically find the current
422-
platform and execute the dev environment. Now we don't have to manually update the `package.json` scripts when you are
423-
on a different platform!
424-
425404
## Contributors
426405

427406
Thanks goes to these people ([emoji key][emojis]):

cli-test/__snapshots__/cli.test.js.snap

+12-13
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
exports[`test with --require 1`] = `
22
Object {
33
"stderr": "",
4-
"stdout": "nps executing: echo \"log\"
4+
"stdout": "[90mnps executing: [39m[32mecho \"log\"[39m
55
log
66
",
77
}
@@ -17,7 +17,7 @@ Object {
1717

1818
exports[`test with a missing config 1`] = `
1919
Object {
20-
"stderr": "Unable to find JS config at \"./something-that-does-not-exist.js\". Attempted to require as \"<projectRootDir>/cli-test/fixtures/something-that-does-not-exist.js\" https://github.com/kentcdodds/p-s/blob/v0.0.0-semantically-released/other/ERRORS_AND_WARNINGS.md#unable-to-find-config
20+
"stderr": "[31mUnable to find JS config at \"./something-that-does-not-exist.js\". Attempted to require as \"<projectRootDir>/cli-test/fixtures/something-that-does-not-exist.js\"[39m https://github.com/kentcdodds/p-s/blob/v0.0.0-semantically-released/other/ERRORS_AND_WARNINGS.md#unable-to-find-config
2121
",
2222
"stdout": "",
2323
}
@@ -26,7 +26,7 @@ Object {
2626
exports[`test with config with default script 1`] = `
2727
Object {
2828
"stderr": "",
29-
"stdout": "nps executing: echo \"default script\"
29+
"stdout": "[90mnps executing: [39m[32mecho \"default script\"[39m
3030
default script
3131
",
3232
}
@@ -40,19 +40,18 @@ Object {
4040
4141
Options:
4242
43-
-h, --help output usage information
44-
-V, --version output the version number
45-
-s, --silent Silent nps output
46-
-p, --parallel <script-name1,script-name2> Scripts to run in parallel (comma seprated)
47-
-c, --config <filepath> Config file to use (defaults to nearest package-scripts.yml or package-scripts.js)
48-
-l, --log-level <level> The log level to use (error, warn, info [default])
49-
-r, --require <module> Module to preload
43+
-h, --help output usage information
44+
-V, --version output the version number
45+
-s, --silent Silent nps output
46+
-c, --config <filepath> Config file to use (defaults to nearest package-scripts.yml or package-scripts.js)
47+
-l, --log-level <level> The log level to use (error, warn, info [default])
48+
-r, --require <module> Module to preload
5049
5150
Available scripts (camel or kebab case accepted)
5251
53-
test - echo \"test script\"
54-
lint - echo \"lint.default\"
55-
lint.sub.thing - this is a description - echo \"deeply nested thing\"
52+
[32mtest[39m - [90mecho \"test script\"[39m
53+
[32mlint[39m - [90mecho \"lint.default\"[39m
54+
[32mlint.sub.thing[39m - [37mthis is a description[39m - [90mecho \"deeply nested thing\"[39m
5655
",
5756
}
5857
`;

other/EXAMPLES.md

+43-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
# package-scripts examples
22

33
## Links to projects
4+
45
Examples of how people us `p-s`:
56

67
- **[p-s](https://github.com/kentcdodds/p-s):** [scripts](https://github.com/kentcdodds/p-s/blob/7a00d750611f08e8a95f24e78dd1cdc0a2213e51/package.json#L6-L10), [`package-scripts.js`](https://github.com/kentcdodds/p-s/blob/7a00d750611f08e8a95f24e78dd1cdc0a2213e51/package-scripts.js)
7-
- **[Smithers](https://github.com/SmithersAssistant/smithers):**
8-
[scripts](https://github.com/SmithersAssistant/smithers/blob/0732fed616d64ff4696110574e51c300cd409d4c/package.json#L67-L70), [`package-scripts.js`](https://github.com/SmithersAssistant/smithers/blob/0732fed616d64ff4696110574e51c300cd409d4c/package-scripts.js)
8+
- **[react-component-template](https://github.com/nkbt/react-component-template)** uses `p-s` to implement shareable npm
9+
scripts. See then how dependent [react-swap](https://github.com/nkbt/react-swap) can reuse them. (Gotcha: - use
10+
`process.cwd()` as the base for all paths).
11+
- **[Hypercubed/EventsSpeedTests](https://github.com/Hypercubed/EventsSpeedTests)** uses `p-s` to automate benchmark
12+
running and reporting in node and the browser. `package-scripts.js` enables us to keep our scripts DRY. Combined with
13+
[grunion](https://github.com/Hypercubed/grunion) allows benchmarks to be run, serially or concurrently, on glob
14+
patterns.
15+
- **[SmithersAssistant/Smithers](https://github.com/SmithersAssistant/smithers)** is an
16+
[electron](https://electron.atom.io) based personal assistant. Smithers works on multiple platforms. Smithers uses `p-s`
17+
to dynamically find the current platform and execute the dev environment. Now we don't have to manually update the
18+
`package.json` scripts when you are on a different platform!
19+
[scripts](https://github.com/SmithersAssistant/smithers/blob/0732fed616d64ff4696110574e51c300cd409d4c/package.json#L67-L70),
20+
[`package-scripts.js`](https://github.com/SmithersAssistant/smithers/blob/0732fed616d64ff4696110574e51c300cd409d4c/package-scripts.js)
921

1022
## Inline Examples
1123

@@ -16,6 +28,9 @@ can't determine the platform in the `package.json`, you have to either have to d
1628
`build:windows` and `build:unix` script), find CLIs that are cross platform (like
1729
[`cross-env`](http://npm.im/cross-env)), or write your logic in a separate file to handle the platform.
1830

31+
You can also use [`cross-var`](http://npm.im/cross-var) in basically the same way to do the same for using environment
32+
variables in your scripts so it works on both windows and mac/linux
33+
1934
With `package-scripts`, you can really easily have a single script that uses the platform to determine what should be
2035
run. For example:
2136

@@ -35,6 +50,32 @@ module.exports = {
3550
Note, in this specific scenario, I'd recommend that you actually use [`rimraf`](http://npm.im/rimraf), but I think you
3651
get the idea 😄. This is a pretty nice win over traditional npm scripts 👍
3752

53+
### parallel scripts
54+
55+
Often, scripts can run concurrently because they are not interdependent. We recommend
56+
[`concurrently`](http://npm.im/concurrently) for this:
57+
58+
```javascript
59+
module.exports = {
60+
scripts: {
61+
validate: concurrent([
62+
'build',
63+
'lint',
64+
'test',
65+
'order.sandwich',
66+
]),
67+
// etc...
68+
}
69+
}
70+
71+
function concurrent(scripts) {
72+
process.env.FORCE_COLOR = true // this is necessary until https://github.com/kimmobrunfeldt/concurrently/issues/86
73+
const names = scripts.join(',')
74+
const quotedScripts = `"nps ${scripts.join('" "nps ')}"`
75+
return `concurrently --prefix "[{name}]" --names "${names}" ${quotedScripts}`
76+
}
77+
```
78+
3879
## Instructions
3980

4081
Thanks for using `p-s`! I'm glad/I hope it's been helpful to you. Please add a link to your example here. If you're

package-scripts.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ module.exports = {
4343
},
4444
validate: {
4545
description: 'This runs several scripts to make sure things look good before committing or on clean install',
46-
script: `nps -p ${validate.join(',')}`,
46+
script: concurrent(validate),
4747
},
4848
addContributor: {
4949
description: 'When new people contribute to the project, run this',
@@ -58,3 +58,10 @@ module.exports = {
5858
silent: false,
5959
},
6060
}
61+
62+
function concurrent(scripts) {
63+
process.env.FORCE_COLOR = true // this is required until https://github.com/kimmobrunfeldt/concurrently/issues/86
64+
const names = scripts.join(',')
65+
const quotedScripts = `"nps ${scripts.join('" "nps ')}"`
66+
return `concurrently --prefix "[{name}]" --names "${names}" ${quotedScripts}`
67+
}

package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"dependencies": {
2424
"arrify": "^1.0.1",
2525
"bluebird": "^3.4.7",
26-
"colors": "^1.1.2",
26+
"chalk": "^1.1.3",
2727
"commander": "^2.9.0",
2828
"find-up": "^2.1.0",
2929
"js-yaml": "^3.7.0",
@@ -56,6 +56,7 @@
5656
"husky": "^0.13.1",
5757
"jest-cli": "^18.1.0",
5858
"opt-cli": "^1.5.1",
59+
"concurrently": "^3.1.0",
5960
"p-s": "*",
6061
"rimraf": "^2.5.4",
6162
"semantic-release": "^6.3.6",

src/bin-utils/index.js

+12-16
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {readFileSync} from 'fs'
33
import {remove, includes, isPlainObject, isUndefined, isEmpty, isFunction} from 'lodash'
44
import typeOf from 'type-detect'
55
import shellEscape from 'shell-escape'
6-
import colors from 'colors/safe'
6+
import chalk from 'chalk'
77
import {safeLoad} from 'js-yaml'
88

99
import getLogger from '../get-logger'
@@ -20,15 +20,15 @@ const log = getLogger()
2020
*/
2121
const preloadModule = getAttemptModuleRequireFn((moduleName, requirePath) => {
2222
log.warn({
23-
message: colors.yellow(`Unable to preload "${moduleName}". Attempted to require as "${requirePath}"`),
23+
message: chalk.yellow(`Unable to preload "${moduleName}". Attempted to require as "${requirePath}"`),
2424
ref: 'unable-to-preload-module',
2525
})
2626
return undefined
2727
})
2828

2929
const loadJSConfig = getAttemptModuleRequireFn(function onFail(configPath, requirePath) {
3030
log.error({
31-
message: colors.red(`Unable to find JS config at "${configPath}". Attempted to require as "${requirePath}"`),
31+
message: chalk.red(`Unable to find JS config at "${configPath}". Attempted to require as "${requirePath}"`),
3232
ref: 'unable-to-find-config',
3333
})
3434
return undefined
@@ -59,7 +59,7 @@ function loadConfig(configPath, input) {
5959
}
6060
if (!isPlainObject(config)) {
6161
log.error({
62-
message: colors.red(
62+
message: chalk.red(
6363
`The package-scripts configuration ("${configPath}") ` +
6464
'must be an object or a function that returns an object.',
6565
),
@@ -86,7 +86,7 @@ function loadYAMLConfig(configPath) {
8686
throw e
8787
}
8888
log.error({
89-
message: colors.red(`Unable to find YML config at "${configPath}".`),
89+
message: chalk.red(`Unable to find YML config at "${configPath}".`),
9090
ref: 'unable-to-find-config',
9191
})
9292
return undefined
@@ -97,22 +97,18 @@ function getScriptsAndArgs(program) {
9797
let scripts = []
9898
let args = ''
9999
const {rawArgs} = program
100-
const parallel = !isEmpty(program.parallel)
101-
if (parallel) {
102-
scripts = program.parallel.split(',')
103-
args = getArgs(program.args, program.rawArgs, scripts)
104-
} else if (!isEmpty(program.args)) {
100+
if (!isEmpty(program.args)) {
105101
const [scriptsString] = program.args
106102
scripts = scriptsString.split(',')
107103
remove(rawArgs, arg => arg === scriptsString)
108104
args = getArgs(program.args.slice(1), rawArgs, scripts)
109105
}
110-
return {scripts, args, parallel}
106+
return {scripts, args}
111107
}
112108

113109
function getArgs(args, rawArgs, scripts) {
114110
const allArgs = rawArgs.slice(2)
115-
const psArgs = ['-p', '--parallel', '-c', '--config', '-r', '--require']
111+
const psArgs = ['-c', '--config', '-r', '--require']
116112
const psFlags = ['-s', '--silent']
117113
const cleanedArgs = remove(allArgs, (item, index, array) => {
118114
const isArgOrFlag = includes(psArgs, item) || includes(psFlags, item)
@@ -165,11 +161,11 @@ function help({scripts}) {
165161
const availableScripts = getAvailableScripts(scripts)
166162
const filteredScripts = availableScripts.filter(script => !script.hiddenFromHelp)
167163
const scriptLines = filteredScripts.map(({name, description, script}) => {
168-
const coloredName = colors.green(name)
169-
const coloredScript = colors.gray(script)
164+
const coloredName = chalk.green(name)
165+
const coloredScript = chalk.gray(script)
170166
let line
171167
if (description) {
172-
line = [coloredName, colors.white(description), coloredScript]
168+
line = [coloredName, chalk.white(description), coloredScript]
173169
} else {
174170
line = [coloredName, coloredScript]
175171
}
@@ -180,7 +176,7 @@ function help({scripts}) {
180176
const message = `${topMessage}\n\n${scriptLines.join('\n')}`
181177
return message
182178
} else {
183-
return colors.yellow('There are no scripts available')
179+
return chalk.yellow('There are no scripts available')
184180
}
185181
}
186182

0 commit comments

Comments
 (0)