Update avaliable. Click RELOAD to update.
目录

从零开始创建并发布NPM Cli的应用

1. 背景

当下 javascript 大行其道,无论从传统web、手机端还是桌面程序,javacsript 无所不能。ES6 的定义 以及 nodejs 大大推动了 javascript 的发展。而大部分的应用都离不开 npm,很多应用都使用 nodejs cli 的方式进行初始化等操作。

本篇文章将创建一个简单的 nodejs cli 的气温应用,以便熟悉如何自己创建 cli 命令行程序。

2. 前提

3. Hello World

3. 1 创建工程基本结构

和很多 javascript 工程一样,需要一个 package.json 文件以及一个入口的 index.js 文件,这里强调简单的应用,所以尽量不做过多依赖库。

[package.json]

{
  "name": "weather-cli",
  "version": "1.0.0",
  "license": "MIT",
  "scripts": {},
  "devDependencies": {},
  "dependencies": {}
}

[index.js]

module.exports = () => {
  console.log('Welcome to the weather app!')
}

3.2 创建一个Bin文件

我们需要一种方法来调用新开发的 javascript 程序并显示欢迎信息,并将其加入到系统路径中,以便在任何地方都可以调用它,bin 文件是实现此目的的好方法。

[bin/weather]

#!/usr/bin/env node
require('../')()

使用 bin 文件运行程序,使用命令 cd bin 进入bin目录,并给 weather 文件赋予执行权限 chmod u+x ./weather,直接执行 ./weather 就可以显示欢迎信息。

但这不是我们想要的,我们需要在任何路径下都可以执行,也就是使用 npm 全局安装的方式 npm install -g weather,所以需要将 bin 文件配置到 package.json 中。

[package.json]

{
  "name": "weather-cli",
  "version": "1.0.0",
  "license": "MIT",
  "bin": {
    "weather": "bin/weather"
  },
  "scripts": {},
  "devDependencies": {},
  "dependencies": {}
}

增加好之后,在工程根路径下执行 npm link 命令会将二进制文件符号链接到系统路径,使其可以在任何地方通过在外部运行访问。

此时你可以在任何路径下都可以执行 weaher 命令了。

到此为止的工程结构为:

weather-cli/
	|- package.json
	|- index.js
	|- bin
		|- weather

4. 气温应用

4.1 命令行参数

如何处理接受的命令行参数,这里使用 minimist 的解析库,在根路径下使用 npm install --save minimist 进行安装

[index.js]

const minimist = require('minimist')

module.exports = () => {
    // slice(2) 删除前两个参数,因为第一个参数是解释器,第二个参数是文件名
    const args = minimist(process.argv.slice(2));
    console.log(args);
}

现在运行 weather today --location "NanJing, ZH",它将会输出 { _: [ 'today' ], location: 'NanJing, ZH' } ,从输出结果可以得知,这是一个清晰的 json 结构,到此我们建立了第一个解析命令行参数的 cli 程序。

4.2 运行命令

将每个命令代码分隔到不同文件中,需要的时候进行加载。这会防止加载不必要的模块并减少启动程序的时间,在这种设置下,每个命令文件都应该导出一个函数,并将参数传递给它。

[index.js]

const minimist = require('minimist')

module.exports = () => {
    // slice(2) 删除前两个参数,因为第一个参数是解释器,第二个参数是文件名
    const args = minimist(process.argv.slice(2));
    // args = { _: [ 'today' ], location: 'NanJing, ZH' }
    // 取第一个命令
    const cmd = args._[0];

    switch (cmd) {
        case 'today':
            // 需要的时候加载运行并把参数进行传递
            require('./cmds/today')(args);
            break;
        default:
            console.error(`"${cmd}" is not a valid command!`)
            break;
    }
}

[cmds/today.js]

module.exports = (args) => {
    console.log('today is sunny')
}

现在如果运行 weather today 则会输出 “today is sunny”,若运行 weather hello 则会输出错误的信息 “hello is not a valid command!”,

4.3 预期与容错

除了程序用命令,还需要额外处理如 versionhelp 此类的命令,当用户输入错误的参数应该会得到正确的反馈信息,以提示用户正确的进行参数的输入。

[index.js]

const minimist = require('minimist')

module.exports = () => {
    const args = minimist(process.argv.slice(2));
    // 取第一个参数命令,若没有获取到则默认使用 help 命令
    let cmd = args._[0] || 'help';

    if (args.version || args.v) {
        cmd = 'version'
    }

    if (args.help || args.h) {
        cmd = 'help'
    }

    switch (cmd) {
        case 'today':
            require('./cmds/today')(args);
            break;
        case 'version':
            require('./cmds/version')(args);
            break;
        case 'help':
            require('./cmds/help')(args);
            break;
        default:
            console.error(`"${cmd}" is not a valid command!`)
            break;
    }
}

