• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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