- 3 min read

Writing Shell Scripts in JavaScript with ZX

img of Writing Shell Scripts in JavaScript with ZX

Photo by JÉSHOOTS from Pexels

I was working in the past on a project, that was still requiring us to do manual releases. We followed a short guide consisting of Terminal commands. Only a minor adjustment was needed for each new release. We could have greatly benefited from writing a simple bash script. However I am not a sysadmin and tend to rarely write shell scripts. It would take way too much time to debug and create a working bash script. We decided copy and pasting code is just more efficient.

Luckily the developers at Google also did not want to write bash scripts. However apparently they got the resources to create something even better:

zx - A tool for writing better scripts

It allows you to combine JavaScript with the Bash commands to create complex scripts. At its core its just a simpler wrapper syntax for working with NodeJS child_process.

The Release Script

Our build steps are basically:

  1. Bump the version in package.json with the command npm version patch
  2. Pass the new version number to the releaseCMD

The reason to do this manually was that there is no easy way to read the new version number from the package.json to store it in a variable to pass it into the releaseCMD

The ZX Script

Output Text to the console

Its always useful to output stuff to the console. You can simply use console.log("Hello") to output text on the console.

Execute Commands

As alternative if you want to execute any terminal command like echo to output text. You write it as $\``echo Hello


The Github Page of ZX has a list of typical commands and examples what you can do with them. One of them is to read the package version

const { version } = await   fs.readJson("./package.json");

Let’s put it all together

I created a directory scripts that will contain all the future projects scripts and created the file release.mjs


console.log('Running Release Script')
await $`npm version patch` // Bumps package version
const { version } = await fs.readJson('./package.json') // Reads new package version
try {
	await $`echo releaseCMD -version ${version}` //Replace with actual command
} catch (p) {
	console.log(`Error: ${p.stderr} ExitCode ${p.exitCode}`) //Logs Error to the terminal

Executing the Script

You either can install zx globally with the command

npm i -g zx

or you can run it with npx zx <scriptname>.

Personally as soon as I am done with the script I don’t want to deal with this anymore. I want that every team member can use the script without any explanation. Thus I added it as a script in package.json


	"scripts": {
		"release": "npx zx ./scripts/release.tjs"

Now you can run it like any other script npm run release


Unfortunatly I did not have a lot of time to play around with ZX. I am sure that it is capable of creating very complex scripts.

For my usecase I did not look into executing scripts as an admin, or creating cross platform scripts etc. Again I am not a sysadmin.

In the end, I think ZX is a great way of creating shell scripts without having to deal with BASH. Thus with your TypeScript/JavaScript knowledge you can quickly create scripts to automate the boring stuff.