├── .gitignore ├── test ├── cache.json └── better-spawn.coffee ├── package.json ├── src └── better-spawn.coffee └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | shrinkwrap.yaml 3 | lib/* -------------------------------------------------------------------------------- /test/cache.json: -------------------------------------------------------------------------------- 1 | {"646498495c8d3adb5a3ee6f2a1fc1fa51":{"set":true,"value":false},"chunks":{"set":true,"value":{"better-spawn.coffee":"9ff875379b93f7d2605a6b577cb7bc11"}},"335f6e1c931bf08e9fa09811604ec1280":{"set":true,"value":{"resolved.exitCode":1,"resolved.isClosed":true,"resolved.isKilled":false}},"ea78281cbdfc14d05c8302fabd8d0fc70":{"set":true,"value":{"resolved.exitCode":0,"resolved.isClosed":true,"resolved.isKilled":false}},"646498495c8d3adb5a3ee6f2a1fc1fa50":{"set":true,"value":{"resolved.exitCode":1,"resolved.isClosed":true,"resolved.isKilled":true}}} 2 | -------------------------------------------------------------------------------- /test/better-spawn.coffee: -------------------------------------------------------------------------------- 1 | {test} = require "snapy" 2 | 3 | spawn = require "../src/better-spawn.coffee" 4 | 5 | waitingProcess = (time=10000) => 6 | return "node -e 'console.log(\"waiting..\");setTimeout(function(){},#{time});'" 7 | failingProcess = "node -e 'console.log(\"throwing error..\");throw new Error();'" 8 | 9 | filter = ["resolved.exitCode","resolved.isClosed","resolved.isKilled"] 10 | 11 | test (snap) => 12 | child = spawn waitingProcess(10), noOut:true 13 | # process should be successful 14 | snap promise: child.closed, filter: filter 15 | 16 | test (snap) => 17 | child = spawn failingProcess, noErr:true 18 | # process should fail 19 | snap promise: child.closed, filter: filter 20 | 21 | test (snap) => 22 | child = spawn waitingProcess(10000), noOut:true 23 | # should be running 24 | snap obj: child.isClosed 25 | # process should be killed 26 | snap promise: child.closed, filter: filter 27 | child.close() 28 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "better-spawn", 3 | "version": "1.0.4", 4 | "description": "a better spawn", 5 | "homepage": "https://github.com/paulpflug", 6 | "author": { 7 | "name": "Paul Pflugradt", 8 | "email": "paul.pflugradt@gmail.com" 9 | }, 10 | "license": "MIT", 11 | "main": "lib/better-spawn.js", 12 | "repository": { 13 | "type": "git", 14 | "url": "git://github.com/paulpflug/better-spawn" 15 | }, 16 | "keywords": [ 17 | "spawn", 18 | "cross plattform", 19 | "shell" 20 | ], 21 | "files": [ 22 | "lib/" 23 | ], 24 | "devDependencies": { 25 | "coffee-loader": "^0.9.0", 26 | "coffeescript": "^2.3.1", 27 | "snapy": "^0.1.6" 28 | }, 29 | "scripts": { 30 | "build": "coffee --no-header --compile --output lib/ src/*.coffee", 31 | "watch": "snapy --watch", 32 | "test": "snapy", 33 | "preversion": "npm test", 34 | "version": "npm run build && git add .", 35 | "postversion": "git push && git push --tags && npm publish" 36 | }, 37 | "dependencies": {} 38 | } 39 | -------------------------------------------------------------------------------- /src/better-spawn.coffee: -------------------------------------------------------------------------------- 1 | {spawn} = require "child_process" 2 | isWin = process.platform == "win32" 3 | {resolve, delimiter} = require "path" 4 | 5 | module.exports = (cmd, options) -> 6 | 7 | if isWin 8 | sh = "cmd" 9 | shFlag = "/c" 10 | cmd = cmd.replace(/"/g,"\"") 11 | else 12 | sh = "sh" 13 | shFlag = "-c" 14 | 15 | options ?= {} 16 | options.cwd ?= process.cwd() 17 | Promise = options.Promise or global.Promise 18 | 19 | unless options.env? 20 | options.env = JSON.parse JSON.stringify process.env 21 | tmp = options.env.PATH.split(delimiter) 22 | tmp.push resolve(options.cwd,"./node_modules/.bin") 23 | options.env.PATH = tmp.join(delimiter) 24 | 25 | unless options.stdio? 26 | stdio = ["pipe"] 27 | stdio.push if options.noOut then "pipe" else "inherit" 28 | stdio.push if options.noErr then "pipe" else "inherit" 29 | options.stdio = stdio 30 | 31 | options.windowsVerbatimArguments = isWin 32 | options.detached = !isWin 33 | 34 | child = spawn sh,[shFlag,cmd], options 35 | 36 | child.cmd = cmd 37 | child.isClosed = false 38 | child.isKilled = false 39 | 40 | child.closed = new Promise (res) -> 41 | child.on "close", -> 42 | child.isClosed = true 43 | res(child) 44 | 45 | child.killed = new Promise (res) -> 46 | child.on "exit", (exitCode, signal) -> 47 | if signal? 48 | child.isKilled = true 49 | res(child) 50 | 51 | child.close = (signal) -> 52 | signal ?= "SIGTERM" 53 | unless child.isClosed or child.isKilled 54 | child.isKilled = true 55 | child.exitCode = 1 56 | if isWin 57 | child.kill signal 58 | else 59 | process.kill -child.pid, signal 60 | #spawn sh, [shFlag, "kill -INT -"+child.pid] 61 | return child.closed 62 | 63 | return child 64 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Better spawn 2 | 3 | Because `child_process.exec` lacks features and `child_process.spawn` acts weird, `better-spawn` was made. 4 | 5 | It is a very simple wrapper around `child_process.spawn` to make opening and closing work consistently in linux and windows. 6 | Used by [script-runner](https://github.com/paulpflug/script-runner) 7 | 8 | ### Install 9 | 10 | ```bash 11 | npm install better-spawn 12 | ``` 13 | 14 | ### Breaking changes @1 15 | `child.closed` and `child.killed` are now promises. 16 | The boolean states are now available at `child.isClosed` and `child.isKilled`. 17 | 18 | ### Usage 19 | 20 | ```js 21 | spawn = require('better-spawn') 22 | child = spawn('node', options) 23 | ``` 24 | 25 | ### Options 26 | 27 | Name | type | default | description 28 | ---:| --- | ---| --- 29 | cwd | String | process.cwd | current working directory 30 | env | Object | process.env | environment variables 31 | env.PATH | String | process.env.PATH + ./node_modules/.bin | used to resolve commands 32 | stdio | [See documentation](https://nodejs.org/api/child_process.html#child_process_options_stdio) | `["pipe","inherit","inherit"]` | to control output 33 | noOut | Boolean | `null` | sets `stdio[1] = "pipe"` 34 | noErr | Boolean | `null` | sets `stdio[2] = "pipe"` 35 | windowsVerbatimArguments | Boolean | isWindows | to support windows 36 | detach | Boolean | !isWindows | to support killing on unix 37 | Promise | Function | global.Promise | supply your own Promise lib 38 | 39 | #### Props 40 | Name | type | description 41 | ---:| --- | --- 42 | cmd | String | cmd called 43 | isKilled | Boolean | is child process killed 44 | isClosed | Boolean | is child process closed 45 | killed | Promise | fulfilled when child process killed 46 | closed | Promise | fulfilled when child process closed 47 | close | Function | call to kill child process 48 | ### Examples 49 | 50 | ```js 51 | // pipe to shell without losing color 52 | child = spawn('node') 53 | // suppress normal output, but maintain err output 54 | child = spawn('node',{noOut:true}) 55 | // set empty env (default in node) 56 | child = spawn('node',{env: {PATH:""}}) 57 | ``` 58 | 59 | ### Compare to other solutions 60 | 61 | - `child_process.exec`, spawns in shell but output has to be piped - color information will be lost. 62 | - `child_process.spawn`, doesn't spawn in shell, so it has to be done by hand (differs in linux and windows) 63 | Main problem is, `sh` won't kill its children by `child.kill()`, see: [node#2098](https://github.com/nodejs/node/issues/2098) 64 | - `cross-spawn-async` a wrapper for `child_process.spawn` to support windows quirks like `PATHEXT` or `shebangs` not working 65 | - `execa` a wrapper for `cross-spawn-async` which adds the shell logic, to behave like `child_process.exec`, adds promises, modifies `PATH` 66 | 67 | `better-spawn` doesn't support `PATHEXT` or `shebangs on windows` 68 | ## License 69 | Copyright (c) 2016 Paul Pflugradt 70 | Licensed under the MIT license. 71 | --------------------------------------------------------------------------------