This package serves as an alternative to google/zx for example. The key difference is to provide Unix shell commands in a cross-platform compatible way and usable inside JavaScript. This is primarily achieved by using shelljs/shelljs library.
You can compare the final script code to zx
example:
#!/usr/bin/env nodejsscript
echo(s.grep("name", "package.json"));
s.run`git branch --show-current`
.xargs(s.run, "dep deploy --branch={}");
s.run`sleep 1; echo 1`;
s.run`sleep 2; echo 2`;
s.run`sleep 3; echo 3`;
pipe( $.xdg.temp, s.mkdir )("foo bar");
…also see examples or Show And Tell · Discussions.
Open ‘▸’ sections for quick overview and/or navigate to link(s) for more detailed information and documentation.
s #shelljs namespace (unix shell-like commands in JavaScript)
s.ls().forEach(echo); // ShellArray
s.cat("package.json").xargs(JSON.parse).trim(); // ShellString
s.read().then(echo); // Promise<ShellString>
s.runA`git branch --show-current`.then(echo); // Promise<ShellString>
const { code, stdout, stderr } = s.run`git branch --show-current`; // ShellString
Contains functions from shelljs/shelljs library mimic the bash utilities
and some additional added by nodejsscript. Typically s.cat
/s.grep
/…,
to run other than builtin commands use s.run
/s.runA
.
These functions returns ShellArray
/ShellString
/Promise<ShellString>
,
these types are union types of string[]
/string
with ShellReturnValueNJS
.
In simple terms, you can use it as string[]
/string
/Promise<string>
or
read the commad exit code
and stdout
/stderr
. If it makes sence, you can
pipe output to other shelljs commands. Special pipeing is to
/toEnd
for
redirectiong output to the file.
s.echo("Hello World!").to("hello.txt");
$ ( $.api() #sade, $.xdg, … ) namespace (nodejsscript/cli related functions/variables)
// ls.mjs
$.api()
.command("ls [folder]", "list files")
.option("-a", "list all files")
.action((folder, options)=> {
if(Object.keys(options).length === 0)
s.ls(folder);
else {
const opts= pipe(
Object.entries,
o=> o.map(([k, v])=> [ "-"+k, v ]),
Object.fromEntries
)(options);
s.ls(opts, folder);
}
$.exit(0);
})
.parse();
// ls.mjs ls -a
- contains cli/nodejsscript related functions
- for processing script arguments you can use
$[0]
/$[1]
/… (compare with bash$0
/$1
/…) or $.api()
: allows to quickly create script cli API, internally uses sade library (compare with commander)$.isMain(import.meta)
: detects if the script is executed as main or if it is imported from another script file$.xdg
: provides cross-platform file system access for specific locations (home, temp, config, … directory)$.stdin
: handles standard input when the script is run in shell pipe (can be helpful fornodejsscript --eval
/nodejsscript --print
bellow)- …for more see related section in docs
echo() #css-in-console function/namespace
const css= echo.css`
.blue { color: blue; }
.spin { list-style: --terminal-spin; }
.success { color: green; list-style: "✓ "; }
`;
echo("Hello %cWorld", css.blue);
for(let i= 0; i < 10; i++){
echo.use("-R", "%cLoading…", css.spin);
s.run`sleep .5`;
}
echo("%cDone", css.success);
- prints to console, also supports styling using CSS like syntax
- internally uses css-in-console
pipe() function
pipe(
Number,
v=> `Result is: ${v}`,
echo
)("42");
Provides functional way to combine JavaScript functions.
fetch()
, new AbortController()
These are supported in nodejsscript:
- uses native
fetch()
/AbortController
or - fallbacks
nodejsscript --eval
/nodejsscript --print
(quickly eval javascript code in terminal)
curl https://api.spacexdata.com/v4/launches/latest | \
nodejsscript -p '$.stdin.json()' Object.entries 'e=> e.filter(([_,v])=> Array.isArray(v))'
- similar to
node --eval
/node --print
- you can use less verbose syntax
njs -e
/njs -p
nodejsscript --inspect
Use to debug your script, similar to node inspect
(Node.js — Debugging Node.js).
nodejsscript --interactive
(REPL)
Use to run REPL, similar to node
/node --interactive
/node -i
.
Idea: you can use REPL to analyze your JSON log files (pseudo code):
// njs --interactive
> s.ls("*.json").flatMap(f=> s.cat(f).xargs(JSON.parse)).filter(x=> x.error)
> _.map(x=> x.error===404)
REPL supports tab-completion (also for folders and files).
nodejsscript --completion
(bash completions for nodejsscript and scripts)
- provide shell completion for nodejsscript and scripts written using
nodejsscript (using
$.api()
) - (for now) only for bash
- add
eval "$(nodejsscript --completion bash)"
to your '.bashrc' file - prepare your script cli API using
$.api()
- register your scritp autocompletion using
nodejsscript --completion register <target>
- use global script name (your script must be also included in the PATH) to automatically enable completions on the shell start
- or (relative) path to enable completions on demand see ↙
- use
eval "$(nodejsscript --completion bash-local [target])"
- empty target or path to the directory enables completions for all scripts in the given directory recursively
- script path as target enables completions for specific script only
- see help
nodejsscript --completion
/nodejsscript --completion help
~/.config/nodejsscript/nodejsscriptrc.mjs
(.bashrc
for nodejsscript)
//nodejsscriptrc.mjs
// … my code evaluated for each nodejsscript invocation
/** Custom uncaughtException function */
export function uncaughtException(){};
/** Place for custom code when script starts */
export function onscript(){}
/** Place for custom code when REPL starts (`--interactive`) */
export function onrepl(){}
/** Place for custom code when eval starts (`--eval`/`--print`) */
export function oneval(){}
This is very similar to .bashrc
file, but for nodejsscript.
Use nodejsscript --help
to find out the location of
the nodejsscriptrc.mjs
file.
njs
alias for nodejsscript
You can use njs
instead of nodejsscript
, so see less verbose syntax:
njs -e
/njs -p
njs --inspect
njs
/njs -i
/njs --interactive
njs --completion
npx nodejsscript
// some script file
#!/usr/bin/env -S npx nodejsscript
You can install/use nodejsscript
for specific project, for example
in combination with jaandrle/bs: The simplest possible build system using executables.
import … from "node:…";
(node JS built-ins for “free”)
import { setTimeout } from "node:timers/promises";
import { join, resolve } from "node:path";
//.current file URL
import.meta.url;
//.URL to path
import { fileURLToPath } from "node:url";
const file_path= fileURLToPath(import.meta.url);
// URL is supported! (see relative reading)
s.cat(new URL('relative_file', import.meta.url));
//.crypto utils
import { randomUUID } from "node:crypto";
// …
…and more, see for example Node.js v17.9.1 Documentation.
nodejsscript --tldr
(show quick summary of nodejsscript functions)
nodejsscript --tldr s.
nodejsscript --tldr s.cat
…this shows lits all functions and variables in s.*
and quick summary
of s.cat()
. You can see all manuals in ./tldr.md.
nodejsscript --global-jsconfig
(experimental helper for developing)
nodejsscript --global-jsconfig add script_file
…this creates jsconfig.json
in current working directory with include
property containing script_file
and current path to the nodejsscript
to enable proper suggestions in IDEs (and type checking). Tested for
VSCode and Vim with neoclide/coc.nvim.
You don’t need this hack if you use nodejsscript
in your project locally.
- migration from 0.9.*: see API changes 0.9 → 1.0
- migration from 0.8.*: see API changes 0.8 → 0.9
- migration from
zx
- You (may) not need to use
nodejsscript
- Ideas (for new features)
- Examples: examples folder or Show And Tell · Discussions
- Security guidelines — “use
s.run
/s.runA
, watch out globbing” - Contribute
- Getting started ↙ — installation and first steps (usage)
One-paragraph guide: install npm package
npm install nodejsscript --location=global
, create executable script file
touch script.mjs && chmod +x script.mjs
with shebang
#!/usr/bin/env nodejsscript
and run it ./script.mjs
.
- install NodeJS using nvm-sh/nvm: Node Version Manager1 — tested/used on
node@v20
–node@v16
- install
nodejsscript
package from npm registry2npm install nodejsscript --location=global
: to use globallynpm install nodejsscript
: to use locally in the package
Write your scripts in a file with an .mjs
extension in order to process
the script as an ESM module. This is preferred way as it is more compatible
with current JavaScript standards. E. g. you can use await
at the top level.
Alternatively, use the .js
extension to use “old style” commonJS code.
E. g. you must wrap your scripts in something like (async function () {...})()
.
Add the following shebang to the beginning of your nodejsscript
scripts:
#!/usr/bin/env nodejsscript
Now you will be able to run your script like so:
chmod +x ./script.mjs
./script.mjs
Or via the nodejsscript
executable:
nodejsscript ./script.mjs
Alternatively when installed locally
#!/usr/bin/env -S npx nodejsscript
npx nodejsscript ./script.mjs
All function (shelljs
, fetch
, …) are registered as global
namespaces/functions: … see Goods or full
documentation generated from type definitions (focus on Public
items): docs/. Conventionally, camelCase names are used for
functions and snake_case for variables/constants.
run()
/runA()
command injection: this advice applies to
child_process.exec()
just as much as it applies to s.run()
. It is
potentially risky to run commands passed for example by user input:
function curlUnsafe(urlToDownload){ return s.run('curl ' + urlToDownload); }
curlUnsafe('https://some/url ; rm -rf $HOME');
//=> curl https://some/url ; rm -rf $HOME
Therefore, nodejsscript
s s.run()
provide way to escapes untrusted parameters:
function curl(url){ return s.run("curl ::url::", { url }); }
curl('https://some/url ; rm -rf $HOME');
//=> curl 'https://some/url ; rm -rf $HOME'
…you can also use as template function (but without command specific options):
function curl(url){ return s.run`curl ${url}`; }
curl('https://some/url ; rm -rf $HOME');
//=> curl 'https://some/url ; rm -rf $HOME'
…Note: The 'xargs()' by default also escapes piped strings.
…Note 2: s.run(…cmd, …vars)
is also helpful for escaping parameters passed
as variables (e.g. arrays).
…Note 3: ShellJS also provides s.exec
, but s.run
should be preferred way
to execute commands.
Glob injection (all commands): Most ShellJS commands support glob expansion,
expanding wildcards such as *
to match files. While this is very powerful,
dependent modules should exercise caution. Unsanitized user input may contain
wildcard characters. Consider for example that the *.txt
is valid file name,
however the s.rm("*.txt")
by default (using the globbing) delete all txt
files.
Keep in mind that you can always turn off this for next command by using:
s.$("-g").rm("*.txt");
The runA
is almost identical to $
:
await $`cat package.json | grep name`;
await s.runA`cat package.json | grep name`;
…but for cp
/mv
/… you need to rewrite code to s.*
:
echo(s.cat("package.json").grep("name"));
// or
echo(s.grep("name", "package.json"));