@ianwremmel/tooling.js
v1.10.4
Published
[](https://greenkeeper.io/)
Readme
tooling.js (@ianwremmel/tooling.js)
tooling.js makes it easier to use nodejs for automation tasks.
Inspired by Jenkins Pipelines (specifically, the groovy based Jenkinsfile), tooling.js aims to make JavaScript friendlier for writing tooling for software projects.
Background
Bash is clearly the defacto interpreter for the tasks required for building and testing software projects. However, as projects grow in size, bash can start to get unwieldy: error handling is complex, everything is in global scope, and parallelism requires arcane syntax and dropping state into tmp files.
JavaScript (especially with async/await), on the other hand, makes parallism and error handling loads better than bash. try/catch makes error handling reasonable straightforward (certainly easier than spending ten minutes on stack overflow to figure out if you should use == or -eq).
Of course, to take advantage of some of the most convenient advantes of JavaScript, we need to introduce babel and its requisite plugins - which is fine, but not work that we should repeat in every project we write. Enter tooling.js.
Tooling.js accepts a script file as an argument and passes it through two compilation stages. The second stage simply uses babel-present-env to ensure that all syntax is compatible with your local node version. The first stage introduces the globals described below.
Install
npm install -g @ianwremmel/tooling.jsor
npm install --save-dev @ianwremmel/tooling.jswith the save-dev option, you'll want to define your executables with npm scripts.
Usage
Invoke tooling.js with
tooling automation.jsor
cat automation.js | toolingIn addition to injecting the globals described below, tooling.js wraps your script in an async IIFE, thus allowing you to use the await keyword at the top level of your script.
Note: Due to the semantics of the
importandexport, scripts that use theexportkeyword will not be wrapped in an async IIFE and allimportstatements must be at the top of the script.
Examples
Run three grunt tasks in parallel
parallel(
sh(`grunt test:unit`),
sh(`grunt test:node`),
sh(`grunt test:automation`)
)Handle failing shell scripts
try {
sh(`exit 1`)
}
catch (err) {
if (err.code === 1) {
echo(`yowzers`);
}
else {
echo(`this should never be reached`);
}
}Handle shell scripts with meaningful error codes
const result = sh(`exit $RANDOM`, {complex: true});
if (result.code === 1) {
echo(`exit with one`)
}
else {
echo(`did not exit with one`)
}require-hook
Tooling.js provides a require hook at @ianwremmel/tooling.js/register. The following should work:
node -r @ianwremmel/tooling.js/register automation.jsor
require(`@ianwremmel/tooling.js/register`);
require(`./automation.js`);Programmtic API
const transform = require(`@ianwremmel/tooling.js`);
eval(transform(require(`./automation.js`)));API
node fs functions
All async functions from fs are promisified and automatically prefixed with await. mkdir is replaced by mkirp.
const file = readFile(`in.txt`)
readFiledefaults to utf8 encoding
cd
Changes the current directory
const os = require(`os`);
cd(os.tmpdir());echo
Shorthand for console.log.
echo(`1`)env
Shorthand for process.env.
env.TEST_VAR = 5;parallel
Run multiple items in parallel. Note: every argument is wrapped in a promise, so arguments can be anything that can be passed to a function.
Options
- concurrency: Number - maximum number of concurrent tasks to execute
parallel(
console.log(1),
new Promise((resolve) => {
process.nextTick(() => {
console.log(2);
resolve();
})
}),
console.log(3)
);parallel({concurrency: 2},
console.log(1),
new Promise((resolve) => {
process.nextTick(() => {
console.log(2);
resolve();
})
}),
console.log(3)
);pwd
prints the current directory when not assigned or returns it when assigned.
prints the current directory
pwd()returns and does not print the current directory
const dir = pwd()readJSON
Reads a file and parses its contents as JSON. Will throw if the file does not contain valid JSON.
const json = readJSON(`in.json`);retry
Execute an expression multiple times.
Options
- repeat: Boolean - if true, the expression will be executed max times, even if it succeeds. default: false
- max: Number - maximum number of iterations. default: 3
retry(
new Promise((resolve, reject) => {
reject(new Error(`this will fail 3 times`));
})
)Note: rejected Promises must be rejected with
Errorobjects. This seems to have something to do with babel's async/await support.
retry({max: 2, repeat: true}
new Promise((resolve) => {
console.log(`this will print twice`);
})
)The variables ITERATION and MAX_ITERATIONS are injected into the running expression.
retry({max: 2, repeat: true}
new Promise((resolve) => {
console.log(`{ITERATION} out of ${MAX_ITERATIONS}`);
})
)Note:
ITERATIONis zero-based, so will never equalMAX_ITERATIONS.
sh
Execute a shell command "synchronously" (actually wraps child_process.spawn in a promise and drops an await in front of it).
options
- complex: Boolean - if true, return the full object returned by spawn instead of just stdout
- spawn: Object - an object of options to pass directly to spawn.
sh(`echo 1`)try {
sh(`exit 5`);
}
catch(err) {
require(`assert`).equal(err.code, 5);
}const one = sh(`echo 1`, {complex: true}).stdout;tee
Send stderr/stdout to additional locations.
Send stderr and stdout to a single file while continuing to print to the console
tee({file: `out.log`, stderr: true, stdout: true});
echo(1);Supress output while redirecting stdout and stderr
tee({file: `out.log`, stderr: true, stdout: true});
tee.silent = true;
echo(1);Note: Though it doesn't matter when you sent
tee.silent, doing so won't have any impact until after tee is called for the first time.
Redirect only certain parts of your script
echo(1);
const t = tee({file: `out.log`, stderr: true, stdout: true})
echo(2);
t.stop();
echo(3);Contribute
PRs accepted. Please lint and test your code with npm test
