这是关于如何构建成功、可移植且用户友好的Node.js命令行工具(CLI)的最佳实践集合。为什么写这篇文章?一个糟糕的CLI工具可能会让用户难以使用,构建一个成功的CLI需要密切关注很多细节,同时设身处地为用户着想,创造良好的用户体验。做到这一点并不容易。在本指南中,我列出了每个重点领域的最佳实践,以便在与CLI工具交互时获得最佳用户体验。特点:?构建成功的Node.jsCLI工具的21个最佳实践??帮助翻译成其他语言?欢迎捐款最后更新:2020-02-14为什么是我?我叫LiranTal,我一直专注于构建命令行工具。我最近的一些工作是构建Node.jsCLI,包括以下开源项目:Docklynpqlockfile-lintis-website-vulnerable用于管理Docker容器和服务的沉浸式终端界面安全地使用npm/yarn安装的包组织npm或yarn锁定文件分析和检测安全问题在网站引用的JS库中查找公共安全漏洞1命令行体验本节将介绍创建美观且高可用的Node.js命令运行工具相关的最佳实践。1.1尊重POSIX?正确:使用POSIX兼容的命令行语法,因为这是广泛接受的命令行工具标准。?误区:用户在使用CLI时,其命令行参数与自己习惯的不一致,会感觉难以适应。??详情:类Unix操作系统普及命令行工具,如awk、sed。这些工具有效地标准化了命令行选项“选项”(又名标志“标志”)、选项参数和其他操作的行为。一些示例:在帮助“help”中标记选项参数“option-arguments”,用方括号([])表示它们是可选的,或者用尖括号(<>)表示它们是必需的。参数可以使用单个字符缩写,通常是-后跟字母或数字。多个没有值的选项可以组合,例如:cli-abc等同于cli-a-b-c。用户通常希望您的命令行工具具有与其他Unix工具类似的约定。1.2构建友好的CLI?正确:输出尽可能多的信息,帮助用户成功使用CLI。?错误:由于CLI一直无法启动并且没有为用户提供足够的帮助,用户明显感到沮丧。??详情:命令行工具的界面应该在一定程度上类似于网页界面,尽可能保证程序可以正常使用。构建一个用户友好的CLI应该尽可能地支持用户。例如,让我们讨论curl命令的交互,当命令行提示用户阅读curl--help的输出时,它需要一个URL作为主要数据输入,而用户没有提供URL。但是,用户友好的CLI工具会显示交互式提示,捕获用户输入以正常运行。1.3有状态数据?正确:在多次调用CLI的过程中,提供有状态的体验,记住这些数据,提供无缝的交互体验。?Bugs:用户在多次调用CLI时重复提供相同的信息而感到恼火。??详情:你需要为CLI工具提供持久缓存,比如记住用户名、邮箱、token,或者CLI多次调用的一些偏好。以下工具可用于为用户保留这些配置。configstoreconf1.4提供丰富多彩的体验?正确:使用颜色高亮显示CLI工具中的一些信息,并提供fallback解决方案,检测,自动退出,避免乱码输出。?错误:苍白的输出会导致用户错过重要信息,尤其是当文本很多时。??详情:大多数命令行工具都支持彩色文本,由特定的ANSI编码启用。命令行工具输出的彩色文本可提供更丰富的体验和更多的交互性。但是,不支持的终端可能会在屏幕上输出乱码消息。此外,CLI还可用于不支持颜色输出的持续集成。chalkcolors1.5丰富的交互?正确:提供文字输入以外的其他交互形式,为用户提供更丰富的体验。?误区:当输入的信息是固定选项(如下拉菜单)时,文本输入的形式可能会给用户带来麻烦。??详情:可以通过提示输入的方式引入更丰富的交互方式,比自由文本输入更高级。例如,下拉列表、单选按钮切换、隐藏密码输入。丰富交互的另一个方面是动画和进度条,它们都在CLI执行异步工作时提供更好的用户体验。许多CLI提供默认命令行参数,无需进一步的用户交互。不要强迫用户提供一些非必需的参数。prompsenquirerinkora1.6无处不在的超链接?正确:URL(https://www.github.com)和源代码(src/Util.js:2:75)使用格式正确的文本输出,因为两者都是现代终端的可点击链接.?错误:避免使用像git.io/abc这样需要用户手动复制和粘贴的非交互式链接。??详情:如果您要分享的信息在Url链接中,或者文件的特定行中,您需要以正确的格式向用户提供链接。一旦用户点击它们,它们将打开浏览器或跳转到IDE中的特定文件。地点。1.7零配置?正确:通过自动检测所需的配置和命令行参数来实现开箱即用的体验。?False:如果可以以可靠的方式自动检测命令行参数,并且调用的操作不需要明确的用户确认(例如确认删除),则不要强制用户交互。??详细信息:旨在在运行CLI工具时提供“开箱即用”的体验。TheJestJavaScriptTestingFrameworkParcel,一个Web应用程序捆绑器2版本本节描述了有关如何最好地分发和打包Node.jsCLI工具的最佳实践。2.1最小化依赖?正确:最小化生产环境的依赖,使用最小的可以替换的依赖包,确保这是一个尽可能小的Node.js包。但是,对于通过重新发明轮子来过度优化依赖项,不应过于谨慎。?错误:应用程序中依赖项的大小将决定CLI安装时间,从而导致用户体验不佳。??详情:使用npx快速调用通过npminstall安装的Node.jsCLI模块,提供更好的用户体验。这有助于将整体依赖性和传递依赖性保持在合理的大小。npm全局安装模块时,安装过程变慢,体验很差。当前项目安装的模块(当前文件夹的node_modules)总是通过npx获取,因此使用npx调用CLI可能会降低性能。2.2使用文件锁?更正:通过npm提供的package-lock.json对安装包进行锁定,确保安装时用户使用的依赖版本准确无误。?错误:不锁定依赖项的版本意味着npm将在安装过程中自行解决它们,从而导致安装的依赖项范围更广,这会引入无法控制的更改,可能会导致CLI无法成功运行。??详情:通常,一个npm包只在发布时定义它的直接依赖和它们的版本范围,npm在安装时解析所有间接依赖的版本。间接依赖版本会随时间变化,因为新版本的依赖会随时发布。尽管版本控制语义被维护者广泛使用,但npm为已安装的包引入了许多间接依赖性,从而增加了破坏应用程序的风险。使用package-lock.json会给用户更好的安全感。将要安装的依赖项固定到特定版本,因此即使发布了这些依赖项的更新版本,也不会安装它们。这将使您有责任关注您的依赖项,了解其中任何与安全相关的修复,并通过定期发布CLI工具进行安全更新。考虑使用Snyk自动修复整个依赖树中的安全问题。注意:我是Snyk的开发人员。参考:你真的知道yarn和npm包的锁文件是如何工作的吗?Yarn文档:锁文件应该提交到存储库吗?3一般性本节介绍有关使Node.jsCLI与其他命令行工具最佳实践无缝集成并遵循CLI正常运行约定的最重要事项。本节回答以下问题:我可以导出CLI的输出以供分析吗?我可以将CLI的输出通过管道传输到另一个命令行工具的输入吗?是否可以将其他工具的结果通过管道传输到此CLI中?3.1接受STDIN作为输入?正确:对于数据驱动的命令行应用程序,用户可以轻松地将数据通过管道传输到STDIN。?Bug:其他命令行工具可能无法直接向您的CLI提供数据输入,这会阻止某些代码正常工作,例如:$curl-s"https://api.example.com/data.json"|your_cli??详情:如果命令行工具需要处理一些数据,比如指定一个JSON文件来执行某项任务,一般使用命令行参数--filefile.json。3.2结构化输出?正确:一个参数用于允许应用程序结果的结构化输出,这使得数据更容易处理和解析。?错误:用户可能需要使用复杂的正则表达式来解析和匹配CLI输出。??详细信息:对于CLI的用户来说,解析数据并使用它来执行其他任务(例如,提供给网络仪表板或电子邮件)通常很有用。能够从命令行输出中轻松获取所需数据将为CLI用户提供更好的体验。3.3跨平台?正确:如果你想让你的CLI跨平台工作,你必须注意命令行shell和文件系统等子系统的语义。?错误:由于路径分隔符错误等原因,CLI在某些操作系统上无法运行,即使代码中没有明显的功能差异。??细节:纯粹从代码的角度来看,功能并没有被剥离,并且应该在不同的操作系统上表现良好,但是一些缺失的细节可能会导致程序无法运行。让我们检查一些必须遵守跨平台规范的情况。产生错误的命令有时我们需要运行Node.js程序的进程,假设你有如下脚本://program.js#!/usr/bin/envbin//你的app代码然后使用下面的方法开始。constcliExecPath='program.js'constprocess=childProcess.spawn(cliExecPath,[])上面的代码有效,但下面的更好。constcliExecPath='program.js'constprocess=childProcess.spawn('node',[cliExecPath])为什么这样更好?因为program.js代码以类UnixShebang)符号开头,但由于这不是跨平台标准,Windows不知道如何解析它。package.json中也是如此,这样定义npmscripts是不正确的:"scripts":{"postinstall":"myInstall.js"}这是因为Windows无法理解myinstall.js中的shebang并且不知道如何使用节点解释器运行它。相反,使用类似这样的东西:例如,Windows的命令提示符不会像bashshell那样将单引号视为双引号,因此它不知道单引号内的所有字符都属于同一个字符串组,这会导致错误。以下命令在Windows环境中会失败://package.json"scripts":{"format":"prettier-standard'**/*.js'",...}应该如下所示://package.json"scripts":{"format":"prettier-standard\"**/*.js\"",...}避免手动连接路径不同的平台会使用不同的路径连接器,当手动连接的时候,会导致程序不能在不同平台上互操作。让我们看一个糟糕的情况:constmyPath=`${__dirname}/../bin/myBin.js`,它使用正斜杠,但在Windows上使用反斜杠作为路径分隔符。因此,我们没有手动构建文件系统路径,而是使用Node.js路径模块:constmyPath=path.join(__dirname,'..','bin','myBin.js')避免使用分号链接命令我们通常使用分号链接要在Linux上按顺序运行的命令,例如:cd/tmp;ls。但是,在Windows上执行相同操作会失败。constprocess=childProcess.exec(`${cliExecPath};${cliExecPath2}`)我们可以使用&&或||:constprocess=childProcess.exec(`${cliExecPath}||${cliExecPath2}`)3.4许可环境覆盖?正确:允许从环境变量中读取配置,并允许在与命令行参数冲突时覆盖环境变量。?BAD:尽量不要使用自定义配置。??详情:使用环境变量调整配置是许多工具中用来修改CLI工具行为的常用方法。当命令行参数和环境变量配置相同的设置时,环境变量应该被赋予优先级以覆盖该设置。4易用性本节将介绍当用户缺乏开发者设计工具所需的环境时,如何更方便地使用Node.jsCLI。4.1允许环境覆盖?正确:为CLI创建一个docker镜像并将其发布到DockerHub等公共存储库,以便没有Node.js环境的用户可以使用它。?错误:没有Node.js环境的用户将没有npm或npx,因此将无法运行您的CLI工具。??详情:从npm存储库下载Node.jsCLI工具通常使用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.3Node.js版本兼容?正确:支持当前维护的Node.js版本。?错误:试图与不受支持的Node.js版本保持兼容的代码库将难以维护,并且会失去使用该语言新功能的优势。??详细信息:有时可能需要专门针对缺少新ECAMScript功能的旧Node.js版本的兼容性。例如,如果您主要为DevOps构建Node.jsCLI,他们可能没有理想的Node.js环境或最新的运行时。例如,DebianStretch(oldstable)随Node.js8.11.1一起发布。如果你的需求兼容Node.js8、6、4等老版本的Node.js,最好使用Babel等编译器,保证生成的代码兼容V8JavaScript引擎的版本,以及那些版本。Node.js运行时兼容。绝对不要简化你的代码来使用一些旧的ECMAScript语言规范,因为这会导致与代码维护相关的问题。4.4自动检测Node.js运行时?正确:在shebang声明中使用独立于安装的引用,根据运行时环境自动定位Node.js运行时。?错误:#!/usr/local/bin/node等硬编码Node.js运行时位置仅特定于您自己的环境,这可能会阻止CLI工具在Node.js安装目录不同的其他环境中工作。??详情:首先在cli.js文件的最前面添加#!/usr/local/bin/node,然后通过nodecli.js启动Node.jsCLI,很简单。然而,这是一种有缺陷的方法,因为节点可执行文件的位置不能由其他用户的环境保证。我们可以使用#!/usr/bin/envnode作为最佳实践,但这仍然假设Node.js运行时由bin/node引用,而不是bin/nodejs或其他。5测试5.1不要相信语言环境?正确:不要假设输出文本等同于您声明的字符串,因为测试可能在与您的语言环境不同的系统上运行,例如非英语环境。?Bugs:开发者在非英文语言环境的系统上测试时,会遇到测试失败的情况。??详细信息:当您运行CLI并解析输出以测试CLI时,您可能会想使用grep命令来确保输出中存在某些字符,例如在不带参数运行CLI时:constoutput=execSync(cli);expect(output).to.contain("Examples:"));如果测试在非英语语言环境中运行,并且CLI参数解析库支持自动检测语言环境并采用该语言环境,则输出来自Examples如果语言转换为“语言环境”,测试将失败。6错误6.1错误信息?更正:显示错误信息时,提供可在项目文档中找到的可追溯错误代码,从而简化错误信息的排查。?错误:一般的错误信息往往是模棱两可的,让用户很难搜索到解决方案。??详情:当返回错误消息时,确保它们包含特定的错误代码,以便您稍后参考。很像HTTP状态代码,因此CLI工具需要命名或编码错误。例如:$my-cli-tool--doSomethingError(E4002):pleaseprovideanAPItokenviaenvironmentvariables6.2可操作的错误?正确:错误消息应该告诉用户解决方案是什么,而不仅仅是提示有错误。?错误:面对错误信息,如果没有任何提示来解决错误,用户可能无法成功使用CLI。??详情:eg:$my-cli-tool--doSomethingError(E4002):pleaseprovideanAPItokenviaenvironmentvariables6.3providedebugmode?Correct:如果高级用户需要诊断问题,给他们更详细的信息?Error:Do不关闭调试。因为仅从用户那里收集反馈并让他们查明错误原因将特别困难。??Verbose:使用环境变量或命令行参数设置调试模式并开启详细输出。在代码中有意义的地方植入调试消息,以帮助用户和维护人员理解程序、程序的输入和输出,以及其他有助于轻松解决问题的信息。参考开源软件包:调试作者Node.jsCLIAppsBestPractices?LiranTal,ReleasedunderCCBY-SA4.0License.
