Skip to content

Commit 39123b4

Browse files
committed
feat: Initial implementation
0 parents  commit 39123b4

12 files changed

+490
-0
lines changed

.gitignore

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
*.tgz
2+
node_modules
3+
coverage
4+
.nyc_output
5+
package

.npmignore

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
*.tgz
2+
.taprc
3+
.travis.yml
4+
.nyc_output/
5+
nyc.config.js
6+
fixtures/
7+
test/

.npmrc

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package-lock=false

.taprc

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
esm: false
2+
100: true
3+
timeout: 60000
4+
nyc-arg:
5+
- "--all"

.travis.yml

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
os:
2+
- windows
3+
- linux
4+
- osx
5+
6+
language: node_js
7+
8+
node_js:
9+
- 12
10+
- 10
11+
- 8

LICENSE

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2019 CFWare, LLC
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# process-on-spawn
2+
3+
[![Travis CI][travis-image]][travis-url]
4+
[![Greenkeeper badge][gk-image]](https://greenkeeper.io/)
5+
[![NPM Version][npm-image]][npm-url]
6+
[![NPM Downloads][downloads-image]][downloads-url]
7+
[![MIT][license-image]](LICENSE)
8+
9+
Execute callbacks when child processes are spawned.
10+
11+
## Usage
12+
13+
```js
14+
'use strict';
15+
16+
const processOnSpawn = require('process-on-spawn');
17+
processOnSpawn.addListener(opts => {
18+
opts.env.CHILD_VARIABLE = 'value';
19+
});
20+
```
21+
22+
### listener(opts)
23+
24+
* `options` \<[Object]\>
25+
* `execPath` \<[string]\> The command to run.
26+
* `args` \<[string\[\]][string]\> Arguments of the child process.
27+
* `cwd` \<[string]\> Current working directory of the child process.
28+
* `detached` \<[boolean]\> The child will be prepared to run independently of its parent process.
29+
* `uid` \<[number]\> The user identity to be used by the child.
30+
* `gid` \<[number]\> The group identity to be used by the child.
31+
* `windowsVerbatimArguments` \<[boolean]\> No quoting or escaping of arguments will be done on Windows.
32+
* `windowsHide` \<[boolean]\> The subprocess console window that would normally be created on Windows systems will be hidden.
33+
34+
All properties except `env` are read-only.
35+
36+
### processOnSpawn.addListener(listener)
37+
38+
Add a listener to be called after any listeners already attached.
39+
40+
### processOnSpawn.prependListener(listener)
41+
42+
Insert a listener to be called before any listeners already attached.
43+
44+
### processOnSpawn.removeListener(listener)
45+
46+
Remove the specified listener. If the listener was added multiple times only
47+
the first is removed.
48+
49+
### processOnSpawn.removeAllListeners()
50+
51+
Remove all attached listeners.
52+
53+
[npm-image]: https://img.shields.io/npm/v/process-on-spawn.svg
54+
[npm-url]: https://npmjs.org/package/process-on-spawn
55+
[travis-image]: https://travis-ci.org/cfware/process-on-spawn.svg?branch=master
56+
[travis-url]: https://travis-ci.org/cfware/process-on-spawn
57+
[gk-image]: https://badges.greenkeeper.io/cfware/process-on-spawn.svg
58+
[downloads-image]: https://img.shields.io/npm/dm/process-on-spawn.svg
59+
[downloads-url]: https://npmjs.org/package/process-on-spawn
60+
[license-image]: https://img.shields.io/npm/l/process-on-spawn.svg
61+
[Object]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object
62+
[string]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type
63+
[boolean]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type
64+
[number]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Number_type

fixtures/dump-env.js

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/usr/bin/env node
2+
'use strict';
3+
4+
const env = {...process.env};
5+
for (const key of Object.keys(env)) {
6+
if (/^path$/i.test(key)) {
7+
delete env[key];
8+
}
9+
}
10+
11+
console.log(JSON.stringify(env));

index.js

+112
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
'use strict';
2+
3+
/* Drop this dependency once node.js 12 is required. */
4+
const fromEntries = require('fromentries');
5+
6+
const state = getState(1);
7+
8+
function getState(version) {
9+
const stateId = Symbol.for('process-on-spawn@*:singletonId');
10+
11+
/* istanbul ignore next: cannot cover this once nyc depends on this module */
12+
if (stateId in global === false) {
13+
/* Hopefully version and unwrap forward compatibility is never actually needed */
14+
Object.defineProperty(global, stateId, {
15+
writable: true,
16+
value: {
17+
version,
18+
listeners: [],
19+
unwrap: wrapSpawnFunctions()
20+
}
21+
});
22+
}
23+
24+
return global[stateId];
25+
}
26+
27+
function wrappedSpawnFunction(fn) {
28+
return function (options) {
29+
let env = fromEntries(
30+
options.envPairs.map(nvp => nvp.split(/^([^=]*)=/).slice(1))
31+
);
32+
33+
const opts = Object.create(null, {
34+
env: {
35+
enumerable: true,
36+
get() {
37+
return env;
38+
},
39+
set(value) {
40+
if (!value || typeof value !== 'object') {
41+
throw new TypeError('env must be an object');
42+
}
43+
44+
env = value;
45+
}
46+
},
47+
cwd: {
48+
enumerable: true,
49+
get() {
50+
return options.cwd || process.cwd();
51+
}
52+
}
53+
});
54+
55+
const args = [...options.args];
56+
Object.freeze(args);
57+
Object.assign(opts, {
58+
execPath: options.file,
59+
args,
60+
detached: Boolean(options.detached),
61+
uid: options.uid,
62+
gid: options.gid,
63+
windowsVerbatimArguments: Boolean(options.windowsVerbatimArguments),
64+
windowsHide: Boolean(options.windowsHide)
65+
});
66+
Object.freeze(opts);
67+
68+
state.listeners.forEach(listener => {
69+
listener(opts);
70+
});
71+
72+
options.envPairs = Object.entries(opts.env).map(([name, value]) => `${name}=${value}`);
73+
74+
return fn.call(this, options);
75+
};
76+
}
77+
78+
function wrapSpawnFunctions() {
79+
const {ChildProcess} = require('child_process');
80+
81+
/* eslint-disable-next-line node/no-deprecated-api */
82+
const spawnSyncBinding = process.binding('spawn_sync');
83+
const originalSync = spawnSyncBinding.spawn;
84+
const originalAsync = ChildProcess.prototype.spawn;
85+
86+
spawnSyncBinding.spawn = wrappedSpawnFunction(spawnSyncBinding.spawn);
87+
ChildProcess.prototype.spawn = wrappedSpawnFunction(ChildProcess.prototype.spawn);
88+
89+
/* istanbul ignore next: forward compatibility code */
90+
return () => {
91+
spawnSyncBinding.spawn = originalSync;
92+
ChildProcess.prototype.spawn = originalAsync;
93+
};
94+
}
95+
96+
module.exports = {
97+
addListener(listener) {
98+
state.listeners.push(listener);
99+
},
100+
prependListener(listener) {
101+
state.listeners.unshift(listener);
102+
},
103+
removeListener(listener) {
104+
const idx = state.listeners.indexOf(listener);
105+
if (idx !== -1) {
106+
state.listeners.splice(idx, 1);
107+
}
108+
},
109+
removeAllListeners() {
110+
state.listeners = [];
111+
}
112+
};

nyc.config.js

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
'use strict';
2+
3+
module.exports = {
4+
include: ['*.js']
5+
};

package.json

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "process-on-spawn",
3+
"version": "1.0.0",
4+
"description": "Execute callbacks when child processes are spawned",
5+
"scripts": {
6+
"release": "standard-version --sign",
7+
"pretest": "xo",
8+
"test": "tap"
9+
},
10+
"engines": {
11+
"node": ">=8"
12+
},
13+
"author": "Corey Farrell",
14+
"license": "MIT",
15+
"repository": {
16+
"type": "git",
17+
"url": "git+https://github.com/cfware/process-on-spawn.git"
18+
},
19+
"bugs": {
20+
"url": "https://github.com/cfware/process-on-spawn/issues"
21+
},
22+
"homepage": "https://github.com/cfware/process-on-spawn#readme",
23+
"dependencies": {
24+
"fromentries": "^1.2.0"
25+
},
26+
"devDependencies": {
27+
"standard-version": "^7.0.0",
28+
"tap": "=14.10.2-unbundled",
29+
"xo": "^0.25.3"
30+
}
31+
}

0 commit comments

Comments
 (0)