1. 场景

  用于将当前 package.json 中的包与包管理工具库(如 npm)进行比对,若存在可以更新的包,则进行提示。

2. 使用

1)安装

$ npm install update-notifier

2)使用

const updateNotifier = require('update-notifier');
const pkg = require('./package.json');const notifier = updateNotifier({pkg});
notifier.notify();console.log(notifier.update);
/*
{latest: '1.0.1',current: '1.0.0',type: 'patch', // Possible values: latest, major, minor, patch, prerelease, buildname: 'pageres'
}
*/

3. 源码

1)阅读 readme.md

源码地址:https://github.com/yeoman/update-notifier

readme 中可了解到如下内容

  • 可以运行根目录下的 example.js 进行调试

  • 为了用户体验,update-notifier 并不会每次运行都检查,而是会在 一定时间间隔后进行检查

  • update-notifier 检查会开一个子进程来进行,尽管调用 process.exit 退出进程,也不会影响到检查

2)克隆源码仓库

# 克隆
git clone https://github.com/yeoman/update-notifier.git# 安装依赖
cd update-notifier
npm install

3)运行调试

1)通过 package.json 可以看到,调试命令如下

npm run test

2)查看调试结果

  首次运行时不会打印信息,只有在第二次开始才会提示更新信息。

  这是因为 首次启动时找不到旧版本进行对比,因此会先将首次的信息持久化地存储起来,第二次执行时将上次存储的信息,与本次运行的结果进行对比。

4)源码解读

example.js

'use strict';
const updateNotifier = require('.');// 将一个包信息传入
// 首次运行会将该包 0.9.2 版本存储起来
// 第二次运行会拿到 0.9.2 与现有库中最新的版本对比
updateNotifier({pkg: {name: 'public-ip',version: '0.9.2'},// 检查的时间间隔updateCheckInterval: 0
}).notify();

check.js

/* eslint-disable unicorn/no-process-exit */
'use strict';
let updateNotifier = require('.');const options = JSON.parse(process.argv[2]);updateNotifier = new updateNotifier.UpdateNotifier(options);(async () => {// 若运行超时,则退出进程setTimeout(process.exit, 1000 * 30);// 获取更新检查得到的包版本信息const update = await updateNotifier.fetchInfo();// 将当前时间作为最后一次检查更新时间updateNotifier.config.set('lastUpdateCheck', Date.now());// 如果当前包不是最新版本,则抛出提示if (update.type && update.type !== 'latest') {updateNotifier.config.set('update', update);}process.exit();
})().catch(error => {console.error(error);process.exit(1);
});

index.js

注:index.js 源码较多,以下会分成多个代码块进行解读

总代码流程个人理解为:

  1. 首次运行,提取出当前 package.json 中存储的所有包的版本信息,生成文件并进行持久化存储,记录下当前的时间
  2. 下次运行时,将当前时间与上一次生成文件的时间 比对,若超过检查间隔,则进行 udpate check。检查比对 npm 库,若发现包版本有可以更新的,则在控制台进行提示

跟随流程来阅读源码

1)引入工具包

'use strict';
const {spawn} = require('child_process');
const path = require('path');
const {format} = require('util');
// 懒加载模块:只有在调用到对应包的时候,再进行引入
const importLazy = require('import-lazy')(require);const configstore = importLazy('configstore');
const chalk = importLazy('chalk');
const semver = importLazy('semver');
const semverDiff = importLazy('semver-diff');
const latestVersion = importLazy('latest-version');
const isNpm = importLazy('is-npm');
const isInstalledGlobally = importLazy('is-installed-globally');
const isYarnGlobal = importLazy('is-yarn-global');
const hasYarn = importLazy('has-yarn');
const boxen = importLazy('boxen');
const xdgBasedir = importLazy('xdg-basedir');
const isCi = importLazy('is-ci');
const pupa = importLazy('pupa');const ONE_DAY = 1000 * 60 * 60 * 24;

2)声明 UpdateNotifier 类,后续无特殊说明,作用域均在类中

class UpdateNotifier {...
}

3)声明类的构造函数

class UpdateNotifier {constructor(options = {}) {this.options = options;options.pkg = options.pkg || {};options.distTag = options.distTag || 'latest';// 读取包名和版本号信息options.pkg = {name: options.pkg.name || options.packageName,version: options.pkg.version || options.packageVersion};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;// 更新检查间隔,默认值:1天this.updateCheckInterval = typeof options.updateCheckInterval === 'number' ? options.updateCheckInterval : ONE_DAY;this.disabled = 'NO_UPDATE_NOTIFIER' in process.env ||process.env.NODE_ENV === 'test' ||// process.argv:Node.js 进程时传入的命令行参数process.argv.includes('--no-update-notifier') ||// CI持续集成环境isCi();this.shouldNotifyInNpmScript = options.shouldNotifyInNpmScript;if (!this.disabled) {// 生成文件存储版本信息try {const ConfigStore = configstore();this.config = new ConfigStore(`update-notifier-${this.packageName}`, {optOut: false,// 生成文件时,将当前时间记录起来,方便下次比对是否超出检查的时间间隔lastUpdateCheck: Date.now()});} catch {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'}));});}}}...
}

4)声明 check 方法

class UpdateNotifier {...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');}// Only check for updates on a set interval// 仅在检查时间间隔内进行检查,如:设置1周内不重复检查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'}).unref();}
}

5)获取版本信息

