JavaScript >> Javascript 文檔 >  >> Node.js

如何使用 Node.js 構建命令行界面 (CLI)

如何使用 Commander.js 庫構建與 JSON 佔位符 API 對話的命令行界面 (CLI)。

開始使用

對於本教程,我們將從頭開始創建一個新的 Node.js 項目。在撰寫本文時,我們將假設我們使用的是最新版本的 Node.js (v16)。

在您的計算機上,首先創建一個存放 CLI 代碼的文件夾:

終端

mkdir jsonp

接下來,cd 進入項目文件夾並運行 npm init -f 強制創建 package.json 項目文件:

終端

npm init -f

使用 package.json 文件,接下來,我們要添加兩個依賴項:commander (我們將用於構建 CLI 的包)和 node-fetch 我們將使用它來運行對 JSON 佔位符 API 的 HTTP 請求:

終端

npm i commander node-fetch

我們的依賴準備好了,最後,我們要修改我們的 package.json 文件以通過添加 "type": "module" 來啟用 JavaScript 模塊支持 屬性:

/package.json

{
  "name": "jsonp",
  "type": "module",
  "version": "1.0.0",
  ...
}

有了這個,我們就可以開始了。

在你的 package.json 中添加一個 bin 標誌

在我們關閉 package.json 之前 文件,很快我們將繼續前進並添加 bin 屬性,當我們的包安裝時,將指定的值添加到我們用戶的命令行 PATH 變量:

/package.json

{
  "name": "jsonp",
  "type": "module",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "bin": {
    "jsonp": "index.js"
  },
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "commander": "^8.1.0",
    "node-fetch": "^2.6.1"
  }
}

在這裡,我們設置 bin 到具有 jsonp 屬性的對象 設置為 index.js 的值 .這裡,jsonp 是我們的 CLI 將作為 jsonp 訪問的名稱 通過命令行(例如,$ jsonp posts )。 index.js 部分指向我們要與該命令關聯的腳本的位置。

讓我們創建 index.js 立即文件並開始構建我們的 CLI。我們將重新審視這個 bin 的意義 教程後面的設置。

設置主 CLI 命令

幸運的是,感謝 commander 我們之前安裝的依賴項,設置我們的 CLI 相當簡單。

/index.js

#!/usr/bin/env node

import cli from "commander";

cli.description("Access the JSON Placeholder API");
cli.name("jsonp");

cli.parse(process.argv);

讓我們建立起來,這裡有一些不同的東西。首先,因為我們的腳本將通過命令行執行(例如,通過 bash 外殼或 zsh shell),我們需要添加所謂的 shebang 線(不要令人毛骨悚然)。這告訴命令行應該通過哪個解釋器運行傳遞的腳本。在這種情況下,我們希望我們的代碼能夠被 Node.js 解釋。

因此,當我們通過命令行運行這個文件時,它的代碼將被交給 Node.js 進行解釋。如果我們排除 這一行,我們希望命令行會拋出錯誤,因為它無法理解代碼。

在這一行下面,我們深入研究我們的實際代碼。一、從commander 我們導入的包 cli .在這裡,因為我們期望一個默認導出(這意味著 Commander 在內部沒有為它導出的值使用特定名稱),所以我們將其導入為 cli 而不是 commander 更好地對我們文件中的代碼進行上下文化。

接下來,我們用 .description() 添加描述和名稱 和 .name() 分別。注意這裡的語法。在使用 Commander 時,我們所做的一切都是基於主 Commander 實例構建的,這裡表示為 cli .

最後,在我們文件的底部,我們添加對 cli.parse() 的調用 傳入 process.argv . process.argv 正在拉入傳遞給 Node.js process 的參數 (加載後我們腳本的內存名稱)存儲在 argv process 上的屬性 目的。需要注意的是,這是一個 Node.js 概念,與指揮官無關。

指揮官部分是 cli.parse() .顧名思義,此方法解析傳遞給我們腳本的參數。從這裡,Commander 接收傳遞給腳本的任何參數,並嘗試解釋它們並將它們與我們 CLI 中的命令和選項匹配。

雖然我們預計不會發生任何事情,但為了測試這一點,在你的命令行中,cd 進入 jsonp 的根目錄 我們創建並運行的文件夾 node index.js .如果到目前為止一切設置正確,則該命令應該執行並返回,而不會在終端中打印任何內容。

添加詳細信息和單個命令

