一、update-notifier是什么?
update-notifier是一个npm包,它的作用就是传入一个npm包的名称和使用的版本,然后在一些工具npm包的帮助下,判断当前使用的这个版本是否是最新版本,并将最新的版本信息返回,同时告诉你该如何升级。
二、安装及调用
1、安装
【yeoman/update-notifier】: github.com/yeoman/upda…
$ npm install update-notifier
2、调用
example.js
const updateNotifier = require("update-notifier");
updateNotifier({
pkg: {
name: "vue",
version: "2.6.11"
},
updateCheckInterval: 0
}).notify();
3、踩坑
根据readme.md中的示例,误以为传入的是整个package.json返回的对象,实际上并非如此,按照示例代码未能实现预期效果。
const updateNotifier = require("update-notifier");
const pkg = require("./package.json")
updateNotifier({pkg}).notify();
三、调试
1、UpdateNotifier constructor
调用constructor方法时传入的是一个对象,如图所示:
class UpdateNotifier {
constructor(options = {}) {
this.options = options;
options.pkg = options.pkg || {};
options.distTag = options.distTag || 'latest';
// 对 name 和 version 做处理,支持传入 packageName 和 packageVersion
options.pkg = {
name: options.pkg.name || options.packageName,
version: options.pkg.version || options.packageVersion
};
// 如果 name 或 version 有一个值不存在,则抛出异常
if (!options.pkg.name || !options.pkg.version) {
throw new Error('pkg.name and pkg.version required');
}
this.packageName = options.pkg.name;
this.packageVersion = options.pkg.version;
// 判断 传入的 updateCheckInterval 的类型是否是 number
// 如果不是 number,则给一个常量值
this.updateCheckInterval = typeof options.updateCheckInterval === 'number' ? options.updateCheckInterval : ONE_DAY;
// 判断 process.env 中 是否有 NO_UPDATE_NOTIFIER
// 或 process.env.NODE_ENV 等于 test
// 或 process.argv 包含 --no-update-notifier
// 或 当前环境是否是 持续集成的服务器
this.disabled = 'NO_UPDATE_NOTIFIER' in process.env ||
process.env.NODE_ENV === 'test' ||
process.argv.includes('--no-update-notifier') ||
isCi();
this.shouldNotifyInNpmScript = options.shouldNotifyInNpmScript;
if (!this.disabled) {
try {
const ConfigStore = configstore();
this.config = new ConfigStore(`update-notifier-${this.packageName}`, {
optOut: false,
// Init with the current time so the first check is only
// after the set interval, so not to bother users right away
lastUpdateCheck: Date.now()
});
} catch {
// Expecting error code EACCES or EPERM
const message =
chalk().yellow(format(' %s update check failed ', options.pkg.name)) +
format('\n Try running with %s or get access ', chalk().cyan('sudo')) +
'\n to the local update config store via \n' +
chalk().cyan(format(' sudo chown -R $USER:$(id -gn $USER) %s ', xdgBasedir().config));
process.on('exit', () => {
console.error(boxen()(message, {align: 'center'}));
});
}
}
}
check() {
}
async fetchInfo() {
}
notify(options) {
}
}
module.exports = options => {
const updateNotifier = new UpdateNotifier(options);
updateNotifier.check();
return updateNotifier();
}
module.exports.UpdateNotifier = UpdateNotifier;
2、check
check() {
// 判断是否存在配置 或 是否禁用
if (!this.config || this.config.get('optOut') || this.disabled) {
return;
}
// 获取更新信息
this.update = this.config.get('update');
if (!this.update) {
// 如果没有,则将当前版本赋值给更新信息
this.update.current = this.packageVersion;
this.config.delete('update');
}
// 判断当前时间减去配置信息中最后一次更新检查的时间 是否小于更新检查间隔,如果是 则返回
if (Date.now() - this.config.get('lastUpdateCheck') < this.updateCheckInterval) {
return;
}
spawn(process.execPath, [path.join(__dirname, 'check.js'), JSON.stringify(this.options)], {
detached: true,
stdio: 'ignore'
}).uunref();
}
3、fetchInfo
async fetchInfo() {
const {distTag} = this.options;
// 用来获取最新的版本信息
const latest = await latestVersion()(this.packageName, {version: distTag});
return {
latest,
current: this.packageVersion,
// 比较两个版本之间的差异
type: semverDiff()(this.packageVersion, latest) || distTag,
name: this.packageName
}
}
4、notify
notify(options) {
const suppressForNpm = !this.shouldNotifyInNpmScript && isNpm().isNpmOrYarn;
if (!process.stdout.isTTY || suppressForNpm || !this.update || !semver().gt(this.update.latest, this.update.current)) {
return this;
}
options = {
isGlobal: isInstalledGlobally(),
isYarnGlobal: isYarnGlobal()(),
...options
}
let installCommand;
// 判断命令类型
// yarn 全局安装
// npm 全局安装
// yarn 安装
// npm 安装
if (options.isYarnGlobal) {
installCommand = `yarn global add ${this.packageName}`;
} else if (options.isGlobal) {
installCommand = `npm i -g ${this.packageName}`;
} else if (hasYarn()()) {
installCommand = `yarn add ${this.packageName}`;
} else {
installCommand = `npm i ${this.packageName}`;
}
const defaultTemplate = 'Update available ' +
chalk().dim('{currentVersion}') +
chalk().reset(' → ') +
chalk().green('${latestVersion}') +
' \nRun ' + chalk().cyan('{updateCommand}') + ' to update';
const template = options.message || defalutTemplate;
options.boxenOptions = options.boxenOptios || {
padding: 1,
margin: 1,
align: 'center',
borderColor: 'yellow',
borderStyle: 'round'
}
const message = boxen(){
pupa()(template, {
packageName: this.packageName,
currentVersion: this.update.current,
latestVersion: this.update.latest,
updateCommand: installCommand
}),
options.boxenOptions
}
if (options.defer === false) {
console.error(message);
} else {
process.on('exit', () => {
console.error(message);
});
process.on('SIGINT', () => {
console.error('');
process.exit();
})
}
return this;
}
四、工具npm包
1、import-lazy
引入懒加载模块,按照自己的理解,加上后面那个require,表明是立即执行。
const importLazy = require('import-lazy')(require);
2、is-ci
判断当前环境是否是持续集成的服务器。
const isCi = importLazy('is-ci');
3、configstore
用来将npm包最后一次更新检查的时间保存下来。
const configstore = importLazy('configstore');
4、spawn
const spawn = imoortLazy('spawn');
5、latest-version
const latestVersion = require('latest-version');
6、semver-diff
返回两个版本之间的差异类型,可能的值有以下:
major,premajor,minor,preminor,patch,prepatch,prerelease,build,undefined
const semverDiff = importLazy('semver-diff');
semverDiff()(this.packageVersion, latest) || distTag
7、is-npm
判断是否是npm包。
const isNpm = importLazy('is-npm');
import {isNpmOrYarn, isNpm, isYarn} from 'is-npm';
console.log(isNpmOrYarn, isNpm, isYarn);
8、semver
用于npm包的版本比较。
const semver = importLazy('semver');
// gt 表示判断 最新版本 是否大于 当前版本
semver().gt(this.update.latest, this.update.current);
// 其他示例
gt(v1, v2): v1 > v2
gte(v1, v2): v1 >= v2
lt(v1, v2): v1 < v2
lte(v1, v2): v1 <= v2
eq(v1, v2): v1 == v2
9、chalk
console控制台输出样式和高亮显示。
const chalk = importLazy('chalk');
// 使文字具有较低的透明度
chalk().dim('{currentVersion}');
// 重置当前的样式
chalk().reset(' → ');
// 设置文字的颜色为绿色
chalk().green('{latestVersion}');
// 设置文字的颜色为蓝绿色
chalk().cyan('{updateCommand}');
10、pupa
用于在console控制台模板中填充一些提示信息。
const pupa = importLazy('pupa');
const template = options.message || defaultTemplate;
pupa()(template, {
packageName: this.packageName,
currentVersion: this.update.current,
latestVersion: this.update.latest,
updateCommand: installCommand
})
11、is-installed-globally
判断包是否是全局安装。
const isInstalledGlobally = importLazy('is-installed-globally');
12、is-yarn-global
判断yarn是否是全局安装。
const isYarnGlobal = importLazy('is-yarn-global');
13、has-yarn
判断项目是否使用了yarn。
const hasYarn = importLazy('has-yarn');
五、其他
1、latest-version/index.js
'use strict';
const packageJson = require('package-json');
const latestVersion = async (packageName, options) => {
const {version} = await packageJson(packageName,toLowerCase(), options);
return version;
}
module.exports = latestVersion;
module.exports.default = latestVersion;
六、收获
(1)如果某个包不是必须使用到的,可以尝试使用懒加载。
(2)在代码调试的过程中,认识了一些在工作中可能会用到的工具npm包,比如懒加载(impoort-lazy),获取npm包的版本信息(latest-version),对npm包的两个版本进行比较(semver、semver-diff),设置console控制台文字样式和高亮显示(chalk),判断是否安装了yarn以及是否是全局安装(has-yarn,is-yarn-global)等等。
(2)从UpdateNotifier的constructor方法中还学到了一个对入参做处理的小技巧:如果某个方法被多个地方用到,且传入的参数名称可能会不一样,就应考虑先将传入的参数做统一处理(比如定义一个新的变量去接收它的值),然后在方法里使用这个变量,而不是使用传入的参数。