Node.js CLI 工具最佳實踐

這是一個關於如何構建成功的、可移植的、對用戶友好的Node.js 命令行工具(CLI)最佳實踐的集合。html

爲何寫這篇文章?

一個糟糕的 CLI 工具會讓用戶以爲難用,而構建一個成功的 CLI 須要密切關注不少細節,同時須要站在用戶的角度,創造良好的用戶體驗。要作到這些特別不容易。node

在這個指南中,我列出了在各個重點領域的最佳實踐,都是 CLI 工具交互最理想的用戶體驗。git

特性:

  • ✅ 構建成功的 Node.js CLI 工具的 21 種最佳實踐
  • ❤️ 幫忙翻譯成其餘語言
  • 🙏 歡迎捐贈
  • 最近更新時間:2020-02-14

爲何是我?

我叫Liran Tal,我一直專一於構建命令行工具。github

我最近的一些工做就是構建Node.js CLI,包括如下開源項目:web

Dockly npq lockfile-lint is-website-vulnerable
沉浸式終端界面,用於管理Docker容器和服務 經過在安裝過程當中進行檢查,以安全地使用npm / yarn 安裝的軟件包 整理 npm 或 yarn 的 lock 文件以分析和檢測安全問題 在網站引用的 JS 庫中查找公開的安全漏洞

1 命令行的經驗

本節將會介紹建立美觀且高可用的 Node.js 命令行工具相關的最佳實踐。docker

1.1 尊重 POSIX

正確: 使用兼容 POSIX-compliant 命令行的語法,由於這是被普遍接受的命令行工具的標準。shell

錯誤: 當用戶使用CLI,其命令行參數與他們過去的使用習慣不一致時,會感受很難適應。npm

➡️ 細節:json

Unix-like 操做系統普及了命令行工具,好比awk,sed。這樣的工具已經有效地標準化了命令行選項「options」(又名標誌「flags」),選項參數和其餘操做的行爲。api

一些案例:

  • 在幫助「help」中將選項參數「option-arguments」標記爲方括號([]),以表示它們是可選的,或者使用尖括號(<>),表示它們是必需的。
  • 參數可使用單字符縮寫,通常是 - 加上一個字母或數字。
  • 多個沒有值的選型可進行組合,好比:cli -abc 等價於 cli -a -b -c

用戶通常都會但願你的命令行工具與其餘Unix工具具備相似的約定。

1.2 構建友好的 CLI

正確: 儘量多的輸出一些信息以幫助用戶成功使用 CLI。

錯誤: 因爲 CLI 一直啓動失敗,又沒有爲用戶提供足夠的幫助,會讓用戶產生明顯的挫敗感。

➡️ 細節:

命令行工具的界面必定程度上應與 Web 用戶界面相似,儘量的保證程序能正常使用。

構建一個對用戶友好的 CLI 應該儘量的爲用戶提供支持。做爲實例,咱們討論下 curl 命令的交互,該命令指望將 URL 做爲主要的數據輸入,而用戶卻沒有提供 URL,這時候命令行會提示用戶通讀 curl --help 的輸出信息。可是,對用戶友好的 CLI 工具會顯示一個可交互式的提示,捕獲用戶的輸入,從而正常運行。

1.3 有狀態的數據

正確: 在屢次調用 CLI 的過程當中,提供有狀態的體驗,記住這些數據,以提供無縫的交互體驗。

錯誤: 用戶屢次調用 CLI 重複提供相同的信息,會讓用戶感到厭煩。

➡️ 細節:

你須要爲 CLI 工具提供持續緩存,好比記住用戶名、電子郵件、token 或者是 CLI 屢次調用的一些首選項。可使用如下工具來保留用戶的這些配置。

1.4 提供多彩的體驗

正確: 在 CLI 工具中使用顏色來突出顯示一些信息,而且提供降級方案,進行檢測,自動退出以避免輸出亂碼。

錯誤: 蒼白的輸出可能會讓用戶丟失重要的信息,尤爲是文本較多的時候。

