Node.js 使用命令行工具检查更新


Posted in Javascript onJune 08, 2017

随着 Node.js 的“走红”,使用 Node.js 开发命令行工具越来越简单。一个成熟的命令行工具应该从一开始就要考虑好之后的版本更新如何“优雅”的告知用户。最好的方法当然是当用户在终端执行命令时,将相关信息提示给用户。

这篇文章将给出一个易用、高效、可定制的方法。源码在这里: GITHUB ,欢迎大家顺手点赞。接下来我将讲解其实现思路。

使用

我们先简单看看这个 npm 包的使用方法:

const updater = require('pkg-updater');
const pkg = require('./package.json'); // 命令行工具自己的 package 信息

updater({'pkg': pkg}) .then(() => { /* 在这里启动命令行工具 */ });

updater({
 'pkg': pkg, 
 // 自定义 registry
 'registry': 'http://xxx.registry.com',
 // 自定义请求的 dist-tag,默认是 latest
 'tag': 'next',
 // 自定义检查间隔,默认是 1h
 'checkInterval': 24 * 60 * 60 * 1000,
 // 自定义更新提示信息
 'updateMessage': 'package update from <%=current%> to <%=latest%>.',
 // 自定义强制更新的版本更新级别,默认是 major
 'level': 'minor'
}).then(() => { /* 在这里启动命令行工具 */ });

updater({
 'pkg': pkg,
 // 完全自定义版本更新时的逻辑
 'onVersionChange': function* (opts) {

 }
}).then(() => { /* 在这里启动命令行工具 */ });

效果如图:

Node.js 使用命令行工具检查更新

Node.js 使用命令行工具检查更新

实现

使用方法很简单,我们一起来看看其实现方法。

需求

我们先来梳理下需求,一个命令行检查更新器应该至少提供如下功能:

能从远程获取最新版本

能根据检查结果进行提示

在版本不兼容时可以直接退出,强制用户升级程序

获取版本

获取最新版本这个功能看起来很简单,就是发送一个请求从“某处”获取信息。但是有一些问题需要我们考虑:

从哪里获取版本信息?

获取版本信息的策略是怎样的?(什么时候获取?获取的信息如何处理?)

从哪里获取版本信息

我们的命令行工具一般都是使用 npm 进行分发,最简便的方法就是直接通过 registry 获取。通过请求 https://registry.npmjs.org/{name}/{dist-tag} 就可以得到 package 对应 tag 的版本信息。结果类似下面这样:

// https://registry.npmjs.org/co/latest
{
 "name": "co",
 "version": "4.5.0"
}

在实际实现时,我们应该允许调用者自定义 registry 地址、请求的 dist-tag 等,这样可以有更多的定制性。

获取版本信息的策略

首先想到的方法是用户每次执行命令时都去获取一次版本信息,这样的获取策略应该是最简单和实时的。

但是这个策略其实并不合适:

每次执行命令都要去发请求进行检查,如果网络延迟,会阻塞命令执行,影响用户体验

工具的版本更新其实并不会很频繁,没有必要进行实时检查

网络请求的影响因素很多,不能保证每次都成功,应该提供本地缓存机制来存储请求成功的结果,避免版本信息的不可用

综合上面的几点,我们设计如下的获取策略:

将发送网络请求获取版本信息的逻辑放在一个独立的后台进程去执行,保证不阻塞主命令执行

请求成功后将版本信息、检查时间缓存到用户机器

每次执行命令时,只是读取本地缓存下来的版本信息,不去发送网络请求

根据缓存下来的检查时间和当前时间,在一个间隔之内不去额外创建后台检查进程

将上面的策略翻译成代码大概就是下面这样:

// 读取本地缓存的检查结果
const checkInfo = yield updater.readCheckInfo(opts);
const lastCheck = checkInfo.lastCheck;
const lastVersion = checkInfo.lastVersion;

// 根据版本信息提示用户
// ...

// 在时间间隔内,直接返回
if (Date.now() - lastCheck < opts.checkInterval) {
 return;
}

