這是一個關於如何構建成功的、可移植的、對用戶友好的Node.js 命令行工具(CLI)最佳實踐的集合。html
一個糟糕的 CLI 工具會讓用戶以爲難用,而構建一個成功的 CLI 須要密切關注不少細節,同時須要站在用戶的角度,創造良好的用戶體驗。要作到這些特別不容易。node
在這個指南中,我列出了在各個重點領域的最佳實踐,都是 CLI 工具交互最理想的用戶體驗。git
我叫Liran Tal,我一直專一於構建命令行工具。github
我最近的一些工做就是構建Node.js CLI,包括如下開源項目:web
Dockly | npq | lockfile-lint | is-website-vulnerable |
---|---|---|---|
沉浸式終端界面,用於管理Docker容器和服務 | 經過在安裝過程當中進行檢查,以安全地使用npm / yarn 安裝的軟件包 | 整理 npm 或 yarn 的 lock 文件以分析和檢測安全問題 | 在網站引用的 JS 庫中查找公開的安全漏洞 |
本節將會介紹建立美觀且高可用的 Node.js 命令行工具相關的最佳實踐。docker
✅ 正確: 使用兼容 POSIX-compliant 命令行的語法,由於這是被普遍接受的命令行工具的標準。shell
❌ 錯誤: 當用戶使用CLI,其命令行參數與他們過去的使用習慣不一致時,會感受很難適應。npm
➡️ 細節:json
Unix-like 操做系統普及了命令行工具,好比awk,sed。這樣的工具已經有效地標準化了命令行選項「options」(又名標誌「flags」),選項參數和其餘操做的行爲。api
一些案例:
-
加上一個字母或數字。cli -abc
等價於 cli -a -b -c
。用戶通常都會但願你的命令行工具與其餘Unix工具具備相似的約定。
✅ 正確: 儘量多的輸出一些信息以幫助用戶成功使用 CLI。
❌ 錯誤: 因爲 CLI 一直啓動失敗,又沒有爲用戶提供足夠的幫助,會讓用戶產生明顯的挫敗感。
➡️ 細節:
命令行工具的界面必定程度上應與 Web 用戶界面相似,儘量的保證程序能正常使用。
構建一個對用戶友好的 CLI 應該儘量的爲用戶提供支持。做爲實例,咱們討論下 curl
命令的交互,該命令指望將 URL 做爲主要的數據輸入,而用戶卻沒有提供 URL,這時候命令行會提示用戶通讀 curl --help
的輸出信息。可是,對用戶友好的 CLI 工具會顯示一個可交互式的提示,捕獲用戶的輸入,從而正常運行。
✅ 正確: 在屢次調用 CLI 的過程當中,提供有狀態的體驗,記住這些數據,以提供無縫的交互體驗。
❌ 錯誤: 用戶屢次調用 CLI 重複提供相同的信息,會讓用戶感到厭煩。
➡️ 細節:
你須要爲 CLI 工具提供持續緩存,好比記住用戶名、電子郵件、token 或者是 CLI 屢次調用的一些首選項。可使用如下工具來保留用戶的這些配置。
✅ 正確: 在 CLI 工具中使用顏色來突出顯示一些信息,而且提供降級方案,進行檢測,自動退出以避免輸出亂碼。
❌ 錯誤: 蒼白的輸出可能會讓用戶丟失重要的信息,尤爲是文本較多的時候。
➡️ 細節:
大多數的命令行工具都支持彩色文本,經過特定的 ANSI 編碼來啓用。
命令行工具輸出彩色文本可帶來更豐富的體驗和更多的交互。可是,不受支持的終端可能會在屏幕上以亂碼信息的形式輸出。此外,CLI 也可能用於不支持彩色輸出的連續集成中。
✅ 正確: 提供除了文本輸入以外的其餘交互形式,爲用戶提供更加豐富的體驗。
❌ 錯誤: 當輸入的信息是固定的選項(相似下拉菜單)時,文本輸入的形式可能會給用戶帶來麻煩。
➡️ 細節:
能夠以提示輸入的方式引入更加豐富的交互方式,提示輸入比自由的文本輸入更高端。例如,下拉列表、單選按鈕切換、隱藏密碼輸入。豐富交互的另外一個方面就是動畫以及進度條,在 CLI 執行異步工做時,都能爲用戶提供更好的體驗。
許多 CLI 提供默認的命令行參數,而無需用戶進一步交互。不強迫用戶提供一些非必要的參數。
✅ 正確: URL(https://www.github.com)和源代碼(src/Util.js:2:75
)使用格式正確的文本輸出,由於這二者都是現代終端可點擊的連接。
❌ 錯誤: 避免使用git.io/abc
之類的非交互式的連接,該連接須要用戶手動複製和粘貼。
➡️ 細節:
若是你要分享的信息在 Url 連接中,或者是某個文件的特定行列,則須要向用戶提供正確的格式的連接,用戶一旦點擊它們,就會打開瀏覽器或者在IDE跳到特定位置。
✅ 正確: 經過自動檢測所需的配置和命令行參數,達到即開即用的體驗。
❌ 錯誤: 若是能夠以可靠的方式自動檢測命令行參數,而且調用的操做不需用戶顯式確認(例如確認刪除),則不要強制用戶交互。
➡️ 細節:
旨在在運行 CLI 工具時提供「即開即用」的體驗。
本節介紹瞭如何以最佳方式分發和打包 Node.js CLI 工具的最佳實踐。
✅ 正確: 最大程度地減小生產環境的依賴項,而且使用可替代的最小的依賴包,確保這是一個儘量小的 Node.js 包。可是,也不能過於謹慎所以重複發明輪子而過分優化依賴。
❌ 錯誤: 應用中依賴的大小將決定 CLI 的安裝時間,從而致使糟糕的用戶體驗。
➡️ 細節:
使用 npx
能夠快速調用經過 npm install
安裝的 Node.js CLI 模塊,這可提供更好的用戶體驗。這有助於將總體的依賴關係和傳遞依賴關係保持在合理大小。
npm 全局安裝模塊,安裝過程會變得緩慢,這是一個糟糕的體驗。經過 npx 老是獲取當前項目安裝的模塊(當前文件夾的node_modules),所以使用 npx
來調用 CLI 可能會下降性能。
✅ 正確: 經過 npm 提供的 package-lock.json 來鎖定安裝包,以確保用戶安裝的時候使用的依賴版本是準確的。
❌ 錯誤: 不鎖定依賴的版本,意味着 npm 將在安裝過程當中本身解決他們,從而致使安裝依賴的版本範圍擴大,這會引入沒法控制的更改,可能會讓 CLI 沒法成功運行。
➡️ 細節:
一般,npm 包在發佈時只定義其直接的依賴項及其版本範圍,而且 npm 會在安裝時解析全部間接依賴項的版本。隨着時間的流逝,間接的依賴項版本會有所不一樣,由於依賴項隨時會發布新版本。
儘管維護人員已普遍使用版本控制語義,可是 npm 會爲安裝的包引入許多間接的依賴關係,這些間接依賴提高了破壞您的應用程序的風險。
使用 package-lock.json 會帶給用戶更好的安全感。將要安裝的依賴項固定到特定版本,所以,即便這些依賴項發佈了較新的版本,也不會安裝它們。這將讓您有責任保持對依賴項的關注,瞭解依賴項中任何安全相關的修復,並經過按期發佈 CLI 工具進行安全更新。能夠考慮使用Snyk 來自動修復整個依賴性樹中的安全性問題。注:我是Snyk的開發者開發者。
參考:
本節將介紹使 Node.js CLI 與其餘命令行工具無縫集成有關的最佳實踐,並遵循 CLI 正常運行的約定。
本節將回答如下問題:
✅ 正確: 對於數據驅動的命令行應用,用戶能夠輕鬆的經過管道將數據輸入到 STDIN。
❌ 錯誤: 其餘的命令行工具可能沒法直接提供數據輸入到你的 CLI 中,這會阻止某些代碼的正常運行,例如:
$ curl -s "https://api.example.com/data.json" | your_cli
➡️ 細節:
若是命令行工具須要處理某些數據,好比,指定 JSON 文件執行某種任務,通常使用 --file file.json
的命令行參數。
✅ 正確: 經過某個參數來容許應用的結果進行結構化的輸出,這樣使得數據更容易處理和解析。
❌ 錯誤: 用戶可能須要使用複雜的正則來解析和匹配 CLI 的輸出結果。
➡️ 細節:
對於 CLI 的用戶來講,解析數據並使用數據來執行其餘任務(好比,提供給 web 儀表盤或電子郵件)一般頗有用。
可以輕鬆地從命令行輸出中獲得須要的數據,這將爲 CLI 的用戶提供更好的體驗。
✅ 正確: 若是但願 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 解釋器都能獲得相同的處理。
例如, 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}`)
✅ 正確: 容許從環境變量中讀取配置,而且當它與命令行參數衝突時,容許環境變量被覆蓋。
❌ 錯誤: 儘可能不要使用自定義配置。
➡️ 細節:
使用環境變量調整配置,這是許多工具中用於修改 CLI 工具行爲的經常使用方法。
當命令行參數和環境變量都配置相同的設置時,應該給環境變量一個優先級來覆蓋該設置。
本節將介紹,如何在用戶缺少開發者設計工具所需環境的狀況下,更加容易地使用 Node.js CLI。
✅ 正確: 爲 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 環境以外)。
✅ 正確: 在用戶不受支持的環境中提供沒有彩色和豐富交互的輸出,好比跳過某些交互直接提供 JSON 格式的輸出。
❌ 錯誤: 對於不受支持的終端用戶,使用終端交互可能會顯著下降最終用戶體驗,並阻止他們使用您的 CLI 工具。
➡️ 細節:
對於那些擁有豐富交互形式的終端的用戶來講,彩色輸出、ascii圖表、終端動畫會帶來很好的用戶體驗,可是對於沒有這些特性的終端用戶來講,它可能會顯示一下亂碼或者徹底沒法操做。
要使終端不受支持的用戶正確使用您的 CLI 工具,您有以下選擇:
--json
命令行參數來強制輸出原始數據。✅ 正確: 支持目前還在維護的 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 語言規範,由於這會產生代碼維護相關的問題。
✅ 正確: 在 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 或其餘。
✅ 正確: 不要假定輸出文本與您聲明的字符串等效,由於測試可能在與您的語言環境不一樣,好比在非英語環境的系統上運行。
❌ 錯誤: 當開發人員在非英語語言環境的系統上進行測試時,開發人員將遇到測試失敗。
➡️ 細節:
當您運行 CLI 並解析輸出來測試 CLI 時,您可能傾向於使用 grep 命令,以確保某些字符存在於輸出中,例如在不帶參數的狀況下運行 CLI 時:
const output = execSync(cli); expect(output).to.contain("Examples:"));
若是在非英語的語言環境中運行測試,而且 CLI 參數解析庫支持自動檢測語言環境並採用該語言環境,則輸出從 Examples
轉換成了 「語言環境」 的語言,測試將失敗。
✅ 正確: 在展現錯誤信息時,提供能夠在項目文檔中查找的可跟蹤錯誤的代碼,從而簡化錯誤消息的排除。
❌ 錯誤: 通常的錯誤消息每每模棱兩可,用戶很難搜索解決方案。
➡️ 細節:
返回錯誤消息時,請確保它們包含特定的錯誤代碼,以便之後查閱。與HTTP狀態代碼很是類似,所以 CLI 工具須要命名或編碼錯誤。
例如:
$ my-cli-tool --doSomething Error (E4002): please provide an API token via environment variables
✅ 正確: 錯誤消息應告訴用戶解決方案是什麼,而不是僅僅提示這裏存在錯誤。
❌ 錯誤: 面對錯誤消息,若是沒有任何解決錯誤的提示,則用戶可能沒法成功使用 CLI。
➡️ 細節:
例如:
$ my-cli-tool --doSomething Error (E4002): please provide an API token via environment variables
✅ 正確: 若是高級用戶須要診斷問題,則給他們提供更詳細的信息
❌ 錯誤: 不要關閉調試功能。由於只是從用戶那裏收集反饋,並讓他們查明錯誤緣由將特別困難。
➡️ 細節:
使用環境變量或命令行參數來設置調試模式並打開詳細輸出信息。在代碼中有意義的地方,植入調試消息,以幫助用戶和維護者理解程序,輸入和輸出以及其餘使解決問題變得容易的信息。
參考開源軟件包:
Node.js CLI Apps Best Practices © Liran Tal, Released under CC BY-SA 4.0 License.