run-in-cwd
v0.0.8
Published
Run CLI commands with Node
Downloads
3
Maintainers
Readme
run-in-cwd
Run CLI commands with Node.
Table Of Contents
Get Started
- Install:
$ npm install run-in-cwd
- Require:
const createCwd = require('run-in-cwd')
- Get a
Cwd
Instance:const projectDir = createCwd('/path/to/target/folder')
- Run a command:
// run to completion projectDir.runCmd('git status').then((results) => {...}) // run with more control projectDir.spawn('git status') .on('line/out', (line) => {...}) .on('close', (exitCode) => {...})
WARNING: If you let your users pass in the command and/or any of its arguments - make sure they are safe.
Default Instance
run-in-cwd
exports a default instance ready to use if your target folder is in which the current process runs in. Meaning, what process.cwd()
returns.
const cwd = require('run-in-cwd');
cwd.runCmd('git status').then(...)
It's the same instance you would get if you run createCwd('./')
or even createCwd()
NOTE: For context's sake, do not extract cwd methods:
// DO NOT:
const {runCmd} = require('run-in-cwd')
Instance API
.runCmd( cmd, [args, [options] ] )
Executes a command and returns a completion promise (command exits).
Similar to Node's child_process.exec
and child_process.execFile
(see docs).
Don't forget to handle errors with promise.catch()
or a try-catch
wrapper.
Arguments:
- cmd (Required) - A command string (e.g.
'npm'
) - args - An array of the command's arguments (e.g.
['-flag', 'key=value']
). Could also be a string or a number for a single argument.
NOTE:
cmd
string could also include the arguments e.g..runCmd('npm install')
- options - spawn options object. See Node's docs.
Additional option:maxCacheSize
- Limit the command cache in MegaBytes. The limit is the total output for bothstdout
&stderr
streams. Default value is10
. An exception is thrown when max size is exceeded.cwd.runCmd('cat longFile.txt', {maxCacheSize: 20}) // 20 MB limit
Returns: A promise for a command results object.
A result object has the following properties:
exitCode
- Number - The command exit code.isOk
- Boolean -true
if command exit code is 0.false
otherwise.stdout
- String - The commands'sstdout
output string (utf-8 encoded).stderr
- String - The commands'sstderr
output string (utf-8 encoded).output
- String - Both streams' output string (utf-8 encoded).stdoutLines
- Array - The commands'sstdout
output, split to lines.stderrLines
- Array -The commands'sstderr
output, split to lines.outputLines
- Array - Both streams' output, split to lines.
Examples:
Promise style:
cwd.runCmd('npm install')
.then({isOk, stderr} => {
// ...
})
.catch((err) => {
// handle exception...
});
Async-Await style:
(async () => {
try {
const {isOk, stdoutLines} = await projectDir.runCmd('npm install')
// ...
}
catch (err) {
// handle exception...
}
})();
The difference between !isOk
, stderr
and error
:
A CLI command creates a process that has two output channels it utilize to communicate with its parent process: one for the command's standard output (stdout
), and one for its errors (stderr
).
Every command also finishes with an exit code. The consensus is exit code 0
for success (a kind of an HTTP "200 OK" status code) and non-zero otherwise. Different commands use those channels and exit codes differently, even "incorrectly" as there is no standard other than subjective common sense...
So when a command fails it will probably send some details through its stderr
channel and exit with a non-zero exit code but it will be completed. No errors will be thrown in the Node process that executed the command.
An exception
is thrown when there was a problem with the command execution in our environment. For example when the command itself is not found (e.g. a typo like ggit
).
.spawn( cmd, [args, [options] ] )
Runs a command and returns a child process.
It is highly recommended to handle errors:
childProc.on('error', (err) => { /* handle error */ })
Arguments:
- cmd (Required) - A command string (e.g.
'npm'
) - args - An array of the command's arguments (e.g.
['-flag', 'cache=500']
). Could also be a string or a number for a single argument.
NOTE:
cmd
string could also include the arguments e.g..spawn('npm install')
- options - spawn options object. See Node's docs.
Returns: <child_process>
Examples:
Calling .spawn()
is only the first part of spawning a process. We then need to handle the returned process's lifecycle events.
First, spawn a child process:
const childProc = cwd.spawn('npm install --save')
<child_process>
.spawn
's return object is Node's native ChildProcess with additional events.
Event:
'line'
Is triggered for each line of both
stdout
andstderr
streams. Text is split to lines by newline characters.Event:
'line/out'
Same as
line
event, but forstdout
only.Event:
'line/err'
Same as
line
event, but forstderr
only.
const cwd = require('run-in-cwd');
const childProc = cwd.spawn('git status')
let isClean = false;
childProc.on('line/out', (line) => {
if (line.includes('nothing to commit')) {
isClean = true;
}
})
childProc.on('close', (exitCode) => {
if (exitCode === 0 && isClean) { // 0 is like 200 OK.
console.log('Branch is clean')
}
else {
console.log('Stage & Commit')
}
})
.runShellCmd( cmd, [args, [options] ] )
.spawnShell( cmd, [args, [options] ] )
Those are the shelled versions of .runcCmd()
and .spawn()
, respectivly. Meaning, the option {shell: true}
is used. Use the shelled versions when you need to chain commands, redirect I/O, file globbing patterns and other shell behvior.
For example:
cwd.runShellCmd('git commit -m "nice feature" && git status 1>last-status.txt')
.parentProcess
Returns a cwd instance that redirects all of its commands' I/O to its parent process. It utilizes Node's {stdio:inherit}
option. Read more.
cwd.parentProcess.runCmd('git status')
Is the equivalent of:
cwd.runCmd('git status', {stdio: 'inherit'})
NOTE: All
cwd
instances (including the default instance) have the.parentProcess
prop.
When the parent process is Node's global process
object, it usually means write output to the terminal screen and read input from the keyboard. In this case the above code acts just like running git status
from the terminal yourself.
What is CWD?
CWD
stands for: Current Working Directory.
When working with a Command Line Interface (CLI) you execute commands from within a certain directory, your code
folder, for example:
# Windows
C:\path\code\>
# Linux
~/path/code $
This basic concept of running commands from within a certain folder was the main trigger to create run-in-cwd
.
run-in-cwd
vs. Node's child_process
Command arguments
With Node, when you want to run a simple command with arguments like: git status
you would normally either (1) pass a command string and an arguments array (even for a single argument) or (2) add the {shell: true}
option.
const childProc = require('child_process')
childProc.spawn('git', ['status'])
childProc.spawn('git status', {shell: true})
run-in-cwd
takes care of the arguments for you
const cwd = require('run-in-cwd')
cwd.spawn('git status')
And when you really need a shell:
cwd.spawnShell('git status')
Setting the working directory
Both ways use process.cwd()
as the default working directory for running commands.
When you need to run commands on the same directory but it's not your Current Working Directory, with Node, you will find yourself repetitively using the "cwd" option.
Node's child process:
const childProc = require('child_process')
childProc.spawn('git', ['status'], {cwd: '../path-to/my-folder'})
childProc.spawn('git', ['commit'], {cwd: '../path-to/my-folder'})
childProc.spawn('git', ['push', 'origin', 'master'], {cwd: '../path-to/my-folder'})
With run-in-cwd
you only do it once:
const createCwd = require('run-in-cwd')
const cwd = createCwd('../path-to/my-folder')
cwd.spawn('git status')
cwd.spawn('git commit')
cwd.spawn('git push origin master')
Data Events
To read a command's output we need to listen to the command streams 'data'
events.
Data events emit chunks:
cp.stdout.on('data', (chunk) => {
console.log(chunk)
// <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
})
Data chunks, besides being buffers, as random pieces of data cannot gurantee you get a whole word or a full sentence.
run-in-cwd
provides some higher-level events that emit text (UTF-8 strings), split into lines.
So the equivalent of the native way:
// Native Node
const childProc = require('child_process')
const cp = childProc.spawn('git', ['status']);
cp.stdout.on('data', (chunk) => ...)
cp.stderr.on('data', (chunk) => ...)
would be:
// run-in-cwd
const cwd = require('run-in-cwd')
const cp = cwd.spawn('git status');
cp.on('line/out', (line) => ...)
cp.on('line/err', (line) => ...)
// emits for both
cp.on('line', (line) => ...)