开发计划署的JavaScript--用zx编写脚本的介绍

217 阅读5分钟

所以你刚刚在同一句话中读到了DevOps和JavaScript。你是疯了还是好奇心太强了?无论如何,你不会经常把这两者放在一起。在过去的日子里,JavaScript被用来给网页添加一点洒脱的东西。它不可能在DevOps中有自己的位置,对吗?哦,是的,它有。

JavaScript(以及现在的TypeScript)已经渗入到软件工程的每一个毛孔中。首先,它是一个前端的东西。然后,它变成了后端东西(与Node.js)。然后是物联网。而现在,我们终于带着我们的JS行李来到了DevOps的火车站。下了这趟该死的火车,打开你的行李。在你读完这篇文章之后,你将离成为一名JavaScript DevOps工程师更近一步。

Old typewriter cover image

照片:Markus WinkleronUnsplash

介绍zx

首先,让我向你介绍zx。它是一个让你用JavaScript轻松编写脚本的库。要开始使用,你需要把它安装为一个全局包,用

$ npm i -g zx

很好,现在你在本地有了它,让我们写第一个脚本来试试吧。为了在脚本中使用JS,我们需要放一个标题,表明我们将使用zx。因此,打开你喜欢的编辑器,创建一个新的文件test.mjs ,并在第一行写上这个:

#!/usr/bin/env zx

很好,现在让我们在那里添加一些JavaScript:

#!/usr/bin/env zx

console.log("Hello from JS script")

让我们通过在终端运行以下命令使其可执行:

$ chmod +x test.mjs

然后,让我们用以下命令来运行这个脚本:

$ ./test.mjs

你的脚本向你问好了吗?是的,我们得到了它的工作。如果你想让文件变成.js ,你需要使用不同的语法,像这样:

#!/usr/bin/env zx

;(async function () {
  console.log("Hello from JS script")
})()

.mjs 很方便,因为你可以使用顶级的await 调用。但是对于.js ,你需要创建一个async 函数并使用await there

你还可以用运行脚本:

$ zx test.mjs
$ zx test.js

酷,我们复习了使用zx的基本知识。让我们来看看一些复杂的例子,看看zx真正的闪光点在哪里。我在我的dotfiles里有一个安装脚本来设置我的环境。它主要是处理我每天使用的程序的安装问题。不久前我用Ruby写的,但下面让我们用JavaScript重写它。

zx的真正用途

我们将创建一个脚本,为我们安装一些东西:vim-plug ripgrep zsh和oh-my-zsh 一个终端的主题。 它还将从我的dotfiles repo中复制dotfiles到本地磁盘的适当位置。而且,它将在.zshrc 。"哇,哇,哇,慢点"--你一定在想。对不起,我有点超前了。我们将从安装ripgrep开始,学习如何从zx脚本中运行命令。

运行命令

简而言之,ripgrep是你的文本/regex搜索伙伴。它的速度超快,而且易于使用。如果你还没有使用它的话,就去看看吧。在安装它之前,让我们检查一下ripgrep 是否可用。如果它已经在那里了,我们显然不想安装它。我们可以从我们的脚本which rg 中运行一个命令,它应该让我们知道ripgrep是否在那里。让我们试试吧:

#!/usr/bin/env zx

await $`which rg`

将脚本保存为install.mjs ,然后通过zx install.mjs 来运行它。它失败了吗?很好,这正是我们的目的所在。如果你没有安装rg ,那么which rg 这个命令会导致脚本失败,提前退出。为什么呢?嗯,这就是zx的工作原理。你可以用$\some command "来指定一个命令,它将返回一个承诺。基本上,它将产生一个子进程并在那里运行指定的命令。如果命令失败,而你又没有处理失败的承诺,程序就会中断。很聪明,是吧?

好了,让我们来处理一个我们没有安装rg (ripgrep)的情况。我们将try catch which rg 命令并在catch 块中安装ripgrep 。我是在Mac上,我使用brew 来安装东西。你可以使用你所使用的任何软件包管理器,我不会告诉你该怎么做,咄。让我们看看我们如何以编程方式安装它:

#!/usr/bin/env zx

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

try {
  await $`which rg`
  console.log(chalk.green("You already have rg, awesome!"))
} catch {
  console.log(chalk.red("Nope, installing rg (rigrep)"))

  await $`brew install ripgrep`
}

看起来很整洁,是吧?试着把这段代码保存到install.mjs 文件中,然后用zx install.mjs 运行它。你注意到的第一件事将是一个蓝色的文本说--"检查rg是否存在..."。哇,颜色。是的,你可以使用chalk ,开箱即用的zx,并为你的输出文本上色。

如果你没有rg ,你会在终端得到一个红色的文本,后面是负责安装ripgrep 的命令输出。同样,如果你是在Linux上,你可以用apt ,甚至sudo apt 来代替brew 。你最了解你的系统。

如果你有rg可执行,它将以绿色文本打印出 "你已经有rg了,真棒!",我们就可以进入下一步了。

不抛出异常

我们现在就来探讨一下zx 中的nothrow 方法。为了说明我们如何做到这一点,让我们首先尝试实现将.vimrc我的dotfiles复制到home目录下的系统.vimrc

我们的想法是让install.mjs.vimrc 复制到~/.vimrc ,但它应该询问用户是否要覆盖现有的~/.vimrc 。我们可以用cp -i 命令轻松做到这一点,它将询问你是否希望覆盖你所复制的目的地。下面是对-i 标志的解释:

$ man cp

...

     -i    Cause cp to write a prompt to the standard error output before copying a file that would overwrite an existing file. If the
           response from the standard input begins with the character ‘y’ or ‘Y’, the file copy is attempted. (The -i option overrides any
           previous -n option.)
...

让我们在我们的install.mjs 中这样做:

#!/usr/bin/env zx

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

保存文件并运行zx install.mjs 。如果你第一次运行它,并且没有文件,你将不会得到覆盖的提示。但是,如果你重新运行它,它问你是否要覆盖--输入n ,表示不,就会停止脚本。为什么?嗯,cp -i .vimrc ~/.vimrc 会像这样返回退出代码1:

$ zx test.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:///Users/.../test.mjs:4:8
    exit code: 1

你的第一个想法一定是--让我们做一个try catch 块,抓住那个命令,不让它结束我们的脚本。你是对的,这可以做到。但是,我们想在这里尝试一下nothrow 方法。让我们像这样把我们的命令包在里面:

#!/usr/bin/env zx

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

然后,当我们运行zx install.mjs ,我们得到:

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

所以,nothrow 不会使我们的脚本突然结束,而且它将默默地忽略 "失败 "的命令。多么整洁啊!我们可以在这里做别的事情吗?当然可以。准备好迎接奖励回合吧。

奖励回合:获得操作系统的归属地

在前面的例子中,我们经常使用~/ 。我们怎样才能使它变得更加不可知和 "正确 "呢?幸运的是,有os.homedir() ,我们可以使用并确保我们是安全的。对吗?对的。让我们重构一下我们的代码以使用它:

#!/usr/bin/env zx

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

哦,哇,但这能行吗?你知道该怎么做,保存文件,然后运行zx install.mjs 。你应该得到与下面类似的东西:

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

现在每个会读到你的JS脚本的人都会--我很幸运能认识/雇用/工作/和这个人在一起,因为你是如此贴心和关心。但玩笑归玩笑,这里有一个极好的事情是,你不需要明确地导入os 来使用它,zx 已经为我们做了,这导致了一个干净而简短的脚本来复制一个文件。谢谢你,zx ,你真棒。

让我们尝试一下zx 中的另一个功能--提问的能力。

提出问题

让我们使用之前复制文件的例子,但让我们写出我们的逻辑,询问用户是否要覆盖该文件。感谢zx ,我们有了可以使用的question 方法。让我们像这样试一下:

