【源码学习】探索update-notifier 检测 npm 包是否更新之旅【4】

简介

本文档记录了我参加 update-notifier 检测 npm 包是否更新共读的过程中的学习和思考。

参与目的:

  • 探索源码,理解工作原理
  • 学习和成长
  • 提升技术水平

作为一名前端开发者,参与这次源码共读活动。这是一个很好的机会,与志同道合的开发者一起深入探索源码、理解工作原理,并在过程中学习和成长。

本文参与学习目标:

  • 了解 update-notifier 作用和使用场景
  • 应用场景:检测npm包是否更新,比如组件库更新或者其他npm包更新,在控制台提醒

源码分析

Update Notifier是一个轻量级的Node.js模块,主要用于自动检测应用程序或依赖项是否有新版本可用,并在用户端显示更新提示。这个工具可以集成到各种应用中,帮助用户及时了解新功能和改进,从而提升用户体验和产品质量。同时,它也可以用于检测npm包的更新情况,可以帮助开发者和用户更好地管理和更新他们的应用程序和依赖项。

源码准备

update-notifier源码仓库
源码克隆

Bash 复制代码
git clone https://github.com/yeoman/update-notifier.git

源码调试

Javascript 复制代码
// 获取了当前JavaScript模块所在的文件夹路径,并将其存储在常量`__dirname`中
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// 定义了一个常量`ONE_DAY`,它代表了一天的毫秒数,即24小时转换成毫秒的值(86400000毫秒)
const ONE_DAY = 1000 * 60 * 60 * 24;

代码概括

Javascript 复制代码
export default class UpdateNotifier {
	// Public
	config;
	update;

	// Semi-private (used for tests)
	_packageName;
	_shouldNotifyInNpmScript;

	#options;
	#packageVersion;
	#updateCheckInterval;
	#isDisabled;
// 初始化
constructor(options = {}) {...}

// 处理软件包更新通知
check() {...}

// 处理包(package)版本信息
async fetchInfo() {...}

// 通知用户某个软件包存在可用更新,并提供相应的更新命令
notify(options) {...}
}

初始化模块

Javascript 复制代码
	constructor(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;
		this.#updateCheckInterval = typeof options.updateCheckInterval === 'number' ? options.updateCheckInterval : ONE_DAY;
		this.#isDisabled = 'NO_UPDATE_NOTIFIER' in process.env
			|| process.env.NODE_ENV === 'test'
			|| process.argv.includes('--no-update-notifier')
			|| isInCi;
		this._shouldNotifyInNpmScript = options.shouldNotifyInNpmScript;

		if (!this.#isDisabled) {
			try {
				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 ', xdgConfig));

				process.on('exit', () => {
					console.error(boxen(message, {textAlignment: 'center'}));
				});
			}
		}
	}

该模块代码是构造函数(constructor)接收一个名为options的参数,该参数是一个对象,包含了一些配置选项 它的主要功能有:

  1. 设置默认选项
  2. 处理包信息
  3. 设置更新检查间隔
  4. 确定是否禁用更新通知器
  5. 配置存储
  6. 错误处理

此外,构造函数还设置了一些私有属性(如#options#packageVersion等)和一个公共属性(_packageName)。私有属性在类的外部是不可访问的,而公共属性可以通过类的实例来访问。

需要注意的是,代码中引用了一些外部变量和函数(如chalkformatxdgConfigConfigStoreboxen等),这些都需要在构造函数所在的类或模块中定义或导入。

总的来说,这个构造函数的主要目的是初始化一个用于处理包更新通知的类实例,包括设置选项,处理包信息,配置更新检查间隔,以及处理与更新配置存储相关的错误。

处理软件包更新通知模块

Javascript 复制代码
check() {
		if (
			!this.config
			|| this.config.get('optOut')
			|| this.#isDisabled
		) {
			return;
		}

		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
		spawn(process.execPath, [path.join(__dirname, 'check.js'), JSON.stringify(this.#options)], {
			detached: true,
			stdio: 'ignore',
		}).unref();
	}

该模块代码主要目的是在适当的时机检查是否有可用的更新,并且以一种不会阻塞主进程的方式执行这个检查

  1. 检查是否需要跳过更新检查
  2. 获取更新信息
  3. 检查是否达到了更新检查间隔
  4. 执行更新检查

总的来说,check 方法的作用是以一种非阻塞的方式定期检查软件包的更新。如果发现有更新可用,它可能会通过其他方式(可能是 check.js 脚本中的逻辑)来通知用户。这种设计使得更新检查可以在不干扰主应用程序运行的情况下进行。

处理包(package)版本信息模块

Javascript 复制代码
	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,
		};
	}	

