🚀 Big News: Socket Acquires Coana to Bring Reachability Analysis to Every Appsec Team.Learn more
Socket
Sign inDemoInstall
Socket

@npmcli/run-script

Package Overview
Dependencies
Maintainers
5
Versions
59
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@npmcli/run-script - npm Package Compare versions

Comparing version

to
7.0.2

34

lib/make-spawn-args.js
/* eslint camelcase: "off" */
const isWindows = require('./is-windows.js')
const setPATH = require('./set-path.js')
const {resolve} = require('path')
const { resolve } = require('path')
const npm_config_node_gyp = require.resolve('node-gyp/bin/node-gyp.js')

@@ -11,31 +10,32 @@

path,
scriptShell = isWindows ? process.env.ComSpec || 'cmd' : 'sh',
scriptShell = true,
binPaths,
env = {},
stdio,
cmd,
stdioString = false,
args = [],
stdioString,
} = options
const isCmd = /(?:^|\\)cmd(?:\.exe)?$/i.test(scriptShell)
const args = isCmd ? ['/d', '/s', '/c', cmd] : ['-c', cmd]
const spawnEnv = setPATH(path, binPaths, {
// we need to at least save the PATH environment var
...process.env,
...env,
npm_package_json: resolve(path, 'package.json'),
npm_lifecycle_event: event,
npm_lifecycle_script: cmd,
npm_config_node_gyp,
})
const spawnOpts = {
env: setPATH(path, {
// we need to at least save the PATH environment var
...process.env,
...env,
npm_package_json: resolve(path, 'package.json'),
npm_lifecycle_event: event,
npm_lifecycle_script: cmd,
npm_config_node_gyp,
}),
env: spawnEnv,
stdioString,
stdio,
cwd: path,
...(isCmd ? { windowsVerbatimArguments: true } : {}),
shell: scriptShell,
}
return [scriptShell, args, spawnOpts]
return [cmd, args, spawnOpts]
}
module.exports = makeSpawnArgs

@@ -9,8 +9,9 @@ // https://github.com/npm/rfcs/pull/183

for (const [key, val] of Object.entries(vals)) {
if (val === undefined)
if (val === undefined) {
continue
else if (val && !Array.isArray(val) && typeof val === 'object')
} else if (val && !Array.isArray(val) && typeof val === 'object') {
packageEnvs(env, val, `${prefix}${key}_`)
else
} else {
env[`${prefix}${key}`] = envVal(val)
}
}

@@ -17,0 +18,0 @@ return env

@@ -9,4 +9,13 @@ const makeSpawnArgs = require('./make-spawn-args.js')

// you wouldn't like me when I'm angry...
const bruce = (id, event, cmd) =>
`\n> ${id ? id + ' ' : ''}${event}\n> ${cmd.trim().replace(/\n/g, '\n> ')}\n`
const bruce = (id, event, cmd, args) => {
let banner = id
? `\n> ${id} ${event}\n`
: `\n> ${event}\n`
banner += `> ${cmd.trim().replace(/\n/g, '\n> ')}`
if (args.length) {
banner += ` ${args.join(' ')}`
}
banner += '\n'
return banner
}