現在是有趣的部分。截至目前,我們的 CLI 是無用的。我們想要做的是添加單獨的命令,這些命令是 CLI 的一部分,我們可以運行或“執行”以執行某些任務。同樣,我們的目標是構建一個簡單的 CLI 來訪問 JSON 佔位符 API。我們將重點關註三個命令:

  1. posts 將從 API 中檢索帖子列表,或者單個帖子(我們將學習如何將參數傳遞給我們的命令以實現這一點)。
  2. comments 將從 API 檢索評論列表。我們將有意保持簡單以顯示我們的命令之間的差異。
  3. users 將從 API 或單個用戶中檢索用戶列表。這將與 posts 的行為相同 命令,只是訪問 API 上的不同資源。

在我們添加我們的命令之前,非常快,我們想添加一些更多的 cli 級設置來清理用戶體驗:

/index.js

#!/usr/bin/env node

import cli from "commander";

cli.description("Access the JSON Placeholder API");
cli.name("jsonp");
cli.usage("<command>");
cli.addHelpCommand(false);
cli.helpOption(false);

cli.parse(process.argv);

在這裡,在我們對 cli.name() 的調用之下 我們又添加了三個設置:cli.usage() , cli.addHelpCommand() , 和 cli.helpOption() .

第一個,cli.usage() ,幫助我們在通過命令行調用 CLI 時在 CLI 頂部添加使用說明。例如,如果我們要運行 jsonp 在我們的終端中(假設而言),我們會看到一條消息,內容類似於...

Usage: jsonp <command>

在這裡,我們建議您通過調用 jsonp 來使用 CLI 函數並傳遞您想從該 CLI 運行的子命令的名稱。

.addHelpCommand() 這裡的方法正在傳遞 false 說我們做 希望指揮官添加默認的help 命令到我們的 CLI。這對於更複雜的 CLI 很有幫助,但對我們來說,它只會增加混亂。

同樣,我們也設置.helpOption()false 實現相同的目的,但不是刪除幫助 命令 ,我們去掉內置的-h--help 選項標誌。

現在,讓我們連接 posts 我們在上面提示過的命令,然後看看如何通過 JSON Placeholder API 獲取數據。

/index.js

#!/usr/bin/env node

import cli from "commander";
import posts from "./commands/posts.js";

cli.description("Access the JSON Placeholder API");
cli.name("jsonp");
...

cli
  .command("posts")
  .argument("[postId]", "ID of post you'd like to retrieve.")
  .option("-p, --pretty", "Pretty-print output from the API.")
  .description(
    "Retrieve a list of all posts or one post by passing the post ID (e.g., posts 1)."
  )
  .action(posts);

cli.parse(process.argv);

同樣,對我們 CLI 的所有修改都是在主 cli 上完成的 我們從 commander 導入的對象 包裹。在這裡,我們通過運行 cli.command() 定義了一個單獨的命令 ,傳遞我們要定義的命令的名稱 posts .接下來,使用 Commander 的方法鏈特性(這意味著我們可以一個接一個地運行後續方法,Commander 會理解它),我們定義一個 .argument() postId .在這裡,我們傳遞兩個選項:參數的名稱(使用 [] 方括號語法表示參數是可選 ——必填參數使用 <> 尖括號)和對該論點意圖的描述。

接下來,為了展示選項標誌,我們添加 .option() ,首先通過逗號分隔的標誌的短格式和長格式版本(此處為 -p--pretty ) 然後是標誌的描述。在這種情況下,--pretty 將在與我們的命令相關的函數內部使用,以決定我們是否將從 JSON 佔位符 API 返回的數據“漂亮打印”(意思是,用兩個空格格式化)。

為了完善我們的命令設置,我們調用 .description() 添加我們希望在沒有特定命令的情況下運行 CLI 時顯示的描述(實際上是手冊或“幫助”頁面)。

最後,重要的部分,我們添加 .action() 並在運行此命令時傳入我們要調用的函數。在頂部,我們已經導入了一個函數 posts 來自 commands 中的文件 我們現在要添加的文件夾。

/commands/posts.js

import fetch from "node-fetch";

export default (postId, options) => {
  let url = "https://jsonplaceholder.typicode.com/posts";

  if (postId) {
    url += `/${postId}`;
  }

  fetch(url).then(async (response) => {
    const data = await response.json();

    if (options.pretty) {
      return console.log(data);
    }

    return console.log(JSON.stringify(data));
  });
};

