Skip to content

Commit 9882380

Browse files
committed
update spawning to run as sudo/admin, and handle on stderr events
1 parent 3319d3d commit 9882380

File tree

5 files changed

+138
-17
lines changed

5 files changed

+138
-17
lines changed

README.md

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -56,13 +56,19 @@ installer('vim')
5656
});
5757
```
5858

59-
## API - spawning(command, arguments, progress, options)
59+
## API - spawning(command, arguments, progressOptions, options)
6060

61-
`spawning` takes an additional argument, `progress`, its [`options`](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options) are the same as those of `child_process.spawn`.
61+
`Spawning` takes an additional argument, `progressOptions`, its [`options`](https://nodejs.org/api/child_process.html#child_process_child_process_spawn_command_args_options) are the same as those of `child_process.spawn` plus:
6262

63-
* `progress`: callback handler for `stdout.on('data')` events.
63+
```js
64+
sudo: boolean, // run as administrator
65+
onerror: callable, // callback for `stderr.on('data')` event.
66+
onprogress: callable, // callback for `stdout.on('data')` event.
67+
// onmessage: callable, // callback for send `on('message')` event.
68+
```
69+
70+
`Spawning` returns a promise whose result will be any output or any data return in the progress callback.
6471

65-
It returns a promise whose result will be any output or any data return in the progress callback.
6672
*The progress callback will receive an object with these properties:*
6773

6874
* `handle:` *Object* - Spawned child process instance handler.

index.js

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -148,28 +148,78 @@ export const where = Sys.where = function (executable) {
148148
}
149149

150150
/**
151-
* Spawn subprocess with `Promise` features, and `progress` callback for `on('data') `event.
151+
* Determine if a value is a Function
152152
*
153-
* @param {String} cmd - platform command
153+
* @param {Object} value The value to test
154+
* @returns {boolean} True if value is a Function, otherwise false
155+
*/
156+
function isFunction(value) {
157+
return Object.prototype.toString.call(value) === '[object Function]';
158+
}
159+
160+
/**
161+
* Spawn subprocess with `Promise` features, and `progress` callback for `stdout.on('data') `event.
162+
*
163+
* @param {String} command - platform command
154164
* @param {Array} argument - command arguments
155-
* @param {Function} progress - callback for `on('data')` event.
165+
* @param {Function|Object} progressOptions - either callback for `stdout.on('data')` event or spawn options.
156166
*```js
157167
* { handle: object, output: string }
158168
*```
159-
* - the callback will received object, `instance` **handle** of the spawned child processes, and any **output** data.
169+
* - the callback will received an object, child process `instance` **handle**, and any **output** data.
160170
* - any returns will be the **`resolve()` .then()** handler.
161171
* @param {Object} options - Any child process `spawn` options, defaults: stdio: 'pipe'.
172+
* - Additionally:
173+
*```js
174+
* sudo: boolean, // run as administrator
175+
* onerror: callable, // callback for `stderr.on('data')` event.
176+
* onprogress: callable, // callback for `stdout.on('data')` event.
177+
// * onmessage: callable, // callback for `on('message')` event.
178+
*```
162179
*/
163-
export const spawning = Sys.spawning = function (cmd, argument = [], progress = () => { }, options = { stdio: 'pipe', }) {
180+
export const spawning = Sys.spawning = function (command, argument = [], progressOptions = null, options = { stdio: 'pipe', }) {
164181
return new Promise((resolve, reject) => {
182+
options = options || {
183+
stdio: 'pipe',
184+
sudo: false,
185+
onerror: null,
186+
onprogress: null,
187+
// onmessage: null,
188+
};
189+
190+
let progress = progressOptions;
191+
if (typeof progressOptions == 'object' && !isFunction(progressOptions)) {
192+
options = Object.assign(options, progressOptions);
193+
progress = options.onprogress || null;
194+
}
195+
196+
let err = null;
165197
let output = null;
166-
const child = spawn(cmd, argument, options);
198+
let sudo = options.sudo || false;
199+
let onerror = options.onerror || null;
200+
//let onmessage = options.onmessage || null;
201+
202+
delete options.sudo;
203+
delete options.onerror;
204+
delete options.onprogress;
205+
//delete options.onmessage;
206+
207+
if (sudo) {
208+
argument = [command].concat(argument);
209+
command = (process.platform == 'win32') ? join(__dirname, 'bin', 'sudo.bat') : 'sudo';
210+
}
211+
212+
const child = spawn(command, argument, options);
167213
child.on('error', (data) => {
168214
return reject(data);
169215
});
170216

171-
child.on('close', () => {
172-
return resolve(output);
217+
child.on('close', (code) => {
218+
if (code === 0) {
219+
return resolve(output);
220+
}
221+
222+
return reject(err, code);
173223
});
174224

175225
child.on('exit', () => {
@@ -180,7 +230,7 @@ export const spawning = Sys.spawning = function (cmd, argument = [], progress =
180230
let input = data.toString();
181231
let onProgress = null
182232
try {
183-
if (progress) {
233+
if (isFunction(progress)) {
184234
onProgress = progress({ handle: child, output: input });
185235
}
186236
} catch (e) {
@@ -200,9 +250,19 @@ export const spawning = Sys.spawning = function (cmd, argument = [], progress =
200250
});
201251

202252
child.stderr.on('data', (data) => {
203-
return reject(data.toString());
204-
});
253+
err = data.toString();
254+
if (isFunction(onerror)) {
255+
err = onerror(err);
256+
}
205257

258+
return reject(err);
259+
});
260+
/*
261+
child.on('message', (data) => {
262+
if (isFunction(onmessage))
263+
onmessage(data);
264+
});
265+
*/
206266
if (argument.includes('fake-js') && Object.getOwnPropertyDescriptor(process, 'platform').value == 'darwin') {
207267
child.kill('SIGKILL');
208268
return resolve('For testing only. ' + output);

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "node-sys",
3-
"version": "1.0.4",
3+
"version": "1.0.5",
44
"description": "Universal package installer, get the command for managing packages, or auto install any package, using one command for all platforms. Automate the installation of macOS Brew, and Windows Chocolatey package managers. A promisify child process of spawn.",
55
"type": "module",
66
"main": "index.js",

test/test.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,61 @@ describe('Method: `spawning`', function () {
251251
done();
252252
});
253253
});
254+
255+
it('should return on successful install with output from `onprogress`', function (done) {
256+
spawning('echo', [''], {
257+
stdio: 'pipe',
258+
shell: true,
259+
onerror: (err) => { return 'testing: ' + err; },
260+
onprogress: (object) => {
261+
expect(object).to.be.a('object');
262+
expect(object.handle).to.be.instanceOf(Object);
263+
expect(object.output).to.be.a('string');
264+
return 'hello';
265+
},
266+
})
267+
.then(function (data) {
268+
expect(data).to.be.a('string');
269+
expect(data).to.equal('hello');
270+
done();
271+
})
272+
.catch(function (err) {
273+
expect(err).to.be.empty;
274+
done();
275+
});
276+
});
277+
278+
it('should catch error on throw from `onprogress`', function (done) {
279+
spawning('echo', [''], {
280+
stdio: 'pipe',
281+
shell: true,
282+
onerror: (err) => { return 'testing: ' + err; },
283+
onprogress: () => {
284+
throw 'hello';
285+
},
286+
})
287+
.catch(function (err) {
288+
expect(err).to.equal('hello');
289+
done();
290+
});
291+
});
292+
293+
it('should return on successful `Sudo` run and catch any exceptions', function (done) {
294+
spawning((process.platform == 'win32' ? 'dir' : 'ls'), ['..'], {
295+
stdio: 'pipe',
296+
shell: true,
297+
sudo: true,
298+
onerror: (err) => { return 'testing: ' + err; },
299+
// onmessage: (data) => { console.log('messaging: ' + data); },
300+
})
301+
.then(function () {
302+
done();
303+
})
304+
.catch(function (err) {
305+
expect(err).to.contains('testing: ');
306+
done();
307+
});
308+
});
254309
});
255310

256311
describe('Method: `where`', function () {

0 commit comments

Comments
 (0)