Tuesday, June 25, 2024
HomeRuby On RailsDevOps JavaScript - Intro to Writing Scripts With zx

DevOps JavaScript – Intro to Writing Scripts With zx

So that you simply learn DevOps and JavaScript in the identical sentence. Are you mad or madly curious? In any case, you don’t put these two collectively fairly often. JavaScript was used so as to add a little bit of sprinkle to the online pages again within the day. No manner it has its place in DevOps, proper? Oh, sure, it does.

JavaScript (and now TypeScript) has poured down into each pore of software program engineering. First, it was a frontend thingy. Then, it turned a backend thingy (with Node.js). Then web of issues thingy. And now, lastly, we arrived on the DevOps prepare station with our JS baggage. Get off the frickin’ prepare and unpack your luggage. You’re going to be one step nearer to changing into a JavaScript DevOps engineer after you learn this submit.

Old typewriter cover image

Photograph by Markus Winkler on Unsplash

Introducing zx

First, let me introduce you to zx. It’s a library that allows you to write scripts simply utilizing JavaScript. To get began, it is advisable set up it as a worldwide bundle with

Good, now that you’ve it regionally, let’s write the primary script to attempt it out. To make use of JS inside scripts, we have to put a header that signifies we’ll use zx. So open up your favourite editor, create a brand new file check.mjs, and put this on the primary line:

Wonderful, now let’s add some JavaScript there:

console.log("Hey from JS script")

Let’s make it executable by operating the next command within the terminal:

After which, let’s run the script with:

Did your script greet you? Yay, we received it working. If you wish to have the file as .js, it is advisable use a special syntax like so:

