1module.exports = runScript 2 3var lifecycle = require('./utils/lifecycle.js') 4var npm = require('./npm.js') 5var path = require('path') 6var readJson = require('read-package-json') 7var log = require('npmlog') 8var chain = require('slide').chain 9var usage = require('./utils/usage') 10var output = require('./utils/output.js') 11var didYouMean = require('./utils/did-you-mean') 12var isWindowsShell = require('./utils/is-windows-shell.js') 13 14runScript.usage = usage( 15 'run-script', 16 'npm run-script <command> [-- <args>...]' 17) 18 19runScript.completion = function (opts, cb) { 20 // see if there's already a package specified. 21 var argv = opts.conf.argv.remain 22 23 if (argv.length >= 4) return cb() 24 25 if (argv.length === 3) { 26 // either specified a script locally, in which case, done, 27 // or a package, in which case, complete against its scripts 28 var json = path.join(npm.localPrefix, 'package.json') 29 return readJson(json, function (er, d) { 30 if (er && er.code !== 'ENOENT' && er.code !== 'ENOTDIR') return cb(er) 31 if (er) d = {} 32 var scripts = Object.keys(d.scripts || {}) 33 console.error('local scripts', scripts) 34 if (scripts.indexOf(argv[2]) !== -1) return cb() 35 // ok, try to find out which package it was, then 36 var pref = npm.config.get('global') ? npm.config.get('prefix') 37 : npm.localPrefix 38 var pkgDir = path.resolve(pref, 'node_modules', argv[2], 'package.json') 39 readJson(pkgDir, function (er, d) { 40 if (er && er.code !== 'ENOENT' && er.code !== 'ENOTDIR') return cb(er) 41 if (er) d = {} 42 var scripts = Object.keys(d.scripts || {}) 43 return cb(null, scripts) 44 }) 45 }) 46 } 47 48 readJson(path.join(npm.localPrefix, 'package.json'), function (er, d) { 49 if (er && er.code !== 'ENOENT' && er.code !== 'ENOTDIR') return cb(er) 50 d = d || {} 51 cb(null, Object.keys(d.scripts || {})) 52 }) 53} 54 55function runScript (args, cb) { 56 if (!args.length) return list(cb) 57 58 var pkgdir = npm.localPrefix 59 var cmd = args.shift() 60 61 readJson(path.resolve(pkgdir, 'package.json'), function (er, d) { 62 if (er) return cb(er) 63 run(d, pkgdir, cmd, args, cb) 64 }) 65} 66 67function list (cb) { 68 var json = path.join(npm.localPrefix, 'package.json') 69 var cmdList = [ 70 'publish', 71 'install', 72 'uninstall', 73 'test', 74 'stop', 75 'start', 76 'restart', 77 'version' 78 ].reduce(function (l, p) { 79 return l.concat(['pre' + p, p, 'post' + p]) 80 }, []) 81 return readJson(json, function (er, d) { 82 if (er && er.code !== 'ENOENT' && er.code !== 'ENOTDIR') return cb(er) 83 if (er) d = {} 84 var allScripts = Object.keys(d.scripts || {}) 85 var scripts = [] 86 var runScripts = [] 87 allScripts.forEach(function (script) { 88 if (cmdList.indexOf(script) !== -1) scripts.push(script) 89 else runScripts.push(script) 90 }) 91 92 if (log.level === 'silent') { 93 return cb(null, allScripts) 94 } 95 96 if (npm.config.get('json')) { 97 output(JSON.stringify(d.scripts || {}, null, 2)) 98 return cb(null, allScripts) 99 } 100 101 if (npm.config.get('parseable')) { 102 allScripts.forEach(function (script) { 103 output(script + ':' + d.scripts[script]) 104 }) 105 return cb(null, allScripts) 106 } 107 108 var s = '\n ' 109 var prefix = ' ' 110 if (scripts.length) { 111 output('Lifecycle scripts included in %s:', d.name) 112 } 113 scripts.forEach(function (script) { 114 output(prefix + script + s + d.scripts[script]) 115 }) 116 if (!scripts.length && runScripts.length) { 117 output('Scripts available in %s via `npm run-script`:', d.name) 118 } else if (runScripts.length) { 119 output('\navailable via `npm run-script`:') 120 } 121 runScripts.forEach(function (script) { 122 output(prefix + script + s + d.scripts[script]) 123 }) 124 return cb(null, allScripts) 125 }) 126} 127 128function run (pkg, wd, cmd, args, cb) { 129 if (!pkg.scripts) pkg.scripts = {} 130 131 var cmds 132 if (cmd === 'restart' && !pkg.scripts.restart) { 133 cmds = [ 134 'prestop', 'stop', 'poststop', 135 'restart', 136 'prestart', 'start', 'poststart' 137 ] 138 } else { 139 if (pkg.scripts[cmd] == null) { 140 if (cmd === 'test') { 141 pkg.scripts.test = 'echo \'Error: no test specified\'' 142 } else if (cmd === 'env') { 143 if (isWindowsShell) { 144 log.verbose('run-script using default platform env: SET (Windows)') 145 pkg.scripts[cmd] = 'SET' 146 } else { 147 log.verbose('run-script using default platform env: env (Unix)') 148 pkg.scripts[cmd] = 'env' 149 } 150 } else if (npm.config.get('if-present')) { 151 return cb(null) 152 } else { 153 let suggestions = didYouMean(cmd, Object.keys(pkg.scripts)) 154 suggestions = suggestions ? '\n' + suggestions : '' 155 return cb(new Error('missing script: ' + cmd + suggestions)) 156 } 157 } 158 cmds = [cmd] 159 } 160 161 if (!cmd.match(/^(pre|post)/)) { 162 cmds = ['pre' + cmd].concat(cmds).concat('post' + cmd) 163 } 164 165 log.verbose('run-script', cmds) 166 chain(cmds.map(function (c) { 167 // pass cli arguments after -- to script. 168 if (pkg.scripts[c] && c === cmd) { 169 pkg.scripts[c] = pkg.scripts[c] + joinArgs(args) 170 } 171 172 // when running scripts explicitly, assume that they're trusted. 173 return [lifecycle, pkg, c, wd, { unsafePerm: true }] 174 }), cb) 175} 176 177// join arguments after '--' and pass them to script, 178// handle special characters such as ', ", ' '. 179function joinArgs (args) { 180 var joinedArgs = '' 181 args.forEach(function (arg) { 182 joinedArgs += ' "' + arg.replace(/"/g, '\\"') + '"' 183 }) 184 return joinedArgs 185} 186