#!/usr/bin/env zx

const homeDir = os.homedir()

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

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

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

这里有很多事情要做。我们首先检查${homeDir}/.vimrc 是否存在。如果存在,我们询问用户是否要覆盖它。如果小写字母的答案是 "y",我们就覆盖该文件。如果不是,我们就打印出该脚本不会覆盖该文件。最后,如果没有${homeDir}/.vimrc ,我们就调用基本的cp 命令,而没有之前的内置提示。

如果我们运行脚本并说'y',这就是输出结果:

$ zx test.mjs
Copying .vimrc to /Users/nikolalsvk/.vimrc
Do you want to overwrite /Users/nikolalsvk/.vimrc? (y/n [n]) y
Overwriting /Users/nikolalsvk/.vimrc
$ cp .vimrc /Users/nikolalsvk/.vimrc

而且,如果我们输入其他东西或者直接按回车键,我们得到的结果是这样的:

$ zx test.mjs
Copying .vimrc to /Users/nikolalsvk/.vimrc
Do you want to overwrite /Users/nikolalsvk/.vimrc? (y/n [n])
Not overwritting /Users/nikolalsvk/.vimrc

很好,我们现在已经了解了zx 的几乎所有基本功能,这些功能使用起来非常容易。

其他功能

我们的安装脚本已经涵盖了一些功能。让我们看看还有什么。

你能fetch 我那个东西吗?

你还可以使用fetch ,以获取任何URL。用它来试试吧:

#!/usr/bin/env zx

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

运行后,这是我得到的东西:

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

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

cd 你的回家之路

你可以使用cd 命令来轻松地在文件系统中移动。让我们打印出你(也许)不知道你的Unix系统上有的《指环王》日历:

#!/usr/bin/env zx

cd("/usr/share/calendar")

await $`cat calendar.lotr`

你至少应该看到这一部分:

01/05   Fellowship enters Moria
01/09   Fellowship reaches Lorien
01/17   Passing of Gandalf
02/07   Fellowship leaves Lorien
02/17   Death 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 Dead
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 unexpected party
04/23   Crowning of King Elessar
05/19   Arwen leaves Lorien to wed King Elessar
06/11   Sauron attacks Osgiliath
06/13   Bilbo returns to Bag End
06/23   Wedding of Elessar & Arwen
07/04   Gandalf imprisoned by Saruman
07/24   The ring comes to 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 company 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   End of War of the Ring
11/16   Bilbo reaches the Lonely Mountain
12/05   Death of Smaug
12/16   Fellowship begins Quest

还有一些zx"的功能,你可以在GitHub上的官方版本中查看。README是相当详细的,可以广泛地帮助你建立任何你想要的东西。

总结

谢谢你读到这里,这对我意义重大。今天我们学到了很多关于zx 。你现在离成为一名JavaScript DevOps工程师又近了一步,恭喜你 🎉。你是否已经感到自豪和富有成效?不错,很高兴我帮了忙。

我们复习了zx 的几个特点:

  • $\command`调用命令的能力 - 它将产生一个你需要等待的进程。它也可以抛出一个你需要捕捉的异常,所以要小心。
  • nothrow ,可以确保该命令不会破坏你的脚本。
  • 我们了解了questions ,以及如何使你的脚本具有交互性。
  • fetch ,可以从网上获取URL(也许还有本地的?)
  • 你可以用cd 进行导航。

这篇博文的内容就差不多了。加入新闻通讯,因为我打算用TypeScript做所有这些事。是的,每个人都在重写他们的代码库,这是一个热门的东西。另外,如果你喜欢DevOps JS的想法,请告诉我,我会写更多关于它的内容。

你可以在GitHub上我的dotfiles中找到我从Ruby转换为JavaScript的脚本。这里是完整的install.mjs 文件。如果你喜欢,请留下一颗星。如果你不喜欢,请迅速关闭浏览器标签。