➡️ 細節:

大多數的命令行工具都支持彩色文本,經過特定的 ANSI 編碼來啓用。
命令行工具輸出彩色文本可帶來更豐富的體驗和更多的交互。可是,不受支持的終端可能會在屏幕上以亂碼信息的形式輸出。此外,CLI 也可能用於不支持彩色輸出的連續集成中。

1.5 豐富的交互

正確: 提供除了文本輸入以外的其餘交互形式,爲用戶提供更加豐富的體驗。

錯誤: 當輸入的信息是固定的選項(相似下拉菜單)時,文本輸入的形式可能會給用戶帶來麻煩。

➡️ 細節:

能夠以提示輸入的方式引入更加豐富的交互方式,提示輸入比自由的文本輸入更高端。例如,下拉列表、單選按鈕切換、隱藏密碼輸入。豐富交互的另外一個方面就是動畫以及進度條,在 CLI 執行異步工做時,都能爲用戶提供更好的體驗。

許多 CLI 提供默認的命令行參數,而無需用戶進一步交互。不強迫用戶提供一些非必要的參數。

1.6 無處不在的超連接

正確: URL(https://www.github.com)和源代碼(src/Util.js:2:75)使用格式正確的文本輸出,由於這二者都是現代終端可點擊的連接。

錯誤: 避免使用git.io/abc之類的非交互式的連接,該連接須要用戶手動複製和粘貼。

➡️ 細節:

若是你要分享的信息在 Url 連接中,或者是某個文件的特定行列,則須要向用戶提供正確的格式的連接,用戶一旦點擊它們,就會打開瀏覽器或者在IDE跳到特定位置。

1.7 零配置

正確: 經過自動檢測所需的配置和命令行參數,達到即開即用的體驗。

錯誤: 若是能夠以可靠的方式自動檢測命令行參數,而且調用的操做不需用戶顯式確認(例如確認刪除),則不要強制用戶交互。

➡️ 細節:

旨在在運行 CLI 工具時提供「即開即用」的體驗。

2 發佈

本節介紹瞭如何以最佳方式分發和打包 Node.js CLI 工具的最佳實踐。

2.1 最小化的依賴

正確: 最大程度地減小生產環境的依賴項,而且使用可替代的最小的依賴包,確保這是一個儘量小的 Node.js 包。可是,也不能過於謹慎所以重複發明輪子而過分優化依賴。

錯誤: 應用中依賴的大小將決定 CLI 的安裝時間,從而致使糟糕的用戶體驗。

➡️ 細節:

使用 npx 能夠快速調用經過 npm install 安裝的 Node.js CLI 模塊,這可提供更好的用戶體驗。這有助於將總體的依賴關係和傳遞依賴關係保持在合理大小。

npm 全局安裝模塊,安裝過程會變得緩慢,這是一個糟糕的體驗。經過 npx 老是獲取當前項目安裝的模塊(當前文件夾的node_modules),所以使用 npx 來調用 CLI 可能會下降性能。

2.2 使用文件鎖

正確: 經過 npm 提供的 package-lock.json 來鎖定安裝包,以確保用戶安裝的時候使用的依賴版本是準確的。

錯誤: 不鎖定依賴的版本,意味着 npm 將在安裝過程當中本身解決他們,從而致使安裝依賴的版本範圍擴大,這會引入沒法控制的更改,可能會讓 CLI 沒法成功運行。

➡️ 細節:

一般,npm 包在發佈時只定義其直接的依賴項及其版本範圍,而且 npm 會在安裝時解析全部間接依賴項的版本。隨着時間的流逝,間接的依賴項版本會有所不一樣,由於依賴項隨時會發布新版本。
儘管維護人員已普遍使用版本控制語義,可是 npm 會爲安裝的包引入許多間接的依賴關係,這些間接依賴提高了破壞您的應用程序的風險。
使用 package-lock.json 會帶給用戶更好的安全感。將要安裝的依賴項固定到特定版本,所以,即便這些依賴項發佈了較新的版本,也不會安裝它們。這將讓您有責任保持對依賴項的關注,瞭解依賴項中任何安全相關的修復,並經過按期發佈 CLI 工具進行安全更新。能夠考慮使用Snyk 來自動修復整個依賴性樹中的安全性問題。注:我是Snyk的開發者開發者。
參考:

3 通用性

本節將介紹使 Node.js CLI 與其餘命令行工具無縫集成有關的最佳實踐,並遵循 CLI 正常運行的約定。

本節將回答如下問題:

  • 我能夠導出 CLI 的輸出以便於分析嗎?
  • 我能夠將 CLI 的輸出經過管道傳遞到另外一個命令行工具的輸入嗎?
  • 是否能夠將其餘工具的結果經過管道傳輸到此 CLI?

3.1 接受 STDIN 做爲輸入

正確: 對於數據驅動的命令行應用,用戶能夠輕鬆的經過管道將數據輸入到 STDIN。

錯誤: 其餘的命令行工具可能沒法直接提供數據輸入到你的 CLI 中,這會阻止某些代碼的正常運行,例如:

$ curl -s "https://api.example.com/data.json" | your_cli

➡️ 細節:

若是命令行工具須要處理某些數據,好比,指定 JSON 文件執行某種任務,通常使用 --file file.json 的命令行參數。

3.2 結構化輸出

正確: 經過某個參數來容許應用的結果進行結構化的輸出,這樣使得數據更容易處理和解析。

錯誤: 用戶可能須要使用複雜的正則來解析和匹配 CLI 的輸出結果。

➡️ 細節:

對於 CLI 的用戶來講,解析數據並使用數據來執行其餘任務(好比,提供給 web 儀表盤或電子郵件)一般頗有用。
可以輕鬆地從命令行輸出中獲得須要的數據,這將爲 CLI 的用戶提供更好的體驗。

3.3 跨平臺

正確: 若是但願 CLI 可以跨平臺工做,則必須注意命令行 shell 和子系統(如文件系統)的語義。

錯誤: 因爲錯誤的路徑分隔符等因素,CLI 將在一些操做系統上沒法運行,即便代碼中沒有明顯的功能差別。

➡️ 細節:

單純從代碼的角度來看,功能沒有被剝離,而且應該在不一樣的操做系統中執行良好,可是一些遺漏的細節可能會使程序沒法運行。讓咱們來研究幾個必須遵照跨平臺規範的案例。

產生錯誤的命令

有時候咱們須要運行 Node.js 程序的進程,假設您有以下的腳本:

// program.js
#!/usr/bin/env bin

// your app code

而後使用以下方式啓動。

const cliExecPath = 'program.js'
const process = childProcess.spawn(cliExecPath, [])

上面的代碼能工做,可是下面這樣更好。

const cliExecPath = 'program.js'
const process = childProcess.spawn('node', [cliExecPath])

爲何這樣更好呢?由於 program.js 代碼以類 Unix 的 Shebang) 符號開始,可是因爲這不是跨平臺的標準,Windows 不知道如何解析。