class UpdateNotifier {...// 获取版本信息async fetchInfo() {const {distTag} = this.options;const latest = await latestVersion()(this.packageName, {version: distTag});return {latest,current: this.packageVersion,// 判断包是否为最新版本,联系 check.js 中 update.type === 'latest'type: semverDiff()(this.packageVersion, latest) || distTag,name: this.packageName};}
}

6)发出通知提醒

class UpdateNotifier {...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;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 || defaultTemplate;// 消息提醒边框样式options.boxenOptions = options.boxenOptions || {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;}
}

【源码阅读 | 04】update-notifier 检查包更新相关推荐

  1. Darknet源码阅读【吐血整理,持续更新中】

    github地址 https://github.com/BBuf/Darknet Darknet源码阅读 Darknet是一个较为轻型的完全基于C与CUDA的开源深度学习框架,其主要特点就是容易安装, ...

  2. r8169驱动源码阅读记录

    r8169驱动源码阅读记录 初始化 发包 收包 源码地址:linux-4.19.90\drivers\net\ethernet\realtek\r8169.c 源码阅读环境:Windows 搭建 op ...

  3. java经典源码 阅读_公开!阿里甩出“源码阅读指南”,原来源码才是最经典的学习范例...

    我们为啥要阅读源码? 为什么面试要问源码?为什么我们Java程序员要去看源码?相信大多数程序员看到源码第一感觉都是:枯燥无味,费力不讨好!要不是为了"涨薪"我才不去看这个鬼东西!但 ...

  4. Ubuntu 22.04环境下安装lxr源码阅读器详细过程

    Ubuntu 22.04环境下安装lxr源码阅读器详细过程 一.lxr介绍 二.依赖关系 三.安装过程 1.下载源码 2.执行检查 3.安装依赖 4.安装数据库和服务器 四.配置过程 1.主配置 2. ...

  5. Transformers包tokenizer.encode()方法源码阅读笔记

    Transformers包tokenizer.encode()方法源码阅读笔记_天才小呵呵的博客-CSDN博客_tokenizer.encode

  6. 封装成jar包_通用源码阅读指导mybatis源码详解:io包

    io包 io包即输入/输出包,负责完成 MyBatis中与输入/输出相关的操作. 说到输入/输出,首先想到的就是对磁盘文件的读写.在 MyBatis的工作中,与磁盘文件的交互主要是对 xml配置文件的 ...

  7. gh-ost大表DDL工具源码阅读

    gh-ost大表DDL工具源码阅读 最终目的 开发环境与测试数据库准备 一个简单的ddl案例 debug分析程序执行过程 vscode debug配置 变量介绍 核心处理逻辑 分析我的需求 最终目的 ...

  8. TiDB 源码阅读系列文章(十六)INSERT 语句详解

    在之前的一篇文章 <TiDB 源码阅读系列文章(四)INSERT 语句概览> 中,我们已经介绍了 INSERT 语句的大体流程.为什么需要为 INSERT 单独再写一篇?因为在 TiDB ...

  9. surefire 拉起 junit 单元测试类 源码阅读(一)

    根据surefire 拉起Junit单元测试类 输出的报错日志 跟踪执行过程: 日志1: java.lang.reflect.InvocationTargetExceptionat sun.refle ...

最新文章

  1. android -自定义view
  2. bool类型_C语言编程第11讲——C语言的布尔类型
  3. 将PPT内容导出为JPG图片
  4. linux ns机制,Linux内核API ns_to_timespec
  5. QT中串口通信程序(转)
  6. 满满干货!mysql定时任务每天固定时间执行
  7. poj 1887 Testing the CATCHER dp 最大降序
  8. 10个小技巧:快速用Python进行数据分析
  9. 安徽掀起新一轮大规模清房行动 官员急抛房产
  10. DIY_实现光敏电阻传感器简单控制LED
  11. uni-app(登录页面)
  12. 股票量化分析指标公式是什么?
  13. Python学习之路(四)——Python核心编程3(面向对象、模块_包_异常)
  14. 网络计算机不能打印,如果共享打印机后局域网计算机无法打印,该怎么办?
  15. 请编程序将china译成密码,密码规律是:用原来的字母后面第4个字母代替原来的字母。例如:字母A后面4个字母为E,因此,China应译为Glmre。
  16. SHELL脚本下获取文件时间转换时间戳,使用时间戳计算日期差
  17. iOS锁屏显示歌曲信息
  18. java动效_前端实现炫酷动效_Lottie-前端实现AE动效
  19. js检查违禁词汇敏感词汇代码
  20. oracle创建列默认值,表列添加默认值的方法

热门文章

  1. gridcontrol 添加行删除行
  2. 微信接口第三方php原理,微信第三方登录原理
  3. 亚马逊云EC2助力5G产品测试
  4. springboot基于Elasticsearch6.x版本进行ES同义词、停用词(停止词)插件配置,远程词典热加载及数据库词典热加载总结,es停用词热更新,es同义词热更新
  5. xposed新版52下载_kyqp游戏合集-kyqp游戏下载推荐
  6. vue 不同条件展示不同页面_vue根据条件不同显示不同按钮的操作
  7. 牛年第一瓜!阿里女员工被初中文化男子骗走 500 多万元。。。
  8. 计算机毕业设计ssm焦虑自测与交流平台k43cf系统+程序+源码+lw+远程部署
  9. Manger配置同步任务
  10. Field [price] of type [text] is not supported for aggregation [avg]