// 创建后台检查进程
try {
 require('child_process').spawn(
 process.execPath,
 [require('path').join(__dirname, '_check.js'), JSON.stringify({
  'pkg': opts.pkg, // package 信息
  'tag': opts.tag, // 检查的 dist-tag
  'logFile': opts.logFile, // 缓存文件路径
  'registry': opts.registry // registry 地址
 })],
 {'stdio': ['ignore', 'ignore', 'ignore'], 'detached': true}
 ).unref();
} catch(e) {}

后台进程执行的 _check.js 文件也很简单,如下所示:

const opts = JSON.parse(process.argv[2]);

let lastVersion = '';
try {
 // 发送请求获取最新版本
 const url = normalizeUrl(opts.registry + '/' + opts.pkg.name + '/' + (opts.tag || 'latest'));
 const res = yield got.get(url, {
 'json': true,
 'timeout': 60 * 1000
 });
 if (res && res.body && res.body.version) {
 lastVersion = res.body.version;
 }
} catch(e) {}

// 如果获取失败了,最新版本就是当前版本(package.version)
if (!lastVersion) {
 lastVersion = opts.pkg.version;
}

let data = yield util.readJson(opts.logFile);
if (!data[opts.pkg.name]) {
 data[opts.pkg.name] = {};
}
data[opts.pkg.name].lastVersion = lastVersion; // 最新版本
data[opts.pkg.name].lastCheck = Date.now(); // 检查时间

// 写入缓存
yield util.writeJson(opts.logFile, data);

提示

当版本更新了,我们应该在终端提示用户。这里有两个问题:

提示文案的问题

提示文案显示间隔的问题(一直显示?每隔一段时间显示?)

这里我们采取的策略是:

提供默认提示文案,清晰的说明当前版本、最新版本、更新方法,允许调用者自定义提示文案

只要有更新就一直显示提示文案,因为我们希望用户经常的进行更新

实现代码大概如下:

// 比对版本
const type = updater.diffType(opts.pkg.version, lastVersion, opts.level);
if (type) {
 // 根据模板渲染提示信息
 const str = updater.template(opts.updateMessage || updater.defaultOpts.updateMessage)({
 'colors': updater.colors,
 'name': opts.pkg.name,
 'current': opts.pkg.version,
 'latest': opts.lastVersion,
 'command': 'npm i -g ' + opts.pkg.name
 });
 // 进行提示
 console.log(
 updater.boxen(str, {
  'padding': 1,
  'margin': 1,
  'borderStyle': 'classic'
 })
 );
}

强制升级

对于 npm 模块来说,版本 a.b.c 的更新一般有三种情况:

patch:c 位,小版本更新,一般是 bug 修复

minor:b 位,中版本更新,一般增加新功能、bug 修复

major,a 位,大版本更新,一般是不兼容的升级

我们希望当远程版本的更新如果是 major 形式,命令行工具将直接退出,强制用户进行升级后才能使用。这可以保证我们推送一个大版本后,所有的用户都能够马上更新掉,而不是继续使用老版本,造成版本碎片的问题。

实现代码大致如下:

// 比对版本
const type = updater.diffType(opts.pkg.version, lastVersion, opts.level);
if (type) {
 // 根据模板渲染提示信息
 const str = updater.template(opts.updateMessage || updater.defaultOpts.updateMessage)({
 'colors': updater.colors,
 'name': opts.pkg.name,
 'current': opts.pkg.version,
 'latest': opts.lastVersion,
 'command': 'npm i -g ' + opts.pkg.name
 });
 // 进行提示
 console.log(
 updater.boxen(str, {
  'padding': 1,
  'margin': 1,
  'borderStyle': 'classic'
 })
 );

 // 不兼容的更新,直接让进程退出
 if (type == 'incompatible') {
 process.exit(1);
 }
}

总结

命令行检查更新看似简单,其实仔细思考,还是有很多细节。希望这篇文章对你有所启发。