package.json 中也是如此,以下方式定義 npm script 是不正確的:

"scripts": {
  "postinstall": "myInstall.js"
}

這是由於 Windows 沒法理解 myinstall.js 中的 Shebang ,而且不知道如何使用 node 解釋器運行它。

相反,請使用以下方法:

"scripts": {
  "postinstall": "node myInstall.js"
}

不一樣的 shell 解釋器

並非全部的字符在不一樣的 shell 解釋器都能獲得相同的處理。

例如, Windows 的命令提示符不會像 bash shell 那樣將單引號當作雙引號,所以它不知道單引號內的全部字符屬於同一個字符串組,這會致使錯誤。

下面的命令會致使在 Windows 環境下失效:

// package.json
"scripts": {
  "format": "prettier-standard '**/*.js'",
  ...
}

應該按照以下方式:

// package.json
"scripts": {
  "format": "prettier-standard \"**/*.js\"",
  ...
}

避免手動鏈接路徑

不一樣平臺會使用不一樣的路徑鏈接符,當經過手動鏈接它們時,會致使程序不能在不一樣的平臺以前相互操做。

讓咱們看看一個很差的案例:

const myPath = `${__dirname}/../bin/myBin.js`

它使用的是正斜槓,可是 Windows 上是使用反斜槓做爲路徑的分割符。因此咱們不要經過手動的方式構建文件系統路徑,而是使用 Node.js 的路徑模塊:

const myPath = path.join(__dirname, '..', 'bin', 'myBin.js')

避免使用分號連接命令

咱們在 Linux 上通常都使用分號來順序連接要運行的命令,例如:cd /tmp;ls。可是,在 Windows 上執行相同的操做會失敗。

const process = childProcess.exec(`${cliExecPath}; ${cliExecPath2}`)

咱們可使用 && 或者 ||

const process = childProcess.exec(`${cliExecPath} || ${cliExecPath2}`)

3.4 容許環境覆蓋

正確: 容許從環境變量中讀取配置,而且當它與命令行參數衝突時,容許環境變量被覆蓋。

錯誤: 儘可能不要使用自定義配置。

➡️ 細節:

使用環境變量調整配置,這是許多工具中用於修改 CLI 工具行爲的經常使用方法。

當命令行參數和環境變量都配置相同的設置時,應該給環境變量一個優先級來覆蓋該設置。

4 易用性

本節將介紹,如何在用戶缺少開發者設計工具所需環境的狀況下,更加容易地使用 Node.js CLI。

4.1 容許環境覆蓋

正確: 爲 CLI 建立一個 docker 鏡像,並將其發佈到Docker Hub之類的公共倉庫中,以便沒有 Node.js 環境的用戶可使用它。

錯誤: 沒有 Node.js 環境的用戶將沒有 npm 或 npx ,所以將沒法運行您的 CLI 工具。

➡️ 細節:

從 npm 倉庫中下載 Node.js CLI 工具一般將使用 Node.js 工具鏈(例如 npm 或 npx)來完成。這在JavaScript 和 Node.js 開發者中很容易完成。

可是,若是您將 CLI 程序提供給大衆使用,而無論他們是否熟悉 JavaScript 或該工具是否可用,那麼將限制 CLI 程序僅以 npm 倉庫形式的安裝分發。若是您的 CLI 工具打算在CI環境中使用,則可能還須要安裝那些與Node.js 相關的工具鏈依賴項。

打包和分發可執行文件的方式有不少,將預先綁定了 CLI 工具的Docker容器進行容器化,這是一種容易使用方法而且不須要太多依賴關係(除了須要 Docker 環境以外)。

4.2 優雅降級

正確: 在用戶不受支持的環境中提供沒有彩色和豐富交互的輸出,好比跳過某些交互直接提供 JSON 格式的輸出。

錯誤: 對於不受支持的終端用戶,使用終端交互可能會顯著下降最終用戶體驗,並阻止他們使用您的 CLI 工具。

➡️ 細節:

對於那些擁有豐富交互形式的終端的用戶來講,彩色輸出、ascii圖表、終端動畫會帶來很好的用戶體驗,可是對於沒有這些特性的終端用戶來講,它可能會顯示一下亂碼或者徹底沒法操做。

要使終端不受支持的用戶正確使用您的 CLI 工具,您有以下選擇:

  • 自動檢測終端能力,並在運行時評估是否對 CLI 的交互性進行降級;
  • 爲用戶提供一個選項來顯式地進行降級,例如經過提供一個 --json 命令行參數來強制輸出原始數據。

4.3 Node.js 版本兼容

正確: 支持目前還在維護的 Node.js 版本

錯誤: 試圖與不受支持的Node.js版本保持兼容的代碼庫將很難維護,而且會失去使用語言新特性的有點。

➡️ 細節:

