What is @npmcli/run-script?
The @npmcli/run-script package is a utility that allows you to execute npm package scripts programmatically. It is part of the npm CLI and is used internally to handle the 'npm run' command. This package provides an API for running scripts defined in a package.json file, including lifecycle scripts and custom user-defined scripts.
What are @npmcli/run-script's main functionalities?
Executing npm lifecycle scripts
This feature allows you to execute standard npm lifecycle scripts such as 'start', 'test', 'prepublish', etc., that are defined in a package.json file.
const runScript = require('@npmcli/run-script');
runScript({
event: 'start',
path: '/path/to/package',
stdio: 'inherit'
}).then(() => {
console.log('Lifecycle script executed successfully.');
}).catch((err) => {
console.error('Error executing lifecycle script:', err);
});
Running custom scripts
This feature allows you to run custom scripts defined in the scripts section of a package.json file.
const runScript = require('@npmcli/run-script');
runScript({
event: 'custom-script',
path: '/path/to/package',
stdio: 'inherit'
}).then(() => {
console.log('Custom script executed successfully.');
}).catch((err) => {
console.error('Error executing custom script:', err);
});
Passing arguments to scripts
This feature allows you to pass arguments to the scripts being executed, similar to how you would use 'npm run test -- --coverage' in the command line.
const runScript = require('@npmcli/run-script');
runScript({
event: 'test',
args: ['--coverage'],
path: '/path/to/package',
stdio: 'inherit'
}).then(() => {
console.log('Test script executed with coverage.');
}).catch((err) => {
console.error('Error executing test script with coverage:', err);
});
Other packages similar to @npmcli/run-script
cross-env
cross-env is a package that allows you to set environment variables across platforms before running scripts. It is similar to @npmcli/run-script in that it is often used in conjunction with npm scripts, but it focuses on environment variables rather than script execution itself.
npm-run-all
npm-run-all is a CLI tool to run multiple npm-scripts in parallel or sequential. It provides a way to run scripts defined in package.json with more control over their execution order and concurrency, which is a different approach compared to the single script execution model of @npmcli/run-script.
shelljs
shelljs is a portable Unix shell commands for Node.js. It allows you to execute shell commands programmatically, which can be used to run scripts or perform tasks similar to npm scripts. However, it is more general-purpose and not specifically tied to npm or package.json scripts.
@npmcli/run-script
Run a lifecycle script for a package (descendant of npm-lifecycle)
USAGE
const runScript = require('@npmcli/run-script')
runScript({
event: 'install',
args: [],
path: '/path/to/package/folder',
binPaths: [
'/path/to/npx/node_modules/.bin',
'/path/to/npm/prefix/node_modules/.bin',
]
scriptShell: '/bin/bash',
stdioString: true,
env: {
npm_package_from: 'foo@bar',
npm_package_resolved: 'https://registry.npmjs.org/foo/-/foo-1.2.3.tgz',
npm_package_integrity: 'sha512-foobarbaz',
},
stdio: 'inherit',
banner: true,
})
.then(({ code, signal, stdout, stderr, pkgid, path, event, script }) => {
})
.catch(er => {
})
API
Call the exported runScript
function with an options object.
Returns a promise that resolves to the result of the execution. Promise
rejects if the execution fails (exits non-zero) or has any other error.
Rejected errors are decorated with the same values as the result object.
If the stdio options mean that it'll have a piped stdin, then the stdin is
ended immediately on the child process. If stdin is shared with the parent
terminal, then it is up to the user to end it, of course.
Results
code
Process exit codesignal
Process exit signalstdout
stdout data (Buffer, or String when stdioString
set to true)stderr
stderr data (Buffer, or String when stdioString
set to true)path
Path to the package executing its scriptevent
Lifecycle event being runscript
Command being run
Options
path
Required. The path to the package having its script run.event
Required. The event being executed.args
Optional, default []
. Extra arguments to pass to the script.env
Optional, object of fields to add to the environment of the
subprocess. Note that process.env IS inherited by default These are
always set:
npm_package_json
The package.json file in the foldernpm_lifecycle_event
The event that this is being run fornpm_lifecycle_script
The script being run- The
package.json
fields described in
RFC183.
scriptShell
Optional, defaults to /bin/sh
on Unix, defaults to
env.ComSpec
or cmd
on Windows. Custom script to use to execute the
command.stdio
Optional, defaults to 'pipe'
. The same as the stdio
argument
passed to child_process
functions in Node.js. Note that if a stdio
output is set to anything other than pipe
, it will not be present in
the result/error object.cmd
Optional. Override the script from the package.json
with
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.banner
Optional, defaults to true
. If the stdio
option is set to
'inherit'
, then print a banner with the package name and version, event
name, and script command to be run. Set explicitly to false
to disable
for inherited stdio.
Note that this does not run pre-event and post-event scripts. The
caller has to manage that process themselves.
This is an implementation to satisfy RFC
90, RFC
77, and RFC
73.
Apart from those behavior changes in npm v7, this is also just refresh of
the codebase, with modern coding techniques and better test coverage.
Functionally, this means:
- Output is not dumped to the top level process's stdio by default.
- Less stuff is put into the environment.
- It is not opinionated about logging. (So, at least with the logging
framework in npm v7.0 and before, the caller has to call
log.disableProgress()
and log.enableProgress()
at the appropriate
times, if necessary.) - The directory containing the
node
executable is never added to the
PATH
environment variable. (Ie, --scripts-prepend-node-path
is
effectively always set to false
.) Doing so causes more unintended side
effects than it ever prevented. - Hook scripts are not run by this module. If the caller wishes to run
hook scripts, then they can override the default package script with an
explicit
cmd
option pointing to the node_modules/.hook/${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
.