;(async perform () {
  console.log("Hey from JS script")

The .mjs is helpful as a result of you need to use top-level await calls. However with .js, it is advisable create an async perform and use await there.

You may also run scripts with:

$ zx check.mjs
$ zx check.js

Cool, we went over the fundamentals of utilizing zx. Let’s go over some difficult examples and see the place zx really shines. I’ve an set up script in my dotfiles to arrange my atmosphere. It primarily offers with the set up of packages I exploit in my each day. I wrote it in Ruby some time in the past, however let’s rewrite it in JavaScript beneath.

Actual use for zx

We’re going to create a script that installs a few issues for us:
zsh and oh-my-zsh
a theme for the terminal
It’ll additionally copy dotfiles from my dotfiles repo into the correct place on the native disk. And, it’s going to add one line to .zshrc. “Wow, wow, wow, decelerate” – you should be considering. Sorry, I received a bit bit forward of myself. We’ll begin with putting in ripgrep and discover ways to run instructions from zx scripts.

Operating instructions

To maintain it brief, ripgrep is your textual content/regex search buddy. It’s super-fast and simple to make use of. Test it out should you’re not utilizing it. Earlier than putting in it, let’s test if ripgrep is obtainable. We don’t need to set up it if it’s already there, clearly. We will run a command from our script which rg that ought to tell us if ripgrep is there or not. Let’s attempt it out:

Save the script as set up.mjs and run it by way of zx set up.mjs. Did it fail? Good, that is precisely the purpose we have been aiming for. When you don’t have rg put in, the command which rg will trigger the script to fail and exit early. Why? Effectively, that is how zx works. You may specify a command with $some command“ and it’ll return a promise. Mainly, it’s going to spawn a toddler course of and run the required command there. This system will break if the command fails and also you don’t deal with the failed promise. Fairly good, huh?

OK, so let’s deal with a case the place we don’t have rg (ripgrep) put in. We’re going to attempt catch the which rg command and set up ripgrep within the catch block. I’m on a Mac, and I exploit brew to put in issues. You need to use no matter bundle supervisor you employ, I’m not going to inform you what to do, duh. Let’s see how we are able to programmatically set up it:

console.log(chalk.blue("Checking if rg exists..."))

attempt {
  await $`which rg`
  console.log(chalk.inexperienced("You have already got rg, superior!"))
} catch {
  console.log(chalk.purple("Nope, putting in rg (rigrep)"))

  await $`brew set up ripgrep`

Seems to be neat, huh? Strive saving this code into the set up.mjs file and run it with zx set up.mjs. The very first thing you discover will likely be a blue textual content saying – “Checking if rg exists…“. Wow, colours. Yeah, you need to use chalk out of the field with zx and shade your output textual content on the go.

When you don’t have rg, you’re going to get a purple textual content in your terminal, adopted by the command’s output accountable for putting in ripgrep. Once more, should you’re on Linux, you may substitute brew with apt and even sudo apt. your system the most effective.

When you have rg executable, it’s going to print out “You have already got rg, superior!” in inexperienced textual content, and we’re good to go to our subsequent step.

Not throwing exceptions

We’ll discover the nothrow methodology from zx now. To point out how we are able to do it, let’s first attempt to implement the copying of .vimrc from my dotfiles to the system .vimrc within the house listing.

The thought is to have the set up.mjs copy the .vimrc to ~/.vimrc, however it ought to ask whether or not the person desires to overwrite the prevailing ~/.vimrc. We will do that simply with the cp -i command, which is able to ask whether or not you want to overwrite the vacation spot you’re copying to. Right here’s the reason of the -i flag:

$ man cp


     -i    Trigger cp to write a immediate to the usual error output earlier than copying a file that might overwrite an current file. If the
           response from the usual enter begins with the character ‘y’ or ‘Y’, the file copy is tried. (The -i choice overrides any
           earlier -n choice.)


Let’s do it like this in our set up.mjs:

console.log(chalk.blue("Copying .vimrc to ~/.vimrc"))
await $`cp -i .vimrc ~/.vimrc`

Save the file and run the zx set up.mjs. When you run it the primary time and there’s no file, you gained’t get the overwrite immediate. However, should you rerun it and it asks you whether or not to overwrite or not – inputting n for no will cease the script. Why? Effectively, cp -i .vimrc ~/.vimrc returns exit code 1 like so:

$ zx check.mjs
Copying .vimrc to ~/.vimrc
$ cp -i .vimrc ~/.vimrc
overwrite ~/.vimrc? (y/n [n]) n
not overwritten
Error: overwrite ~/.vimrc? (y/n [n]) not overwritten
    at file:///Customers/.../check.mjs:4:8
    exit code: 1

Your first thought should be – let’s do a attempt catch block and catch that command from ending our script. And you might be proper, it may possibly work. However, we need to check out the nothrow methodology right here. Let’s wrap our command in it like so:

console.log(chalk.blue("Copying .vimrc to ~/.vimrc"))
await nothrow($`cp -i .vimrc ~/.vimrc`)

After which, once we run zx set up.mjs, we get:

$ zx check.mjs
Copying .vimrc to ~/.vimrc
$ cp -i .vimrc ~/.vimrc
overwrite /Customers/nikolalsvk/.vimrc? (y/n [n]) n
not overwritten

So nothrow gained’t make our script finish abruptly, and it’ll silently ignore the “failed” command. How neat! Can we do one thing else right here? You wager we are able to. Prepare for the bonus spherical.

BONUS ROUND: get the OS homedir

We use the ~/ loads within the earlier examples. How can we make it extra agnostic and ‘proper’? Fortunately, there’s os.homedir() to the rescue that we are able to use and make certain we’re protected. Proper? Proper. Let’s refactor our code a bit to make use of it.

const homeDir = os.homedir()
console.log(chalk.blue(`Copying .vimrc to ${homeDir}/.vimrc`))
await nothrow($`cp -i .vimrc ${homeDir}/.vimrc`)

Oh wow, however will it work? the drill, save the file, and run zx set up.mjs. It’s best to get one thing just like beneath:

$ zx check.mjs
Copying .vimrc to /Customers/nikolalsvk/.vimrc
$ cp -i .vimrc /Customers/nikolalsvk/.vimrc
overwrite /Customers/nikolalsvk/.vimrc? (y/n [n]) n
not overwritten

Now everybody who will learn your JS scripts will likely be – I’m fortunate to know/employed/labored/frolicked with this man since you’re so candy and caring. However jokes apart, a wonderful factor right here is that you just don’t should explicitly import os to make use of it, zx already does it for us, which results in a clear and temporary script to repeat a file. Thanks, zx, you rock.

Let’s check out another characteristic from zx – the power to ask questions.

Asking questions

Let’s use our earlier instance of copying a file, however let’s write our logic that may ask the person whether or not he desires to overwrite the file or not. Because of zx, we now have the query methodology that we are able to use. Let’s attempt it out like so:

const homeDir = os.homedir()

console.log(chalk.blue(`Copying .vimrc to ${homeDir}/.vimrc`))

if (fs.exists(`${homeDir}/.vimrc`)) {
  const overwrite = await query(
    `Do you need to overwrite ${homeDir}/.vimrc? (y/n [n]) `

  if (overwrite.toLowerCase().startsWith("y")) {
    console.log(chalk.inexperienced(`Overwriting ${homeDir}/.vimrc`))
    await $`cp .vimrc ${homeDir}/.vimrc`
  } else {
    console.log(chalk.blue(`Not overwritting ${homeDir}/.vimrc`))
} else {
  await $`cp .vimrc ${homeDir}/.vimrc`

Numerous issues happening right here. We first test whether or not ${homeDir}/.vimrc exists. In that case, we ask the person whether or not they need to overwrite it. We overwrite the file if the lowercased reply matches ‘y’. If not, we print out that the script gained’t overwrite the file. And at last, if there’s no ${homeDir}/.vimrc, we name the essential cp command with out the built-in immediate we had earlier than.

If we run the script and say ‘y’, that is the output:

$ zx check.mjs
Copying .vimrc to /Customers/nikolalsvk/.vimrc
Do you need to overwrite /Customers/nikolalsvk/.vimrc? (y/n [n]) y
Overwriting /Customers/nikolalsvk/.vimrc
$ cp .vimrc /Customers/nikolalsvk/.vimrc

And, if we enter one thing else or simply press enter, that is what we get:

$ zx check.mjs
Copying .vimrc to /Customers/nikolalsvk/.vimrc
Do you need to overwrite /Customers/nikolalsvk/.vimrc? (y/n [n])
Not overwritting /Customers/nikolalsvk/.vimrc

Cool, we now have now gone by means of nearly all the important options of zx, that are a breeze to make use of.

Different options

We coated a pair with our set up script. Let’s see what else is there.

Are you able to fetch me that factor?

You even have the fetch out there to fetch any URL. Strive it out with:

const response = await fetch("https://api.github.com/octocat")
console.log(await response.textual content())

After operating it, that is what I received:

$ zx fetch.mjs
$ fetch https://api.github.com/octocat

               MMM.           .MMM
               MMMMMMMMMMMMMMMMMMM      ____________________________
              MMMMMMMMMMMMMMMMMMMMM    |                            |
             MMMMMMMMMMMMMMMMMMMMMMM   | Maintain it logically superior. |
            MMMMMMMMMMMMMMMMMMMMMMMM   |_   ________________________|
            MMMM::- -:::::::- -::MMMM    |/
             MM~:~ 00~:::::~ 00~:~MM
        .. MMMMM::.00:::+:::.00::MMMMM ..
              .MM::::: ._. :::::MM.
          -MM        MMMMMMM
          ^  M+     MMMMMMMMM
              MMMMMMM MM MM MM
                   MM MM MM MM
                   MM MM MM MM

cd your manner house

You need to use the cd command to maneuver across the file system simply. Let’s print out the Lord of the Rings calendar you (perhaps) didn’t know you had in your Unix system:


await $`cat calendar.lotr`

It’s best to see no less than this half:

01/05   Fellowship enters Moria
01/09   Fellowship reaches Lorien
01/17   Passing of Gandalf
02/07   Fellowship leaves Lorien
02/17   Demise of Boromir
02/20   Meriadoc & Pippin meet Treebeard
02/22   Passing of King Elessar
02/24   Ents destroy Isengard
02/26   Aragorn takes the Paths of the Useless
03/05   Frodo & Samwise encounter Shelob
03/08   Deaths of Denethor & Theoden
03/18   Destruction of the Ring
03/29   Flowering of the Mallorn
04/04   Gandalf visits Bilbo
04/17   An sudden occasion
04/23   Crowning of King Elessar
05/19   Arwen leaves Lorien to wed King Elessar
06/11   Sauron assaults Osgiliath
06/13   Bilbo returns to Bag Finish
06/23   Wedding ceremony of Elessar & Arwen
07/04   Gandalf imprisoned by Saruman
07/24   The ring involves Bilbo
07/26   Bilbo rescued from Wargs by Eagles
08/03   Funeral of King Theoden
08/29   Saruman enters the Shire
09/10   Gandalf escapes from Orthanc
09/14   Frodo & Bilbo's birthday
09/15   Black riders enter the Shire
09/18   Frodo and firm rescued by Bombadil
09/28   Frodo wounded at Weathertop
10/05   Frodo crosses bridge of Mitheithel
10/16   Boromir reaches Rivendell
10/17   Council of Elrond
10/25   Finish of Warfare of the Ring
11/16   Bilbo reaches the Lonely Mountain
12/05   Demise of Smaug
12/16   Fellowship begins Quest

There are a pair extra of zx’s characteristic, and you’ll test them out on the official repo on GitHub. The README is fairly detailed and will help you extensively with no matter you’re attempting to construct.

Summing up

Thanks for studying this far, it means loads to me. As we speak we realized loads about zx. You at the moment are one step nearer to changing into a JavaScript DevOps engineer, congrats 🎉. Do you already really feel proud and productive? Good, glad I helped.

We went over a few options of zx:

  • The flexibility to name a command with $command` – it’s going to spawn a course of that it is advisable await. It will probably additionally throw an exception it is advisable catch, so watch out for that.
  • There’s nothrow that may be certain the command doesn’t break your script.
  • We realized in regards to the questions and how you can make your script interactive.
  • There’s fetch to fetch URLs from the online (and perhaps regionally?)
  • You may navigate with cd.

And that’s just about it for this weblog submit. Be part of the publication as a result of I plan to do all of this however in TypeScript. Yep, the recent thingy everyone seems to be rewriting their codebases to. Additionally, let me know should you like the thought of DevOps JS, and I’ll write extra on it.

You’ll find the script I transformed from Ruby to JavaScript in my dotfiles on GitHub. Right here’s the full set up.mjs file. Go away a star should you like. Shut the browser tab rapidly should you don’t.

Think about sharing the weblog submit with your mates and colleagues. Perhaps somebody is simply ready for one of these content material. Right here’s a fast technique to do it on Twitter:

That’s all I’ve for you at this time. Catch you within the subsequent one.




Please enter your comment!
Please enter your name here

Most Popular

Recent Comments