有時可能須要專門針對缺乏新的 ECAMScript 特性的舊 Node.js 版本兼容。例如,若是您正在構建一個主要面向DevOps 的Node.js CLI,那麼他們可能沒有一個理想的 Node.js 環境或者是最新的 runtime。好比,Debian Stretch (oldstable) 附帶就是 Node.js 8.11.1.。

若是你的須要兼容舊版本的 Node. js 如 Node. js 八、六、4,最好是使用 Babel 之類的編譯器來確保生成的代碼與V8 JavaScript 引擎的版本兼容,並與這些版本附帶的Node.js runtime 兼容。

絕對不要所以簡化你的代碼,來使用一些舊的 ECMAScript 語言規範,由於這會產生代碼維護相關的問題。

4.4 自動檢測 Node.js runtime

正確: 在 Shebang 聲明中使用與安裝位置無關的引用,該引用可根據運行時環境自動定位 Node.js run

time。

錯誤: 硬編碼 Node.js runtime 位置,如 #!/usr/local/bin/node ,僅特定於您本身的環境,這可能使 CLI 工具在其餘 Node.js 安裝目錄不一樣的環境中沒法工做。

➡️ 細節:

首先在 cli.js 文件的頂部添加 #!/usr/local/bin/node,而後經過 node cli.js 來啓動 Node.js CLI,這是一個容易的開始。可是,這是一種有缺陷的方法,由於其餘用戶的環境沒法保證 node 可執行文件的位置。

咱們能夠將 #!/usr/bin/env node 做爲最佳實踐,可是這仍然假設 Node.js runtime 是被 bin/node 引用,而不是 bin/nodejs 或其餘。

5 測試

5.1 不要信任語言環境

正確: 不要假定輸出文本與您聲明的字符串等效,由於測試可能在與您的語言環境不一樣,好比在非英語環境的系統上運行。

錯誤: 當開發人員在非英語語言環境的系統上進行測試時,開發人員將遇到測試失敗。

➡️ 細節:

當您運行 CLI 並解析輸出來測試 CLI 時,您可能傾向於使用 grep 命令,以確保某些字符存在於輸出中,例如在不帶參數的狀況下運行 CLI 時:

const output = execSync(cli);
expect(output).to.contain("Examples:"));

若是在非英語的語言環境中運行測試,而且 CLI 參數解析庫支持自動檢測語言環境並採用該語言環境,則輸出從 Examples 轉換成了 「語言環境」 的語言,測試將失敗。

6 錯誤

6.1 錯誤信息

正確: 在展現錯誤信息時,提供能夠在項目文檔中查找的可跟蹤錯誤的代碼,從而簡化錯誤消息的排除。

錯誤: 通常的錯誤消息每每模棱兩可,用戶很難搜索解決方案。

➡️ 細節:

返回錯誤消息時,請確保它們包含特定的錯誤代碼,以便之後查閱。與HTTP狀態代碼很是類似,所以 CLI 工具須要命名或編碼錯誤。

例如:

$ my-cli-tool --doSomething

Error (E4002): please provide an API token via environment variables

6.2 可行的錯誤

正確: 錯誤消息應告訴用戶解決方案是什麼,而不是僅僅提示這裏存在錯誤。

錯誤: 面對錯誤消息,若是沒有任何解決錯誤的提示,則用戶可能沒法成功使用 CLI。

➡️ 細節:

例如:

$ my-cli-tool --doSomething

Error (E4002): please provide an API token via environment variables

6.3 提供調試模式

正確: 若是高級用戶須要診斷問題,則給他們提供更詳細的信息

錯誤: 不要關閉調試功能。由於只是從用戶那裏收集反饋,並讓他們查明錯誤緣由將特別困難。

➡️ 細節:

使用環境變量或命令行參數來設置調試模式並打開詳細輸出信息。在代碼中有意義的地方,植入調試消息,以幫助用戶和維護者理解程序,輸入和輸出以及其餘使解決問題變得容易的信息。

參考開源軟件包:


做者

Node.js CLI Apps Best Practices © Liran Tal, Released under CC BY-SA 4.0 License.

相關文章
相關標籤/搜索