@@ -18,2 +27,3 @@ const runScriptPkg = async options => {

scriptShell,
binPaths = false,
env = {},

@@ -23,3 +33,3 @@ stdio = 'pipe',

args = [],
stdioString = false,
stdioString,
// note: only used when stdio:inherit

@@ -32,9 +42,10 @@ banner = true,

const {scripts = {}, gypfile} = pkg
const { scripts = {}, gypfile } = pkg
let cmd = null
if (options.cmd)
if (options.cmd) {
cmd = options.cmd
else if (pkg.scripts && pkg.scripts[event])
cmd = pkg.scripts[event] + args.map(a => ` ${JSON.stringify(a)}`).join('')
else if ( // If there is no preinstall or install script, default to rebuilding node-gyp packages.
} else if (pkg.scripts && pkg.scripts[event]) {
cmd = pkg.scripts[event]
} else if (
// If there is no preinstall or install script, default to rebuilding node-gyp packages.
event === 'install' &&

@@ -45,24 +56,30 @@ !scripts.install &&

await isNodeGypPackage(path)
)
) {
cmd = defaultGypInstallScript
else if (event === 'start' && await isServerPackage(path))
cmd = 'node server.js' + args.map(a => ` ${JSON.stringify(a)}`).join('')
} else if (event === 'start' && await isServerPackage(path)) {
cmd = 'node server.js'
}
if (!cmd)
if (!cmd) {
return { code: 0, signal: null }
}
if (stdio === 'inherit' && banner !== false) {
// we're dumping to the parent's stdout, so print the banner
console.log(bruce(pkg._id, event, cmd))
console.log(bruce(pkg._id, event, cmd, args))
}
const p = promiseSpawn(...makeSpawnArgs({
const [spawnShell, spawnArgs, spawnOpts] = makeSpawnArgs({
event,
path,
scriptShell,
binPaths,
env: packageEnvs(env, pkg),
stdio,
cmd,
args,
stdioString,
}), {
})
const p = promiseSpawn(spawnShell, spawnArgs, spawnOpts, {
event,

@@ -74,7 +91,9 @@ script: cmd,

if (stdio === 'inherit')
if (stdio === 'inherit') {
signalManager.add(p.process)
}
if (p.stdin)
if (p.stdin) {
p.stdin.end()
}

@@ -84,3 +103,7 @@ return p.catch(er => {

if (stdio === 'inherit' && signal) {
// by the time we reach here, the child has already exited. we send the
// signal back to ourselves again so that npm will exit with the same
// status as the child
process.kill(process.pid, signal)
// just in case we don't die, reject after 500ms

@@ -90,4 +113,5 @@ // this also keeps the node process open long enough to actually

return new Promise((res, rej) => setTimeout(() => rej(er), signalTimeout))
} else
} else {
throw er
}
})

@@ -94,0 +118,0 @@ }

@@ -8,7 +8,8 @@ const rpj = require('read-package-json-fast')

validateOptions(options)
const {pkg, path} = options
const { pkg, path } = options
return pkg ? runScriptPkg(options)
: rpj(path + '/package.json').then(pkg => runScriptPkg({...options, pkg}))
: rpj(path + '/package.json')
.then(readPackage => runScriptPkg({ ...options, pkg: readPackage }))
}
module.exports = Object.assign(runScript, { isServerPackage })

@@ -1,3 +0,2 @@

const {resolve, dirname} = require('path')
const isWindows = require('./is-windows.js')
const { resolve, dirname, delimiter } = require('path')
// the path here is relative, even though it does not need to be

@@ -10,14 +9,15 @@ // in order to make the posix tests pass in windows

// all together in the order they appear in the object.
const setPATH = (projectPath, env) => {
// not require('path').delimiter, because we fake this for testing
const delimiter = isWindows ? ';' : ':'
const setPATH = (projectPath, binPaths, env) => {
const PATH = Object.keys(env).filter(p => /^path$/i.test(p) && env[p])
.map(p => env[p].split(delimiter))
.reduce((set, p) => set.concat(p.filter(p => !set.includes(p))), [])
.reduce((set, p) => set.concat(p.filter(concatted => !set.includes(concatted))), [])
.join(delimiter)
const pathArr = []
if (binPaths) {
pathArr.push(...binPaths)
}
// unshift the ./node_modules/.bin from every folder
// walk up until dirname() does nothing, at the root
// XXX should we specify a cwd that we don't go above?
// XXX we should specify a cwd that we don't go above
let p = projectPath

@@ -38,4 +38,5 @@ let pp

for (const key of Object.keys(env)) {
if (/^path$/i.test(key))
if (/^path$/i.test(key)) {
env[key] = pathVal
}
}

@@ -42,0 +43,0 @@

const runningProcs = new Set()
let handlersInstalled = false
// NOTE: these signals aren't actually forwarded anywhere. they're trapped and
// ignored until all child processes have exited. in our next breaking change
// we should rename this
const forwardedSignals = [
'SIGINT',
'SIGTERM'
'SIGTERM',
]
const handleSignal = signal => {
for (const proc of runningProcs) {
proc.kill(signal)
}
}
// no-op, this is so receiving the signal doesn't cause us to exit immediately
// instead, we exit after all children have exited when we re-send the signal
// to ourselves. see the catch handler at the bottom of run-script-pkg.js
// istanbul ignore next - this function does nothing
const handleSignal = () => {}
const setupListeners = () => {

@@ -33,4 +35,5 @@ for (const signal of forwardedSignals) {

runningProcs.add(proc)
if (!handlersInstalled)
if (!handlersInstalled) {
setupListeners()
}

@@ -46,3 +49,3 @@ proc.once('exit', () => {

handleSignal,
forwardedSignals
forwardedSignals,
}
const validateOptions = options => {
if (typeof options !== 'object' || !options)
if (typeof options !== 'object' || !options) {
throw new TypeError('invalid options object provided to runScript')
}

@@ -15,18 +16,25 @@ const {

if (!event || typeof event !== 'string')
if (!event || typeof event !== 'string') {
throw new TypeError('valid event not provided to runScript')
if (!path || typeof path !== 'string')
}
if (!path || typeof path !== 'string') {
throw new TypeError('valid path not provided to runScript')
if (scriptShell !== undefined && typeof scriptShell !== 'string')
}
if (scriptShell !== undefined && typeof scriptShell !== 'string') {
throw new TypeError('invalid scriptShell option provided to runScript')
if (typeof env !== 'object' || !env)
}
if (typeof env !== 'object' || !env) {
throw new TypeError('invalid env option provided to runScript')
if (typeof stdio !== 'string' && !Array.isArray(stdio))
}
if (typeof stdio !== 'string' && !Array.isArray(stdio)) {
throw new TypeError('invalid stdio option provided to runScript')
if (!Array.isArray(args) || args.some(a => typeof a !== 'string'))
}
if (!Array.isArray(args) || args.some(a => typeof a !== 'string')) {
throw new TypeError('invalid args option provided to runScript')
if (cmd !== undefined && typeof cmd !== 'string')
}
if (cmd !== undefined && typeof cmd !== 'string') {
throw new TypeError('invalid cmd option provided to runScript')
}
}
module.exports = validateOptions
{
"name": "@npmcli/run-script",
"version": "1.8.6",
"version": "7.0.2",
"description": "Run a lifecycle script for a package (descendant of npm-lifecycle)",
"author": "Isaac Z. Schlueter <i@izs.me> (https://izs.me)",
"author": "GitHub Inc.",
"license": "ISC",
"scripts": {
"test": "tap",
"preversion": "npm test",
"postversion": "npm publish",
"prepublishOnly": "git push origin --follow-tags",
"eslint": "eslint",
"lint": "npm run eslint -- \"lib/**/*.js\"",
"lintfix": "npm run lint -- --fix"
"lint": "eslint \"**/*.js\"",
"lintfix": "npm run lint -- --fix",
"postlint": "template-oss-check",
"snap": "tap",
"posttest": "npm run lint",
"template-oss-apply": "template-oss-apply --force"
},
"tap": {
"check-coverage": true,
"coverage-map": "map.js"
},
"devDependencies": {
"eslint": "^7.19.0",
"eslint-plugin-import": "^2.22.1",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-promise": "^4.2.1",
"eslint-plugin-standard": "^5.0.0",
"minipass": "^3.1.1",
"@npmcli/eslint-config": "^4.0.0",
"@npmcli/template-oss": "4.19.0",
"require-inject": "^1.4.4",
"tap": "^15.0.4"
"tap": "^16.0.1"
},
"dependencies": {
"@npmcli/node-gyp": "^1.0.2",
"@npmcli/promise-spawn": "^1.3.2",
"node-gyp": "^7.1.0",
"read-package-json-fast": "^2.0.1"
"@npmcli/node-gyp": "^3.0.0",
"@npmcli/promise-spawn": "^7.0.0",
"node-gyp": "^10.0.0",
"read-package-json-fast": "^3.0.0",
"which": "^4.0.0"
},
"files": [
"lib/**/*.js",
"lib/node-gyp-bin"
"bin/",
"lib/"
],

@@ -43,4 +37,18 @@ "main": "lib/run-script.js",

"type": "git",
"url": "git+https://github.com/npm/run-script.git"
"url": "https://github.com/npm/run-script.git"
},
"engines": {
"node": "^16.14.0 || >=18.0.0"
},
"templateOSS": {
"//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
"version": "4.19.0",
"publish": "true"
},
"tap": {
"nyc-arg": [
"--exclude",
"tap-snapshots/**"
]
}
}

@@ -20,8 +20,18 @@ # @npmcli/run-script

// optional, these paths will be put at the beginning of `$PATH`, even
// after run-script adds the node_modules/.bin folder(s) from
// `process.cwd()`. This is for commands like `npm init`, `npm exec`,
// and `npx` to make sure manually installed packages come before
// anything that happens to be in the tree in `process.cwd()`.
binPaths: [
'/path/to/npx/node_modules/.bin',
'/path/to/npm/prefix/node_modules/.bin',
],
// optional, defaults to /bin/sh on unix, or cmd.exe on windows
scriptShell: '/bin/bash',
// optional, defaults to false
// optional, passed directly to `@npmcli/promise-spawn` which defaults it to true
// return stdout and stderr as strings rather than buffers
stdioString: true,
stdioString: false,

@@ -115,4 +125,5 @@ // optional, additional environment variables to add

something else, which will be run in an otherwise matching environment.
- `stdioString` Optional, defaults to `false`. Return string values for
`stderr` and `stdout` rather than Buffers.
- `stdioString` Optional, passed directly to `@npmcli/promise-spawn` which
defaults it to `true`. Return string values for `stderr` and `stdout` rather
than Buffers.
- `banner` Optional, defaults to `true`. If the `stdio` option is set to

@@ -152,1 +163,16 @@ `'inherit'`, then print a banner with the package name and version, event

script.
## Escaping
In order to ensure that arguments are handled consistently, this module
writes a temporary script file containing the command as it exists in
the package.json, followed by the user supplied arguments having been
escaped to ensure they are processed as literal strings. We then instruct
the shell to execute the script file, and when the process exits we remove
the temporary file.
In Windows, when the shell is cmd, and when the initial command in the script
is a known batch file (i.e. `something.cmd`) we double escape additional
arguments so that the shim scripts npm installs work correctly.
The actual implementation of the escaping is in `lib/escape.js`.