{"id":18184932,"url":"https://github.com/jaandrle/nodejsscript","last_synced_at":"2026-01-06T17:23:57.709Z","repository":{"id":60290327,"uuid":"537087457","full_name":"jaandrle/nodejsscript","owner":"jaandrle","description":"A tool for writing better “one–file” scripts","archived":false,"fork":false,"pushed_at":"2024-09-05T12:29:21.000Z","size":318,"stargazers_count":4,"open_issues_count":2,"forks_count":1,"subscribers_count":2,"default_branch":"main","last_synced_at":"2024-10-05T17:17:54.484Z","etag":null,"topics":["bash","cli","javascript","nodejs","scripting","shell","shell-script","shelljs"],"latest_commit_sha":null,"homepage":"https://www.npmjs.com/package/nodejsscript","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jaandrle.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null}},"created_at":"2022-09-15T15:21:00.000Z","updated_at":"2024-09-05T14:51:48.000Z","dependencies_parsed_at":"2023-02-17T21:31:16.331Z","dependency_job_id":"5f535262-3954-4c4b-bac7-d43a5e51ca28","html_url":"https://github.com/jaandrle/nodejsscript","commit_stats":{"total_commits":32,"total_committers":1,"mean_commits":32.0,"dds":0.0,"last_synced_commit":"86a8a95a6c5476b2066d109f12749f9788f00000"},"previous_names":[],"tags_count":6,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaandrle%2Fnodejsscript","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaandrle%2Fnodejsscript/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaandrle%2Fnodejsscript/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jaandrle%2Fnodejsscript/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jaandrle","download_url":"https://codeload.github.com/jaandrle/nodejsscript/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":222779989,"owners_count":17036546,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bash","cli","javascript","nodejs","scripting","shell","shell-script","shelljs"],"created_at":"2024-11-02T22:05:18.774Z","updated_at":"2026-01-06T17:23:57.704Z","avatar_url":"https://github.com/jaandrle.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# NodeJS Script – Easy cross-platform “one–file” scripting\n\nThis package serves as an alternative to [google/zx](https://github.com/google/zx) for example. The key difference\nis to provide Unix shell commands in a cross-platform compatible way and usable\ninside JavaScript.  This is primarily achieved by using [shelljs/shelljs](https://github.com/shelljs/shelljs) library.\n\nYou can compare the final script code to `zx` example:\n```javascript\n#!/usr/bin/env nodejsscript\n\necho(s.grep(\"name\", \"package.json\"));\n\ns.run`git branch --show-current`\n.xargs(s.run, \"dep deploy --branch={}\");\n\ns.run`sleep 1; echo 1`;\ns.run`sleep 2; echo 2`;\ns.run`sleep 3; echo 3`;\n\npipe( $.xdg.temp, s.mkdir )(\"foo bar\");\n```\n…also see [examples](./examples) or [Show And Tell · Discussions](https://github.com/jaandrle/nodejsscript/discussions/categories/show-and-tell).\n\n## Goods\nOpen ‘▸’ sections for quick overview and/or navigate to link(s) for more detailed\ninformation and documentation.\n\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ca href=\"./docs/nodejsscript/namespaces/s/README.md\"\u003es #shelljs\u003c/a\u003e namespace \u003cem\u003e(unix shell-like commands in JavaScript)\u003c/em\u003e\u003c/summary\u003e\n\n```js\ns.ls().forEach(echo); // ShellArray\ns.cat(\"package.json\").xargs(JSON.parse).trim(); // ShellString\ns.read().then(echo); // Promise\u003cShellString\u003e\ns.runA`git branch --show-current`.then(echo); // Promise\u003cShellString\u003e\n\nconst { code, stdout, stderr } = s.run`git branch --show-current`; // ShellString\n```\n\nContains functions from [shelljs/shelljs](https://github.com/shelljs/shelljs) library mimic the bash utilities\nand some additional added by nodejsscript. Typically `s.cat`/`s.grep`/…,\nto run other than builtin commands use `s.run`/`s.runA`.\n\nThese functions returns `ShellArray`/`ShellString`/`Promise\u003cShellString\u003e`,\nthese types are union types of `string[]`/`string` with [`ShellReturnValueNJS`](./docs/nodejsscript/namespaces/s/interfaces/ShellReturnValueNJS.md).\nIn simple terms, you can use it as `string[]`/`string`/`Promise\u003cstring\u003e` or\nread the commad exit `code` and `stdout`/`stderr`. If it makes sence, you can\npipe output to other shelljs commands. Special pipeing is `to`/`toEnd` for\nredirectiong output to the file.\n\n```js\ns.echo(\"Hello World!\").to(\"hello.txt\");\n```\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ca href=\"./docs/nodejsscript/namespaces/$/README.md\"\u003e$\u003c/a\u003e (\n\t\t\u003ca href=\"./docs/nodejsscript/namespaces/$/functions/api.md\"\u003e$.api() #sade\u003c/a\u003e,\n\t\t\u003ca href=\"./docs/nodejsscript/namespaces/xdg/namespaces/xdg/README.md\"\u003e$.xdg\u003c/a\u003e,\n\t\t…\n\t)\n\tnamespace \u003cem\u003e(nodejsscript/cli related functions/variables)\u003c/em\u003e\u003c/summary\u003e\n\n```js\n// ls.mjs\n$.api()\n.command(\"ls [folder]\", \"list files\")\n.option(\"-a\", \"list all files\")\n.action((folder, options)=\u003e {\n\tif(Object.keys(options).length === 0)\n\t\ts.ls(folder);\n\telse {\n\t\tconst opts= pipe(\n\t\t\tObject.entries,\n\t\t\to=\u003e o.map(([k, v])=\u003e [ \"-\"+k, v ]),\n\t\t\tObject.fromEntries\n\t\t)(options);\n\t\ts.ls(opts, folder);\n\t}\n\t$.exit(0);\n})\n.parse();\n// ls.mjs ls -a\n```\n\n- contains cli/nodejsscript related functions\n- for processing script arguments you can use `$[0]`/`$[1]`/… (compare with bash\n  `$0`/`$1`/…) or\n- **`$.api()`: allows to quickly create script cli API, internally uses [sade](https://github.com/lukeed/sade)\n  library (compare with [commander](https://github.com/tj/commander.js))**\n- `$.isMain(import.meta)`: detects if the script is executed as main or if it\n  is imported from another script file\n- `$.xdg`: provides cross-platform file system access for specific locations\n  (home, temp, config, … directory)\n- `$.stdin`: handles standard input when the script is run in shell pipe (can be\n  helpful for `nodejsscript --eval`/`nodejsscript --print` bellow)\n- …for more see [related section in docs](./docs/nodejsscript/namespaces/$/README.md)\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ca href=\"./docs/variables/echo.md\"\u003eecho() #css-in-console\u003c/a\u003e function/namespace\u003c/summary\u003e\n\n```js\nconst css= echo.css`\n\t.blue { color: blue; }\n\t.spin { list-style: --terminal-spin; }\n\t.success { color: green; list-style: \"✓ \"; }\n`;\necho(\"Hello %cWorld\", css.blue);\nfor(let i= 0; i \u003c 10; i++){\n\techo.use(\"-R\", \"%cLoading…\", css.spin);\n\ts.run`sleep .5`;\n}\necho(\"%cDone\", css.success);\n```\n\n- prints to console, also supports styling using CSS like syntax\n- internally uses [css-in-console](https://www.npmjs.com/package/css-in-console)\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ca href=\"./docs/functions/pipe.md\"\u003epipe()\u003c/a\u003e function\u003c/summary\u003e\n\n```js\npipe(\n\tNumber,\n\tv=\u003e `Result is: ${v}`,\n\techo\n)(\"42\");\n```\nProvides functional way to combine JavaScript functions.\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ccode\u003efetch()\u003c/code\u003e, \u003ccode\u003enew AbortController()\u003c/code\u003e\u003c/summary\u003e\n\nThese are supported in nodejsscript:\n\n- uses native `fetch()`/`AbortController` or\n- fallbacks\n\t- [node-fetch - npm](https://www.npmjs.com/package/node-fetch)\n\t- [abort-controller - npm](https://www.npmjs.com/package/abort-controller)\n\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ca href=\"./examples/eval_print.md\"\n\t\u003e\u003ccode\u003enodejsscript --eval\u003c/code\u003e/\u003ccode\u003enodejsscript --print\u003c/code\u003e\u003c/a\u003e \u003cem\u003e(quickly eval javascript code in terminal)\u003c/em\u003e\u003c/summary\u003e\n\n```bash\ncurl https://api.spacexdata.com/v4/launches/latest | \\\nnodejsscript -p '$.stdin.json()' Object.entries 'e=\u003e e.filter(([_,v])=\u003e Array.isArray(v))'\n```\n\n- *similar to `node --eval`/`node --print`*\n- you can use less verbose syntax `njs -e`/`njs -p`\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ccode\u003enodejsscript --inspect\u003c/code\u003e\u003c/summary\u003e\n\nUse to debug your script, similar to [`node inspect`](https://nodejs.org/api/debugger.html) ([Node.js — Debugging Node.js](https://nodejs.org/en/learn/getting-started/debugging)).\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ccode\u003enodejsscript --interactive\u003c/code\u003e \u003cem\u003e(REPL)\u003c/em\u003e\u003c/summary\u003e\n\nUse to run [REPL, similar to `node`/`node --interactive`/`node -i`](https://nodejs.org/en/learn/command-line/how-to-use-the-nodejs-repl).\n\n*Idea*: you can use REPL to analyze your JSON log files (pseudo code):\n\n```js\n// njs --interactive\n\u003e s.ls(\"*.json\").flatMap(f=\u003e s.cat(f).xargs(JSON.parse)).filter(x=\u003e x.error)\n\u003e _.map(x=\u003e x.error===404)\n```\n\nREPL supports tab-completion (also for folders and files).\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ccode\u003enodejsscript --completion\u003c/code\u003e \u003cem\u003e(bash completions for nodejsscript and scripts)\u003c/em\u003e\u003c/summary\u003e\n\n- provide shell completion for nodejsscript and scripts written using\n  nodejsscript (**using `$.api()`**)\n- **(for now) only for bash**\n- add `eval \"$(nodejsscript --completion bash)\"` to your '.bashrc' file\n- prepare your script cli API using `$.api()`\n- register your scritp autocompletion using\n  `nodejsscript --completion register \u003ctarget\u003e`\n\t- use global script name (your script must be also included in the PATH)\n\t  to automatically enable completions on the shell start\n\t- or (relative) path to enable completions on demand see ↙\n- use `eval \"$(nodejsscript --completion bash-local [target])\"`\n\t- empty target or path to the directory enables completions for all scripts\n\t  in the given directory recursively\n\t- script path as target enables completions for specific script only\n- see help `nodejsscript --completion`/`nodejsscript --completion help`\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ca href=\"./examples/nodejsscriptrc.md\"\n\t\u003e\u003ccode\u003e~/.config/nodejsscript/nodejsscriptrc.mjs\u003c/code\u003e\u003c/a\u003e \u003cem\u003e(\u003ccode\u003e.bashrc\u003c/code\u003e for nodejsscript)\u003c/em\u003e\u003c/summary\u003e\n\n```js\n//nodejsscriptrc.mjs\n// … my code evaluated for each nodejsscript invocation\n\n/** Custom uncaughtException function */\nexport function uncaughtException(){};\n/** Place for custom code when script starts */\nexport function onscript(){}\n/** Place for custom code when REPL starts (`--interactive`) */\nexport function onrepl(){}\n/** Place for custom code when eval starts (`--eval`/`--print`) */\nexport function oneval(){}\n```\n\nThis is very similar to `.bashrc` file, but for nodejsscript.\nUse `nodejsscript --help` to find out the location of\nthe `nodejsscriptrc.mjs` file.\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ccode\u003enjs\u003c/code\u003e alias for \u003ccode\u003enodejsscript\u003c/code\u003e\u003c/summary\u003e\n\nYou can use `njs` instead of `nodejsscript`, so see less verbose syntax:\n\n- `njs -e`/`njs -p`\n- `njs --inspect`\n- `njs`/`njs -i`/`njs --interactive`\n- `njs --completion`\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ccode\u003enpx nodejsscript\u003c/code\u003e\u003c/summary\u003e\n\n```js\n// some script file\n#!/usr/bin/env -S npx nodejsscript\n```\nYou can install/use `nodejsscript` for specific project, for example\nin combination with [jaandrle/bs: The simplest possible build system using executables](https://github.com/jaandrle/bs).\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ccode\u003eimport … from \"node:…\";\u003c/code\u003e \u003cem\u003e(node JS built-ins for \u003cstrong\u003e“free”\u003c/strong\u003e)\u003c/em\u003e\u003c/summary\u003e\n\n```js\nimport { setTimeout } from \"node:timers/promises\";\nimport { join, resolve } from \"node:path\";\n\n//.current file URL\nimport.meta.url;\n//.URL to path\nimport { fileURLToPath } from \"node:url\";\nconst file_path= fileURLToPath(import.meta.url);\n// URL is supported! (see relative reading)\ns.cat(new URL('relative_file', import.meta.url));\n\n//.crypto utils\nimport { randomUUID } from \"node:crypto\";\n\n// …\n```\n…and more, see for example [Node.js v17.9.1 Documentation](https://nodejs.org/docs/latest-v17.x/api/documentation.html#stability-overview).\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ccode\u003enodejsscript --tldr\u003c/code\u003e \u003cem\u003e(show quick summary of nodejsscript functions)\u003c/em\u003e\u003c/summary\u003e\n\n```bash\nnodejsscript --tldr s.\nnodejsscript --tldr s.cat\n```\n…this shows lits all functions and variables in `s.*` and quick summary\nof `s.cat()`. You can see all manuals in [./tldr.md](./tldr.md).\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003e\u003ccode\u003enodejsscript --global-jsconfig\u003c/code\u003e \u003cem\u003e(experimental helper for developing)\u003c/em\u003e\u003c/summary\u003e\n\n```bash\nnodejsscript --global-jsconfig add script_file\n```\n…this creates `jsconfig.json` in current working directory with `include`\nproperty containing `script_file` and current path to the `nodejsscript`\nto enable proper suggestions in IDEs (and type checking). Tested for\nVSCode and Vim with [neoclide/coc.nvim](https://github.com/neoclide/coc.nvim).\n\nYou don’t need this hack if you use `nodejsscript` in your project locally.\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\n## Quick links/info\n- changelogs and migrations info: see [Release notes](https://github.com/jaandrle/nodejsscript/releases)\n- [**You (may) not need to use `nodejsscript`**](./examples/no-nodejsscript.md)\n- [Ideas (for new features)](https://github.com/jaandrle/nodejsscript/discussions/categories/ideas)\n- Examples: [examples folder](./examples) or [Show And Tell · Discussions](https://github.com/jaandrle/nodejsscript/discussions/categories/show-and-tell)\n- **[Security guidelines](#security-guidelines) — “use `s.run`/`s.runA`, watch out globbing”**\n- [Contribute](#contribute)\n- Getting started ↙ — installation and first steps (usage)\n\n## Getting started\nOne-paragraph guide: install npm package\n`npm install nodejsscript --location=global`, create executable script file\n`touch script.mjs \u0026\u0026 chmod +x script.mjs` with shebang\n`#!/usr/bin/env nodejsscript` and run it `./script.mjs`.\n\n### Installation\n\n1. install *NodeJS* using [nvm-sh/nvm: Node Version Manager](https://github.com/nvm-sh/nvm)[^ORnpm] — tested/used on\n   `node@v20`–`node@v16`\n1. install `nodejsscript` package from npm registry[^ORnjs]\n\t- `npm install nodejsscript --location=global`: to use globally\n\t- `npm install nodejsscript`: to use locally in the package\n\n### Usage\nWrite your scripts in a file with an `.mjs` extension in order to process\nthe script as an ESM module. This is preferred way as it is more compatible\nwith current JavaScript standards. E. g. you can use `await` at the top level.\n\nAlternatively, use the `.js` extension to use “old style” commonJS code.\nE. g. you must wrap your scripts in something like `(async function () {...})()`.\n\nAdd the following shebang to the beginning of your `nodejsscript` scripts:\n```bash\n#!/usr/bin/env nodejsscript\n```\n\nNow you will be able to run your script like so:\n```bash\nchmod +x ./script.mjs\n./script.mjs\n```\n\nOr via the `nodejsscript` executable:\n\n```bash\nnodejsscript ./script.mjs\n```\n- - -\n\n\u003c!-- #region --\u003e\u003cdetails\u003e \u003csummary\u003eAlternatively when installed locally\u003c/summary\u003e\n\n```bash\n#!/usr/bin/env -S npx nodejsscript\n```\n```bash\nnpx nodejsscript ./script.mjs\n```\n\n\u003c!-- #endregion --\u003e\n\u003c/details\u003e\n\n- - -\n\nAll function (`shelljs`, `fetch`, …) are registered as global\nnamespaces/functions: … **see [Goods](#goods)** or full\n*documentation generated from type definitions (focus on **Public**\nitems)*: [**docs/**](./docs/README.md). Conventionally, camelCase names are used for\nfunctions and snake\\_case for variables/constants.\n\n## Security guidelines\n**`run()`/`runA()` command injection**: this advice applies to\n`child_process.exec()` just as much as it applies to `s.run()`. It is\npotentially risky to run commands passed for example by user input:\n```js\nfunction curlUnsafe(urlToDownload){ return s.run('curl ' + urlToDownload); }\ncurlUnsafe('https://some/url ; rm -rf $HOME');\n//=\u003e curl https://some/url ; rm -rf $HOME\n```\nTherefore, `nodejsscript`s `s.run()` provide way to escapes untrusted parameters:\n```js\nfunction curl(url){ return s.run(\"curl ::url::\", { url }); }\ncurl('https://some/url ; rm -rf $HOME');\n//=\u003e curl 'https://some/url ; rm -rf $HOME'\n```\n…you can also use as template function (but without command specific options):\n```js\nfunction curl(url){ return s.run`curl ${url}`; }\ncurl('https://some/url ; rm -rf $HOME');\n//=\u003e curl 'https://some/url ; rm -rf $HOME'\n```\n\n…*Note: The ['xargs()'](../interfaces/s.XargsFunction.md) by default also\nescapes piped strings.*\n\n*…Note 2: `s.run(…cmd, …vars)` is also helpful for escaping parameters passed\nas variables (e.g. arrays).*\n\n*…Note 3: ShellJS also provides `s.exec`, but `s.run` should be preferred way\nto execute commands.*\n\n**Glob injection (all commands)**: Most ShellJS commands support [glob](https://github.com/isaacs/node-glob) expansion,\nexpanding wildcards such as `*` to match files. While this is very powerful,\ndependent modules should exercise caution. Unsanitized user input may contain\nwildcard characters. Consider for example that the `*.txt` is valid file name,\nhowever the `s.rm(\"*.txt\")` by default (using the globbing) delete all `txt` files.\nKeep in mind that you can always turn off this for next command by using:\n```js\ns.$(\"-g\").rm(\"*.txt\");\n```\n\n## Migration from `zx`\nThe `runA` is almost identical to `$`:\n```js\nawait $`cat package.json | grep name`;\nawait s.runA`cat package.json | grep name`;\n```\n…but for `cp`/`mv`/… you need to rewrite code to `s.*`:\n```js\necho(s.cat(\"package.json\").grep(\"name\"));\n// or\necho(s.grep(\"name\", \"package.json\"));\n```\n\n## Contribute\n- [![git3moji–v1.0](https://img.shields.io/badge/git3moji–v1.0-%E2%9A%A1%EF%B8%8F%F0%9F%90%9B%F0%9F%93%BA%F0%9F%91%AE%F0%9F%94%A4-fffad8.svg?style=flat-square)](https://robinpokorny.github.io/git3moji/)\n- [Contributor Covenant Code of Conduct](./CODE_OF_CONDUCT.md)\n- [How to contribute](./CONTRIBUTING.md)\n\n[^ORnpm]: Alternatively `curl -sL install-node.vercel.app/20 | bash`\n[^ORnjs]: Or: `npm install https://github.com/jaandrle/nodejsscript --global`\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaandrle%2Fnodejsscript","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjaandrle%2Fnodejsscript","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjaandrle%2Fnodejsscript/lists"}