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 相关文章推荐
比较详细的关于javascript中void(0)的具体含义解释
Aug 02 Javascript
JQuery显示、隐藏div的几种方法简明总结
Apr 16 Javascript
JavaScript中Cookies的相关使用教程
Jun 04 Javascript
JS实现的左侧竖向滑动菜单效果代码
Oct 19 Javascript
jQuery插件echarts实现的循环生成图效果示例【附demo源码下载】
Mar 04 Javascript
最全的JavaScript开发工具列表 总有一款适合你
Jun 29 Javascript
Vue.js上传图片到阿里云OSS存储的方法示例
Dec 13 Javascript
vue.js多页面开发环境搭建过程
Apr 24 Javascript
elementUI select组件value值注意事项详解
May 29 Javascript
vue搜索页开发实例代码详解(热门搜索,历史搜索,淘宝接口演示)
Apr 11 Javascript
vue实现在进行增删改操作后刷新页面
Aug 05 Javascript
OpenLayers3实现测量功能
Sep 25 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 服务器调试 Zend Debugger 的安装教程
2009/09/25 PHP
PHP生成Flash动画的实现代码
2010/03/12 PHP
ThinkPHP分页类使用详解
2014/03/05 PHP
通过dbi使用perl连接mysql数据库的方法
2014/04/16 PHP
Thinkphp中import的几个用法详细介绍
2014/07/02 PHP
php清空(删除)指定目录下的文件,不删除目录文件夹的实现代码
2014/09/04 PHP
为你总结一些php系统类函数
2015/10/21 PHP
php 与 nginx 的处理方式及nginx与php-fpm通信的两种方式
2018/09/28 PHP
tp5(thinkPHP5)框架数据库Db增删改查常见操作总结
2019/01/10 PHP
Thinkphp5.0 框架使用模型Model添加、更新、删除数据操作详解
2019/10/11 PHP
jQuery 一个图片切换的插件
2011/10/09 Javascript
js浏览器本地存储store.js介绍及应用
2014/05/13 Javascript
基于jQuery实现复选框的全选 全不选 反选功能
2014/11/24 Javascript
jquery实现具有嵌套功能的选项卡
2016/02/12 Javascript
jQuery简单创建节点的方法
2016/09/09 Javascript
jQuery实现点击任意位置弹出层外关闭弹出层效果
2016/10/19 Javascript
js图片延迟加载(Lazyload)三种实现方式
2017/03/01 Javascript
ES6新特性五:Set与Map的数据结构实例分析
2017/04/21 Javascript
Vue多种方法实现表头和首列固定的示例代码
2018/02/02 Javascript
[18:16]sakonoko 2017年卡尔集锦
2018/02/06 DOTA
Python入门篇之编程习惯与特点
2014/10/17 Python
Python提示[Errno 32]Broken pipe导致线程crash错误解决方法
2014/11/19 Python
python Django模板的使用方法
2016/01/14 Python
2018年Python值得关注的开源库、工具和开发者(总结篇)
2018/01/04 Python
Python for循环中的陷阱详解
2018/07/13 Python
pycharm配置QtDesigner的超详细方法
2021/01/25 Python
Raffaello Network德国:意大利拉斐尔时尚购物网
2019/05/01 全球购物
Solid & Striped官网:美国泳装品牌
2019/06/19 全球购物
分公司经理岗位职责
2013/11/11 职场文书
理财投资建议书
2014/03/12 职场文书
明信片寄语大全
2014/04/08 职场文书
出生医学证明书
2014/09/15 职场文书
刑事代理授权委托书
2014/09/17 职场文书
部门2014年度工作总结
2014/11/12 职场文书
2015年新农村建设工作总结
2015/05/22 职场文书
六种css3实现的边框过渡效果
2021/04/22 HTML / CSS