為了讓我們繼續前進,我們在這裡添加了 posts 的完整代碼 命令。這裡的想法相當簡單。我們要導出的函數將傳遞兩個參數:postId 如果指定了 ID 並且 options 這將是像 --pretty 這樣的任何標誌 傳入的。

在該函數內部,我們為 /posts 設置基本 URL 變量 url 中 JSON 佔位符 API 上的端點 ,確保使用 let 定義,因此我們可以有條件地覆蓋該值。我們需要在 postId 傳入。如果有,我們修改url 附加 /${postId} ,給我們一個更新的 URL,比如 https://jsonplaceholder.typicode.com/posts/1 (假設我們輸入了 jsonp posts 1 在命令行上)。

接下來,使用我們的 url ,我們使用 fetch() 我們從 node-fetch 導入的方法 向上傳遞我們的 url .因為我們希望這個調用返回一個 JavaScript Promise,所以我們添加了一個 .then() 方法來處理對我們請求的響應。

處理該響應,我們使用 JavaScript 異步/等待模式來 awaitresponse.json() 的調用 (這會將原始響應轉換為 JSON 對象)然後將響應存儲在我們的 data 變量。

接下來,我們檢查是否 options.pretty 已定義(意味著當我們的命令運行時,-p--pretty flag 也被傳遞了),如果是,我們只記錄我們剛剛存儲在 data 中的原始 JSON 對象 .如果 options.pretty 不是 通過了,我們調用 JSON.stringify() 傳入我們的 data .這將使我們返回數據的壓縮字符串版本。

要對此進行測試,請打開您的終端並運行以下命令:

node index.js posts --pretty

如果一切正常,您應該會看到一些從 JSON 佔位符 API 返回的數據,並漂亮地打印在屏幕上。

[
  {
    userId: 10,
    id: 99,
    title: 'temporibus sit alias delectus eligendi possimus magni',
    body: 'quo deleniti praesentium dicta non quod\n' +
      'aut est molestias\n' +
      'molestias et officia quis nihil\n' +
      'itaque dolorem quia'
  },
  {
    userId: 10,
    id: 100,
    title: 'at nam consequatur ea labore ea harum',
    body: 'cupiditate quo est a modi nesciunt soluta\n' +
      'ipsa voluptas error itaque dicta in\n' +
      'autem qui minus magnam et distinctio eum\n' +
      'accusamus ratione error aut'
  }
]

如果您刪除 --pretty 從該命令標記並添加數字 1 (如 node index.js posts 1 ),您應該會看到單個帖子的壓縮字符串化版本:

{"userId":1,"id":1,"title":"sunt aut facere repellat provident occaecati excepturi optio reprehenderit","body":"quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"}

這為我們的其餘命令設置了一個模板。總結一下,讓我們繼續添加這兩個命令(以及它們在 /commands 中的函數 目錄)并快速討論它們的工作原理。

/index.js

#!/usr/bin/env node

import cli from "commander";
import posts from "./commands/posts.js";
import comments from "./commands/comments.js";
import users from "./commands/users.js";

cli.description("Access the JSON Placeholder API");
...

cli
  .command("posts")
  .argument("[postId]", "ID of post you'd like to retrieve.")
  .option("-p, --pretty", "Pretty-print output from the API.")
  .description(
    "Retrieve a list of all posts or one post by passing the post ID (e.g., posts 1)."
  )
  .action(posts);

cli
  .command("comments")
  .option("-p, --pretty", "Pretty-print output from the API.")
  .description("Retrieve a list of all comments.")
  .action(comments);

cli
  .command("users")
  .argument("[userId]", "ID of the user you'd like to retrieve.")
  .option("-p, --pretty", "Pretty-print output from the API.")
  .description(
    "Retrieve a list of all users or one user by passing the user ID (e.g., users 1)."
  )
  .action(users);

cli.parse(process.argv);

為了展示多個命令,我們在這裡添加了兩個額外的命令:commentsusers .兩者都設置為以與我們的 posts 完全相同的方式與 JSON 佔位符 API 對話 命令。

你會注意到 users 與我們的 posts 相同 命令—保存名稱和描述—而 comments 命令缺少 .argument() .這是故意的。我們想在這裡展示 Commander 的靈活性,展示什麼是必需的,什麼不是必需的。

我們在上面學到的仍然適用。方法一個接一個地鏈接,最終調用 .action() 當我們通過命令行運行命令時,我們傳入要調用的函數。

我們來看看commentsusers 現在運行,看看我們是否能發現任何主要的差異:

/commands/comments.js