[cmds/version]

const { version } = require('../package.json')

module.exports = (args) => {
  console.log(`v${version}`)
}

[cmds/help]

const menus = {
    main : `
        weather [command] <options>

        today   ........... show weather for today
        version ........... show program version
        help    ........... show help menu for a command
    `,

    today : `
        weather today <options>

        --location, -l .... the location to use
    `,    
}

module.exports = (args) => {
    const subCmd = args._[0] === 'help' ? args._[1] : args._[0];

    console.log(menus[subCmd] || menus.main)
}

此时如果运行 weather help today 或者 weather today -h,将会看见 today 命令的帮助信息。运行 weather 或者 weather -h 将会输出主菜单帮助信息。

4.4 进度与请求

某些命令可能会执行很长时间,为了让用户得到正在运行的反馈状态,应该在执行时显示 进度,使用命令 npm install --save axios ora 安装第三方的进度与网络请求的库。

现在我们开始请求天气,使用的是 雅虎 的 API,此 API 使用的 “YQL” 的语法查询,关键的是它不需要 API Key 就可以直接进行接口的调用,非常方便。

[utils/yahoo.js]

const axios = require('axios')

module.exports = async (location) => {
    const results = await axios({
        method : 'get',
        url : 'https://query.yahooapis.com/v1/public/yql',
        params : {
            format : 'json',
            q: `select item from weather.forecast where woeid in
            (select woeid from geo.places(1) where text="${location}")`,
        }
    });

    return results.data.query.results.channel.item
}

[cmds/today.js]

const ora = require('ora')
const getWeather = require('../utils/yahoo')

module.exports = async (args) => {
    // 开启进度
    const spinner = ora().start()

    try {
        const location = args.location || args.l
        const weather = await getWeather(location)

        // 关闭进度
        spinner.stop()

        console.log(`Current conditions in ${location}:`)
        console.log(`\t${weather.condition.temp}° ${weather.condition.text}`)
    } catch (err) {
        // 关闭进度
        spinner.stop()

        console.error(err)
    }
}

这里在 yahoo.js 中封装了调用温度的接口,在命令文件 today.js 中进行调用,加载进度并解析返回值,当输入 weather today --location "BeiJing, ZH" 时,将会输出指定城市的气温,注意这里只是个示例,只取了返回值的 temp 字段。

4.5 错误与退出码

至此我们的程序结束了吗?没有。如果你的 cli 程序有严重错误,应该使用 process.exit(1) 的方式进行退出,这可以告诉终端此进程异常终止。

[utils/error.js]

module.exports = (message, exit) => {
    console.error(message)
    exit && process.exit(1)
}

[index.js]

// ...
const error = require('./utils/error')

module.exports = () => {
  // ...
  default:
    // console.error(`"${cmd}" is not a valid command!`)
    error(`"${cmd}" is not a valid command!`, true)
    break
    // ...
}

5. 发布到NPM仓库

为了让更多人可以使用到你编写的 nodejs 库,应该上传到 npm 仓库中,当然这只是个例子,为了能够上传,需要完善 package.json 文件

{
  "name": "weather-cli",
  "version": "1.0.0",
  "description": "A cli app that gives you the weather",
  "license": "MIT",
  "homepage": "https://github.com/wangjunneil/weather-cli",
  "repository": {
    "type": "git",
    "url": "git+https://github.com/wangjunneil/weather-cli"
  },
  "engines": {
    "node": ">=8"
  },
  "keywords": [
    "weather",
    "vinny",
    "javascript"
  ],
  "preferGlobal": true,
  "bin": {
    "weather": "bin/weather"
  },
  "scripts": {},
  "devDependencies": {},
  "dependencies": {
    "axios": "^0.18.0",
    "minimist": "^1.2.0",
    "ora": "^2.1.0"
  }
}

首次上传的话你需要有 npm 的账号信息,若没有请去 https://www.npmjs.com/ 进行注册,注册完成后,先使用 npm adduser 将账号信息在电脑中进行验证,最后在根路径中执行 npm publish 命令进行发布即可。

本文中所有的示例代码在 https://github.com/wangjunneil/weather-cli 可以获取到

版权所有,本作品采用知识共享署名-非商业性使用 3.0 未本地化版本许可协议进行许可。转载请注明出处:https://www.wangjun.dev//2018/05/create-publish-nodejs-cli/