Javascript 相关文章推荐
给moz-firefox下添加IE方法和属性
Apr 10 Javascript
JavaScript 学习笔记(十四) 正则表达式
Jan 22 Javascript
html页面显示年月日时分秒和星期几的两种方式
Aug 20 Javascript
node.js中的querystring.stringify方法使用说明
Dec 10 Javascript
JavaScript访问字符串中单个字符的两种方法
Jul 03 Javascript
jQuery实现控制文字内容溢出用省略号(…)表示的方法
Feb 26 Javascript
基于MVC+EasyUI的web开发框架之使用云打印控件C-Lodop打印页面或套打报关运单信息
Aug 29 Javascript
ES6概念 ymbol.for()方法
Dec 25 Javascript
Web技术实现移动监测的介绍
Sep 18 Javascript
Vue element-ui父组件控制子组件的表单校验操作
Jul 17 Javascript
使用js获取身份证年龄的示例代码
Dec 11 Javascript
详解JVM系列之内存模型
Jun 10 Javascript
在vue.js中抽出公共代码的方法示例
Jun 08 #Javascript
Ionic3 UI组件之autocomplete详解
Jun 08 #Javascript
jQuery+ajax实现局部刷新的两种方法
Jun 08 #jQuery
gulp解决跨域的配置文件问题
Jun 08 #Javascript
angular 用拦截器统一处理http请求和响应的方法
Jun 08 #Javascript
jQuery 添加样式属性的优先级别方法(推荐)
Jun 08 #jQuery
Ionic项目中Native Camera的使用方法
Jun 07 #Javascript
You might like
PHP的一个完美GIF等比缩放类,附带去除缩放黑背景
2014/04/01 PHP
用PHP写的一个冒泡排序法的函数简单实例
2016/05/26 PHP
php设计模式之策略模式实例分析【星际争霸游戏案例】
2020/03/26 PHP
对象特征检测法判断浏览器对javascript对象的支持
2009/07/25 Javascript
js 小数取整的函数
2010/05/10 Javascript
让图片旋转任意角度及JQuery插件使用介绍
2013/03/20 Javascript
深入解读JavaScript中的Iterator和for-of循环
2015/07/28 Javascript
js实现TAB切换对应不同颜色的代码
2015/08/31 Javascript
JS响应鼠标点击实现两个滑块区间拖动效果
2015/10/26 Javascript
jquery在ie7下选择器的问题导致append失效的解决方法
2016/01/10 Javascript
深入理解jQuery事件绑定
2016/06/02 Javascript
js仿腾讯QQ的web登陆界面
2016/08/19 Javascript
Javascript typeof与instanceof的区别
2016/10/18 Javascript
jQuery得到多个值只能用取Class ,不能用取ID的方法
2016/12/04 Javascript
详解nodejs微信jssdk后端接口
2017/05/25 NodeJs
vue无限轮播插件代码实例
2019/05/10 Javascript
vue $set 给数据赋值的实例
2019/11/09 Javascript
vue 实现通过vuex 存储值 在不同界面使用
2019/11/11 Javascript
微信小程序 自定义弹窗实现过程(附代码)
2019/12/05 Javascript
[46:59]完美世界DOTA2联赛PWL S2 GXR vs Ink 第二场 11.19
2020/11/20 DOTA
对python中使用requests模块参数编码的不同处理方法
2018/05/18 Python
pandas.dataframe中根据条件获取元素所在的位置方法(索引)
2018/06/07 Python
对Python生成汉字字库文字,以及转换为文字图片的实例详解
2019/01/29 Python
Python实现将字符串的首字母变为大写,其余都变为小写的方法
2019/06/11 Python
Python企业编码生成系统之主程序模块设计详解
2019/07/26 Python
解决django同步数据库的时候app models表没有成功创建的问题
2019/08/09 Python
详解python播放音频的三种方法
2019/09/23 Python
python可迭代对象去重实例
2020/05/15 Python
Python连接Mysql进行增删改查的示例代码
2020/08/03 Python
CSS3制作气泡对话框的实例教程
2016/05/10 HTML / CSS
小学生班会演讲稿
2014/01/09 职场文书
幼儿园春季开学寄语
2014/04/03 职场文书
爱心倡议书范文
2014/05/12 职场文书
母亲节主题班会
2015/08/14 职场文书
浅谈Python项目的服务器部署
2021/04/25 Python
python神经网络 使用Keras构建RNN训练
2022/05/04 Python