├── .hound.yml ├── bin ├── elevate-ia32.exe └── elevate-x64.exe ├── .editorconfig ├── .gitignore ├── appveyor.yml ├── package.json ├── LICENSE ├── CHANGELOG.md ├── tests ├── utils.spec.js └── command.spec.js ├── gulpfile.js ├── doc └── README.hbs ├── lib ├── utils.js ├── command.js └── elevator.js ├── README.md └── .jshintrc /.hound.yml: -------------------------------------------------------------------------------- 1 | javascript: 2 | config_file: .jshintrc 3 | -------------------------------------------------------------------------------- /bin/elevate-ia32.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balena-io-modules/elevator/HEAD/bin/elevate-ia32.exe -------------------------------------------------------------------------------- /bin/elevate-x64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/balena-io-modules/elevator/HEAD/bin/elevate-x64.exe -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 28 | node_modules 29 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # appveyor file 2 | # http://www.appveyor.com/docs/appveyor-yml 3 | 4 | init: 5 | - git config --global core.autocrlf input 6 | 7 | cache: 8 | - C:\Users\appveyor\.node-gyp 9 | - '%AppData%\npm-cache' 10 | 11 | # what combinations to test 12 | environment: 13 | matrix: 14 | - nodejs_version: 0.10 15 | - nodejs_version: 0.11 16 | - nodejs_version: 0.12 17 | 18 | install: 19 | - ps: Install-Product node $env:nodejs_version x64 20 | - npm -g install npm@2 21 | - set PATH=%APPDATA%\npm;%PATH% 22 | - npm install 23 | 24 | build: off 25 | 26 | test_script: 27 | - node --version 28 | - npm --version 29 | - cmd: npm test 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "elevator", 3 | "version": "2.2.3", 4 | "description": "Windows UAC elevation that just works", 5 | "main": "lib/elevator.js", 6 | "homepage": "https://github.com/resin-io-modules/elevator", 7 | "os": [ 8 | "win32" 9 | ], 10 | "repository": { 11 | "type": "git", 12 | "url": "git://github.com/resin-io-modules/elevator.git" 13 | }, 14 | "directories": { 15 | "test": "tests" 16 | }, 17 | "scripts": { 18 | "test": "gulp test", 19 | "prepublish": "gulp test", 20 | "readme": "jsdoc2md --template doc/README.hbs lib/elevator.js > README.md" 21 | }, 22 | "keywords": [ 23 | "windows", 24 | "win32", 25 | "uac", 26 | "elevate", 27 | "elevator" 28 | ], 29 | "author": "Juan Cruz Viotti ", 30 | "license": "MIT", 31 | "devDependencies": { 32 | "gulp": "^3.9.0", 33 | "gulp-jshint": "^1.11.2", 34 | "gulp-mocha": "^2.1.3", 35 | "jsdoc-to-markdown": "^1.1.1", 36 | "jshint-stylish": "^2.0.1", 37 | "mochainon": "^1.0.0" 38 | }, 39 | "dependencies": { 40 | "debug": "^2.6.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2015 Resin.io. https://resin.io 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | 3 | All notable changes to this project will be documented in this file. 4 | This project adheres to [Semantic Versioning](http://semver.org/). 5 | 6 | ## [v2.2.3] - 2017-04-20 7 | 8 | ### Changed 9 | 10 | - Fix logical error when yielding back a cancellation error. 11 | 12 | ## [v2.2.2] - 2017-04-14 13 | 14 | ### Changed 15 | 16 | - Log debug messages to `stdout`. 17 | 18 | ## [v2.2.1] - 2017-04-14 19 | 20 | ### Changed 21 | 22 | - Add error code to error message. 23 | 24 | ## [v2.2.0] - 2017-04-13 25 | 26 | ### Changed 27 | 28 | - Log debugging information using the `debug` module. 29 | 30 | ## [v2.1.0] - 2016-06-17 31 | 32 | ### Added 33 | 34 | - Throw a distinguishable error if the user cancels elevation. 35 | 36 | ## [v2.0.0] - 2016-06-17 37 | 38 | ### Added 39 | 40 | - Add `hidden` option. 41 | - Add `doNotPushdCurrentDirectory` option. 42 | 43 | ### Removed 44 | 45 | - Remove `pushdCurrentDirectory` option. 46 | 47 | [v2.2.3]: https://github.com/resin-io-modules/elevator/compare/v2.2.2...v2.2.3 48 | [v2.2.2]: https://github.com/resin-io-modules/elevator/compare/v2.2.1...v2.2.2 49 | [v2.2.1]: https://github.com/resin-io-modules/elevator/compare/v2.2.0...v2.2.1 50 | [v2.2.0]: https://github.com/resin-io-modules/elevator/compare/v2.1.0...v2.2.0 51 | [v2.1.0]: https://github.com/resin-io-modules/elevator/compare/v2.0.0...v2.1.0 52 | [v2.0.0]: https://github.com/resin-io-modules/elevator/compare/v1.0.0...v2.0.0 53 | -------------------------------------------------------------------------------- /tests/utils.spec.js: -------------------------------------------------------------------------------- 1 | var m = require('mochainon'); 2 | var utils = require('../lib/utils'); 3 | 4 | describe('Utils', function() { 5 | 6 | describe('.doesErrorMeansElevationWasCancelled()', function() { 7 | 8 | it('should return true if the error message contains the elevation string', function() { 9 | var error = new Error('foo ' + utils.ELEVATE_EXE_CANCELLED_MESSAGE + ' bar'); 10 | m.chai.expect(utils.doesErrorMeansElevationWasCancelled(error)).to.be.true; 11 | }); 12 | 13 | it('should return false if the error message does not contain the elevation string', function() { 14 | var error = new Error('foo bar'); 15 | m.chai.expect(utils.doesErrorMeansElevationWasCancelled(error)).to.be.false; 16 | }); 17 | 18 | it('should return true if the error message contains the elevation string after a new line', function() { 19 | var error = new Error([ 20 | 'foo', 21 | utils.ELEVATE_EXE_CANCELLED_MESSAGE, 22 | 'bar' 23 | ].join('\n')); 24 | 25 | m.chai.expect(utils.doesErrorMeansElevationWasCancelled(error)).to.be.true; 26 | }); 27 | 28 | }); 29 | 30 | describe('.ElevateCancelledError', function() { 31 | 32 | it('should be an instance of Error', function() { 33 | m.chai.expect(utils.ElevateCancelledError).to.be.an.instanceof(Error); 34 | }); 35 | 36 | it('should have an error code that equals ELEVATE_CANCELLED', function() { 37 | m.chai.expect(utils.ElevateCancelledError.code).to.equal('ELEVATE_CANCELLED'); 38 | }); 39 | 40 | }); 41 | 42 | }); 43 | -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015 Resin.io. https://resin.io 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | var gulp = require('gulp'); 26 | var jshint = require('gulp-jshint'); 27 | var jshintStylish = require('jshint-stylish'); 28 | var mocha = require('gulp-mocha'); 29 | 30 | var paths = { 31 | scripts: [ 32 | './lib/**/*.js', 33 | './tests/**/*.spec.js', 34 | 'gulpfile.js' 35 | ], 36 | tests: [ 37 | './tests/**/*.spec.js' 38 | ] 39 | }; 40 | 41 | gulp.task('lint', function() { 42 | 'use strict'; 43 | return gulp.src(paths.scripts) 44 | .pipe(jshint()) 45 | .pipe(jshint.reporter(jshintStylish)); 46 | }); 47 | 48 | gulp.task('test', [ 'lint' ], function () { 49 | 'use strict'; 50 | return gulp.src(paths.tests, { 51 | read: false 52 | }) 53 | .pipe(mocha({ 54 | reporter: 'nyan' 55 | })); 56 | }); 57 | 58 | gulp.task('watch', [ 'test' ], function() { 59 | 'use strict'; 60 | gulp.watch(paths.scripts, [ 'test' ]); 61 | gulp.watch(paths.tests, [ 'test' ]); 62 | }); 63 | 64 | gulp.task('default', [ 'test' ]); 65 | -------------------------------------------------------------------------------- /doc/README.hbs: -------------------------------------------------------------------------------- 1 | elevator 2 | ======== 3 | 4 | > Windows UAC elevation that just works. 5 | 6 | [![npm version](https://badge.fury.io/js/elevator.svg)](http://badge.fury.io/js/elevator) 7 | [![dependencies](https://david-dm.org/resin-io-modules/elevator.svg)](https://david-dm.org/resin-io-modules/elevator.svg) 8 | [![Build status](https://ci.appveyor.com/api/projects/status/ysweh6h4ed4ak114/branch/master?svg=true)](https://ci.appveyor.com/project/resin-io/elevator/branch/master) 9 | [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/resin-io/chat) 10 | 11 | Description 12 | ----------- 13 | 14 | This module is a thin wrapper around the awesome [elevate.exe utility by kliu](http://code.kliu.org/misc/elevate/), which is a small C open source application with x64 and ia32 support that just works. 15 | 16 | This module doesn't make use of NodeJS C/C++ bindings and instead relies on executing the `.exe` file direcly in a way that is [electron](http://electron.atom.io) friendly even when the application is packaged in an `asar` archive. 17 | 18 | Of course, this means that this NodeJS module is subjected to the Windows versions and architectures such supports. Windows Vista (or newer) is required. 19 | 20 | Installation 21 | ------------ 22 | 23 | Install `elevator` by running: 24 | 25 | ```sh 26 | $ npm install --save elevator 27 | ``` 28 | 29 | Documentation 30 | ------------- 31 | 32 | {{#module name="elevator"}} 33 | {{>body~}} 34 | {{>member-index~}} 35 | {{>separator~}} 36 | {{>members~}} 37 | {{/module}} 38 | 39 | Support 40 | ------- 41 | 42 | If you're having any problem, please [raise an issue](https://github.com/resin-io-modules/elevator/issues/new) on GitHub and the Resin.io team will be happy to help. 43 | 44 | Tests 45 | ----- 46 | 47 | Run the test suite by doing: 48 | 49 | ```sh 50 | $ gulp test 51 | ``` 52 | 53 | Contribute 54 | ---------- 55 | 56 | - Issue Tracker: [github.com/resin-io-modules/elevator/issues](https://github.com/resin-io-modules/elevator/issues) 57 | - Source Code: [github.com/resin-io-modules/elevator](https://github.com/resin-io-modules/elevator) 58 | 59 | Before submitting a PR, please make sure that you include tests, and that [jshint](http://jshint.com) runs without any warning: 60 | 61 | ```sh 62 | $ gulp lint 63 | ``` 64 | 65 | License 66 | ------- 67 | 68 | The project is licensed under the MIT license. 69 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015 Resin.io. https://resin.io 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * @summary The string printed by `elevate.exe` when the elevation is cancelled 27 | * @constant 28 | * @type {String} 29 | * @protected 30 | * 31 | * @description 32 | * This constant is exported for testability reasons. 33 | */ 34 | exports.ELEVATE_EXE_CANCELLED_MESSAGE = 'The operation was canceled by the user'; 35 | 36 | /** 37 | * @summary Check if an error message represents an elevation cancellation condition 38 | * @public 39 | * @function 40 | * 41 | * @param {Error} error - error object 42 | * @returns {Boolean} whether the error is a cancellation error 43 | * 44 | * @example 45 | * if (utils.doesErrorMeansElevationWasCancelled(error)) { 46 | * console.log('The error means that the user cancelled the elevation request'); 47 | * } 48 | */ 49 | exports.doesErrorMeansElevationWasCancelled = function(error) { 50 | return error.message.indexOf(exports.ELEVATE_EXE_CANCELLED_MESSAGE) !== -1; 51 | }; 52 | 53 | /** 54 | * @summary An extended elevation cancelled Error object 55 | * @constant 56 | * @type {Error} 57 | * @public 58 | */ 59 | exports.ElevateCancelledError = new Error(exports.ELEVATE_EXE_CANCELLED_MESSAGE); 60 | exports.ElevateCancelledError.code = 'ELEVATE_CANCELLED'; 61 | -------------------------------------------------------------------------------- /lib/command.js: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015 Resin.io. https://resin.io 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | var os = require('os'); 26 | var path = require('path'); 27 | var binPath = path.join(__dirname, '..', 'bin'); 28 | 29 | /** 30 | * @summary Get elevate.exe binary path 31 | * @function 32 | * @public 33 | * 34 | * @returns {String} path to elevate.exe 35 | * 36 | * @throws Will throw if the architecture is not supported. 37 | * 38 | * @example 39 | * var path = command.getBinaryPath(); 40 | */ 41 | exports.getBinaryPath = function() { 42 | var arch = os.arch(); 43 | 44 | if (arch !== 'x64' && arch !== 'ia32') { 45 | throw new Error('Unsupported arch: ' + arch); 46 | } 47 | 48 | return path.join(binPath, 'elevate-' + arch + '.exe'); 49 | }; 50 | 51 | /* 52 | * @summary Build command arguments 53 | * @function 54 | * @public 55 | * 56 | * @param {String[]} command - command 57 | * @param {Object} [options={}] - options 58 | * @param {Boolean} [options.terminating] - Launches a terminating command processor; equivalent to "cmd /c command". 59 | * @param {Boolean} [options.persistent] - Launches a persistent command processor; equivalent to "cmd /k command". 60 | * @param {Boolean} [options.doNotPushdCurrentDirectory] - When using -c or -k, do not pushd the current directory before execution. 61 | * @param {Boolean} [options.unicode] - When using -c or -k, use Unicode; equivalent to "cmd /u". 62 | * @param {Boolean} [options.hidden] - When using -c or -k, start "cmd" in hidden mode. 63 | * @param {Boolean} [options.waitForTermination] - Waits for termination; equivalent to "start /wait command". 64 | * 65 | * @returns {String} command arguments 66 | * 67 | * @example 68 | * var args = command.build([ 'foo.exe' ], { 69 | * terminating: true, 70 | * unicode: true 71 | * }); 72 | */ 73 | exports.build = function(command, options) { 74 | if (!command) { 75 | throw new Error('Missing command'); 76 | } 77 | 78 | options = options || {}; 79 | 80 | if (options.terminating && options.persistent) { 81 | throw new Error('Can\'t have a both persistent and terminating command processor'); 82 | } 83 | 84 | if (options.doNotPushdCurrentDirectory && !options.terminating && !options.persistent) { 85 | throw new Error('doNotPushdCurrentDirectory requires the terminating or persistent option'); 86 | } 87 | 88 | if (options.unicode && !options.terminating && !options.persistent) { 89 | throw new Error('unicode requires the terminating or persistent option'); 90 | } 91 | 92 | if (options.hidden && !options.terminating && !options.persistent) { 93 | throw new Error('hidden requires the terminating or persistent option'); 94 | } 95 | 96 | var args = []; 97 | 98 | if (options.terminating) { 99 | args.push('-c'); 100 | } 101 | 102 | if (options.persistent) { 103 | args.push('-k'); 104 | } 105 | 106 | if (options.doNotPushdCurrentDirectory) { 107 | args.push('-n'); 108 | } 109 | 110 | if (options.unicode) { 111 | args.push('-u'); 112 | } 113 | 114 | if (options.hidden) { 115 | args.push('-h'); 116 | } 117 | 118 | if (options.waitForTermination) { 119 | args.push('-w'); 120 | } 121 | 122 | return args.concat(command); 123 | }; 124 | -------------------------------------------------------------------------------- /lib/elevator.js: -------------------------------------------------------------------------------- 1 | /* 2 | * The MIT License 3 | * 4 | * Copyright (c) 2015 Resin.io. https://resin.io 5 | * 6 | * Permission is hereby granted, free of charge, to any person obtaining a copy 7 | * of this software and associated documentation files (the "Software"), to deal 8 | * in the Software without restriction, including without limitation the rights 9 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | * copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | * THE SOFTWARE. 23 | */ 24 | 25 | /** 26 | * @module elevator 27 | */ 28 | 29 | var child_process = require('child_process'); 30 | var commandBuilder = require('./command'); 31 | var debug = require('debug')(require('../package.json').name); 32 | 33 | // Send debug information to `stdout` 34 | debug.log = console.log.bind(console); 35 | 36 | var utils = require('./utils'); 37 | 38 | /** 39 | * @summary Execute a command with UAC elevation 40 | * @function 41 | * @public 42 | * 43 | * @description 44 | * This function will yield an `Error` containing a code that equals 45 | * `ELEVATE_CANCELLED` if the elevation was cancelled by the user. 46 | * 47 | * @param {String[]} command - command 48 | * @param {Object} [options={}] - options 49 | * @param {Boolean} [options.terminating] - Launches a terminating command processor; equivalent to "cmd /c command". 50 | * @param {Boolean} [options.persistent] - Launches a persistent command processor; equivalent to "cmd /k command". 51 | * @param {Boolean} [options.doNotPushdCurrentDirectory] - When using -c or -k, do not pushd the current directory before execution. 52 | * @param {Boolean} [options.unicode] - When using -c or -k, use Unicode; equivalent to "cmd /u". 53 | * @param {Boolean} [options.hidden] - When using -c or -k, start "cmd" in hidden mode. 54 | * @param {Boolean} [options.waitForTermination] - Waits for termination; equivalent to "start /wait command". 55 | * @param {Function} callback - callback (error, stdout, stderr) 56 | * 57 | * @example 58 | * elevator.execute([ 'cmd.exe' ], { 59 | * waitForTermination: true 60 | * }, function(error, stdout, stderr) { 61 | * if (error) { 62 | * throw error; 63 | * } 64 | * 65 | * console.log(stdout); 66 | * console.log(stderr); 67 | * }); 68 | */ 69 | exports.execute = function(command, options, callback) { 70 | var binaryPath = commandBuilder.getBinaryPath(); 71 | debug('executing: %s', binaryPath); 72 | 73 | child_process.execFile(binaryPath, commandBuilder.build(command, options), { 74 | encoding: 'utf8' 75 | }, function(error, stdout, stderr) { 76 | debug('stderr: %s', stderr); 77 | debug('stdout: %s', stdout); 78 | 79 | if (error) { 80 | if (utils.doesErrorMeansElevationWasCancelled(error)) { 81 | return callback.call(this, utils.ElevateCancelledError); 82 | } 83 | 84 | var elevationError = new Error('Couldn\'t elevate, exit code ' + error.code); 85 | elevationError.description = error.message; 86 | return callback.call(this, elevationError); 87 | } 88 | 89 | 90 | return callback.apply(this, arguments); 91 | }); 92 | }; 93 | 94 | /** 95 | * @summary Execute a command with UAC elevation (Sync) 96 | * @function 97 | * @public 98 | * 99 | * @description 100 | * This function will throw an `Error` containing a code that equals 101 | * `ELEVATE_CANCELLED` if the elevation was cancelled by the user. 102 | * 103 | * @param {String[]} command - command 104 | * @param {Object} [options={}] - options 105 | * @param {Boolean} [options.terminating] - Launches a terminating command processor; equivalent to "cmd /c command". 106 | * @param {Boolean} [options.persistent] - Launches a persistent command processor; equivalent to "cmd /k command". 107 | * @param {Boolean} [options.doNotPushdCurrentDirectory] - When using -c or -k, do not pushd the current directory before execution. 108 | * @param {Boolean} [options.unicode] - When using -c or -k, use Unicode; equivalent to "cmd /u". 109 | * @param {Boolean} [options.hidden] - When using -c or -k, start "cmd" in hidden mode. 110 | * @param {Boolean} [options.waitForTermination] - Waits for termination; equivalent to "start /wait command". 111 | * @returns {String} stdout buffer 112 | * 113 | * @example 114 | * elevator.executeSync([ 'cmd.exe' ], { 115 | * waitForTermination: true 116 | * }); 117 | */ 118 | exports.executeSync = function(command, options) { 119 | try { 120 | var binaryPath = commandBuilder.getBinaryPath(); 121 | debug('executing: %s', binaryPath); 122 | 123 | return child_process.execFileSync(binaryPath, commandBuilder.build(command, options), { 124 | encoding: 'utf8' 125 | }); 126 | } catch (error) { 127 | 128 | if (error.error && utils.doesErrorMeansElevationWasCancelled(error.error)) { 129 | throw utils.ElevateCancelledError; 130 | } 131 | 132 | throw error.error; 133 | } 134 | }; 135 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | elevator 2 | ======== 3 | 4 | > Windows UAC elevation that just works. 5 | 6 | [![npm version](https://badge.fury.io/js/elevator.svg)](http://badge.fury.io/js/elevator) 7 | [![dependencies](https://david-dm.org/resin-io-modules/elevator.svg)](https://david-dm.org/resin-io-modules/elevator.svg) 8 | [![Build status](https://ci.appveyor.com/api/projects/status/ysweh6h4ed4ak114/branch/master?svg=true)](https://ci.appveyor.com/project/resin-io/elevator/branch/master) 9 | [![Gitter](https://badges.gitter.im/Join Chat.svg)](https://gitter.im/resin-io/chat) 10 | 11 | Description 12 | ----------- 13 | 14 | This module is a thin wrapper around the awesome [elevate.exe utility by kliu](http://code.kliu.org/misc/elevate/), which is a small C open source application with x64 and ia32 support that just works. 15 | 16 | This module doesn't make use of NodeJS C/C++ bindings and instead relies on executing the `.exe` file direcly in a way that is [electron](http://electron.atom.io) friendly even when the application is packaged in an `asar` archive. 17 | 18 | Of course, this means that this NodeJS module is subjected to the Windows versions and architectures such supports. Windows Vista (or newer) is required. 19 | 20 | Installation 21 | ------------ 22 | 23 | Install `elevator` by running: 24 | 25 | ```sh 26 | $ npm install --save elevator 27 | ``` 28 | 29 | Documentation 30 | ------------- 31 | 32 | 33 | * [elevator](#module_elevator) 34 | * [.execute(command, [options], callback)](#module_elevator.execute) 35 | * [.executeSync(command, [options])](#module_elevator.executeSync) ⇒ String 36 | 37 | 38 | 39 | ### elevator.execute(command, [options], callback) 40 | This function will yield an `Error` containing a code that equals 41 | `ELEVATE_CANCELLED` if the elevation was cancelled by the user. 42 | 43 | **Kind**: static method of [elevator](#module_elevator) 44 | **Summary**: Execute a command with UAC elevation 45 | **Access:** public 46 | 47 | | Param | Type | Default | Description | 48 | | --- | --- | --- | --- | 49 | | command | Array.<String> | | command | 50 | | [options] | Object | {} | options | 51 | | [options.terminating] | Boolean | | Launches a terminating command processor; equivalent to "cmd /c command". | 52 | | [options.persistent] | Boolean | | Launches a persistent command processor; equivalent to "cmd /k command". | 53 | | [options.doNotPushdCurrentDirectory] | Boolean | | When using -c or -k, do not pushd the current directory before execution. | 54 | | [options.unicode] | Boolean | | When using -c or -k, use Unicode; equivalent to "cmd /u". | 55 | | [options.hidden] | Boolean | | When using -c or -k, start "cmd" in hidden mode. | 56 | | [options.waitForTermination] | Boolean | | Waits for termination; equivalent to "start /wait command". | 57 | | callback | function | | callback (error, stdout, stderr) | 58 | 59 | **Example** 60 | ```js 61 | elevator.execute([ 'cmd.exe' ], { 62 | waitForTermination: true 63 | }, function(error, stdout, stderr) { 64 | if (error) { 65 | throw error; 66 | } 67 | 68 | console.log(stdout); 69 | console.log(stderr); 70 | }); 71 | ``` 72 | 73 | 74 | ### elevator.executeSync(command, [options]) ⇒ String 75 | This function will throw an `Error` containing a code that equals 76 | `ELEVATE_CANCELLED` if the elevation was cancelled by the user. 77 | 78 | **Kind**: static method of [elevator](#module_elevator) 79 | **Summary**: Execute a command with UAC elevation (Sync) 80 | **Returns**: String - stdout buffer 81 | **Access:** public 82 | 83 | | Param | Type | Default | Description | 84 | | --- | --- | --- | --- | 85 | | command | Array.<String> | | command | 86 | | [options] | Object | {} | options | 87 | | [options.terminating] | Boolean | | Launches a terminating command processor; equivalent to "cmd /c command". | 88 | | [options.persistent] | Boolean | | Launches a persistent command processor; equivalent to "cmd /k command". | 89 | | [options.doNotPushdCurrentDirectory] | Boolean | | When using -c or -k, do not pushd the current directory before execution. | 90 | | [options.unicode] | Boolean | | When using -c or -k, use Unicode; equivalent to "cmd /u". | 91 | | [options.hidden] | Boolean | | When using -c or -k, start "cmd" in hidden mode. | 92 | | [options.waitForTermination] | Boolean | | Waits for termination; equivalent to "start /wait command". | 93 | 94 | **Example** 95 | ```js 96 | elevator.executeSync([ 'cmd.exe' ], { 97 | waitForTermination: true 98 | }); 99 | ``` 100 | 101 | Support 102 | ------- 103 | 104 | If you're having any problem, please [raise an issue](https://github.com/resin-io-modules/elevator/issues/new) on GitHub and the Resin.io team will be happy to help. 105 | 106 | Tests 107 | ----- 108 | 109 | Run the test suite by doing: 110 | 111 | ```sh 112 | $ gulp test 113 | ``` 114 | 115 | Contribute 116 | ---------- 117 | 118 | - Issue Tracker: [github.com/resin-io-modules/elevator/issues](https://github.com/resin-io-modules/elevator/issues) 119 | - Source Code: [github.com/resin-io-modules/elevator](https://github.com/resin-io-modules/elevator) 120 | 121 | Before submitting a PR, please make sure that you include tests, and that [jshint](http://jshint.com) runs without any warning: 122 | 123 | ```sh 124 | $ gulp lint 125 | ``` 126 | 127 | License 128 | ------- 129 | 130 | The project is licensed under the MIT license. 131 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | // -------------------------------------------------------------------- 3 | // JSHint Configuration, Strict Edition 4 | // -------------------------------------------------------------------- 5 | // 6 | // This is a options template for [JSHint][1], using [JSHint example][2] 7 | // and [Ory Band's example][3] as basis and setting config values to 8 | // be most strict: 9 | // 10 | // * set all enforcing options to true 11 | // * set all relaxing options to false 12 | // * set all environment options to false, except the browser value 13 | // * set all JSLint legacy options to false 14 | // 15 | // [1]: http://www.jshint.com/ 16 | // [2]: https://github.com/jshint/node-jshint/blob/master/example/config.json 17 | // [3]: https://github.com/oryband/dotfiles/blob/master/jshintrc 18 | // 19 | // @author http://michael.haschke.biz/ 20 | // @license http://unlicense.org/ 21 | 22 | // == Enforcing Options =============================================== 23 | // 24 | // These options tell JSHint to be more strict towards your code. Use 25 | // them if you want to allow only a safe subset of JavaScript, very 26 | // useful when your codebase is shared with a big number of developers 27 | // with different skill levels. 28 | 29 | "bitwise" : true, // Prohibit bitwise operators (&, |, ^, etc.). 30 | "curly" : true, // Require {} for every new block or scope. 31 | "eqeqeq" : true, // Require triple equals i.e. `===`. 32 | "forin" : true, // Tolerate `for in` loops without `hasOwnPrototype`. 33 | "immed" : true, // Require immediate invocations to be wrapped in parens e.g. `( function(){}() );` 34 | "latedef" : true, // Prohibit variable use before definition. 35 | "newcap" : true, // Require capitalization of all constructor functions e.g. `new F()`. 36 | "noarg" : true, // Prohibit use of `arguments.caller` and `arguments.callee`. 37 | "noempty" : true, // Prohibit use of empty blocks. 38 | "nonew" : true, // Prohibit use of constructors for side-effects. 39 | "plusplus" : true, // Prohibit use of `++` & `--`. 40 | "regexp" : true, // Prohibit `.` and `[^...]` in regular expressions. 41 | "undef" : true, // Require all non-global variables be declared before they are used. 42 | "strict" : false, // Require `use strict` pragma in every file. 43 | "trailing" : true, // Prohibit trailing whitespaces. 44 | 45 | // == Relaxing Options ================================================ 46 | // 47 | // These options allow you to suppress certain types of warnings. Use 48 | // them only if you are absolutely positive that you know what you are 49 | // doing. 50 | 51 | "asi" : false, // Tolerate Automatic Semicolon Insertion (no semicolons). 52 | "boss" : false, // Tolerate assignments inside if, for & while. Usually conditions & loops are for comparison, not assignments. 53 | "debug" : false, // Allow debugger statements e.g. browser breakpoints. 54 | "eqnull" : false, // Tolerate use of `== null`. 55 | "es5" : false, // Allow EcmaScript 5 syntax. 56 | "esnext" : false, // Allow ES.next specific features such as `const` and `let`. 57 | "evil" : false, // Tolerate use of `eval`. 58 | "expr" : true, // Tolerate `ExpressionStatement` as Programs. 59 | "funcscope" : false, // Tolerate declarations of variables inside of control structures while accessing them later from the outside. 60 | "globalstrict" : false, // Allow global "use strict" (also enables 'strict'). 61 | "iterator" : false, // Allow usage of __iterator__ property. 62 | "lastsemic" : false, // Tolerat missing semicolons when the it is omitted for the last statement in a one-line block. 63 | "laxbreak" : false, // Tolerate unsafe line breaks e.g. `return [\n] x` without semicolons. 64 | "laxcomma" : false, // Suppress warnings about comma-first coding style. 65 | "loopfunc" : false, // Allow functions to be defined within loops. 66 | "multistr" : false, // Tolerate multi-line strings. 67 | "onecase" : false, // Tolerate switches with just one case. 68 | "proto" : false, // Tolerate __proto__ property. This property is deprecated. 69 | "regexdash" : false, // Tolerate unescaped last dash i.e. `[-...]`. 70 | "scripturl" : false, // Tolerate script-targeted URLs. 71 | "smarttabs" : false, // Tolerate mixed tabs and spaces when the latter are used for alignmnent only. 72 | "shadow" : false, // Allows re-define variables later in code e.g. `var x=1; x=2;`. 73 | "sub" : false, // Tolerate all forms of subscript notation besides dot notation e.g. `dict['key']` instead of `dict.key`. 74 | "supernew" : false, // Tolerate `new function () { ... };` and `new Object;`. 75 | "validthis" : false, // Tolerate strict violations when the code is running in strict mode and you use this in a non-constructor function. 76 | 77 | // == Environments ==================================================== 78 | // 79 | // These options pre-define global variables that are exposed by 80 | // popular JavaScript libraries and runtime environments—such as 81 | // browser or node.js. 82 | 83 | "browser" : true, // Standard browser globals e.g. `window`, `document`. 84 | "couch" : false, // Enable globals exposed by CouchDB. 85 | "devel" : false, // Allow development statements e.g. `console.log();`. 86 | "dojo" : false, // Enable globals exposed by Dojo Toolkit. 87 | "mocha" : true, // Enable globals exposed by Mocha. 88 | "jquery" : false, // Enable globals exposed by jQuery JavaScript library. 89 | "mootools" : false, // Enable globals exposed by MooTools JavaScript framework. 90 | "node" : true, // Enable globals available when code is running inside of the NodeJS runtime environment. 91 | "nonstandard" : false, // Define non-standard but widely adopted globals such as escape and unescape. 92 | "prototypejs" : false, // Enable globals exposed by Prototype JavaScript framework. 93 | "rhino" : false, // Enable globals available when your code is running inside of the Rhino runtime environment. 94 | "wsh" : false, // Enable globals available when your code is running as a script for the Windows Script Host. 95 | 96 | // == JSLint Legacy =================================================== 97 | // 98 | // These options are legacy from JSLint. Aside from bug fixes they will 99 | // not be improved in any way and might be removed at any point. 100 | 101 | "nomen" : false, // Prohibit use of initial or trailing underbars in names. 102 | "onevar" : false, // Allow only one `var` statement per function. 103 | "passfail" : false, // Stop on first error. 104 | "white" : false, // Check against strict whitespace and indentation rules. 105 | 106 | // == Undocumented Options ============================================ 107 | // 108 | // While I've found these options in [example1][2] and [example2][3] 109 | // they are not described in the [JSHint Options documentation][4]. 110 | // 111 | // [4]: http://www.jshint.com/options/ 112 | 113 | "maxerr" : 100, // Maximum errors before stopping. 114 | "maxlen" : 180, // Maximum line length. 115 | "indent" : 4 // Specify indentation spacing 116 | } 117 | -------------------------------------------------------------------------------- /tests/command.spec.js: -------------------------------------------------------------------------------- 1 | var m = require('mochainon'); 2 | var os = require('os'); 3 | var fs = require('fs'); 4 | var path = require('path'); 5 | var command = require('../lib/command'); 6 | 7 | describe('Command', function() { 8 | 9 | describe('.getBinaryPath()', function() { 10 | 11 | var isAbsolute = function(x) { 12 | return path.resolve(x) === x; 13 | }; 14 | 15 | describe('given x86', function() { 16 | 17 | beforeEach(function() { 18 | this.osArchStub = m.sinon.stub(os, 'arch'); 19 | this.osArchStub.returns('x64'); 20 | }); 21 | 22 | afterEach(function() { 23 | this.osArchStub.restore(); 24 | }); 25 | 26 | it('should return the path to the x64 executable', function() { 27 | var binPath = command.getBinaryPath(); 28 | var expected = path.join(__dirname, '..', 'bin', 'elevate-x64.exe'); 29 | m.chai.expect(binPath).to.equal(expected); 30 | }); 31 | 32 | it('should return an absolute path', function() { 33 | var binPath = command.getBinaryPath(); 34 | m.chai.expect(isAbsolute(binPath)).to.be.true; 35 | }); 36 | 37 | it('should exist', function() { 38 | m.chai.expect(function() { 39 | fs.statSync(command.getBinaryPath()); 40 | }).to.not.throw(); 41 | }); 42 | 43 | }); 44 | 45 | describe('given ia32', function() { 46 | 47 | beforeEach(function() { 48 | this.osArchStub = m.sinon.stub(os, 'arch'); 49 | this.osArchStub.returns('ia32'); 50 | }); 51 | 52 | afterEach(function() { 53 | this.osArchStub.restore(); 54 | }); 55 | 56 | it('should return the path to the ia32 executable', function() { 57 | var binPath = command.getBinaryPath(); 58 | var expected = path.join(__dirname, '..', 'bin', 'elevate-ia32.exe'); 59 | m.chai.expect(binPath).to.equal(expected); 60 | }); 61 | 62 | it('should return an absolute path', function() { 63 | var binPath = command.getBinaryPath(); 64 | m.chai.expect(isAbsolute(binPath)).to.be.true; 65 | }); 66 | 67 | it('should exist', function() { 68 | m.chai.expect(function() { 69 | fs.statSync(command.getBinaryPath()); 70 | }).to.not.throw(); 71 | }); 72 | 73 | }); 74 | 75 | describe('given arm', function() { 76 | 77 | beforeEach(function() { 78 | this.osArchStub = m.sinon.stub(os, 'arch'); 79 | this.osArchStub.returns('arm'); 80 | }); 81 | 82 | afterEach(function() { 83 | this.osArchStub.restore(); 84 | }); 85 | 86 | it('should throw a non supported error', function() { 87 | m.chai.expect(function() { 88 | command.getBinaryPath(); 89 | }).to.throw('Unsupported arch: arm'); 90 | }); 91 | 92 | }); 93 | 94 | }); 95 | 96 | describe('.build()', function() { 97 | 98 | describe('given no command', function() { 99 | 100 | it('should throw an error', function() { 101 | m.chai.expect(function() { 102 | command.build(null); 103 | }).to.throw('Missing command'); 104 | }); 105 | 106 | }); 107 | 108 | describe('given no options', function() { 109 | 110 | it('should return the correct command', function() { 111 | var result = command.build([ 'foo' ]); 112 | m.chai.expect(result).to.deep.equal([ 'foo' ]); 113 | }); 114 | 115 | }); 116 | 117 | describe('given terminating = true', function() { 118 | 119 | it('should return the correct command', function() { 120 | var result = command.build([ 'foo' ], { 121 | terminating: true 122 | }); 123 | 124 | m.chai.expect(result).to.deep.equal([ '-c', 'foo' ]); 125 | }); 126 | 127 | }); 128 | 129 | describe('given terminating = true and a multiple word command', function() { 130 | 131 | it('should return the correct command', function() { 132 | var result = command.build([ 'foo', 'bar' ], { 133 | terminating: true 134 | }); 135 | 136 | m.chai.expect(result).to.deep.equal([ '-c', 'foo', 'bar' ]); 137 | }); 138 | 139 | }); 140 | 141 | describe('given persistent = true', function() { 142 | 143 | it('should return the correct command', function() { 144 | var result = command.build([ 'foo' ], { 145 | persistent: true 146 | }); 147 | 148 | m.chai.expect(result).to.deep.equal([ '-k', 'foo' ]); 149 | }); 150 | 151 | }); 152 | 153 | describe('given persistent = true and terminating = true', function() { 154 | 155 | it('should throw an error', function() { 156 | m.chai.expect(function() { 157 | command.build([ 'foo' ], { 158 | terminating: true, 159 | persistent: true 160 | }); 161 | }).to.throw('Can\'t have a both persistent and terminating command processor'); 162 | }); 163 | 164 | }); 165 | 166 | describe('given terminating = true and doNotPushdCurrentDirectory = true', function() { 167 | 168 | it('should return the correct command', function() { 169 | var result = command.build([ 'foo' ], { 170 | terminating: true, 171 | doNotPushdCurrentDirectory: true 172 | }); 173 | 174 | m.chai.expect(result).to.deep.equal([ '-c', '-n', 'foo' ]); 175 | }); 176 | 177 | }); 178 | 179 | describe('given persistent = true and doNotPushdCurrentDirectory = true', function() { 180 | 181 | it('should return the correct command', function() { 182 | var result = command.build([ 'foo' ], { 183 | persistent: true, 184 | doNotPushdCurrentDirectory: true 185 | }); 186 | 187 | m.chai.expect(result).to.deep.equal([ '-k', '-n', 'foo' ]); 188 | }); 189 | 190 | }); 191 | 192 | describe('given only doNotPushdCurrentDirectory = true', function() { 193 | 194 | it('should throw an error', function() { 195 | m.chai.expect(function() { 196 | command.build([ 'foo' ], { 197 | doNotPushdCurrentDirectory: true 198 | }); 199 | }).to.throw('doNotPushdCurrentDirectory requires the terminating or persistent option'); 200 | }); 201 | 202 | }); 203 | 204 | describe('given terminating = true and unicode = true', function() { 205 | 206 | it('should return the correct command', function() { 207 | var result = command.build([ 'foo' ], { 208 | terminating: true, 209 | unicode: true 210 | }); 211 | 212 | m.chai.expect(result).to.deep.equal([ '-c', '-u', 'foo' ]); 213 | }); 214 | 215 | }); 216 | 217 | describe('given persistent = true and unicode = true', function() { 218 | 219 | it('should return the correct command', function() { 220 | var result = command.build([ 'foo' ], { 221 | persistent: true, 222 | unicode: true 223 | }); 224 | 225 | m.chai.expect(result).to.deep.equal([ '-k', '-u', 'foo' ]); 226 | }); 227 | 228 | }); 229 | 230 | describe('given only unicode = true', function() { 231 | 232 | it('should throw an error', function() { 233 | m.chai.expect(function() { 234 | command.build([ 'foo' ], { 235 | unicode: true 236 | }); 237 | }).to.throw('unicode requires the terminating or persistent option'); 238 | }); 239 | 240 | }); 241 | 242 | describe('given terminating = true and hidden = true', function() { 243 | 244 | it('should return the correct command', function() { 245 | var result = command.build([ 'foo' ], { 246 | terminating: true, 247 | hidden: true 248 | }); 249 | 250 | m.chai.expect(result).to.deep.equal([ '-c', '-h', 'foo' ]); 251 | }); 252 | 253 | }); 254 | 255 | describe('given persistent = true and hidden = true', function() { 256 | 257 | it('should return the correct command', function() { 258 | var result = command.build([ 'foo' ], { 259 | persistent: true, 260 | hidden: true 261 | }); 262 | 263 | m.chai.expect(result).to.deep.equal([ '-k', '-h', 'foo' ]); 264 | }); 265 | 266 | }); 267 | 268 | describe('given only hidden = true', function() { 269 | 270 | it('should throw an error', function() { 271 | m.chai.expect(function() { 272 | command.build([ 'foo' ], { 273 | hidden: true 274 | }); 275 | }).to.throw('hidden requires the terminating or persistent option'); 276 | }); 277 | 278 | }); 279 | 280 | describe('given waitForTermination = true', function() { 281 | 282 | it('should return the correct command', function() { 283 | var result = command.build([ 'foo' ], { 284 | waitForTermination: true 285 | }); 286 | 287 | m.chai.expect(result).to.deep.equal([ '-w', 'foo' ]); 288 | }); 289 | 290 | }); 291 | 292 | }); 293 | 294 | }); 295 | --------------------------------------------------------------------------------