Node.js 使用命令行工具檢查更新
隨著 Node.js 的“走紅”,使用 Node.js 開發(fā)命令行工具越來(lái)越簡(jiǎn)單。一個(gè)成熟的命令行工具應(yīng)該從一開始就要考慮好之后的版本更新如何“優(yōu)雅”的告知用戶。最好的方法當(dāng)然是當(dāng)用戶在終端執(zhí)行命令時(shí),將相關(guān)信息提示給用戶。
這篇文章將給出一個(gè)易用、高效、可定制的方法。源碼在這里: GITHUB ,歡迎大家順手點(diǎn)贊。接下來(lái)我將講解其實(shí)現(xiàn)思路。
使用
我們先簡(jiǎn)單看看這個(gè) npm 包的使用方法:
const updater = require('pkg-updater');
const pkg = require('./package.json'); // 命令行工具自己的 package 信息
updater({'pkg': pkg}) .then(() => { /* 在這里啟動(dòng)命令行工具 */ });
updater({
'pkg': pkg,
// 自定義 registry
'registry': 'http://xxx.registry.com',
// 自定義請(qǐng)求的 dist-tag,默認(rèn)是 latest
'tag': 'next',
// 自定義檢查間隔,默認(rèn)是 1h
'checkInterval': 24 * 60 * 60 * 1000,
// 自定義更新提示信息
'updateMessage': 'package update from <%=current%> to <%=latest%>.',
// 自定義強(qiáng)制更新的版本更新級(jí)別,默認(rèn)是 major
'level': 'minor'
}).then(() => { /* 在這里啟動(dòng)命令行工具 */ });
updater({
'pkg': pkg,
// 完全自定義版本更新時(shí)的邏輯
'onVersionChange': function* (opts) {
}
}).then(() => { /* 在這里啟動(dòng)命令行工具 */ });
效果如圖:


實(shí)現(xiàn)
使用方法很簡(jiǎn)單,我們一起來(lái)看看其實(shí)現(xiàn)方法。
需求
我們先來(lái)梳理下需求,一個(gè)命令行檢查更新器應(yīng)該至少提供如下功能:
能從遠(yuǎn)程獲取最新版本
能根據(jù)檢查結(jié)果進(jìn)行提示
在版本不兼容時(shí)可以直接退出,強(qiáng)制用戶升級(jí)程序
獲取版本
獲取最新版本這個(gè)功能看起來(lái)很簡(jiǎn)單,就是發(fā)送一個(gè)請(qǐng)求從“某處”獲取信息。但是有一些問(wèn)題需要我們考慮:
從哪里獲取版本信息?
獲取版本信息的策略是怎樣的?(什么時(shí)候獲???獲取的信息如何處理?)
從哪里獲取版本信息
我們的命令行工具一般都是使用 npm 進(jìn)行分發(fā),最簡(jiǎn)便的方法就是直接通過(guò) registry 獲取。通過(guò)請(qǐng)求 https://registry.npmjs.org/{name}/{dist-tag} 就可以得到 package 對(duì)應(yīng) tag 的版本信息。結(jié)果類似下面這樣:
// https://registry.npmjs.org/co/latest
{
"name": "co",
"version": "4.5.0"
}
在實(shí)際實(shí)現(xiàn)時(shí),我們應(yīng)該允許調(diào)用者自定義 registry 地址、請(qǐng)求的 dist-tag 等,這樣可以有更多的定制性。
獲取版本信息的策略
首先想到的方法是用戶每次執(zhí)行命令時(shí)都去獲取一次版本信息,這樣的獲取策略應(yīng)該是最簡(jiǎn)單和實(shí)時(shí)的。
但是這個(gè)策略其實(shí)并不合適:
每次執(zhí)行命令都要去發(fā)請(qǐng)求進(jìn)行檢查,如果網(wǎng)絡(luò)延遲,會(huì)阻塞命令執(zhí)行,影響用戶體驗(yàn)
工具的版本更新其實(shí)并不會(huì)很頻繁,沒(méi)有必要進(jìn)行實(shí)時(shí)檢查
網(wǎng)絡(luò)請(qǐng)求的影響因素很多,不能保證每次都成功,應(yīng)該提供本地緩存機(jī)制來(lái)存儲(chǔ)請(qǐng)求成功的結(jié)果,避免版本信息的不可用
綜合上面的幾點(diǎn),我們?cè)O(shè)計(jì)如下的獲取策略:
將發(fā)送網(wǎng)絡(luò)請(qǐng)求獲取版本信息的邏輯放在一個(gè)獨(dú)立的后臺(tái)進(jìn)程去執(zhí)行,保證不阻塞主命令執(zhí)行
請(qǐng)求成功后將版本信息、檢查時(shí)間緩存到用戶機(jī)器
每次執(zhí)行命令時(shí),只是讀取本地緩存下來(lái)的版本信息,不去發(fā)送網(wǎng)絡(luò)請(qǐng)求
根據(jù)緩存下來(lái)的檢查時(shí)間和當(dāng)前時(shí)間,在一個(gè)間隔之內(nèi)不去額外創(chuàng)建后臺(tái)檢查進(jìn)程
將上面的策略翻譯成代碼大概就是下面這樣:
// 讀取本地緩存的檢查結(jié)果
const checkInfo = yield updater.readCheckInfo(opts);
const lastCheck = checkInfo.lastCheck;
const lastVersion = checkInfo.lastVersion;
// 根據(jù)版本信息提示用戶
// ...
// 在時(shí)間間隔內(nèi),直接返回
if (Date.now() - lastCheck < opts.checkInterval) {
return;
}
// 創(chuàng)建后臺(tái)檢查進(jìn)程
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) {}
后臺(tái)進(jìn)程執(zhí)行的 _check.js 文件也很簡(jiǎn)單,如下所示:
const opts = JSON.parse(process.argv[2]);
let lastVersion = '';
try {
// 發(fā)送請(qǐng)求獲取最新版本
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) {}
// 如果獲取失敗了,最新版本就是當(dāng)前版本(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(); // 檢查時(shí)間
// 寫入緩存
yield util.writeJson(opts.logFile, data);
提示
當(dāng)版本更新了,我們應(yīng)該在終端提示用戶。這里有兩個(gè)問(wèn)題:
提示文案的問(wèn)題
提示文案顯示間隔的問(wèn)題(一直顯示?每隔一段時(shí)間顯示?)
這里我們采取的策略是:
提供默認(rèn)提示文案,清晰的說(shuō)明當(dāng)前版本、最新版本、更新方法,允許調(diào)用者自定義提示文案
只要有更新就一直顯示提示文案,因?yàn)槲覀兿M脩艚?jīng)常的進(jìn)行更新
實(shí)現(xiàn)代碼大概如下:
// 比對(duì)版本
const type = updater.diffType(opts.pkg.version, lastVersion, opts.level);
if (type) {
// 根據(jù)模板渲染提示信息
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
});
// 進(jìn)行提示
console.log(
updater.boxen(str, {
'padding': 1,
'margin': 1,
'borderStyle': 'classic'
})
);
}
強(qiáng)制升級(jí)
對(duì)于 npm 模塊來(lái)說(shuō),版本 a.b.c 的更新一般有三種情況:
patch:c 位,小版本更新,一般是 bug 修復(fù)
minor:b 位,中版本更新,一般增加新功能、bug 修復(fù)
major,a 位,大版本更新,一般是不兼容的升級(jí)
我們希望當(dāng)遠(yuǎn)程版本的更新如果是 major 形式,命令行工具將直接退出,強(qiáng)制用戶進(jìn)行升級(jí)后才能使用。這可以保證我們推送一個(gè)大版本后,所有的用戶都能夠馬上更新掉,而不是繼續(xù)使用老版本,造成版本碎片的問(wèn)題。
實(shí)現(xiàn)代碼大致如下:
// 比對(duì)版本
const type = updater.diffType(opts.pkg.version, lastVersion, opts.level);
if (type) {
// 根據(jù)模板渲染提示信息
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
});
// 進(jìn)行提示
console.log(
updater.boxen(str, {
'padding': 1,
'margin': 1,
'borderStyle': 'classic'
})
);
// 不兼容的更新,直接讓進(jìn)程退出
if (type == 'incompatible') {
process.exit(1);
}
}
總結(jié)
命令行檢查更新看似簡(jiǎn)單,其實(shí)仔細(xì)思考,還是有很多細(xì)節(jié)。希望這篇文章對(duì)你有所啟發(fā)。
相關(guān)文章
npm?list輸出結(jié)果包含extraneous標(biāo)志記錄分析
這篇文章主要為大家介紹了npm?list輸出結(jié)果包含extraneous標(biāo)志記錄分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2024-01-01
說(shuō)說(shuō)node中的可讀流和可寫流的區(qū)別
這篇文章主要介紹了說(shuō)說(shuō)node中的可讀流和可寫流的區(qū)別,詳細(xì)的介紹了可讀流和可寫流,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06
Node.js實(shí)現(xiàn)簡(jiǎn)單聊天服務(wù)器
Node.js 是一個(gè)基于Chrome JavaScript運(yùn)行時(shí)建立的一個(gè)平臺(tái), 用來(lái)方便地搭建快速的,易于擴(kuò)展的網(wǎng)絡(luò)應(yīng)用,今天我們來(lái)探討下,如何使用node.js實(shí)現(xiàn)簡(jiǎn)單的聊天服務(wù)器2014-06-06
node.js正則表達(dá)式獲取網(wǎng)頁(yè)中所有鏈接的代碼實(shí)例
這篇文章主要介紹了node.js正則表達(dá)式獲取網(wǎng)頁(yè)中所有鏈接的代碼實(shí)例,使用正則表達(dá)式實(shí)現(xiàn),需要的朋友可以參考下2014-06-06
nodejs不用electron實(shí)現(xiàn)打開文件資源管理器并選擇文件
最近在開發(fā)一些小腳本,用 nodejs 實(shí)現(xiàn),其中很多功能需要選擇一個(gè)/多個(gè)文件,或者是選擇一個(gè)文件夾,這種情況下網(wǎng)上給出的解決方案都是 electron,但是我一個(gè)小腳本用 electron 屬實(shí)有點(diǎn)夸張了,后來(lái)轉(zhuǎn)念一想可以通過(guò) powershell 來(lái)實(shí)現(xiàn)類似的功能,需要的朋友可以參考下2024-01-01
如何用npm命令刪除開發(fā)項(xiàng)目中的node_modules文件夾
每個(gè)項(xiàng)目都會(huì)產(chǎn)生一個(gè)node_modules,每個(gè)node_modules少則幾十兆,多則幾百甚至上千兆,隨著時(shí)間的積累,維護(hù)項(xiàng)目的增加,整個(gè)項(xiàng)目目錄體積會(huì)越來(lái)越大,這篇文章主要給大家介紹了關(guān)于如何用npm命令刪除開發(fā)項(xiàng)目中的node_modules文件夾,需要的朋友可以參考下2023-12-12
node.js中的http.response.setHeader方法使用說(shuō)明
這篇文章主要介紹了node.js中的http.response.setHeader方法使用說(shuō)明,本文介紹了http.response.setHeader的方法說(shuō)明、語(yǔ)法、接收參數(shù)、使用實(shí)例和實(shí)現(xiàn)源碼,需要的朋友可以參考下2014-12-12

