Node.js CLI 工具最佳实践
為什么寫這篇文章?
一個糟糕的 CLI 工具會讓用戶覺得難用,而構建一個成功的 CLI 需要密切關注很多細節,同時需要站在用戶的角度,創造良好的用戶體驗。要做到這些特別不容易。
在這個指南中,我列出了在各個重點領域的最佳實踐,都是與 CLI 工具交互最理想的用戶體驗。
1 命令行的經驗
本節將會介紹創建美觀且高可用的 Node.js 命令行工具相關的最佳實踐。
1.1 尊重 POSIX
? 正確: 使用兼容 POSIX-compliant 命令行的語法,因為這是被廣泛接受的命令行工具的標準。
? 錯誤: 當用戶使用CLI,其命令行參數與他們過去的使用習慣不一致時,會感覺很難適應。
?? 細節:
Unix-like 操作系統普及了命令行工具,比如awk,sed。這樣的工具已經有效地標準化了命令行選項「options」(又名標志「flags」),選項參數和其他操作的行為。
一些案例:
-
在幫助「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 多次調用的一些首選項。可以使用以下工具來保留用戶的這些配置。
-
configstore
-
conf
1.4 提供多彩的體驗
? 正確: 在 CLI 工具中使用顏色來突出顯示一些信息,并且提供降級方案,進行檢測,自動退出以免輸出亂碼。
? 錯誤: 蒼白的輸出可能會讓用戶丟失重要的信息,尤其是文本較多的時候。
?? 細節:
大多數的命令行工具都支持彩色文本,通過特定的 ANSI 編碼來啟用。
命令行工具輸出彩色文本可帶來更豐富的體驗和更多的交互。但是,不受支持的終端可能會在屏幕上以亂碼信息的形式輸出。此外,CLI 也可能用于不支持彩色輸出的連續集成中。
-
chalk
-
colors
1.5 豐富的交互
? 正確: 提供除了文本輸入之外的其他交互形式,為用戶提供更加豐富的體驗。
? 錯誤: 當輸入的信息是固定的選項(類似下拉菜單)時,文本輸入的形式可能會給用戶帶來麻煩。
?? 細節:
可以以提示輸入的方式引入更加豐富的交互方式,提示輸入比自由的文本輸入更高端。例如,下拉列表、單選按鈕切換、隱藏密碼輸入。豐富交互的另一個方面就是動畫以及進度條,在 CLI 執行異步工作時,都能為用戶提供更好的體驗。
許多 CLI 提供默認的命令行參數,而無需用戶進一步交互。不強迫用戶提供一些非必要的參數。
-
prompts
-
enquirer
-
ink
-
ora
1.6 無處不在的超鏈接
? 正確: URL(https://www.github.com)和源代碼( src/Util.js:2:75)使用格式正確的文本輸出,因為這兩者都是現代終端可點擊的鏈接。
? 錯誤: 避免使用 git.io/abc之類的非交互式的鏈接,該鏈接需要用戶手動復制和粘貼。
?? 細節:
如果你要分享的信息在 Url 鏈接中,或者是某個文件的特定行列,則需要向用戶提供正確的格式的鏈接,用戶一旦點擊它們,就會打開瀏覽器或者在IDE跳到特定位置。
1.7 零配置
? 正確: 通過自動檢測所需的配置和命令行參數,達到即開即用的體驗。
? 錯誤: 如果可以以可靠的方式自動檢測命令行參數,并且調用的操作不需用戶顯式確認(例如確認刪除),則不要強制用戶交互。
?? 細節:
旨在在運行 CLI 工具時提供“即開即用”的體驗。
-
The?Jest JavaScript Testing Framework
-
Parcel, a web application bundler
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的開發者開發者。參考:
-
Do you really know how a lockfile works for yarn and npm packages?
-
Yarn docs: Should lockfiles be committed to the repository?
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 8、6、4,最好是使用 Babel 之類的編譯器來確保生成的代碼與V8 JavaScript 引擎的版本兼容,并與這些版本附帶的Node.js runtime 兼容。
絕對不要因此簡化你的代碼,來使用一些舊的 ECMAScript 語言規范,因為這會產生代碼維護相關的問題。
4.4 自動檢測 Node.js runtime
? 正確: 在 Shebang 聲明中使用與安裝位置無關的引用,該引用可根據運行時環境自動定位 Node.js runtime。
? 錯誤: 硬編碼 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 提供調試模式
? 正確: 如果高級用戶需要診斷問題,則給他們提供更詳細的信息
? 錯誤: 不要關閉調試功能。因為只是從用戶那里收集反饋,并讓他們查明錯誤原因將特別困難。
?? 細節:
使用環境變量或命令行參數來設置調試模式并打開詳細輸出信息。在代碼中有意義的地方,植入調試消息,以幫助用戶和維護者理解程序,輸入和輸出以及其他使解決問題變得容易的信息。
參考開源軟件包:
-
debug
總結
以上是生活随笔為你收集整理的Node.js CLI 工具最佳实践的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: bilibili Saber 实时计算平
- 下一篇: 怎么更改eclipse中tomcat的s