该模块主要目的是获取指定软件包的最新版本信息,并将其与当前安装的版本进行比较

  1. 提取分发标签
  2. 获取最新版本
  3. 准备返回信息
  4. 返回信息对象

这个方法的主要用途是收集关于软件包版本的信息,包括当前安装的版本、可用的最新版本以及这两个版本之间的差异类型。这种信息对于实现自动更新、显示更新通知或生成版本更改日志等功能可能是非常有用的。

需要注意的是,代码中使用了一些私有字段(如 this.#optionsthis.#packageVersion),这意味着它们只能在类的内部访问,而不能从类的外部直接访问。此外,semverDiff 函数的确切行为取决于其实现,但通常它会根据语义化版本控制(semver)规范来确定版本之间的差异。

通知用户某个软件包存在可用更新,并提供相应的更新命令

Javascript 复制代码
	notify(options) {
		const suppressForNpm = !this._shouldNotifyInNpmScript && isNpmOrYarn;
		if (!process.stdout.isTTY || suppressForNpm || !this.update || !semver.gt(this.update.latest, this.update.current)) {
			return this;
		}

		options = {
			isGlobal: isInstalledGlobally,
			...options,
		};

		const installCommand = options.isGlobal ? `npm i -g ${this._packageName}` : `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 ??= {
			padding: 1,
			margin: 1,
			textAlignment: '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;
	}

该模块代码其主要用途是通知用户某个软件包存在可用更新,并提供相应的更新命令

  1. 检查是否需要通知
  2. 设置选项
  3. 构造更新命令
  4. 准备通知模板
  5. 设置Boxen选项
  6. 生成并打印消息
  7. 返回

此方法在以下场景中特别有用:当你在使用命令行工具时,它可以帮助你保持软件包处于最新状态,通过提供明确的更新信息和如何执行更新的命令。其中涉及的库(如chalkpupaboxen)通常用于生成颜色化、格式化的终端输出。

总结

update-notifier 是一个 Node.js 库,主要用于在终端中通知用户有关已安装软件包的更新信息。它具备检查更新、缓存机制、用户通知以及灵活配置等功能。库中的组件包括配置文件处理、网络请求、本地存储、通知生成器和可能的命令行接口。其工作流程涉及初始化、检查更新、处理响应、显示通知以及清理和退出步骤。此外,update-notifier 以异步方式工作,不阻塞主程序,并设计为可灵活定制,通常作为其他项目的依赖项集成。

相关推荐
zqx_71 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己1 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称2 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色2 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript
NiNg_1_2342 小时前
npm、yarn、pnpm之间的区别
前端·npm·node.js
秋殇与星河2 小时前
CSS总结
前端·css
BigYe程普3 小时前
我开发了一个出海全栈SaaS工具,还写了一套全栈开发教程
开发语言·前端·chrome·chatgpt·reactjs·个人开发
余生H3 小时前
前端的全栈混合之路Meteor篇:关于前后端分离及与各框架的对比
前端·javascript·node.js·全栈
程序员-珍3 小时前
使用openapi生成前端请求文件报错 ‘Token “Integer“ does not exist.‘
java·前端·spring boot·后端·restful·个人开发
axihaihai3 小时前
网站开发的发展(后端路由/前后端分离/前端路由)
前端