├── LICENSE ├── README.md ├── index.js └── package.json /LICENSE: -------------------------------------------------------------------------------- 1 | ISC License 2 | 3 | Copyright (c) 2018, Andrea Giammarchi, @WebReflection 4 | 5 | Permission to use, copy, modify, and/or distribute this software for any 6 | purpose with or without fee is hereby granted, provided that the above 7 | copyright notice and this permission notice appear in all copies. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH 10 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY 11 | AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, 12 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM 13 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE 14 | OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR 15 | PERFORMANCE OF THIS SOFTWARE. 16 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # npm-dollar 2 | 3 | A `package.json` scripts helper. 4 | 5 | ### New in V2 6 | 7 | `npm-dollar` is now super greedy in exiting the execution as soon as something goes wrong. 8 | 9 | ### wut 10 | 11 | It simplifies organization of `package.json` scripts using `bash`, instead of `sh`, and passing escaped arguments all along. 12 | 13 | Following a configuration example: 14 | 15 | ```js 16 | // inside the package.json file 17 | { 18 | "devDependencies": { 19 | "npm-dollar": "latest" 20 | }, 21 | "scripts": { 22 | "$": "npm-dollar" 23 | }, 24 | "$": { 25 | "cat": { 26 | "some": "cat $1" 27 | }, 28 | "lint": { 29 | "js": [ 30 | // single line bash comments are allowed too 31 | "# !production is needed to avoid issue in CI", 32 | "!production eslint index.js" 33 | ] 34 | }, 35 | "bash": { 36 | "ls": "ls" 37 | }, 38 | "complex": [ 39 | // each entry is joined as && 40 | "export TEST=123", 41 | "echo $TEST", 42 | // arrays per line are joined via space 43 | [ 44 | "ls $TEST;", 45 | "cat $TEST" 46 | ] 47 | ] 48 | } 49 | } 50 | ``` 51 | 52 | This is how you'd use those scripts 53 | 54 | ```sh 55 | # regular execution 56 | npm run $ cat.some file.js 57 | 58 | # or passing along all arguments 59 | npm run $ -- bash.ls -la 60 | 61 | # or skipping production 62 | npm --production run $ lint 63 | ``` 64 | 65 | When either `--production`, `--prod`, or `--only=production` and `--only=prod` _npm_ flags are used, any command that start with either `!prod` or `!production` (read as _not production_) would simply be ignored. 66 | 67 | The End. 68 | 69 | ### Windows users 70 | 71 | As long as there is a `bash` environment you should be good to go (WLS, Git for Windows, others). 72 | 73 | If not, consider [installing chocolatey](https://chocolatey.org/install) and then do `choco install git` to be able to use `bash` with, or without, the git shell. 74 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | var PACKAGE_NAME = 'npm-dollar'; 4 | var PACKAGE_JSON = 'package.json'; 5 | var RE_COMMENT = /^#/; 6 | var RE_PRODUCTION = /^\!prod(?:uction)?\b\s*/; 7 | var IS_PRODUCTION = /^prod(?:uction)?$/i.test(process.env.npm_config_only) || 8 | !!process.env.npm_config_production; 9 | 10 | var path = require('path'); 11 | var argv = process.argv.slice(2); 12 | var cwd = process.cwd(); 13 | 14 | if (argv.length) { 15 | var childProcess = require('child_process'); 16 | var how = { 17 | cwd: cwd, 18 | env: process.env, 19 | stdio: ['inherit', 'inherit', 'inherit'] 20 | }; 21 | childProcess.exec( 22 | process.platform === 'win32' ? 23 | 'where bash' : 'which bash', 24 | how, 25 | function (err, bash, stderr) { 26 | if (err) { 27 | childProcess.exec( 28 | 'npm config get script-shell', 29 | function (err, bash, stderr) { 30 | if (err || !bash) { 31 | console.error(stderr || 'Unable to find an executable `bash`'); 32 | process.exit(1); 33 | } else 34 | run(childProcess.spawn, bash.trim(), how); 35 | } 36 | ); 37 | } else 38 | run(childProcess.spawn, bash.trim(), how); 39 | } 40 | ); 41 | } else { 42 | var package = require(path.join( 43 | require.resolve(PACKAGE_NAME), 44 | '..', 45 | PACKAGE_JSON 46 | )); 47 | console.log('\x1b[1m' + package.name + '\x1b[0m ' + package.version); 48 | console.log(package.homepage); 49 | console.log(''); 50 | console.log(' npm run $ cat.some file.js'); 51 | console.log(' npm run $ cat file.js'); 52 | console.log(' npm run $ -- bash.ls -la'); 53 | console.log(''); 54 | console.log(JSON.stringify({ 55 | scripts: { 56 | '$': package.name 57 | }, 58 | '$': { 59 | cat: { 60 | some: 'cat $1', 61 | list: 'ls $1' 62 | }, 63 | bash: { 64 | ls: 'ls' 65 | } 66 | } 67 | }, null, ' ').replace(/^/gm, ' ')); 68 | console.log(''); 69 | } 70 | 71 | function dropProduction(command) { 72 | return command.replace(RE_PRODUCTION, ''); 73 | } 74 | 75 | function error(name) { 76 | console.error('\x1B[31mERR!\x1B[0m unable to execute \x1B[1m' + name + '\x1B[0m'); 77 | process.exit(1); 78 | } 79 | 80 | function commandFilter(command) { 81 | return notComment(command) && notProduction(command); 82 | } 83 | 84 | function notComment(command) { 85 | return !RE_COMMENT.test(command); 86 | } 87 | 88 | function notProduction(command) { 89 | return IS_PRODUCTION ? !RE_PRODUCTION.test(command) : true; 90 | } 91 | 92 | function run(spawn, bash, how) { 93 | var package = require(path.join(cwd, PACKAGE_JSON)); 94 | var exe = argv[0].split('.').reduce( 95 | function (o, k) { 96 | if (o[k] == null) 97 | error(argv[0]); 98 | return o[k]; 99 | }, 100 | package.$ || package[PACKAGE_NAME] || package.scripts 101 | ); 102 | // direct execution as in {"$": {"ls": "ls"}} 103 | if (typeof exe === 'string' && /^\S+$/.test(exe)) { 104 | spawn(exe, argv.slice(1), how).on('exit', exitOnError); 105 | } 106 | // indirect / normalized execution through bash -c 107 | else if (exe) { 108 | var params = []; 109 | [].concat(exe).forEach(function add(cmd) { 110 | if (typeof cmd === 'string') 111 | params.push(cmd); 112 | // Arrays inside arrays are joined inline 113 | else if (Array.isArray(cmd)) 114 | params.push(cmd.join(' ')); 115 | else 116 | for (var key in cmd) 117 | [].concat(cmd[key]).forEach(add); 118 | }); 119 | spawn( 120 | bash, 121 | ['-c'].concat( 122 | params 123 | .filter(commandFilter) 124 | .map(dropProduction) 125 | .join(' && ') 126 | .replace( 127 | /(^|;|\s)\$ /g, 128 | ('$1npm run $ ') 129 | ), 130 | bash, 131 | argv.slice(1) 132 | ), 133 | how 134 | ).on('exit', exitOnError); 135 | } 136 | // nothing to do, show there's an error 137 | else 138 | error(argv[0]); 139 | } 140 | 141 | function exitOnError(code) { 142 | if (code) 143 | process.exit(1); 144 | } 145 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-dollar", 3 | "version": "2.2.1", 4 | "description": "A package.json scripts helper.", 5 | "bin": { 6 | "npm-dollar": "index.js" 7 | }, 8 | "$": { 9 | "fail": "lint ./index.js", 10 | "greetings": "!prod echo -e 'Hello \\033[1m$\\033[0m !!!'", 11 | "wrong": "$ greetings.what.ever" 12 | }, 13 | "scripts": { 14 | "$": "node index.js", 15 | "test": "npm run $ greetings" 16 | }, 17 | "keywords": [ 18 | "package", 19 | "helper", 20 | "scripts", 21 | "test", 22 | "bash" 23 | ], 24 | "author": "Andrea Giammarchi", 25 | "license": "ISC", 26 | "main": "index.js", 27 | "repository": { 28 | "type": "git", 29 | "url": "git+https://github.com/WebReflection/node-dollar.git" 30 | }, 31 | "bugs": { 32 | "url": "https://github.com/WebReflection/node-dollar/issues" 33 | }, 34 | "homepage": "https://github.com/WebReflection/node-dollar#readme" 35 | } 36 | --------------------------------------------------------------------------------