Skip to content

Commit

Permalink
Use @npmcli/run-script for npm run
Browse files Browse the repository at this point in the history
  • Loading branch information
isaacs committed May 8, 2020
1 parent 99848a7 commit ac25561
Showing 1 changed file with 109 additions and 128 deletions.
237 changes: 109 additions & 128 deletions lib/run-script.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,21 @@
module.exports = runScript

var lifecycle = require('./utils/lifecycle.js')
var npm = require('./npm.js')
var path = require('path')
var readJson = require('read-package-json')
var log = require('npmlog')
var chain = require('slide').chain
var usage = require('./utils/usage')
var output = require('./utils/output.js')
var didYouMean = require('./utils/did-you-mean')
var isWindowsShell = require('./utils/is-windows-shell.js')

runScript.usage = usage(
module.exports = runScriptCmd

const run = require('@npmcli/run-script')
const npm = require('./npm.js')
const readJson = require('read-package-json-fast')
const { resolve, join } = require('path')
const output = require('./utils/output.js')
const log = require('npmlog')
const usage = require('./utils/usage')
const didYouMean = require('./utils/did-you-mean')
const isWindowsShell = require('./utils/is-windows-shell.js')

runScriptCmd.usage = usage(
'run-script',
'npm run-script <command> [-- <args>...]'
)

runScript.completion = function (opts, cb) {
runScriptCmd.completion = function (opts, cb) {
// see if there's already a package specified.
var argv = opts.conf.argv.remain

Expand All @@ -25,17 +24,16 @@ runScript.completion = function (opts, cb) {
if (argv.length === 3) {
// either specified a script locally, in which case, done,
// or a package, in which case, complete against its scripts
var json = path.join(npm.localPrefix, 'package.json')
var json = join(npm.localPrefix, 'package.json')
return readJson(json, function (er, d) {
if (er && er.code !== 'ENOENT' && er.code !== 'ENOTDIR') return cb(er)
if (er) d = {}
var scripts = Object.keys(d.scripts || {})
console.error('local scripts', scripts)
if (scripts.indexOf(argv[2]) !== -1) return cb()
// ok, try to find out which package it was, then
var pref = npm.config.get('global') ? npm.config.get('prefix')
: npm.localPrefix
var pkgDir = path.resolve(pref, 'node_modules', argv[2], 'package.json')
var pkgDir = resolve(pref, 'node_modules', argv[2], 'package.json')
readJson(pkgDir, function (er, d) {
if (er && er.code !== 'ENOENT' && er.code !== 'ENOTDIR') return cb(er)
if (er) d = {}
Expand All @@ -45,28 +43,71 @@ runScript.completion = function (opts, cb) {
})
}

readJson(path.join(npm.localPrefix, 'package.json'), function (er, d) {
readJson(join(npm.localPrefix, 'package.json'), function (er, d) {
if (er && er.code !== 'ENOENT' && er.code !== 'ENOTDIR') return cb(er)
d = d || {}
cb(null, Object.keys(d.scripts || {}))
})
}

function runScript (args, cb) {
if (!args.length) return list(cb)
function runScriptCmd (args, cb) {
const fn = args.length ? runScript : list
fn(args).then(() => cb()).catch(cb)
}

var pkgdir = npm.localPrefix
var cmd = args.shift()
const runScript = async (args) => {
const path = npm.localPrefix
const event = args.shift()
const { scriptShell } = npm.flatOptions

readJson(path.resolve(pkgdir, 'package.json'), function (er, d) {
if (er) return cb(er)
run(d, pkgdir, cmd, args, cb)
})
const pkg = await readJson(`${path}/package.json`)
const { _id, scripts = {} } = pkg

if (event === 'restart' && !scripts.restart) {
scripts.restart = 'npm stop && npm start'
} else if (event === 'env') {
scripts.env = isWindowsShell ? 'SET' : 'env'
}

if (!scripts[event]) {
if (npm.config.get('if-present')) {
return
}
const suggestions = didYouMean(event, Object.keys(scripts))
throw new Error(`missing script: ${event}${
suggestions ? `\n${suggestions}` : '' }`)
}

// positional args only added to the main event, not pre/post
const events = [[event, args]]
if (scripts[`pre${event}`]) {
events.unshift([`pre${event}`, []])
}
if (scripts[`post${event}`]) {
events.push([`post${event}`, []])
}

const opts = {
path,
args,
scriptShell,
stdio: log.level === 'silent' ? 'pipe' : 'inherit',
pkg
}

for (const [event, args] of events) {
await run({
...opts,
event,
args
})
}
}

function list (cb) {
var json = path.join(npm.localPrefix, 'package.json')
var cmdList = [
const list = async () => {
const path = npm.localPrefix
const { scripts, name } = await readJson(`${path}/package.json`)
const cmdList = [
'publish',
'install',
'uninstall',
Expand All @@ -75,111 +116,51 @@ function list (cb) {
'start',
'restart',
'version'
].reduce(function (l, p) {
return l.concat(['pre' + p, p, 'post' + p])
}, [])
return readJson(json, function (er, d) {
if (er && er.code !== 'ENOENT' && er.code !== 'ENOTDIR') return cb(er)
if (er) d = {}
var allScripts = Object.keys(d.scripts || {})
var scripts = []
var runScripts = []
allScripts.forEach(function (script) {
if (cmdList.indexOf(script) !== -1) scripts.push(script)
else runScripts.push(script)
})

if (log.level === 'silent') {
return cb(null, allScripts)
}

if (npm.config.get('json')) {
output(JSON.stringify(d.scripts || {}, null, 2))
return cb(null, allScripts)
}
].reduce((l, p) => l.concat(['pre' + p, p, 'post' + p]))

if (npm.config.get('parseable')) {
allScripts.forEach(function (script) {
output(script + ':' + d.scripts[script])
})
return cb(null, allScripts)
}

var s = '\n '
var prefix = ' '
if (scripts.length) {
output('Lifecycle scripts included in %s:', d.name)
}
scripts.forEach(function (script) {
output(prefix + script + s + d.scripts[script])
})
if (!scripts.length && runScripts.length) {
output('Scripts available in %s via `npm run-script`:', d.name)
} else if (runScripts.length) {
output('\navailable via `npm run-script`:')
}
runScripts.forEach(function (script) {
output(prefix + script + s + d.scripts[script])
})
return cb(null, allScripts)
})
}
if (!scripts) {
return []
}

function run (pkg, wd, cmd, args, cb) {
if (!pkg.scripts) pkg.scripts = {}

var cmds
if (cmd === 'restart' && !pkg.scripts.restart) {
cmds = [
'prestop', 'stop', 'poststop',
'restart',
'prestart', 'start', 'poststart'
]
} else {
if (pkg.scripts[cmd] == null) {
if (cmd === 'test') {
pkg.scripts.test = 'echo \'Error: no test specified\''
} else if (cmd === 'env') {
if (isWindowsShell) {
log.verbose('run-script using default platform env: SET (Windows)')
pkg.scripts[cmd] = 'SET'
} else {
log.verbose('run-script using default platform env: env (Unix)')
pkg.scripts[cmd] = 'env'
}
} else if (npm.config.get('if-present')) {
return cb(null)
} else {
let suggestions = didYouMean(cmd, Object.keys(pkg.scripts))
suggestions = suggestions ? '\n' + suggestions : ''
return cb(new Error('missing script: ' + cmd + suggestions))
}
}
cmds = [cmd]
const allScripts = scripts ? Object.keys(scripts) : []
if (log.level === 'silent') {
return allScripts
}

if (!cmd.match(/^(pre|post)/)) {
cmds = ['pre' + cmd].concat(cmds).concat('post' + cmd)
if (npm.flatOptions.json) {
output(JSON.stringify(scripts, null, 2))
return allScripts
}

log.verbose('run-script', cmds)
chain(cmds.map(function (c) {
// pass cli arguments after -- to script.
if (pkg.scripts[c] && c === cmd) {
pkg.scripts[c] = pkg.scripts[c] + joinArgs(args)
if (npm.flatOptions.parseable) {
for (const [script, cmd] of Object.entries(scripts)) {
output(`${script}:${cmd}`)
}
return allScripts
}

// when running scripts explicitly, assume that they're trusted.
return [lifecycle, pkg, c, wd, { unsafePerm: true }]
}), cb)
}
const indent = '\n '
const prefix = ' '
const cmds = []
const runScripts = []
for (const script of allScripts) {
const list = cmdList.includes(script) ? cmds : runScripts
list.push(script)
}

// join arguments after '--' and pass them to script,
// handle special characters such as ', ", ' '.
function joinArgs (args) {
var joinedArgs = ''
args.forEach(function (arg) {
joinedArgs += ' "' + arg.replace(/"/g, '\\"') + '"'
})
return joinedArgs
if (cmds.length) {
output(`Lifecycle scripts included in ${name}:`)
}
for (const script of cmds) {
output(prefix + script + indent + scripts[script])
}
if (!cmds.length && runScripts.length) {
output(`Scripts available in ${name} via \`npm run-script\`:`)
} else if (runScripts.length) {
output(`\navailable via \`npm run-script\`:`)
}
for (const script of runScripts) {
output(prefix + script + indent + scripts[script])
}
return allScripts
}

0 comments on commit ac25561

Please sign in to comment.