如何使用 NodeJS 构建命令行工具 - 分步指南

2025-06-08

如何使用 NodeJS 构建命令行工具 - 分步指南

步骤

这篇文章将指导开发人员使用 Node.js 构建 CLI 工具。您还将学习如何将该工具发布到 NPM。Node.js 允许我们使用 JavaScript 构建命令行工具。正如 NPM 仓库所示,Node.js 拥有丰富的包生态系统。
构建人们可以使用的 CLI 工具是提升编程和解决问题能力的好方法。在本文中,我们将探讨我如何创建一个用于检查网站是否正常运行的CLI 工具。您可以在此处找到源代码。

步骤

制定计划

  1. 在 nodejs 上运行应用程序
  2. 从终端获取我的参数
  3. 从isitup检查网站状态
  4. 将响应返回到终端
  5. 如果网站已启动,则创建一个启动选项。

创建 Node 应用程序

让我们为我们的项目创建一个文件夹,并导航到我们终端上的项目目录的根目录。

mkdir cli-project && cd cli-project

初始化节点项目

npm init -y

这将创建一个具有如下 package.json 结构的节点应用程序:

{
  "name": "cli-project",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },

  "keywords": [],
  "author": "",
  "license": "ISC",
}
Enter fullscreen mode Exit fullscreen mode

创建 index.js 文件

touch index.js

打开此文件并将“Hello, here is my first CLI tool”打印到控制台

\\ index.js

console.log("Hello, here is my first CLI tool")
Enter fullscreen mode Exit fullscreen mode

现在返回终端并运行node index

$ node index
Hello, here is my first CLI tool
Enter fullscreen mode Exit fullscreen mode

现在你的 Node 应用已经运行了,是时候把它转换成 Shell 命令了。
为了不用 node 命令直接调用 index.js 文件,请将下面的代码放在#!/usr/bin/env nodeindex.js 文件的顶部。

\\ index.js
#!/usr/bin/env node

console.log("Hello, here is my first CLI tool")
Enter fullscreen mode Exit fullscreen mode

接下来,我们将在 package.json 文件中添加一个 bin 属性。但是,我们的项目将在单个文件上运行,因此我们不会使用 bin 属性来指定命令名称。我们将使用 name 属性来指定。

{
  "name": "cli-project",
  "version": "1.0.0",
  // ...
  "bin": "./index.js",
  // ...
}
Enter fullscreen mode Exit fullscreen mode

如果你cli-project现在在项目目录中运行它应该返回

$ cli-project
Hello, here is my first CLI tool
Enter fullscreen mode Exit fullscreen mode

我们现在要做两点修改。我们不想让 CLI 名称变成。因此,我们将 package.json属性cli-project的值更改为namewebcheck

{
  "name": "webcheck",
  // ...
}
Enter fullscreen mode Exit fullscreen mode

我们的 shell 命令仍然是本地的。现在是时候让它变成全局的了。运行
npm link

离开项目根目录,webcheck从任意目录运行。你应该会看到如下结果。

$ webcheck
Hello, here is my first CLI tool
Enter fullscreen mode Exit fullscreen mode

恭喜!!!你刚刚用 Node 应用创建了你的第一个 Shell 命令。它可以推送到 NPM 供用户下载运行,但由于项目只完成了一半,我建议等到 Node 应用完成后再发布。

从终端解析参数

为了解析来自终端的参数,我们将使用内置的 Node.js 模块argv。根据 Node.js 官方文档,process.argv 属性返回一个数组,其中包含启动 Node.js 进程时传递的命令行参数。第一个元素是 process.execPath。第二个元素是正在执行的 JavaScript 文件的路径。其余元素是任何其他命令行参数。因此,我们传递给终端的任何参数都将是数组的第三个元素。编辑 index.js 文件,使其如下所示。

\\ index.js
#!/usr/bin/env node

console.log(processs.argv);
Enter fullscreen mode Exit fullscreen mode

在终端上运行你的应用。输出应该类似于以下内容。

