├── .gitignore ├── .travis.yml ├── .istanbul.yml ├── .editorconfig ├── .eslintrc ├── LICENSE ├── package.json ├── README.md ├── globstar.js └── test.js /.gitignore: -------------------------------------------------------------------------------- 1 | logs 2 | *.log 3 | lib-cov 4 | coverage 5 | node_modules 6 | 7 | globstar-*.tgz 8 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | script: npm run travis 3 | language: node_js 4 | node_js: 5 | - 'iojs' 6 | - '0.12' 7 | - '0.11' 8 | - '0.10' 9 | -------------------------------------------------------------------------------- /.istanbul.yml: -------------------------------------------------------------------------------- 1 | instrumentation: 2 | include-all-sources: true 3 | excludes: ['test.js'] 4 | reporting: 5 | print: detail 6 | reports: 7 | - lcovonly 8 | - html 9 | check: 10 | global: 11 | statements: 100 12 | lines: 100 13 | branches: 100 14 | functions: 100 15 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | end_of_line = lf 6 | charset = utf-8 7 | insert_final_newline = true 8 | max_line_length = 80 9 | trim_trailing_whitespace = true 10 | indent_style = space 11 | indent_size = 2 12 | 13 | [**.{json, md}] 14 | max_line_length = 9007199254740991 # Number.MAX_SAFE_INTEGER 15 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "node": true 4 | }, 5 | "rules": { 6 | "semi": 2, 7 | "quotes": [2, "single"], 8 | "comma-dangle": 0, 9 | "no-process-exit": 0, 10 | "func-names": 2, 11 | "space-before-blocks": 2, 12 | "brace-style": [2, "1tbs"], 13 | "space-after-keywords": 2, 14 | "space-before-function-parentheses": [2, "never"], 15 | "space-after-function-name": [2, "never"], 16 | "no-spaced-func": 2 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Michael Mayer 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "globstar", 3 | "version": "1.0.0", 4 | "description": "Run programs with glob/globstar support, especially on Windows within npm scripts.", 5 | "keywords": [ 6 | "run", 7 | "execute", 8 | "glob", 9 | "globbing", 10 | "windows", 11 | "platform independent", 12 | "windows", 13 | "npm", 14 | "scripts", 15 | "cli" 16 | ], 17 | "bin": { 18 | "globstar": "globstar.js" 19 | }, 20 | "license": "MIT", 21 | "homepage": "https://github.com/schnittstabil/globstar", 22 | "author": { 23 | "name": "Michael Mayer", 24 | "email": "michael@schnittstabil.de" 25 | }, 26 | "repository": { 27 | "type": "git", 28 | "url": "https://github.com/schnittstabil/globstar.git" 29 | }, 30 | "bugs": { 31 | "url": "https://github.com/schnittstabil/globstar/issues" 32 | }, 33 | "config": { 34 | "ghooks": { 35 | "pre-commit": "npm run -s lint" 36 | } 37 | }, 38 | "scripts": { 39 | "test": "mocha", 40 | "travis": "npm run cover && istanbul check-coverage && npm run lint && npm run upload-coveralls", 41 | "cover": "npm run clean-cover && istanbul cover node_modules/mocha/bin/_mocha -- -R spec", 42 | "upload-coveralls": "cat coverage/lcov.info | coveralls", 43 | "lint": "npm run -s lint-js && npm run -s lint-ec", 44 | "lint-js": "node globstar.js -n --ignore \"coverage/**\" -- eslint \"**/*.js\"", 45 | "lint-ec": "node globstar.js -n --ignore \"coverage/**\" -- editorconfig-tools check \"**/*.js\"", 46 | "clean-cover": "rimraf coverage" 47 | }, 48 | "dependencies": { 49 | "glob": "^5.0.2", 50 | "npmlog": "^1.2.0", 51 | "object-assign": "^2.0.0", 52 | "onetime": "^1.0.0", 53 | "yargs": "^3.5.4" 54 | }, 55 | "devDependencies": { 56 | "coveralls": "^2.11.2", 57 | "editorconfig-tools": "^0.1.1", 58 | "eslint": "^0.16.2", 59 | "ghooks": "^0.2.5", 60 | "istanbul": "^0.3.7", 61 | "mocha": "^2.2.1", 62 | "mocha-lcov-reporter": "0.0.2", 63 | "mock-spawn": "^0.2.4", 64 | "mockery": "^1.4.0", 65 | "rimraf": "^2.3.2" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | globstar [![Build Status](https://travis-ci.org/schnittstabil/globstar.svg?branch=master)](https://travis-ci.org/schnittstabil/globstar) [![Coverage Status](https://coveralls.io/repos/schnittstabil/globstar/badge.svg?branch=master)](https://coveralls.io/r/schnittstabil/globstar?branch=master) 2 | ======== 3 | 4 | Run programs with glob/globstar support, especially on Windows within npm scripts. 5 | 6 | Install 7 | ------- 8 | 9 | ```sh 10 | [sudo] npm install globstar --global 11 | ``` 12 | 13 | Usage 14 | ----- 15 | 16 | ```sh 17 | > globstar -- echo "**/globstar.js" 18 | node_modules/globstar/globstar.js 19 | ``` 20 | 21 | Please note the `--` and that globstar uses forward slashes. 22 | 23 | npm Scripts 24 | ----------- 25 | 26 | ```sh 27 | $ npm install globstar --save-dev 28 | 29 | // e.g. install some linter 30 | $ npm install eslint --save-dev 31 | $ npm install editorconfig-tools --save-dev 32 | ``` 33 | 34 | Please note that Windows needs double quotes: 35 | 36 | ```json 37 | "scripts": { 38 | "lint": "npm run -s lint-js && npm run -s lint-ec", 39 | "lint-js": "globstar --node -- eslint \"**/*.js\"", 40 | "lint-ec": "globstar --node -- editorconfig-tools check \"**/*.js\"" 41 | }, 42 | ``` 43 | 44 | Lint your `**/*.js` files: 45 | 46 | ```sh 47 | $ npm run lint 48 | ``` 49 | 50 | Options 51 | ------- 52 | 53 | ``` 54 | $ globstar --help 55 | Run programs with globstar support. 56 | 57 | Usage: globstar [OPTION]... -- COMMAND [ARG]... 58 | Note the -- between the globstar OPTIONS and the COMMAND and its arguments 59 | 60 | Options: 61 | --nodir glob patterns do not match directories, only files 62 | -i, --ignore add glob pattern to exclude from matches 63 | -n, --node same as `--ignore "node_modules/**"` 64 | -v, --verbose explain what is being done 65 | --version display version information 66 | --help Show help 67 | 68 | Report globstar bugs to 69 | globstar home page: 70 | ``` 71 | 72 | License 73 | ------- 74 | 75 | Copyright © 2015 Michael Mayer 76 | 77 | Licensed under the [MIT license](LICENSE). 78 | -------------------------------------------------------------------------------- /globstar.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 'use strict'; 3 | var path = require('path'); 4 | var EOL = require('os').EOL; 5 | var format = require('util').format; 6 | var spawn = require('child_process').spawn; 7 | var npmlog = require('npmlog'); 8 | var glob = require('glob').sync; 9 | var onetime = require('onetime'); 10 | var objectAssign = require('object-assign'); 11 | var packageJson = require('./package.json'); 12 | var yargs = require('yargs') 13 | .usage([ 14 | 'Run programs with globstar support.', 15 | '', 16 | 'Usage: $0 [OPTION]... -- COMMAND [ARG]... ', 17 | 'Note the -- between the $0 OPTIONS and the COMMAND and its arguments', 18 | ].join(EOL)) 19 | .demand(1, 'COMMAND required') 20 | // nodir 21 | .boolean('nodir') 22 | .describe('nodir', 'glob patterns do not match directories, only files') 23 | // ignore 24 | .array('i') 25 | .alias('i', 'ignore') 26 | .describe('i', 'add glob pattern to exclude from matches') 27 | // node 28 | .boolean('n') 29 | .alias('n', 'node') 30 | .describe('n', 'same as `--ignore "node_modules/**"`') 31 | // verbose 32 | .count('v') 33 | .alias('v', 'verbose') 34 | .describe('v', 'explain what is being done') 35 | // version 36 | .version(packageJson.version, 'version', 'display version information') 37 | // help 38 | .help('help') 39 | // epilog 40 | .epilog([ 41 | 'Report $0 bugs to <' + packageJson.bugs.url + '>', 42 | '$0 home page: <' + packageJson.homepage + '>', 43 | ].join(EOL)); 44 | 45 | function parseArgv(argv) { 46 | var err = null; 47 | var args = yargs.fail(function yargsFail(message) { 48 | err = new Error(message); 49 | err.name = 'YargsError'; 50 | }).parse(argv); 51 | 52 | // set defaults 53 | args = objectAssign({ 54 | _: [], 55 | $0: path.relative(process.cwd(), __filename), 56 | verbose: 0, 57 | }, args); 58 | 59 | // translate args 60 | args.verbose = 2 - Math.max(2 - args.verbose, 0); // 0 <= verbose <= 2 61 | npmlog.level = ['info', 'verbose', 'silly'][args.verbose]; 62 | 63 | if (args.node) { 64 | args.ignore = args.ignore || []; 65 | args.ignore.push('node_modules/**'); 66 | } 67 | 68 | // setup result 69 | var result = err || {}; 70 | result.logLevel = ['info', 'verbose', 'silly'][args.verbose]; 71 | result.cmd = args._[0]; 72 | result.args = args._.slice(1); 73 | result.$0 = args.$0; 74 | result.globOpts = args; 75 | 76 | Object.keys(result.globOpts).filter(function nonGlobFlag(opt) { 77 | switch (opt) { 78 | case '$0': 79 | case 'verbose': 80 | case 'node': 81 | return true; 82 | default: 83 | return opt.length === 1; 84 | } 85 | }).forEach(function deleteFlag(flag) { 86 | delete result.globOpts[flag]; 87 | }); 88 | 89 | return result; 90 | } 91 | 92 | function globArgs(args, globOpts) { 93 | globOpts.nonull = true; // override --no-null etc. 94 | 95 | npmlog.log('silly', 'globstar', {opts: globOpts}); 96 | 97 | return args.map(function globArg(arg) { 98 | return glob(arg, globOpts); 99 | }).reduce(function flatten(previous, current) { 100 | return previous.concat(current); 101 | }, []); 102 | } 103 | 104 | function spawnCommand(cmd, args, cb) { 105 | cb = onetime(cb); 106 | 107 | var opts = { 108 | stdio: 'inherit', 109 | }; 110 | 111 | /* istanbul ignore next: platform specific */ 112 | if (process.platform === 'win32') { 113 | args = ['/c', '"' + cmd + '"'].concat(args); 114 | cmd = 'cmd'; 115 | opts.windowsVerbatimArguments = true; 116 | } 117 | 118 | npmlog.log('verbose', 'globstar', 'Running: `' + cmd + '` ' + format(args)); 119 | 120 | var child = spawn(cmd, args, opts); 121 | child.on('error', cb); 122 | child.on('close', function onClose(status) { 123 | if (status) { 124 | var err = new Error('Exit status ' + status); 125 | err.name = 'SpawnError'; 126 | err.status = status; 127 | cb(err); 128 | } else { 129 | cb(); 130 | } 131 | }); 132 | } 133 | 134 | function main(argv, exit) { 135 | 136 | function errorHandler(err) { 137 | if (err) { 138 | if (err.name !== 'SpawnError') { 139 | if (npmlog.level === 'silly') { 140 | npmlog.log('error', 'globstar', err); 141 | } else { 142 | npmlog.log('error', 'globstar', err.message); 143 | } 144 | 145 | npmlog.log('info', 'globstar', 'Try \'' + argv.$0 + ' --help\' for ' + 146 | 'more information'); 147 | 148 | npmlog.log('verbose', 'globstar@' + packageJson.version, __filename); 149 | } 150 | 151 | exit(err.status || 1); 152 | return; 153 | } 154 | exit(0); 155 | } 156 | 157 | argv = parseArgv(argv); 158 | if (argv instanceof Error) { 159 | errorHandler(argv); 160 | return; 161 | } 162 | 163 | spawnCommand(argv.cmd, globArgs(argv.args, argv.globOpts), errorHandler); 164 | } 165 | 166 | module.exports = main; 167 | 168 | /* istanbul ignore if: coverd by test.js (fs.spawn) */ 169 | if (require.main === module) { 170 | main(process.argv.slice(2), process.exit.bind(process)); 171 | } 172 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | /* eslint-env mocha */ 2 | /* eslint func-names: 0 */ 3 | 'use strict'; 4 | var assert = require('assert'); 5 | var path = require('path'); 6 | var GLOBSTAR = path.join(__dirname, 'globstar.js'); 7 | 8 | function ENOENT(cmd) { 9 | var err = new Error('spawn ' + cmd + ' ENOENT'); 10 | err.code = 'ENOENT'; 11 | err.errno = 'ENOENT'; 12 | err.syscall = 'spawn ' + cmd; 13 | err.path = cmd; 14 | return err; 15 | } 16 | 17 | describe('globstar', function() { 18 | 19 | describe('main', function() { 20 | var mockery = require('mockery'); 21 | var mockSpawn = require('mock-spawn'); 22 | var spawn; 23 | var npmlog; 24 | var logs; 25 | var globstar; 26 | 27 | beforeEach(function() { 28 | logs = []; 29 | npmlog = { 30 | log: function() { 31 | logs.push(arguments); 32 | }, 33 | }; 34 | spawn = mockSpawn(true); 35 | mockery.enable({ 36 | useCleanCache: true, 37 | warnOnReplace: false, 38 | warnOnUnregistered: false, 39 | }); 40 | mockery.registerMock('npmlog', npmlog); 41 | mockery.registerMock('child_process', { spawn: spawn }); 42 | mockery.registerAllowable(GLOBSTAR, true); 43 | globstar = require(GLOBSTAR); 44 | }); 45 | 46 | afterEach(function() { 47 | mockery.deregisterAll(); 48 | mockery.resetCache(); 49 | mockery.disable(); 50 | }); 51 | 52 | it('should throw an Error on missing COMMAND', function(done) { 53 | globstar([], function(status) { 54 | var found = false; 55 | logs.forEach(function(entry) { 56 | var level = entry[0]; 57 | var msg = entry[2]; 58 | if (/COMMAND/i.test(msg)) { 59 | assert.strictEqual(level, 'error'); 60 | found = true; 61 | } 62 | }); 63 | assert.ok(found); 64 | assert.ok(status); 65 | done(); 66 | }); 67 | }); 68 | 69 | it('should throw an Error on failing COMMAND', function(done) { 70 | mockery.deregisterMock('child_process'); 71 | mockery.resetCache(); 72 | require(GLOBSTAR)(['--', 'false'], function(status) { 73 | assert.strictEqual(status, 1); 74 | done(); 75 | }); 76 | }); 77 | 78 | it('should throw an Error on unknown COMMAND', function(done) { 79 | spawn.setDefault(function(cb) { 80 | assert.strictEqual(this.command, 'NOENTRY_COMMAND'); 81 | this.emit('error', new ENOENT('NOENTRY_COMMAND')); 82 | this.emit('close', 1); // see gotwarlost/mock-spawn#4 83 | cb(1); 84 | }); 85 | 86 | globstar(['NOENTRY_COMMAND'], function(status) { 87 | var found = false; 88 | logs.forEach(function(entry) { 89 | var level = entry[0]; 90 | var msg = entry[2]; 91 | if (level === 'error' && /NOENTRY_COMMAND/i.test(msg)) { 92 | found = true; 93 | } 94 | }); 95 | assert.ok(found); 96 | assert.ok(status); 97 | done(); 98 | }); 99 | }); 100 | 101 | it('should respect --ignore', function(done) { 102 | spawn.setDefault(function(cb) { 103 | assert.strictEqual(this.command, 'echo'); 104 | assert.deepEqual(this.args, ['globstar.js']); 105 | this.stdout.write(this.args.join(' ')); 106 | this.emit('close', 0); // see gotwarlost/mock-spawn#4 107 | cb(0); 108 | }); 109 | globstar(['--ignore', 'test.js', '--', 'echo', '*.js'], function(status) { 110 | logs.forEach(function(entry) { 111 | var msg = entry[2]; 112 | if (/Running/i.test(msg)) { 113 | assert.ok(/echo.*globstar.js/.test(msg), msg + ' does not ' + 114 | 'include "echo .* globstar.js"'); 115 | } 116 | }); 117 | assert.strictEqual(status, 0); 118 | done(); 119 | }); 120 | }); 121 | 122 | it('should respect --node', function(done) { 123 | spawn.setDefault(function(cb) { 124 | assert.strictEqual(this.command, 'echo'); 125 | assert.deepEqual(this.args, ['*/*/package.json']); 126 | this.stdout.write(this.args.join(' ')); 127 | this.emit('close', 0); // see gotwarlost/mock-spawn#4 128 | cb(0); 129 | }); 130 | globstar(['--node', '--', 'echo', '*/*/package.json'], function(status) { 131 | assert.strictEqual(status, 0); 132 | done(); 133 | }); 134 | }); 135 | 136 | it('should respect -v', function(done) { 137 | var noEntryError = new ENOENT('NOENTRY_COMMAND'); 138 | spawn.setDefault(function(cb) { 139 | this.emit('error', noEntryError); 140 | this.emit('close', 1); // see gotwarlost/mock-spawn#4 141 | cb(1); 142 | }); 143 | 144 | globstar(['-vv', '--', 'NOENTRY_COMMAND'], function(status) { 145 | var found = false; 146 | logs.forEach(function(entry) { 147 | var level = entry[0]; 148 | var msg = entry[2]; 149 | if (level === 'error' && /NOENTRY_COMMAND/i.test(msg)) { 150 | found = true; 151 | } 152 | }); 153 | assert.ok(found); 154 | assert.ok(status); 155 | done(); 156 | }); 157 | }); 158 | }); 159 | 160 | describe('cli', function() { 161 | var spawn = require('child_process').spawn; 162 | this.timeout(5000); 163 | 164 | function listen(cmd, args, onClose) { 165 | var capture = { 166 | stderr: '', 167 | stdout: '', 168 | }; 169 | var sut = spawn(cmd, args); 170 | 171 | sut.on('close', onClose.bind(capture)); 172 | 173 | [ 174 | 'stderr', 175 | 'stdout', 176 | ].forEach(function(stream) { 177 | sut[stream].setEncoding('utf8'); 178 | sut[stream].on('data', function(data) { 179 | capture[stream] += data; 180 | }); 181 | }); 182 | } 183 | 184 | it('should output a message and return a non-zero exit status on errors', 185 | function(done) { 186 | listen('node', [GLOBSTAR], // missing COMMAND 187 | function onClose(status) { 188 | assert.notStrictEqual(this.stderr, ''); 189 | assert.ok(status !== 0, 'unexpected exit status: ' + status); 190 | done(); 191 | } 192 | ); 193 | } 194 | ); 195 | 196 | it('should echo foobar', function(done) { 197 | listen('node', [GLOBSTAR, '--', 'echo', 'foobar'], 198 | function onClose(status) { 199 | assert.strictEqual(this.stdout.trim(), 'foobar'); 200 | assert.strictEqual(status, 0); 201 | done(); 202 | } 203 | ); 204 | }); 205 | 206 | it('should glob READ*.md', function(done) { 207 | listen('node', [GLOBSTAR, '--', 'echo', 'READ*.md'], 208 | function onClose(status) { 209 | assert.strictEqual(this.stdout.trim(), 'README.md'); 210 | assert.strictEqual(status, 0); 211 | done(); 212 | } 213 | ); 214 | }); 215 | }); 216 | 217 | }); 218 | --------------------------------------------------------------------------------