【源码】update-notifier 检测 npm 包是否更新~

学习目标
  • 了解update-notifier作用和使用场景
  • 应用场景:
    检测npm包是否更新,比如组件库更新或者其他npm 包更新,
资料准备

链接:https://github.com/yeoman/update-notifier

什么是update-notifier

提示包需要更新

本质就是开了child_process运行在后台,就是通过比较 package.json 中的 nameversion 和当前 npm registry 上的最新版,如果检查到有可用更新版,会将结果保存在.update属性中,是用来标记是否有可用更新版,如果有,就输出更新信息,存在可用更新版

前期准备
git clone https://github.com/yeoman/update-notifier
cd update-notifier
npm i
初识–官方文档
  1. 用法
  • simple
    const updateNotifier = require('update-notifier');
	const pkg = require('./package.json');
	
	updateNotifier({pkg}).notify();
  • Comprehensive
	const updateNotifier = require('update-notifier');
	const pkg = require('./package.json');
		
	// Checks for available update and returns an instance
	const notifier = updateNotifier({pkg});
	
	// Notify using the built-in convenience method
	notifier.notify();
	
	// `notifier.update` contains some useful info about the update
	console.log(notifier.update);
	/*
	{
		latest: '1.0.1',
		current: '1.0.0',
		type: 'patch', // Possible values: latest, major, minor, patch, prerelease, build
		name: 'pageres'
	}
	*/
	
  • 选项和自定义消息
    const notifier = updateNotifier({
    	pkg,
    	updateCheckInterval: 1000 * 60 * 60 * 24 * 7 // 1 week
    });
    
    if (notifier.update) {
    	console.log(`Update available: ${notifier.update.latest}`);
    }
    
  1. HOW

    用户第一次运行应用程序时,将检查是否有更新,即使有可用的更新,也会等待指定的updateCheckInterval,然后再通知用户。这样做是为了不打扰用户。

  2. API

    • notifier = updateNotifier(options)
      检查是否有可用的更新。接受下面定义的选项。如果存在可用更新,则返回具有.update属性的实例,否则返回未定义的实例。
          options: {
       			pkg: {}, // 对象类型
       			name: '', //必须的,字符串类型
       			version: '', //必须的,字符串类型
       			updateCheckInterval:'',//number类型,默认值为一天 (1000 * 60 * 60 * 24)
       			shouldNotifyInNpmScript: false,//布尔值,默认为false, 是否允许在作为npm脚本运行时显示通知
       			distTag:'latest'//字符串,默认值为latest
       	}
      
    • notifier.fetchInfo()
      检查更新信息,返回具有以下内容的对象
      {
        latest: '' ,//字符串,最新版本
        current:'',//字符串,当前版本
        type:'',//字符串,当前更新的类型,可能取值为:latest, major, minor, patch, prerelease, build
        name:'' // 字符串,包名
       }
      
    • notifier.notify(options?)
      显示通知消息的便捷方法,仅在有更新且进程为TTY时通知。options:
          {
              defer: true, // 布尔值,默认为true, 在进程退出后将通知延迟显示
              message: '', // string,更新可用时将显示的消息
              isGlobal:true/false, // 布尔值,更新可用时将显示的消息在默认消息的npm i建议中包括-g参数。
              boxenOptions:object,// 一个对象,传给boxen的参数
           }
      
解读index.js

引用包部分

  1. const {spawn} = require('child_process');
  • node.js中 杀死正在执行的子进程
  • const {spawn} = require('child_process');const spawn = require('child_process');的区别:
    1. {x} = v相当于{x:x} = v
    2. {x:y} = v相当于 y = v.x
    3. const {spawn} = require('child_process');相当于 const_ = require('child_process'); const spawn = _.resolve;
    4. 相当于解构出来的东西,在这个当前的文件里面有了这个东西,已经获取到了,可以直接使用,而不需要在通过spawn.xxxx 去拿相应的变量或者方法了
  1. const path = require('path');

node.js中 处理关于文件路径的问题链接

  1. const {format} = require('util');
  • util是常用的函数集合,用于弥补核心js功能过于精简的不足,为了满足nide.js 内部api 的需求
  • util.format(format[, …])
    1. util.form()函数接收 一个格式化字符串format作为第一个参数,并返回格式化后的字符串。
    2. format参数是可用包含零个或多个占位符的字符串,每个占位符都是以一个%字符开始,并最终被对应的参数转换的字符串值取代
  1. const importLazy = require('import-lazy')(require); usage
