npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

nodejsscript

v1.0.3

Published

A tool for writing better “one–file” scripts

Downloads

162

Readme

NodeJS Script – Easy cross-platform “one–file” scripting

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.

Goods

Open ‘▸’ sections for quick overview and/or navigate to link(s) for more detailed information and documentation.

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");
// 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 for nodejsscript --eval/nodejsscript --print bellow)
  • …for more see related section in docs
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(
	Number,
	v=> `Result is: ${v}`,
	echo
)("42");

Provides functional way to combine JavaScript functions.

These are supported in nodejsscript:

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

Use to debug your script, similar to node inspect (Node.js — Debugging Node.js).

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).

  • 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
//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.

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
// 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 { 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 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 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.

Quick links/info

Getting started

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.

Installation

  1. install NodeJS using nvm-sh/nvm: Node Version Manager[^ORnpm] — tested/used on node@v20node@v16
  2. install nodejsscript package from npm registry[^ORnjs]
    • npm install nodejsscript --location=global: to use globally
    • npm install nodejsscript: to use locally in the package

Usage

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

#!/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.

Security guidelines

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, nodejsscripts 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");

Migration from zx

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"));

Contribute

[^ORnpm]: Alternatively curl -sL install-node.vercel.app/20 | bash [^ORnjs]: Or: npm install https://github.com/jaandrle/nodejsscript --global