├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── examples ├── echo.js ├── ignoreCase.js ├── ls-la.js ├── node.js ├── stripColors.js └── wait.js ├── lib └── nexpect.js ├── package-lock.json ├── package.json └── test ├── fixtures ├── log-colors ├── multiple-cases ├── prompt-and-respond ├── show-cwd └── show-env └── nexpect-test.js /.gitattributes: -------------------------------------------------------------------------------- 1 | package-lock.json binary 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | .DS_Store -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | nexpect 2 | 3 | Copyright (c) 2010 Elijah Insua, Marak Squires, Charlie Robbins http://github.com/nodejitsu/nexpect 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining 6 | a copy of this software and associated documentation files (the 7 | "Software"), to deal in the Software without restriction, including 8 | without limitation the rights to use, copy, modify, merge, publish, 9 | distribute, sublicense, and/or sell copies of the Software, and to 10 | permit persons to whom the Software is furnished to do so, subject to 11 | the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be 14 | included in all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 19 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 20 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 21 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 22 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # nexpect 2 | 3 | `nexpect` is a node.js module for spawning child applications (such as ssh) and 4 | seamlessly controlling them using javascript callbacks. nexpect is based on the 5 | ideas of the [expect][0] library by Don Libes and the [pexpect][1] library by 6 | Noah Spurrier. 7 | 8 | ## Motivation 9 | 10 | node.js has good built in control for spawning child processes. `nexpect` builds 11 | on these core methods and allows developers to easily pipe data to child 12 | processes and assert the expected response. `nexpect` also chains, so you can 13 | compose complex terminal interactions. 14 | 15 | ## Installation 16 | 17 | ``` bash 18 | $ npm install --save nexpect 19 | ``` 20 | 21 | ## Usage 22 | 23 | ### require('nexpect') 24 | 25 | The module exposes a single function, `.spawn`. 26 | 27 | ### function spawn (command, [params], [options]) 28 | 29 | * command {string|Array} The command that you wish to spawn, a string will be 30 | split on `' '` to find the params if params not provided (so do not use the 31 | string variant if any arguments have spaces in them) 32 | * params {Array} **Optional** Argv to pass to the child process 33 | * options {Object} **Optional** An object literal which may contain 34 | - cwd: Current working directory of the child process. 35 | - env: Environment variables for the child process. 36 | - ignoreCase: Ignores the case of any output from the child process. 37 | - stripColors: Strips any ANSI colors from the output for `.expect()` and `.wait()` statements. 38 | - stream: Expectations can be written against 'stdout', 'stderr', or 'all', which runs expectations against both stdout and stderr 39 | (defaults to 'stdout') 40 | - verbose: Writes the stdout for the child process to `process.stdout` of the current process, 41 | and any data sent with sendline to the `process.stdout` of the current 42 | process. 43 | 44 | 45 | Top-level entry point for `nexpect` that liberally parses the arguments 46 | and then returns a new chain with the specified `command`, `params`, and `options`. 47 | 48 | ### function expect (expectation) 49 | 50 | * expectation {string|RegExp} Output to assert on the target stream 51 | 52 | Expect that the next line of output matches the expectation. 53 | Throw an error if it does not. 54 | 55 | The expectation can be a string (the line should contain the expected value as 56 | a substring) or a RegExp (the line should match the expression). 57 | 58 | ### function wait (expectation, callback) 59 | 60 | * expectation {string|RegExp} Output to assert on the target stream 61 | * callback {Function} **Optional** Callback to be called when output matches stream 62 | 63 | Wait for a line of output that matches the expectation, discarding lines 64 | that do not match. 65 | 66 | Throw an error if no such line was found. 67 | 68 | The expectation can be a string (the line should contain the expected value as 69 | a substring) or a RegExp (the line should match the expression). 70 | 71 | The callback will be called for every line that matches the expectation. 72 | 73 | ### function sendline (line) 74 | 75 | * line {string} Output to write to the child process. 76 | 77 | Adds a write line to `context.process.stdin` to the `context.queue` 78 | for the current chain. 79 | 80 | ### function sendEof () 81 | 82 | Close child's stdin stream, let the child know there are no more data coming. 83 | 84 | This is useful for testing apps that are using inquirer, 85 | as `inquirer.prompt()` calls `stdin.resume()` at some point, 86 | which causes the app to block on input when the input stream is a pipe. 87 | 88 | ### function run (callback) 89 | 90 | * callback {function} Called when child process closes, with arguments 91 | * err {Error|null} Error if any occurred 92 | * output {Array} Array of lines of output examined 93 | * exit {Number|String} Numeric exit code, or String name of signal 94 | 95 | Runs the `context` against the specified `context.command` and 96 | `context.params`. 97 | 98 | 99 | ## Example 100 | 101 | Lets take a look at some sample usage: 102 | 103 | ``` js 104 | var nexpect = require('nexpect'); 105 | 106 | nexpect.spawn("echo", ["hello"]) 107 | .expect("hello") 108 | .run(function (err, stdout, exitcode) { 109 | if (!err) { 110 | console.log("hello was echoed"); 111 | } 112 | }); 113 | 114 | nexpect.spawn("ls -la /tmp/undefined", { stream: 'stderr' }) 115 | .expect("No such file or directory") 116 | .run(function (err) { 117 | if (!err) { 118 | console.log("checked that file doesn't exists"); 119 | } 120 | }); 121 | 122 | nexpect.spawn("node --interactive") 123 | .expect(">") 124 | .sendline("console.log('testing')") 125 | .expect("testing") 126 | .sendline("process.exit()") 127 | .run(function (err) { 128 | if (!err) { 129 | console.log("node process started, console logged, process exited"); 130 | } 131 | else { 132 | console.log(err) 133 | } 134 | }); 135 | ``` 136 | 137 | If you are looking for more examples take a look at the [examples][2], and [tests][3]. 138 | 139 | ## Tests 140 | 141 | All tests are written with [vows][4]: 142 | 143 | ``` bash 144 | $ npm test 145 | ``` 146 | 147 | ## Authors 148 | 149 | [Elijah Insua][5] [Marak Squires][6], and [Charlie Robbins][7]. 150 | 151 | [0]: http://search.cpan.org/~rgiersig/Expect-1.21/Expect.pod 152 | [1]: http://pexpect.sourceforge.net/pexpect.html 153 | [2]: https://github.com/nodejitsu/nexpect/tree/master/examples 154 | [3]: https://github.com/nodejitsu/nexpect/tree/master/test/nexpect-test.js 155 | [4]: http://vowsjs.org 156 | [5]: http://github.com/tmpvar 157 | [6]: http://github.com/marak 158 | [7]: http://github.com/indexzero 159 | -------------------------------------------------------------------------------- /examples/echo.js: -------------------------------------------------------------------------------- 1 | /* 2 | * echo.js: Simple example for using the `echo` command with nexpect. 3 | * 4 | * (C) 2011, Elijah Insua, Marak Squires, Charlie Robbins. 5 | * 6 | */ 7 | 8 | var nexpect = require('../lib/nexpect'); 9 | 10 | nexpect.spawn("echo", ["hello"]) 11 | .expect("hello") 12 | .run(function (err) { 13 | if (!err) { 14 | console.log("hello was echoed"); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /examples/ignoreCase.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ignoreCase.js: Simple example for using the `ignoreCase` option with nexpect. 3 | * 4 | * (C) 2011, Elijah Insua, Marak Squires, Charlie Robbins. 5 | * 6 | */ 7 | 8 | var path = require('path'), 9 | nexpect = require('../lib/nexpect'); 10 | 11 | nexpect.spawn(path.join(__dirname, '..', 'test', 'fixtures', 'multiple-cases'), { ignoreCase: true }) 12 | .wait('this has many cases') 13 | .expect('this also has many cases') 14 | .run(function (err) { 15 | if (!err) { 16 | console.log('multiple cases were waited and expected'); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /examples/ls-la.js: -------------------------------------------------------------------------------- 1 | /* 2 | * ls-la.js: Simple example for using the `ls -la` command with nexpect. 3 | * 4 | * (C) 2011, Elijah Insua, Marak Squires, Charlie Robbins. 5 | * 6 | */ 7 | 8 | var nexpect = require('../lib/nexpect'); 9 | 10 | nexpect.spawn("ls -la /tmp/undefined", { stream: 'stderr' }) 11 | .expect("No such file or directory") 12 | .run(function (err) { 13 | if (!err) { 14 | console.log("checked that file doesn't exists"); 15 | } 16 | }); 17 | -------------------------------------------------------------------------------- /examples/node.js: -------------------------------------------------------------------------------- 1 | /* 2 | * node.js: Simple example for using the `node` interactive terminal with nexpect. 3 | * 4 | * (C) 2011, Elijah Insua, Marak Squires, Charlie Robbins. 5 | * 6 | */ 7 | 8 | var nexpect = require('../lib/nexpect'); 9 | 10 | nexpect.spawn("node --interactive") 11 | .expect(">") 12 | .sendline("console.log('testing')") 13 | .expect("testing") 14 | .sendline("process.exit()") 15 | .run(function (err) { 16 | if (!err) { 17 | console.log("node process started, console logged, process exited"); 18 | } 19 | else { 20 | console.log(err); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /examples/stripColors.js: -------------------------------------------------------------------------------- 1 | /* 2 | * stripColors.js: Simple example for using the `stripColors` option with nexpect. 3 | * 4 | * (C) 2011, Elijah Insua, Marak Squires, Charlie Robbins. 5 | * 6 | */ 7 | 8 | var path = require('path'), 9 | nexpect = require('../lib/nexpect'); 10 | 11 | nexpect.spawn(path.join(__dirname, '..', 'test', 'fixtures', 'log-colors'), { stripColors: true }) 12 | .wait('second has colors') 13 | .expect('third has colors') 14 | .run(function (err) { 15 | if (!err) { 16 | console.log('colors were ignore, then waited and expected'); 17 | } 18 | }); 19 | -------------------------------------------------------------------------------- /examples/wait.js: -------------------------------------------------------------------------------- 1 | /* 2 | * wait.js: Simple example for using the `.wait()` method with nexpect. 3 | * 4 | * (C) 2011, Elijah Insua, Marak Squires, Charlie Robbins. 5 | * 6 | */ 7 | 8 | var path = require('path'), 9 | nexpect = require('../lib/nexpect'); 10 | 11 | nexpect.spawn(path.join(__dirname, '..', 'test', 'fixtures', 'prompt-and-respond'), { stripColors: true }) 12 | .wait('first') 13 | .sendline('first-prompt') 14 | .expect('first-prompt') 15 | .wait('second') 16 | .sendline('second-prompt') 17 | .expect('second-prompt') 18 | .run(function (err) { 19 | if (!err) { 20 | console.log('two prompts were waited and responded to'); 21 | } 22 | }); 23 | -------------------------------------------------------------------------------- /lib/nexpect.js: -------------------------------------------------------------------------------- 1 | /* 2 | * nexpect.js: Top-level include for the `nexpect` module. 3 | * 4 | * (C) 2011, Elijah Insua, Marak Squires, Charlie Robbins. 5 | * 6 | */ 7 | var spawn = require('cross-spawn'); 8 | var util = require('util'); 9 | var AssertionError = require('assert').AssertionError; 10 | 11 | function chain (context) { 12 | return { 13 | expect: function (expectation) { 14 | var _expect = function _expect (data) { 15 | return testExpectation(data, expectation); 16 | }; 17 | 18 | _expect.shift = true; 19 | _expect.expectation = expectation; 20 | _expect.description = '[expect] ' + expectation; 21 | _expect.requiresInput = true; 22 | context.queue.push(_expect); 23 | 24 | return chain(context); 25 | }, 26 | wait: function (expectation, callback) { 27 | var _wait = function _wait (data) { 28 | var val = testExpectation(data, expectation); 29 | if (val === true && typeof callback === 'function') { 30 | callback(data); 31 | } 32 | return val; 33 | }; 34 | 35 | _wait.shift = false; 36 | _wait.expectation = expectation; 37 | _wait.description = '[wait] ' + expectation; 38 | _wait.requiresInput = true; 39 | context.queue.push(_wait); 40 | return chain(context); 41 | }, 42 | sendline: function (line) { 43 | var _sendline = function _sendline () { 44 | context.process.stdin.write(line + '\n'); 45 | 46 | if (context.verbose) { 47 | process.stdout.write(line + '\n'); 48 | } 49 | }; 50 | 51 | _sendline.shift = true; 52 | _sendline.description = '[sendline] ' + line; 53 | _sendline.requiresInput = false; 54 | context.queue.push(_sendline); 55 | return chain(context); 56 | }, 57 | sendEof: function() { 58 | var _sendEof = function _sendEof () { 59 | context.process.stdin.destroy(); 60 | }; 61 | _sendEof.shift = true; 62 | _sendEof.description = '[sendEof]'; 63 | _sendEof.requiresInput = false; 64 | context.queue.push(_sendEof); 65 | return chain(context); 66 | }, 67 | run: function (callback) { 68 | var errState = null, 69 | responded = false, 70 | stdout = [], 71 | options; 72 | 73 | // 74 | // **onError** 75 | // 76 | // Helper function to respond to the callback with a 77 | // specified error. Kills the child process if necessary. 78 | // 79 | function onError (err, kill) { 80 | if (errState || responded) { 81 | return; 82 | } 83 | 84 | errState = err; 85 | responded = true; 86 | 87 | if (kill) { 88 | try { context.process.kill(); } 89 | catch (ex) { } 90 | } 91 | 92 | callback(err); 93 | } 94 | 95 | // 96 | // **validateFnType** 97 | // 98 | // Helper function to validate the `currentFn` in the 99 | // `context.queue` for the target chain. 100 | // 101 | function validateFnType (currentFn) { 102 | if (typeof currentFn !== 'function') { 103 | // 104 | // If the `currentFn` is not a function, short-circuit with an error. 105 | // 106 | onError(new Error('Cannot process non-function on nexpect stack.'), true); 107 | return false; 108 | } 109 | else if (['_expect', '_sendline', '_wait', '_sendEof'].indexOf(currentFn.name) === -1) { 110 | // 111 | // If the `currentFn` is a function, but not those set by `.sendline()` or 112 | // `.expect()` then short-circuit with an error. 113 | // 114 | onError(new Error('Unexpected context function name: ' + currentFn.name), true); 115 | return false; 116 | } 117 | 118 | return true; 119 | } 120 | 121 | // 122 | // **evalContext** 123 | // 124 | // Core evaluation logic that evaluates the next function in 125 | // `context.queue` against the specified `data` where the last 126 | // function run had `name`. 127 | // 128 | function evalContext (data, name) { 129 | var currentFn = context.queue[0]; 130 | 131 | if (!currentFn || (name === '_expect' && currentFn.name === '_expect')) { 132 | // 133 | // If there is nothing left on the context or we are trying to 134 | // evaluate two consecutive `_expect` functions, return. 135 | // 136 | return; 137 | } 138 | 139 | if (currentFn.shift) { 140 | context.queue.shift(); 141 | } 142 | 143 | if (!validateFnType(currentFn)) { 144 | return; 145 | } 146 | 147 | if (currentFn.name === '_expect') { 148 | // 149 | // If this is an `_expect` function, then evaluate it and attempt 150 | // to evaluate the next function (in case it is a `_sendline` function). 151 | // 152 | return currentFn(data) === true ? 153 | evalContext(data, '_expect') : 154 | onError(createExpectationError(currentFn.expectation, data), true); 155 | } 156 | else if (currentFn.name === '_wait') { 157 | // 158 | // If this is a `_wait` function, then evaluate it and if it returns true, 159 | // then evaluate the function (in case it is a `_sendline` function). 160 | // 161 | if (currentFn(data) === true) { 162 | context.queue.shift(); 163 | evalContext(data, '_expect'); 164 | } 165 | } 166 | else { 167 | // 168 | // If the `currentFn` is any other function then evaluate it 169 | // 170 | currentFn(); 171 | 172 | // Evaluate the next function if it does not need input 173 | var nextFn = context.queue[0]; 174 | if (nextFn && !nextFn.requiresInput) 175 | evalContext(data); 176 | } 177 | } 178 | 179 | // 180 | // **onLine** 181 | // 182 | // Preprocesses the `data` from `context.process` on the 183 | // specified `context.stream` and then evaluates the processed lines: 184 | // 185 | // 1. Stripping ANSI colors (if necessary) 186 | // 2. Removing case sensitivity (if necessary) 187 | // 3. Splitting `data` into multiple lines. 188 | // 189 | function onLine (data) { 190 | data = data.toString(); 191 | 192 | if (context.stripColors) { 193 | data = data.replace(/\u001b\[\d{0,2}m/g, ''); 194 | } 195 | 196 | if (context.ignoreCase) { 197 | data = data.toLowerCase(); 198 | } 199 | 200 | var lines = data.split('\n').filter(function (line) { return line.length > 0; }); 201 | stdout = stdout.concat(lines); 202 | 203 | while (lines.length > 0) { 204 | evalContext(lines.shift(), null); 205 | } 206 | } 207 | 208 | // 209 | // **flushQueue** 210 | // 211 | // Helper function which flushes any remaining functions from 212 | // `context.queue` and responds to the `callback` accordingly. 213 | // 214 | function flushQueue () { 215 | var remainingQueue = context.queue.slice(), 216 | currentFn = context.queue.shift(), 217 | lastLine = stdout[stdout.length - 1]; 218 | 219 | if (!lastLine) { 220 | onError(createUnexpectedEndError( 221 | 'No data from child with non-empty queue.', remainingQueue)); 222 | return false; 223 | } 224 | else if (context.queue.length > 0) { 225 | onError(createUnexpectedEndError( 226 | 'Non-empty queue on spawn exit.', remainingQueue)); 227 | return false; 228 | } 229 | else if (!validateFnType(currentFn)) { 230 | // onError was called 231 | return false; 232 | } 233 | else if (currentFn.name === '_sendline') { 234 | onError(new Error('Cannot call sendline after the process has exited')); 235 | return false; 236 | } 237 | else if (currentFn.name === '_wait' || currentFn.name === '_expect') { 238 | if (currentFn(lastLine) !== true) { 239 | onError(createExpectationError(currentFn.expectation, lastLine)); 240 | return false; 241 | } 242 | } 243 | 244 | return true; 245 | } 246 | 247 | // 248 | // **onData** 249 | // 250 | // Helper function for writing any data from a stream 251 | // to `process.stdout`. 252 | // 253 | function onData (data) { 254 | process.stdout.write(data); 255 | } 256 | 257 | options = { 258 | cwd: context.cwd, 259 | env: context.env 260 | }; 261 | 262 | // 263 | // Spawn the child process and begin processing the target 264 | // stream for this chain. 265 | // 266 | context.process = spawn(context.command, context.params, options); 267 | 268 | if (context.verbose) { 269 | context.process.stdout.on('data', onData); 270 | context.process.stderr.on('data', onData); 271 | } 272 | 273 | if (context.stream === 'all') { 274 | context.process.stdout.on('data', onLine); 275 | context.process.stderr.on('data', onLine); 276 | } else { 277 | context.process[context.stream].on('data', onLine); 278 | } 279 | 280 | context.process.on('error', onError); 281 | 282 | // 283 | // When the process exits, check the output `code` and `signal`, 284 | // flush `context.queue` (if necessary) and respond to the callback 285 | // appropriately. 286 | // 287 | context.process.on('close', function (code, signal) { 288 | if (code === 127) { 289 | // XXX(sam) Not how node works (anymore?), 127 is what /bin/sh returns, 290 | // but it appears node does not, or not in all conditions, blithely 291 | // return 127 to user, it emits an 'error' from the child_process. 292 | 293 | // 294 | // If the response code is `127` then `context.command` was not found. 295 | // 296 | return onError(new Error('Command not found: ' + context.command)); 297 | } 298 | else if (context.queue.length && !flushQueue()) { 299 | // if flushQueue returned false, onError was called 300 | return; 301 | } 302 | 303 | callback(null, stdout, signal || code); 304 | }); 305 | 306 | return context.process; 307 | } 308 | }; 309 | } 310 | 311 | function testExpectation(data, expectation) { 312 | if (util.isRegExp(expectation)) { 313 | return expectation.test(data); 314 | } else { 315 | return data.indexOf(expectation) > -1; 316 | } 317 | } 318 | 319 | function createUnexpectedEndError(message, remainingQueue) { 320 | var desc = remainingQueue.map(function(it) { return it.description; }); 321 | var msg = message + '\n' + desc.join('\n'); 322 | return new AssertionError({ 323 | message: msg, 324 | expected: [], 325 | actual: desc 326 | }); 327 | } 328 | 329 | function createExpectationError(expected, actual) { 330 | var expectation; 331 | if (util.isRegExp(expected)) 332 | expectation = 'to match ' + expected; 333 | else 334 | expectation = 'to contain ' + JSON.stringify(expected); 335 | 336 | var err = new AssertionError({ 337 | message: util.format('expected %j %s', actual, expectation), 338 | actual: actual, 339 | expected: expected 340 | }); 341 | return err; 342 | } 343 | 344 | function nspawn (command, params, options) { 345 | if (arguments.length === 2) { 346 | if (Array.isArray(arguments[1])) { 347 | options = {}; 348 | } 349 | else { 350 | options = arguments[1]; 351 | params = null; 352 | } 353 | } 354 | 355 | if (Array.isArray(command)) { 356 | params = command; 357 | command = params.shift(); 358 | } 359 | else if (typeof command === 'string') { 360 | command = command.split(' '); 361 | params = params || command.slice(1); 362 | command = command[0]; 363 | } 364 | 365 | options = options || {}; 366 | context = { 367 | command: command, 368 | cwd: options.cwd || undefined, 369 | env: options.env || undefined, 370 | ignoreCase: options.ignoreCase, 371 | params: params, 372 | queue: [], 373 | stream: options.stream || 'stdout', 374 | stripColors: options.stripColors, 375 | verbose: options.verbose 376 | }; 377 | 378 | return chain(context); 379 | } 380 | 381 | // 382 | // Export the core `nspawn` function as well as `nexpect.nspawn` for 383 | // backwards compatibility. 384 | // 385 | module.exports.spawn = nspawn; 386 | module.exports.nspawn = { 387 | spawn: nspawn 388 | }; 389 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nexpect", 3 | "version": "0.6.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "async": { 8 | "version": "0.9.2", 9 | "resolved": "https://registry.npmjs.org/async/-/async-0.9.2.tgz", 10 | "integrity": "sha1-rqdNXmHB+JlhO/ZL2mbUx48v0X0=", 11 | "dev": true 12 | }, 13 | "balanced-match": { 14 | "version": "1.0.0", 15 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 16 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 17 | "dev": true 18 | }, 19 | "brace-expansion": { 20 | "version": "1.1.11", 21 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 22 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 23 | "dev": true, 24 | "requires": { 25 | "balanced-match": "^1.0.0", 26 | "concat-map": "0.0.1" 27 | } 28 | }, 29 | "colors": { 30 | "version": "1.3.3", 31 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.3.3.tgz", 32 | "integrity": "sha512-mmGt/1pZqYRjMxB1axhTo16/snVZ5krrKkcmMeVKxzECMMXoCgnvTPp10QgHfcbQZw8Dq2jMNG6je4JlWU0gWg==", 33 | "dev": true 34 | }, 35 | "concat-map": { 36 | "version": "0.0.1", 37 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 38 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 39 | "dev": true 40 | }, 41 | "cross-spawn": { 42 | "version": "6.0.5", 43 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", 44 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==", 45 | "requires": { 46 | "nice-try": "^1.0.4", 47 | "path-key": "^2.0.1", 48 | "semver": "^5.5.0", 49 | "shebang-command": "^1.2.0", 50 | "which": "^1.2.9" 51 | } 52 | }, 53 | "cycle": { 54 | "version": "1.0.3", 55 | "resolved": "https://registry.npmjs.org/cycle/-/cycle-1.0.3.tgz", 56 | "integrity": "sha1-IegLK+hYD5i0aPN5QwZisEbDStI=", 57 | "dev": true 58 | }, 59 | "deep-equal": { 60 | "version": "0.2.2", 61 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.2.2.tgz", 62 | "integrity": "sha1-hLdFiW80xoTpjyzg5Cq69Du6AX0=", 63 | "dev": true 64 | }, 65 | "diff": { 66 | "version": "1.0.8", 67 | "resolved": "https://registry.npmjs.org/diff/-/diff-1.0.8.tgz", 68 | "integrity": "sha1-NDJ2MI7Jkbe8giZ+1VvBQR+XFmY=", 69 | "dev": true 70 | }, 71 | "eyes": { 72 | "version": "0.1.8", 73 | "resolved": "https://registry.npmjs.org/eyes/-/eyes-0.1.8.tgz", 74 | "integrity": "sha1-Ys8SAjTGg3hdkCNIqADvPgzCC8A=", 75 | "dev": true 76 | }, 77 | "fs.realpath": { 78 | "version": "1.0.0", 79 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 80 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 81 | "dev": true 82 | }, 83 | "glob": { 84 | "version": "7.1.3", 85 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", 86 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", 87 | "dev": true, 88 | "requires": { 89 | "fs.realpath": "^1.0.0", 90 | "inflight": "^1.0.4", 91 | "inherits": "2", 92 | "minimatch": "^3.0.4", 93 | "once": "^1.3.0", 94 | "path-is-absolute": "^1.0.0" 95 | } 96 | }, 97 | "i": { 98 | "version": "0.3.6", 99 | "resolved": "https://registry.npmjs.org/i/-/i-0.3.6.tgz", 100 | "integrity": "sha1-2WyScyB28HJxG2sQ/X1PZa2O4j0=", 101 | "dev": true 102 | }, 103 | "inflight": { 104 | "version": "1.0.6", 105 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 106 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 107 | "dev": true, 108 | "requires": { 109 | "once": "^1.3.0", 110 | "wrappy": "1" 111 | } 112 | }, 113 | "inherits": { 114 | "version": "2.0.3", 115 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 116 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", 117 | "dev": true 118 | }, 119 | "isexe": { 120 | "version": "2.0.0", 121 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", 122 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" 123 | }, 124 | "isstream": { 125 | "version": "0.1.2", 126 | "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", 127 | "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", 128 | "dev": true 129 | }, 130 | "minimatch": { 131 | "version": "3.0.4", 132 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 133 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 134 | "dev": true, 135 | "requires": { 136 | "brace-expansion": "^1.1.7" 137 | } 138 | }, 139 | "minimist": { 140 | "version": "0.0.8", 141 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", 142 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", 143 | "dev": true 144 | }, 145 | "mkdirp": { 146 | "version": "0.5.1", 147 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", 148 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", 149 | "dev": true, 150 | "requires": { 151 | "minimist": "0.0.8" 152 | } 153 | }, 154 | "mute-stream": { 155 | "version": "0.0.8", 156 | "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", 157 | "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", 158 | "dev": true 159 | }, 160 | "ncp": { 161 | "version": "1.0.1", 162 | "resolved": "https://registry.npmjs.org/ncp/-/ncp-1.0.1.tgz", 163 | "integrity": "sha1-0VNn5cuHQyuhF9K/gP30Wuz7QkY=", 164 | "dev": true 165 | }, 166 | "nice-try": { 167 | "version": "1.0.5", 168 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz", 169 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" 170 | }, 171 | "once": { 172 | "version": "1.4.0", 173 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 174 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 175 | "dev": true, 176 | "requires": { 177 | "wrappy": "1" 178 | } 179 | }, 180 | "path-is-absolute": { 181 | "version": "1.0.1", 182 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 183 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 184 | "dev": true 185 | }, 186 | "path-key": { 187 | "version": "2.0.1", 188 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", 189 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" 190 | }, 191 | "pkginfo": { 192 | "version": "0.4.1", 193 | "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.4.1.tgz", 194 | "integrity": "sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=", 195 | "dev": true 196 | }, 197 | "prompt": { 198 | "version": "1.0.0", 199 | "resolved": "https://registry.npmjs.org/prompt/-/prompt-1.0.0.tgz", 200 | "integrity": "sha1-jlcSPDlquYiJf7Mn/Trtw+c15P4=", 201 | "dev": true, 202 | "requires": { 203 | "colors": "^1.1.2", 204 | "pkginfo": "0.x.x", 205 | "read": "1.0.x", 206 | "revalidator": "0.1.x", 207 | "utile": "0.3.x", 208 | "winston": "2.1.x" 209 | } 210 | }, 211 | "read": { 212 | "version": "1.0.7", 213 | "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", 214 | "integrity": "sha1-s9oZvQUkMal2cdRKQmNK33ELQMQ=", 215 | "dev": true, 216 | "requires": { 217 | "mute-stream": "~0.0.4" 218 | } 219 | }, 220 | "revalidator": { 221 | "version": "0.1.8", 222 | "resolved": "https://registry.npmjs.org/revalidator/-/revalidator-0.1.8.tgz", 223 | "integrity": "sha1-/s5hv6DBtSoga9axgZgYS91SOjs=", 224 | "dev": true 225 | }, 226 | "rimraf": { 227 | "version": "2.6.3", 228 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", 229 | "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", 230 | "dev": true, 231 | "requires": { 232 | "glob": "^7.1.3" 233 | } 234 | }, 235 | "semver": { 236 | "version": "5.7.0", 237 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz", 238 | "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==" 239 | }, 240 | "shebang-command": { 241 | "version": "1.2.0", 242 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", 243 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", 244 | "requires": { 245 | "shebang-regex": "^1.0.0" 246 | } 247 | }, 248 | "shebang-regex": { 249 | "version": "1.0.0", 250 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", 251 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" 252 | }, 253 | "stack-trace": { 254 | "version": "0.0.10", 255 | "resolved": "https://registry.npmjs.org/stack-trace/-/stack-trace-0.0.10.tgz", 256 | "integrity": "sha1-VHxws0fo0ytOEI6hoqFZ5f3eGcA=", 257 | "dev": true 258 | }, 259 | "utile": { 260 | "version": "0.3.0", 261 | "resolved": "https://registry.npmjs.org/utile/-/utile-0.3.0.tgz", 262 | "integrity": "sha1-E1LDQOuCDk2N26A5pPv6oy7U7zo=", 263 | "dev": true, 264 | "requires": { 265 | "async": "~0.9.0", 266 | "deep-equal": "~0.2.1", 267 | "i": "0.3.x", 268 | "mkdirp": "0.x.x", 269 | "ncp": "1.0.x", 270 | "rimraf": "2.x.x" 271 | } 272 | }, 273 | "vows": { 274 | "version": "0.8.2", 275 | "resolved": "https://registry.npmjs.org/vows/-/vows-0.8.2.tgz", 276 | "integrity": "sha1-aR95qybM3oC6cm3en+yOc9a88us=", 277 | "dev": true, 278 | "requires": { 279 | "diff": "~1.0.8", 280 | "eyes": "~0.1.6", 281 | "glob": "^7.1.2" 282 | } 283 | }, 284 | "which": { 285 | "version": "1.3.1", 286 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", 287 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", 288 | "requires": { 289 | "isexe": "^2.0.0" 290 | } 291 | }, 292 | "winston": { 293 | "version": "2.1.1", 294 | "resolved": "https://registry.npmjs.org/winston/-/winston-2.1.1.tgz", 295 | "integrity": "sha1-PJNJ0ZYgf9G9/51LxD73JRDjoS4=", 296 | "dev": true, 297 | "requires": { 298 | "async": "~1.0.0", 299 | "colors": "1.0.x", 300 | "cycle": "1.0.x", 301 | "eyes": "0.1.x", 302 | "isstream": "0.1.x", 303 | "pkginfo": "0.3.x", 304 | "stack-trace": "0.0.x" 305 | }, 306 | "dependencies": { 307 | "async": { 308 | "version": "1.0.0", 309 | "resolved": "https://registry.npmjs.org/async/-/async-1.0.0.tgz", 310 | "integrity": "sha1-+PwEyjoTeErenhZBr5hXjPvWR6k=", 311 | "dev": true 312 | }, 313 | "colors": { 314 | "version": "1.0.3", 315 | "resolved": "https://registry.npmjs.org/colors/-/colors-1.0.3.tgz", 316 | "integrity": "sha1-BDP0TYCWgP3rYO0mDxsMJi6CpAs=", 317 | "dev": true 318 | }, 319 | "pkginfo": { 320 | "version": "0.3.1", 321 | "resolved": "https://registry.npmjs.org/pkginfo/-/pkginfo-0.3.1.tgz", 322 | "integrity": "sha1-Wyn2qB9wcXFC4J52W76rl7T4HiE=", 323 | "dev": true 324 | } 325 | } 326 | }, 327 | "wrappy": { 328 | "version": "1.0.2", 329 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 330 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 331 | "dev": true 332 | } 333 | } 334 | } 335 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nexpect", 3 | "description": "Spawns and interacts with child processes using spawn / expect commands", 4 | "version": "0.6.0", 5 | "author": "Nodejitsu Inc. ", 6 | "license": "MIT", 7 | "maintainers": [ 8 | "indexzero " 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "http://github.com/nodejitsu/nexpect.git" 13 | }, 14 | "keywords": [ 15 | "nexpect", 16 | "spawn", 17 | "child process", 18 | "terminal" 19 | ], 20 | "dependencies": { 21 | "cross-spawn": "^6.0.5" 22 | }, 23 | "devDependencies": { 24 | "colors": "^1.3.3", 25 | "prompt": "^1.0.0", 26 | "vows": "^0.8.2" 27 | }, 28 | "main": "./lib/nexpect", 29 | "scripts": { 30 | "test": "vows --spec" 31 | }, 32 | "engines": { 33 | "node": ">=6" 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /test/fixtures/log-colors: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var colors = require('colors'); 4 | 5 | console.log('first has no colors'); 6 | console.log('second has colors'.green); 7 | console.log('third has colors'.yellow); 8 | -------------------------------------------------------------------------------- /test/fixtures/multiple-cases: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | console.log('this is all lowercase'); 4 | console.log('ThIs HAS mAny cases'); 5 | console.log('ThIs also HAS mAny cASEs'); 6 | -------------------------------------------------------------------------------- /test/fixtures/prompt-and-respond: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var prompt = require('prompt'); 4 | 5 | console.log('Starting prompt'); 6 | 7 | prompt.start(); 8 | prompt.get(['first'], function (err, result) { 9 | console.log(result.first); 10 | 11 | prompt.get(['second'], function (err, result) { 12 | console.log(result.second); 13 | process.exit(0); 14 | }); 15 | }); 16 | -------------------------------------------------------------------------------- /test/fixtures/show-cwd: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | console.log('dont output process.cwd() yet'); 4 | console.log('dont output process.cwd() yet'); 5 | console.log('dont output process.cwd() yet'); 6 | console.log(process.cwd()); 7 | -------------------------------------------------------------------------------- /test/fixtures/show-env: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | for (var k in process.env) { 4 | console.log('%s=%s', k, process.env[k]); 5 | } 6 | -------------------------------------------------------------------------------- /test/nexpect-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | * nexpect-test.js: Tests for the `nexpect` module. 3 | * 4 | * (C) 2011, Elijah Insua, Marak Squires, Charlie Robbins. 5 | * 6 | */ 7 | 8 | var assert = require('assert'), 9 | path = require('path'), 10 | vows = require('vows'), 11 | nexpect = require('../lib/nexpect'); 12 | 13 | function assertSpawn (expect) { 14 | return { 15 | topic: function () { 16 | expect.run(this.callback); 17 | }, 18 | "should respond with no error": function (err, stdout) { 19 | assert.isTrue(!err); 20 | assert.isArray(stdout); 21 | } 22 | }; 23 | } 24 | 25 | function assertError (expect) { 26 | return { 27 | topic: function () { 28 | expect.run(this.callback.bind(this, null)); 29 | }, 30 | "should respond with error": function (err) { 31 | assert.isObject(err); 32 | } 33 | }; 34 | } 35 | 36 | vows.describe('nexpect').addBatch({ 37 | "When using the nexpect module": { 38 | "it should have the correct methods defined": function () { 39 | assert.isFunction(nexpect.spawn); 40 | assert.isObject(nexpect.nspawn); 41 | assert.isFunction(nexpect.nspawn.spawn); 42 | }, 43 | "spawning": { 44 | "`echo hello`": assertSpawn( 45 | nexpect.spawn("echo", ["hello"]) 46 | .expect("hello") 47 | ), 48 | "`ls -l /tmp/undefined`": assertSpawn( 49 | nexpect.spawn("ls -la /tmp/undefined", { stream: 'stderr' }) 50 | .expect("No such file or directory") 51 | ), 52 | "a command that does not exist": assertError( 53 | nexpect.spawn("idontexist") 54 | .expect("This will never work") 55 | ), 56 | "and using the sendline() method": assertSpawn( 57 | nexpect.spawn("node --interactive") 58 | .expect(">") 59 | .sendline("console.log('testing')") 60 | .expect("testing") 61 | .sendline("process.exit()") 62 | ), 63 | "and using the expect() method": { 64 | "when RegExp expectation is met": assertSpawn( 65 | nexpect.spawn("echo", ["hello"]) 66 | .expect(/^hello$/) 67 | ), 68 | }, 69 | "and using the wait() method": { 70 | "when assertions are met": assertSpawn( 71 | nexpect.spawn(path.join(__dirname, 'fixtures', 'prompt-and-respond')) 72 | .wait('first') 73 | .sendline('first-prompt') 74 | .expect('first-prompt') 75 | .wait('second') 76 | .sendline('second-prompt') 77 | .expect('second-prompt') 78 | ), 79 | "when the last assertion is never met": assertError( 80 | nexpect.spawn(path.join(__dirname, 'fixtures', 'prompt-and-respond')) 81 | .wait('first') 82 | .sendline('first-prompt') 83 | .expect('first-prompt') 84 | .wait('second') 85 | .sendline('second-prompt') 86 | .wait('this-never-shows-up') 87 | ), 88 | "when a callback is provided and output is matched": { 89 | topic: function() { 90 | nexpect.spawn(path.join(__dirname, 'fixtures', 'prompt-and-respond')) 91 | .wait('first', this.callback) 92 | .sendline('first-prompt') 93 | .expect('first-prompt') 94 | .wait('second') 95 | .sendline('second-prompt') 96 | .expect('second-prompt').run(function() {}); 97 | }, 98 | 'should call callback': function(matchData, b) { 99 | assert.ok(matchData.indexOf('first') > 0, "Found 'first' in output") 100 | } 101 | }, 102 | "when a callback is provided and output is not matched": { 103 | topic: function() { 104 | var args = {hasRunCallback: false}, 105 | waitCallback = function() { 106 | args.hasRunCallback = true; 107 | }; 108 | 109 | nexpect.spawn(path.join(__dirname, 'fixtures', 'prompt-and-respond')) 110 | .wait('first') 111 | .sendline('first-prompt') 112 | .expect('first-prompt') 113 | .wait('second') 114 | .sendline('second-prompt') 115 | .wait('this-never-shows-up', waitCallback).run(this.callback.bind(this, args)); 116 | }, 117 | 'should not call callback': function(args, a) { 118 | assert.equal(args.hasRunCallback, false, 'Should not have run callback'); 119 | } 120 | } 121 | }, 122 | "when options.stripColors is set": assertSpawn( 123 | nexpect.spawn(path.join(__dirname, 'fixtures', 'log-colors'), { stripColors: true }) 124 | .wait('second has colors') 125 | .expect('third has colors') 126 | ), 127 | "when options.ignoreCase is set": assertSpawn( 128 | nexpect.spawn(path.join(__dirname, 'fixtures', 'multiple-cases'), { ignoreCase: true }) 129 | .wait('this has many cases') 130 | .expect('this also has many cases') 131 | ), 132 | "when options.cwd is set": assertSpawn( 133 | nexpect.spawn(path.join(__dirname, 'fixtures', 'show-cwd'), { cwd: path.join(__dirname, 'fixtures') }) 134 | .wait(path.join(__dirname, 'fixtures')) 135 | ), 136 | "when options.env is set": assertSpawn( 137 | nexpect.spawn(path.join(__dirname, 'fixtures', 'show-env'), { env: { foo: 'bar', PATH: process.env.PATH }}) 138 | .expect('foo=bar') 139 | ) 140 | } 141 | } 142 | }).export(module); 143 | --------------------------------------------------------------------------------