import fetch from "node-fetch";

export default (options) => {
  fetch("https://jsonplaceholder.typicode.com/comments").then(
    async (response) => {
      const data = await response.json();

      if (options.pretty) {
        return console.log(data);
      }

      return console.log(JSON.stringify(data));
    }
  );
};

對於 comments ,我們的代碼幾乎與我們之前看到的 posts 相同 有一個小改動:我們省略了存儲 url 在一個變量中,這樣我們就可以根據傳遞給我們命令的參數有條件地修改它(記住,我們已經設置了 comments 期待任何論點)。相反,我們剛剛傳遞了我們想要的 JSON 佔位符 API 端點的 URL——/comments ——然後執行與 posts 完全相同的數據處理 .

/commands/users.js

import fetch from "node-fetch";

export default (userId, options) => {
  let url = "https://jsonplaceholder.typicode.com/users";

  if (userId) {
    url += `/${userId}`;
  }

  fetch(url).then(async (response) => {
    const data = await response.json();

    if (options.pretty) {
      return console.log(data);
    }

    return console.log(JSON.stringify(data));
  });
};

這應該看起來很熟悉。在這裡,我們的 users 函數 等同於 posts ,唯一的區別是 /users 在我們的 url 末尾 而不是 /posts .

而已!在結束之前,我們將學習如何在我們的機器上全局安裝我們的 CLI,以便我們可以實際使用我們的 jsonp 命令,而不必使用 node index.js ... 運行東西 就像我們在上面看到的那樣。

全局安裝 CLI 以進行測試

幸運的是,在我們的機器上全局安裝我們的包非常簡單。回想一下,我們之前添加了一個字段 bin 到我們的 /package.json 文件。當我們安裝我們的包時(或者用戶在我們將它發佈到 NPM 或另一個包存儲庫後安裝它),NPM 將獲取我們在這個對像上設置的屬性並將其添加到我們(或我們的用戶)計算機上的 PATH 變量中.安裝後,我們可以使用這個名稱——在本教程中,我們選擇了 jsonp 為我們的命令的名稱——在我們的控制台中。

要安裝我們的軟件包,請確保您是 cd 'd 進入項目文件夾的根目錄(我們的 index.js 文件定位)然後運行:

終端

npm i -g .

在這裡,我們說“NPM,安裝位於當前目錄 . 的包 全局在我們的計算機上。”運行此命令後,NPM 將安裝該軟件包。之後,您應該可以在控制台中訪問一個新命令,jsonp

終端

jsonp posts -p

您應該會在控制台中看到我們之前設置的輸出:

總結

在本教程中,我們學習瞭如何使用 Node.js 和 Commander.js 構建命令行界面 (CLI)。我們學習瞭如何設置準系統 Node.js 項目,修改 package.json 包含 "type": "module" 的文件 啟用 JavaScript 模塊的字段以及 bin 字段指定要添加到 PATH 的命令 安裝包時我們計算機上的變量。

我們還學習瞭如何使用 shebang 行告訴我們的控制台如何解釋我們的代碼以及如何使用 Commander.js 定義命令並指向接受參數和選項的函數。最後,我們學習瞭如何全局安裝我們的命令行工具,以便我們可以通過我們提供給 bin 的名稱來訪問它 在我們的 package.json 中設置 文件。


Tutorial JavaScript 教程
  1. 樣式化你的 JavaScript 控制台輸出

  2. 為負載均衡器設置 NGINX

  3. 比較流行的 React 組件庫

  4. 編碼#4

  5. 遊戲化! - 命名函數與箭頭函數的遊戲化方法

  6. Internet Explorer 11:“別叫我 IE”

  7. 為什麼以及如何延遲加載 Angular 庫

  1. 最適合程序員的 5 個最佳 YouTube 頻道

  2. Openadhan:穆斯林祈禱時間用 ReactJS 製作的 PWA

  3. 將 HTMX 與 ASP.NET Core MVC 一起使用

  4. 我贈送了 The Little JavaScript Book 的免費副本

  5. Qt Timer JS 事件

  6. 將 Node.js 應用程序 Docker 化 [2022 年修訂]

  7. 開始使用 Imba

  1. 如何修復 nodemon 錯誤 - nodemon.ps1 無法加載,因為在此系統上禁用了運行腳本

  2. 幾個進口的故事

  3. 使用 Puppeteer 和 Node.js 抓取(幾乎)任何東西的簡介

  4. 所以我在 React 中創建了 Spotify 克隆(有點)