$ webcheck
[
  'C:\\Program Files\\nodejs\\node.exe',
  'C:\\Users\\adeniyi\\Desktop\\Projects\\cli-project\\index'
]
Enter fullscreen mode Exit fullscreen mode

现在向您的命令添加一个附加参数,您的输出应该类似于此。

$ webcheck file
[
  'C:\\Program Files\\nodejs\\node.exe',
  'C:\\Users\\adeniyi\\Desktop\\Projects\\cli-project\\index',
  'file'
]
Enter fullscreen mode Exit fullscreen mode

注意:附加的参数越多,数组就越大。为了达到我们的目的,我们将参数限制为字符串,并将其解析到项目中作为数组的第三个元素。
现在是时候将此参数解析到我们的应用程序中,并从isitup api获取信息了。

打开你的 index.js 文件并输入此代码。

   #!/usr/bin/env node
   const fetch = require("node-fetch");

   // console.log(process.argv);
   const website = process.argv[2]; 

   function CheckWeb(name) {
        const info =fetch(`https://isitup.org/${name}.json`)
        .then(response => response.json());

        info.then(function(result) {
            if (result.response_code == 200) {
                console.log('website is up and running')
            } else {
               console.log('website is down')
            }
        }

   CheckWeb(website);
Enter fullscreen mode Exit fullscreen mode

由于 Node.js 不支持原生的 JavaScript fetch,我们需要这个包来帮助我们从isitup APInode-fetch获取数据。运行 我们的 CheckWeb 函数接受一个 name 参数,并从 API 获取相应的响应。现在,我们将命令行参数传递给函数。让我们打开终端,看看代码是如何运行的。npm install node-fetch

$ webcheck duckduckgo.com
website is up and running
Enter fullscreen mode Exit fullscreen mode

耶!!!

$ webcheck google.com
website is down
Enter fullscreen mode Exit fullscreen mode

等等?!
我们来试试看看哪里出了问题。我最喜欢的调试工具(控制台)来帮忙了。

   #!/usr/bin/env node
   //... 

   function CheckWeb(name) {
        const info =fetch(`https://isitup.org/${name}.json`)
        .then(response => response.json());

        info.then(function(result) {
            console.log(result)
        }

   CheckWeb(website);
Enter fullscreen mode Exit fullscreen mode

再次从终端运行该应用程序

$ webcheck google.com
{
  domain: "google.com",
  port: 80,
  status_code: 1,
  response_ip: "216.58.210.206",
  response_code: 301,
  response_time: 0.008
}
Enter fullscreen mode Exit fullscreen mode

因此,301 重定向被认为是将用户从 HTTP 升级到 HTTPS 的最佳实践。我们需要我们的应用知道这一点,并告诉我们 Google 已恢复。我们可以通过两种方式来实现这一点:一连串的 if else 语句,用于导航相应的响应代码,或者查找null响应代码

   #!/usr/bin/env node
   const fetch = require("node-fetch");

   // console.log(process.argv);
   const website = process.argv[2]; 

   function CheckWeb(name) {
        const info =fetch(`https://isitup.org/${name}.json`)
        .then(response => response.json());

        info.then(function(result) {
            if (result.response_code == null) {
                console.log('website is down')
            } else {
               console.log('website is up and running')
            }
        }

   CheckWeb(website);
Enter fullscreen mode Exit fullscreen mode

运行你的应用

$ webcheck google.com
website is up and running
Enter fullscreen mode Exit fullscreen mode

或这个

   #!/usr/bin/env node
   const fetch = require("node-fetch");

   // console.log(process.argv);
   const website = process.argv[2]; 

   function CheckWeb(name) {
        const info =fetch(`https://isitup.org/${name}.json`)
        .then(response => response.json());

        info.then(function(result) {
            if (result.response_code == 200) {
                console.log('\x1b[32m%s\x1b[0m', 'website is up and running');
            } else if (result.response_code == 301) {
                console.log('\x1b[34m%s\x1b[0m', 'website has been moved permanently but is up');
            } else if (result.response_code == 302){
                console.log('\x1b[34m%s\x1b[0m', 'temporary redirect, website is up');
            } else if (result.response_code == 403) {
                console.log('\x1b[33m%s\x1b[0m', 'information not found');
            }
            else {
                console.log('\x1b[31m%s\x1b[0m', 'website is down')
            }
        });

   CheckWeb(website);
Enter fullscreen mode Exit fullscreen mode

这段'\x1b[31m%s\x1b[0m'代码以及您在控制台语句中看到的类似代码决定了响应消息的颜色。
运行您的应用

$ webcheck google.com
website has been moved permanently but is up
Enter fullscreen mode Exit fullscreen mode

现在,我们可以将 cli 工具的第一个版本发布到 NPM 了。您需要创建一个.npmignore文件。将以下内容复制到该文件中。

//.npmignore

node_modules/

Enter fullscreen mode Exit fullscreen mode

这确保你不会将 Node 模块与包一起发布。现在,运行(
npm publish
如果你之前没有从终端登录过 npm,请先登录)
npm login

干杯!用户现在可以前往 NPM 搜索并下载你的 cli 工具了。

从终端启动网站

为此,我们需要open一个能帮助我们打开 URL 的包。然后,我们将编写一个函数来启动网站。
npm install open

编辑 index.js 文件

#!/usr/bin/env node
const fetch = require("node-fetch");
const open = require("open");

const website = process.argv[2]; 

function CheckWeb(name) {
        const info =fetch(`https://isitup.org/${name}.json`)
        .then(response => response.json());

        info.then(function(result) {
            function openWebSite () {
                setTimeout(function()
                { open(`https://${result.domain}`); }, 1000);
            };

            if (result.response_code == 200) {
                console.log('\x1b[32m%s\x1b[0m', 'website is up and running');
                openWebSite();
            } else if (result.response_code == 301) {
                console.log('\x1b[32m%s\x1b[0m', 'website has been moved permanently but is up');
                openWebSite();
            } else if (result.response_code == 302){
                console.log('\x1b[34m%s\x1b[0m', 'temporary redirect, website is up');
                openWebSite();
            } else if (result.response_code == 403) {
                console.log('\x1b[33m%s\x1b[0m', 'information not found');
                openWebSite();
            }
            else {
                console.log('\x1b[31m%s\x1b[0m', 'website is down')
            }
        });
    }
}

CheckWeb(website); 
Enter fullscreen mode Exit fullscreen mode

openWebsite 函数会自动从终端在默认浏览器中启动选中的网站。但是,我们希望用户能够决定是否打开该网站。
我们将安装两个包arginquirer。我们将使用 和 将命令行参数解析为选项arginquirer以提示用户输入值。
npm install arg inquirer

我们将像这样构建我们的 index.js 文件

#!/usr/bin/env node
const fetch = require("node-fetch");
const open = require('open');
const arg = require('arg');
const inquirer = require('inquirer');

function ParseCliArgsIntoOptions() {
    const args = arg(
      {
        '--website': Boolean,
        '--yes': Boolean,
        '-w': '--website',
        '-y': '--yes',
      },
      {
        argv: process.argv.slice(2),
      }
    );
    return {
      website: args['--website'] || false,
    };
}

async function PromptForOptions(options) {
    const questions = [];

    if (!options.website) {
      questions.push({
        type: 'confirm',
        name: 'website',
        message: 'Open the website on your browser?',
        default: false,
      });
    }

    const answers =  await inquirer.prompt(questions);
    return {
      ...options,
      website: options.website || answers.website,
    };
}

async function LaunchWebsite(result) {
    let options = ParseCliArgsIntoOptions();
    options =  await PromptForOptions(options);
    if (options.website == true) {
        open(`https://${result.domain}`); 
    }
}


const website = process.argv[2]; 

function CheckWeb(name) {
// ....
}

Enter fullscreen mode Exit fullscreen mode

我们所做的就是创建一个 LaunchWebsite 函数,它接受另外两个函数,ParseCliArgsIntoOptions()这两个函数会针对该PromptForOptions()函数提示的问题,提供一个布尔值“是/否”选项。如果选项true为“是”,则打开网站。
现在,我们将 LaunchWebsite 函数注入到 Checkweb 函数中,并将操作结果传递fetch给它。

#!/usr/bin/env node
const fetch = require("node-fetch");
const open = require('open');
const arg = require('arg');
const inquirer = require('inquirer');

function ParseCliArgsIntoOptions() {
//...
}
async function PromptForOptions(options) {
//...
}
async function LaunchWebsite(result) {
//...
}

function CheckWeb(name) {
        const info =fetch(`https://isitup.org/${name}.json`)
        .then(response => response.json());

        info.then(function(result) {
            if (result.response_code == 200) {
                console.log('\x1b[32m%s\x1b[0m', 'website is up and running');
                LaunchWebsite(result)
            } else if (result.response_code == 301) {
                console.log('\x1b[32m%s\x1b[0m', 'website has been moved permanently but is up');
                LaunchWebsite(result)
                console.log('\x1b[34m%s\x1b[0m', 'website has been moved permanently but is up');
                LaunchWebsite(result)
            } else if (result.response_code == 302){
                console.log('\x1b[34m%s\x1b[0m', 'temporary redirect, website is up');
                LaunchWebsite(result)
            } else if (result.response_code == 403) {
                console.log('\x1b[33m%s\x1b[0m', 'information not found');
                LaunchWebsite(result)
            }
            else {
                console.log('\x1b[31m%s\x1b[0m', 'website is down')
            }
        });
    }
}
CheckWeb(website);
Enter fullscreen mode Exit fullscreen mode

如果你现在在终端上运行 shell 命令,就会发生这种情况

$ webcheck google.com
website has been moved permanently but is up
? Open the website on your browser? (y/N)
Enter fullscreen mode Exit fullscreen mode

太棒了!旅程即将结束。
让我们为那些可能忘记添加网站扩展程序的用户处理错误,以此作为圆满的结束。网站可能已经正常运行,但这肯定会导致网站无法访问。

$ webcheck google
website is down
Enter fullscreen mode Exit fullscreen mode

有很多方法可以解决这个问题。你可以创建一个包含所有可能扩展名(超过 400 个)的数组,然后编写一个正则表达式函数,用于在我们的网站字符串中搜索数组参数。如果你问我,这有点没必要。或者,你也可以像下面这样,直接搜索参数中的子字符串“.”

#!/usr/bin/env node
const fetch = require("node-fetch");
const open = require('open');
const arg = require('arg');
const inquirer = require('inquirer');

function ParseCliArgsIntoOptions() {
//...
}
async function PromptForOptions(options) {
//...
}
async function LaunchWebsite(result) {
//...
}

function CheckWeb(name) {
      if (name.indexOf('.') > -1) {
        const info =fetch(`https://isitup.org/${name}.json`)
        .then(response => response.json());

        info.then(function(result) {
           //...
        });
    } else {
        console.log('\x1b[31m%s\x1b[0m', 'please append your url extension e.g(mouse.com)')
    }
}
CheckWeb(website);
Enter fullscreen mode Exit fullscreen mode

在终点站。

$ webcheck google
please append your url extension e.g(mouse.com)
Enter fullscreen mode Exit fullscreen mode

现在,让我们再次发布更新的工具。你必须更新版本号。运行
npm version 1.1.0
然后推送到 NPM
npm publish

结论

我们的 CLI 工具已在 NPM 上启动并运行。
如果您对此有任何疑问,欢迎在评论区留言。此外,欢迎纠正或补充我可能遗漏的任何内容。欢迎通过电子邮件或Twitter给我留言。再次强调,您可以在这里
找到源代码 谢谢!

鏂囩珷鏉ユ簮锛�https://dev.to/dendekky/how-to-build-a-command-line-tool-with-nodejs-a-step-by-step-guide-386k
PREV
10 种 JavaScript 数组方法来简化您的代码。
NEXT
Node.JS 和 Express 中的安全性:最低限度 - 第 2 部分。一般的 XSS 攻击、一般的 SQL 注入、RegEx 拒绝服务,就这些了(目前……)