const importLazy = require('import-lazy')(require);//模块懒加载
const configstore = importLazy('configstore');// 轻松加载和持久化配置,而无需考虑在何处以及如何加载。
const chalk = importLazy('chalk'); //画彩色字体通知的
const semver = importLazy('semver'); //npm的语义版本器
const semverDiff = importLazy('semver-diff');// 获取两个semver版本的差异类型 //例如:0.0.1 0.0.2→ patch
const latestVersion = importLazy('latest-version');// 获取npm包的最新版本
const isNpm = importLazy('is-npm');//检查代码是不是作为npm 脚本运行
const isInstalledGlobally = importLazy('is-installed-globally'); //检查软件包是否是通过 npm 全局安装的
const isYarnGlobal = importLazy('is-yarn-global'); // 检查是否在没有任何fs调用的情况下由yarn全局安装
const hasYarn = importLazy('has-yarn'); //检查项目是否正在使用yarn
const boxen = importLazy('boxen');//在终端创建框
const xdgBasedir = importLazy('xdg-basedir');//获取XDG基本目录路径
const isCi = importLazy('is-ci');//如果当前环境是持续集成服务器,则返回true
const pupa = importLazy('pupa');//简易微模版

const ONE_DAY = 1000 * 60 * 60 * 24; //24小时60分钟60秒1000毫秒
UpdateNotifier
class UpdateNotifier {
  constructor(options = {}) { }
  check(){}
  async fetchInfo() {}
  notify(options) {}
}
consructor初始化阶段
class UpdateNotifier {
	constructor(options = {}) {
		// 把package.json的东西给options
		this.options = options;
		// 容错处理
		options.pkg = options.pkg || {};
		options.distTag = options.distTag || 'latest';

		// Reduce pkg to the essential keys. with fallback to deprecated options
		// TODO: Remove deprecated options at some point far into the future
		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;

		/**
		 * 通过读官方文档可以知道 updateCheckInterval 默认是一天(默认是数字格式) 此处检查周期,单位ms
		 * const ONE_DAY = 1000 * 60 * 60 * 24; //24小时60分种60秒1000毫秒
		 */
		this.updateCheckInterval = typeof options.updateCheckInterval === 'number' ? options.updateCheckInterval : ONE_DAY;

		/**
		 * isCi()  如果当前环境是持续集成服务器,则返回true
		 * 禁用状态检测:是否禁止更新通知
		 */
		this.disabled = 'NO_UPDATE_NOTIFIER' in process.env ||
			process.env.NODE_ENV === 'test' ||
			process.argv.includes('--no-update-notifier') ||
			isCi();
		/**
		 * shouldNotifyInNpmScript 是否允许在作为npm脚本运行时显示通知
		 */
		this.shouldNotifyInNpmScript = options.shouldNotifyInNpmScript;

		if (!this.disabled) {
			// 如果没有禁止更新通知
			try {
				//设置配置对象
				const ConfigStore = 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) {}
}
check
	check() {
		/**
		 * 如果获取不到本地该包相关信息的存储 || optOut 为 true || 禁止更新通知 的情况时停止检查
		 */
		if (!this.config || this.config.get('optOut') || this.disabled) {
			return;
		}
		//获取相关包的更新信息,第一次为undefined
		this.update = this.config.get('update');


		if (this.update) {
			// Use the real latest version instead of the cached one
			//如果获取到最新的包的信息了,使用实际值替代缓存值
			this.update.current = this.packageVersion;

			// Clear cached information 清除缓存的值
			this.config.delete('update');
		}

		// Only check for updates on a set interval
		//仅在设定的时间间隔内检查更新,如果还在周期内就返回,停止检查
		if (Date.now() - this.config.get('lastUpdateCheck') < this.updateCheckInterval) {
			return;
		}

		// Spawn a detached process, passing the options as an environment property
		/**
		 * 生成分离的进程,将选项作为环境属性传递
		 * path.join 将多个参数值合并成一个路径
		 */
		spawn(process.execPath, [path.join(__dirname, 'check.js'), JSON.stringify(this.options)], {
			//让子进程独立于其父进程运行子进程可以在父进程退出后继续运行,不管它们是否分离。[链接](http://nodejs.cn/api/child_process/options_detached.html)
			detached: true,
			// 不关注控制台的输入输出,忽略父进程的终止
			stdio: 'ignore'
		}).unref();// unref 方法用来断绝关系,这样“父”进程可以独立退出(不会导致子进程跟着退出)
	}
check.js
/* eslint-disable unicorn/no-process-exit */
'use strict';
// 获取index.js导出的内容
let updateNotifier = require('.');

// 获取options
const options = JSON.parse(process.argv[2]);
//实例化 updateNotifier
updateNotifier = new updateNotifier.UpdateNotifier(options);

//执行一个函数
(async () => {
	// Exit process when offline 脱机时退出进程
	setTimeout(process.exit, 1000 * 30);

	// 调用updateNotifier的fetchInfo方法获取更新信息
	const update = await updateNotifier.fetchInfo();

	// Only update the last update check time on success 仅在成功时更新上次更新的检查时间
	updateNotifier.config.set('lastUpdateCheck', Date.now());
	// 只要不是最新的包 就设置更新字段
	if (update.type && update.type !== 'latest') {
		updateNotifier.config.set('update', update);
	}
	// 获取到的信息,如果版本有更新,存到config
	// Call process exit explicitly to terminate the child process,
	// otherwise the child process will run forever, according to the Node.js docs
	// 显式调用进程退出以终止子进程,根据Node.js文档,否则子进程将永远运行

	process.exit();
})().catch(error => {
	console.error(error);
	process.exit(1);
});

fetchInfo 获取包的相关信息
	//获取包的相关信息
	async fetchInfo() {
		const {distTag} = this.options;
		// latestVersion 获取npm包的最新版本
		// 由于是懒加载的 所以需要先latestVersion() 执行下
		// 这里需要注意latestVersion是一个异步的请求
		const latest = await latestVersion()(this.packageName, {version: distTag});
		// 返回组装后的对象信息
		return {
			latest,
			current: this.packageVersion,
			type: semverDiff()(this.packageVersion, latest) || distTag,
			name: this.packageName
		};
	}
	notify(options) {}
notify 通知
	notify(options) {
		// 是否支持npm-scripts 调用
		const suppressForNpm = !this.shouldNotifyInNpmScript && isNpm().isNpmOrYarn;
		/**
		 * !process.stdout.isTTY node不在终端上运行
		 * suppressForNpm 不支持npm-scripts 调用
		 * !this.update 本地没有检查过更新
		 * !semver().gt(this.update.latest, this.update.current) 当前版本是否落后于最新版本
		 * 满足以上条件时不需要通知(没有更新时不通知)
		 */
		if (!process.stdout.isTTY || suppressForNpm || !this.update || !semver().gt(this.update.latest, this.update.current)) {
			return this;
		}

		options = {
			// 检查软件包是否是通过 npm 全局安装的
			isGlobal: isInstalledGlobally(),
			// 是否是通过 yarn 全局安装的
			// 有个缺陷: 前提是用户没有改变yarn默认的全局安装目录
			isYarnGlobal: isYarnGlobal()(),
			...options
		};
		// 相应的更新命令提示
		let installCommand;
		if (options.isYarnGlobal) { // 是否是通过yarn全局安装
			installCommand = `yarn global add ${this.packageName}`;
		} else if (options.isGlobal) { //是否是通过npm 全局安装
			installCommand = `npm i -g ${this.packageName}`;
		} else if (hasYarn()()) {// hasYarn() 由于 hasYarn 是懒加载的模块,所以需要先执行一下 yarn 局部安装
			installCommand = `yarn add ${this.packageName}`;
		} else {// npm 局部安装
			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'
		};
		/**
		 * boxen 在终端创建框
		 * pupa 简易微模版
		 */
		const message = boxen()(
			pupa()(template, {
				// 将以下信息补全到模板中
                // this.update 是在 check 阶段填充的
				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的目的: 为了链式调用 option配置已改变
		return this;
	}
总结流程
  • const updateNotifier = new UpdateNotifier({})
    • 初始化 constuctor
    • updateNotifier.check()
  • 判断是否执行 check.js,如果执行:
    • updateNotifier.fetchInfo()
    • set(‘lastUpdateCheck’)
    • set(‘update’)
  • notify()
学习目标
  • 了解update-notifier作用和使用场景
  • 应用场景:
    检测npm包是否更新,比如组件库更新或者其他npm 包更新,
资料准备

链接:https://github.com/yeoman/update-notifier

什么是update-notifier

提示包需要更新

本质就是开了child_process运行在后台,就是通过比较 package.json 中的 nameversion 和当前 npm registry 上的最新版,如果检查到有可用更新版,会将结果保存在.update属性中,是用来标记是否有可用更新版,如果有,就输出更新信息,存在可用更新版

前期准备
git clone https://github.com/yeoman/update-notifier
cd update-notifier
npm i
初识–官方文档
  1. 用法
  • simple
    const updateNotifier = require('update-notifier');
	const pkg = require('./package.json');
	
	updateNotifier({pkg}).notify();
  • Comprehensive
	const updateNotifier = require('update-notifier');
	const pkg = require('./package.json');
		
	// Checks for available update and returns an instance
	const notifier = updateNotifier({pkg});
	
	// Notify using the built-in convenience method
	notifier.notify();
	
	// `notifier.update` contains some useful info about the update
	console.log(notifier.update);
	/*
	{
		latest: '1.0.1',
		current: '1.0.0',
		type: 'patch', // Possible values: latest, major, minor, patch, prerelease, build
		name: 'pageres'
	}
	*/
	
  • 选项和自定义消息
    const notifier = updateNotifier({
    	pkg,
    	updateCheckInterval: 1000 * 60 * 60 * 24 * 7 // 1 week
    });
    
    if (notifier.update) {
    	console.log(`Update available: ${notifier.update.latest}`);
    }
    
  1. HOW

    用户第一次运行应用程序时,将检查是否有更新,即使有可用的更新,也会等待指定的updateCheckInterval,然后再通知用户。这样做是为了不打扰用户。

  2. API

    • notifier = updateNotifier(options)
      检查是否有可用的更新。接受下面定义的选项。如果存在可用更新,则返回具有.update属性的实例,否则返回未定义的实例。
          options: {
       			pkg: {}, // 对象类型
       			name: '', //必须的,字符串类型
       			version: '', //必须的,字符串类型
       			updateCheckInterval:'',//number类型,默认值为一天 (1000 * 60 * 60 * 24)
       			shouldNotifyInNpmScript: false,//布尔值,默认为false, 是否允许在作为npm脚本运行时显示通知
       			distTag:'latest'//字符串,默认值为latest
       	}
      
    • notifier.fetchInfo()
      检查更新信息,返回具有以下内容的对象
      {
        latest: '' ,//字符串,最新版本
        current:'',//字符串,当前版本
        type:'',//字符串,当前更新的类型,可能取值为:latest, major, minor, patch, prerelease, build
        name:'' // 字符串,包名
       }
      
    • notifier.notify(options?)
      显示通知消息的便捷方法,仅在有更新且进程为TTY时通知。options:
          {
              defer: true, // 布尔值,默认为true, 在进程退出后将通知延迟显示
              message: '', // string,更新可用时将显示的消息
              isGlobal:true/false, // 布尔值,更新可用时将显示的消息在默认消息的npm i建议中包括-g参数。
              boxenOptions:object,// 一个对象,传给boxen的参数
           }
      
解读index.js

引用包部分

  1. const {spawn} = require('child_process');
  • node.js中 杀死正在执行的子进程
  • const {spawn} = require('child_process');const spawn = require('child_process');的区别:
    1. {x} = v相当于{x:x} = v
    2. {x:y} = v相当于 y = v.x
    3. const {spawn} = require('child_process');相当于 const_ = require('child_process'); const spawn = _.resolve;
    4. 相当于解构出来的东西,在这个当前的文件里面有了这个东西,已经获取到了,可以直接使用,而不需要在通过spawn.xxxx 去拿相应的变量或者方法了
  1. const path = require('path');

node.js中 处理关于文件路径的问题链接

  1. const {format} = require('util');
  • util是常用的函数集合,用于弥补核心js功能过于精简的不足,为了满足nide.js 内部api 的需求
  • util.format(format[, …])
    1. util.form()函数接收 一个格式化字符串format作为第一个参数,并返回格式化后的字符串。
    2. format参数是可用包含零个或多个占位符的字符串,每个占位符都是以一个%字符开始,并最终被对应的参数转换的字符串值取代
  1. const importLazy = require('import-lazy')(require); usage
const importLazy = require('import-lazy')(require);//模块懒加载
const configstore = importLazy('configstore');// 轻松加载和持久化配置,而无需考虑在何处以及如何加载。
const chalk = importLazy('chalk'); //画彩色字体通知的
const semver = importLazy('semver'); //npm的语义版本器
const semverDiff = importLazy('semver-diff');// 获取两个semver版本的差异类型 //例如:0.0.1 0.0.2→ patch
const latestVersion = importLazy('latest-version');// 获取npm包的最新版本
const isNpm = importLazy('is-npm');//检查代码是不是作为npm 脚本运行
const isInstalledGlobally = importLazy('is-installed-globally'); //检查软件包是否是通过 npm 全局安装的
const isYarnGlobal = importLazy('is-yarn-global'); // 检查是否在没有任何fs调用的情况下由yarn全局安装
const hasYarn = importLazy('has-yarn'); //检查项目是否正在使用yarn
const boxen = importLazy('boxen');//在终端创建框
const xdgBasedir = importLazy('xdg-basedir');//获取XDG基本目录路径
const isCi = importLazy('is-ci');//如果当前环境是持续集成服务器,则返回true
const pupa = importLazy('pupa');//简易微模版

const ONE_DAY = 1000 * 60 * 60 * 24; //24小时60分钟60秒1000毫秒
UpdateNotifier
class UpdateNotifier {
  constructor(options = {}) { }
  check(){}
  async fetchInfo() {}
  notify(options) {}
}
consructor初始化阶段
class UpdateNotifier {
	constructor(options = {}) {
		// 把package.json的东西给options
		this.options = options;
		// 容错处理
		options.pkg = options.pkg || {};
		options.distTag = options.distTag || 'latest';

		// Reduce pkg to the essential keys. with fallback to deprecated options
		// TODO: Remove deprecated options at some point far into the future
		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;

		/**
		 * 通过读官方文档可以知道 updateCheckInterval 默认是一天(默认是数字格式) 此处检查周期,单位ms
		 * const ONE_DAY = 1000 * 60 * 60 * 24; //24小时60分种60秒1000毫秒
		 */
		this.updateCheckInterval = typeof options.updateCheckInterval === 'number' ? options.updateCheckInterval : ONE_DAY;

		/**
		 * isCi()  如果当前环境是持续集成服务器,则返回true
		 * 禁用状态检测:是否禁止更新通知
		 */
		this.disabled = 'NO_UPDATE_NOTIFIER' in process.env ||
			process.env.NODE_ENV === 'test' ||
			process.argv.includes('--no-update-notifier') ||
			isCi();
		/**
		 * shouldNotifyInNpmScript 是否允许在作为npm脚本运行时显示通知
		 */
		this.shouldNotifyInNpmScript = options.shouldNotifyInNpmScript;

		if (!this.disabled) {
			// 如果没有禁止更新通知
			try {
				//设置配置对象
				const ConfigStore = 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) {}
}
check
	check() {
		/**
		 * 如果获取不到本地该包相关信息的存储 || optOut 为 true || 禁止更新通知 的情况时停止检查
		 */
		if (!this.config || this.config.get('optOut') || this.disabled) {
			return;
		}
		//获取相关包的更新信息,第一次为undefined
		this.update = this.config.get('update');


		if (this.update) {
			// Use the real latest version instead of the cached one
			//如果获取到最新的包的信息了,使用实际值替代缓存值
			this.update.current = this.packageVersion;

			// Clear cached information 清除缓存的值
			this.config.delete('update');
		}

		// Only check for updates on a set interval
		//仅在设定的时间间隔内检查更新,如果还在周期内就返回,停止检查
		if (Date.now() - this.config.get('lastUpdateCheck') < this.updateCheckInterval) {
			return;
		}

		// Spawn a detached process, passing the options as an environment property
		/**
		 * 生成分离的进程,将选项作为环境属性传递
		 * path.join 将多个参数值合并成一个路径
		 */
		spawn(process.execPath, [path.join(__dirname, 'check.js'), JSON.stringify(this.options)], {
			//让子进程独立于其父进程运行子进程可以在父进程退出后继续运行,不管它们是否分离。[链接](http://nodejs.cn/api/child_process/options_detached.html)
			detached: true,
			// 不关注控制台的输入输出,忽略父进程的终止
			stdio: 'ignore'
		}).unref();// unref 方法用来断绝关系,这样“父”进程可以独立退出(不会导致子进程跟着退出)
	}
check.js
/* eslint-disable unicorn/no-process-exit */
'use strict';
// 获取index.js导出的内容
let updateNotifier = require('.');

// 获取options
const options = JSON.parse(process.argv[2]);
//实例化 updateNotifier
updateNotifier = new updateNotifier.UpdateNotifier(options);

//执行一个函数
(async () => {
	// Exit process when offline 脱机时退出进程
	setTimeout(process.exit, 1000 * 30);

	// 调用updateNotifier的fetchInfo方法获取更新信息
	const update = await updateNotifier.fetchInfo();

	// Only update the last update check time on success 仅在成功时更新上次更新的检查时间
	updateNotifier.config.set('lastUpdateCheck', Date.now());
	// 只要不是最新的包 就设置更新字段
	if (update.type && update.type !== 'latest') {
		updateNotifier.config.set('update', update);
	}
	// 获取到的信息,如果版本有更新,存到config
	// Call process exit explicitly to terminate the child process,
	// otherwise the child process will run forever, according to the Node.js docs
	// 显式调用进程退出以终止子进程,根据Node.js文档,否则子进程将永远运行

	process.exit();
})().catch(error => {
	console.error(error);
	process.exit(1);
});

fetchInfo 获取包的相关信息
	//获取包的相关信息
	async fetchInfo() {
		const {distTag} = this.options;
		// latestVersion 获取npm包的最新版本
		// 由于是懒加载的 所以需要先latestVersion() 执行下
		// 这里需要注意latestVersion是一个异步的请求
		const latest = await latestVersion()(this.packageName, {version: distTag});
		// 返回组装后的对象信息
		return {
			latest,
			current: this.packageVersion,
			type: semverDiff()(this.packageVersion, latest) || distTag,
			name: this.packageName
		};
	}
	notify(options) {}
notify 通知
	notify(options) {
		// 是否支持npm-scripts 调用
		const suppressForNpm = !this.shouldNotifyInNpmScript && isNpm().isNpmOrYarn;
		/**
		 * !process.stdout.isTTY node不在终端上运行
		 * suppressForNpm 不支持npm-scripts 调用
		 * !this.update 本地没有检查过更新
		 * !semver().gt(this.update.latest, this.update.current) 当前版本是否落后于最新版本
		 * 满足以上条件时不需要通知(没有更新时不通知)
		 */
		if (!process.stdout.isTTY || suppressForNpm || !this.update || !semver().gt(this.update.latest, this.update.current)) {
			return this;
		}

		options = {
			// 检查软件包是否是通过 npm 全局安装的
			isGlobal: isInstalledGlobally(),
			// 是否是通过 yarn 全局安装的
			// 有个缺陷: 前提是用户没有改变yarn默认的全局安装目录
			isYarnGlobal: isYarnGlobal()(),
			...options
		};
		// 相应的更新命令提示
		let installCommand;
		if (options.isYarnGlobal) { // 是否是通过yarn全局安装
			installCommand = `yarn global add ${this.packageName}`;
		} else if (options.isGlobal) { //是否是通过npm 全局安装
			installCommand = `npm i -g ${this.packageName}`;
		} else if (hasYarn()()) {// hasYarn() 由于 hasYarn 是懒加载的模块,所以需要先执行一下 yarn 局部安装
			installCommand = `yarn add ${this.packageName}`;
		} else {// npm 局部安装
			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'
		};
		/**
		 * boxen 在终端创建框
		 * pupa 简易微模版
		 */
		const message = boxen()(
			pupa()(template, {
				// 将以下信息补全到模板中
                // this.update 是在 check 阶段填充的
				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的目的: 为了链式调用 option配置已改变
		return this;
	}
总结流程
  • const updateNotifier = new UpdateNotifier({})
    • 初始化 constuctor
    • updateNotifier.check()
  • 判断是否执行 check.js,如果执行:
    • updateNotifier.fetchInfo()
    • set(‘lastUpdateCheck’)
    • set(‘update’)
  • notify()
    在这里插入图片描述
总结

1.加深了对解构赋值的理解
2.根据源码知道了更多的node.js 的api
3.也知道本次的update-notifier更新是否是最新版本的包的工具库,可以加入到实际中使用
4.跟着官网去阅读也是个不错的顺序流程,尤其在集合各位大佬的笔记,理解起来会更加容易上手,收获满满,再接再厉!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值