如何自动检测使用的组件库有更新

在这里插入图片描述

🤖 作者简介:水煮白菜王,一位前端劝退师 👻
👀 文章专栏: 前端专栏 ,记录一下平时在博客写作中,总结出的一些开发技巧和知识归纳总结✍。
感谢支持💕💕💕

目录

  • 实现流程分析
  • update-notifier的实现
    • 内部使用的第三方库
    • 实现
    • 初始化
    • check
    • check.js
    • fetchInfo
    • notify
    • 最后

实现流程分析

New 初始化
获取name, version
设置禁用 检查时间
ConfigStore存储到本地
执行
check
判断是否检查
是否存在update数据
启动子进程执行check.js
fetchinfo
更新configStore信息
notify
判断提示条件
是否全局安装命令
使用chalk boxen提示

update-notifier的实现

update-notifier是在给定的时间间隔内,检查package.json 中的 name 和 version 和当前 npm registry 上是否有新的更新可用,并在有更新时通知用户。它基于 ConfigStore 来保存和读取配置信息,使用了一些第三方库来获取最新版本的信息,并使用 boxen 来创建一个带有边框的通知框。\

内部使用的第三方库

  • process:Node.js 的 process 模块,提供了对进程的操作和控制。
  • spawn:child_process 模块的 spawn 方法,用于启动一个子进程并执行命令。
  • fileURLToPath:url 模块的 fileURLToPath 方法,用于将文件的 URL 转换为本地路径。
  • path:Node.js 的 path 模块,提供了处理文件路径的工具函数。
  • format:util 模块的 format 方法,用于格式化字符串。
  • ConfigStore:一个用于保存配置信息的库。
  • chalk:一个用于在终端中添加颜色和样式的库。
  • semver:用于对语义化版本进行比较和操作的库。
  • semverDiff:用于计算两个版本之间的差异的库。
  • latestVersion:用于获取指定包名的最新版本号的库。
  • isNpmOrYarn:一个用于检测当前项目是使用npm还是yarn的库。
  • isInstalledGlobally:一个用于检测当前包是否全局安装的库。
  • boxen:用于在终端创建带有边框的框的库。
  • xdgConfig:一个用于获取 XDG 配置目录路径的库。
  • isInCi:用于检测当前是否在 CI 环境下运行的库。
  • pupa:用于填充模板字符串的库。

UpdateNotifier 类有一些公共属性和方法,用于检查和通知更新。其中重要的属性和方法包括:

  • config:一个 ConfigStore 实例,用于保存和读取更新通知的配置信息。
  • update:一个保存最新版本信息的对象,包括当前版本、最新版本和版本差异。
  • check():用于检查是否有新的更新可用。
  • fetchInfo():用于获取最新版本的信息。
  • notify():用于通知用户有新的更新可用。

伪代码

const { exec } = require('child_process');// 检查指定组件库的更新
function checkSpecificPackageUpdate(packageName, currentVersion) {exec(`npm view ${packageName} version`, (error, stdout, stderr) => {if (error) {console.error(`Error fetching version for ${packageName}: ${error.message}`);return;}if (stderr) {console.error(`Stderr for ${packageName}: ${stderr}`);return;}const latestVersion = stdout.trim();if (latestVersion !== currentVersion) {console.log(`Update available for ${packageName}: ${currentVersion} -> ${latestVersion}`);} else {console.log(`${packageName} is up to date.`);}});
}// 指定的组件库及其版本
const packagesToCheck = [{ name: 'express', version: '4.17.1' },{ name: 'lodash', version: '4.17.20' }
];// 遍历并检查每个指定的组件库
packagesToCheck.forEach(pkg => {checkSpecificPackageUpdate(pkg.name, pkg.version);
});

实现

class UpdateNotifier {// 初始化操作constructor(options = {}) {}// 检查check() {}// 获取版本信息fetchInfo() {}// 提示notify(options) {}
}

初始化

在UpdateNotifier的构造函数中,初始化操作包括解析传入的选项、设置默认值(如包名和版本号)、确定更新检查的时间间隔以及判断是否需要禁用更新通知。此外,还会创建一个持久化存储来保存配置信息,如用户选择退出更新通知等。

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,};// 必须传项 pkg.name(需要校验的包), pkg.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;// 默认检查间隔为 1 天 小于这个间隔时不再检查this.#updateCheckInterval =typeof options.updateCheckInterval === "number"? options.updateCheckInterval: ONE_DAY;// 禁用提示// 1. 在环境变量中禁用 NO_UPDATE_NOTIFIER// 2. 在环境变量中禁用 NODE_ENV 为 test// 3. 在命令行中禁用 node example.js --no-update-notifier// 4. 在CI中禁用this.#isDisabled ="NO_UPDATE_NOTIFIER" in process.env ||process.env.NODE_ENV === "test" ||process.argv.includes("--no-update-notifier") ||isInCi;// 是否在npm脚本中通知this._shouldNotifyInNpmScript = options.shouldNotifyInNpmScript;// 如果没有禁用提示 继续执行if (!this.#isDisabled) {try {// 持久化存储数据 存储一些默认值如 {optOut: false, lastUpdateCheck: Date.now()} 存储位置 ~/.config/update-notifierthis.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 awaylastUpdateCheck: Date.now(),});} catch {// Expecting error code EACCES or EPERMconst 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" }));});}}
}

check

check()方法用于判断当前是否需要执行更新检查。它首先检查是否满足某些条件(例如是否设置了禁用标志或是否达到预定的检查时间间隔)。如果满足条件,则会启动一个独立的子进程来执行实际的更新检查操作,避免影响主应用的性

	check() {// 判断是否禁用// 1. 是否存在config属性// 2. 是否有optOut字段// 3. 是否禁用提示if (!this.config || this.config.get("optOut") || this.#isDisabled) {return;}// 第一次是undefinedthis.update = this.config.get("update");// 如果存在update 更新当前的最新版本currentif (this.update) {// Use the real latest version instead of the cached onethis.update.current = this.#packageVersion;// Clear cached information 清除上一次缓存信息this.config.delete("update");}// Only check for updates on a set interval// 如果当前检测时间和上一次检测的时间间隔小于 updateCheckInterval, 则不进行校验, 防止校验次数过多, 降低用户体验if (Date.now() - this.config.get("lastUpdateCheck") <this.#updateCheckInterval) {return;}// Spawn a detached process, passing the options as an environment property// 启动一个独立的子进程中执行 check.js 文件,并将 this.options 对象作为参数传递给 check.js。子进程的标准输入、输出和错误流都会被忽略,子进程与父进程的关联也会被解除。// 目的是更新configStore中的update数据spawn(process.execPath,[path.join(__dirname, "check.js"), JSON.stringify(this.#options)],{detached: true,stdio: "ignore",},).unref();}

check.js

check.js是一个单独的脚本文件,通过子进程的方式被调用来执行具体的更新检查任务。它接收来自父进程传递过来的配置选项,并尝试获取指定组件库的最新版本信息。一旦成功获取到更新信息,就会更新本地配置存储中的相关数据,并记录最后一次检查的时间。

/* eslint-disable unicorn/no-process-exit */
import process from "node:process";
import UpdateNotifier from "./update-notifier.js";
// 传入this.#options的配置
const updateNotifier = new UpdateNotifier(JSON.parse(process.argv[2]));try {// Exit process when offline// 30s退出setTimeout(process.exit, 1000 * 30);// 获取最新版本信息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);}// Call process exit explicitly to terminate the child process,// otherwise the child process will run forever, according to the Node.js docsprocess.exit();
} catch (error) {console.error(error);process.exit(1);
}

fetchInfo

fetchInfo()方法负责从npm注册表中获取指定组件库的最新版本信息,并将其与当前安装的版本进行比较。此方法返回的对象包含了最新的版本号、当前版本号以及两者之间的差异类型。

	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,};}

notify

notify()方法根据一系列条件判断是否向用户显示更新通知。这些条件包括当前环境是否支持TTY输出、是否处于npm脚本运行环境中、是否有可用的更新信息以及新版本是否确实比当前版本更高。如果所有条件都满足,则使用chalk和boxen构建并展示一个美观的通知框,指导用户如何更新到最新版本。

	notify(options) {// 如果是npm或yarn且不在npm脚本中通知,则不通知const suppressForNpm = !this._shouldNotifyInNpmScript && isNpmOrYarn;// 是否提示// 1. 判断TTY是否可用// 2. 判断是不是在npm脚本中// 3. 判断是否有没有update信息// 4. 判断是否大于最新版本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}`;// 提示信息 chalk是一个命令行颜色库const defaultTemplate ="Update available " +chalk.dim("{currentVersion}") +chalk.reset(" → ") +chalk.green("{latestVersion}") +" \nRun " +chalk.cyan("{updateCommand}") +" to update";// 如果有传入message则使用传入的, 否则使用默认const template = options.message || defaultTemplate;// 如果没有传入boxenOptions则使用默认  这个库来构建各种不同的方框包裹的的提示信息options.boxenOptions ??= {padding: 1,margin: 1,textAlignment: "center",borderColor: "yellow",borderStyle: "round",};// 渲染提示信息 pupa将数据渲染到模板中的占位符const message = boxen(pupa(template, {packageName: this._packageName,currentVersion: this.update.current,latestVersion: this.update.latest,updateCommand: installCommand,}),options.boxenOptions,);// 如果options.defer属性的值为false,则在控制台输出错误信息。否则,注册一个"exit"事件监听器,该监听器在进程退出时输出错误信息;另外,还注册一个"SIGINT"事件监听器,该监听器在接收到SIGINT信号(通常由用户按下Ctrl+C触发)时输出错误信息,并退出进程。if (options.defer === false) {console.error(message);} else {process.on("exit", () => {console.error(message);});process.on("SIGINT", () => {console.error("");process.exit();});}return this;}

最后

update-notifier库通过其精心设计的流程和方法,提供了一个简便而有效的途径来追踪项目依赖的更新情况。它不仅关注用户体验,通过设定合理的检查间隔避免了频繁打扰用户,还通过环境检测和灵活的通知条件设置,确保通知仅在合适的时机出现。利用如chalk用于美化终端输出、semver用于版本号比较等多种第三方库的支持,使得整个更新通知过程既直观又友好。

从初始化配置开始,到执行检查,直到最后的通知展示,每一个环节都体现了对细节的关注以及对用户需求的深刻理解。对于个人开发者或团队合作来说,update-notifier是一个不可或缺的工具,它帮助用户及时了解并应用最新的组件库更新,从而提高项目的稳定性和安全性。

在这里插入图片描述
如果你觉得这篇文章对你有帮助,请点赞 👍、收藏 👏 并关注我!👀
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/web/75307.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Go语言编写一个进销存Web软件的demo

Go语言编写一个进销存Web软件的demo 用户现在要求用。之前他们已经讨论了用Django实现的方案&#xff0c;现在突然切换到Go&#xff0c;可能有几个原因。首先&#xff0c;用户可能对Go语言感兴趣&#xff0c;或者他们公司的技术栈转向了Go。其次&#xff0c;用户可能希望比较不…

【前缀和】矩阵区域和(medium)

矩阵区域和&#xff08;medium&#xff09; 题⽬描述&#xff1a;解法&#xff1a;代码Java 算法代码&#xff1a;C 算法代码&#xff1a; 题⽬描述&#xff1a; 题⽬链接&#xff1a;1314. 矩阵区域和 给你⼀个 m x n 的矩阵 mat 和⼀个整数 k &#xff0c;请你返回⼀个矩阵 …

Java学习手册:Java发展历史与版本特性

Java作为全球最流行的编程语言之一&#xff0c;其发展历程不仅见证了技术的演进&#xff0c;也反映了软件开发模式的变革。从1995年的首次发布到如今的持续更新&#xff0c;Java始终保持着强大的生命力和广泛的影响力。本文将简要回顾Java的发展历程&#xff0c;并重点介绍其关…

winserver2022备份

安装备份&#xff0c;然后等待安装完成即可 然后可以在这里看到安装好的win server2022备份 一直下一步然后到这里 不要用本地文件夹备份 备份到远程服务器&#xff0c;远程服务器路径 然后确定备份即可 如何恢复呢&#xff1f; 点击右侧的恢复就可以了 打开任务计划程序 这…

Unity 设置弹窗Tips位置

根据鼠标位于屏幕的区域&#xff0c;设置弹窗锚点以及位置 public static void TipsPos(Transform tf) {//获取ui相机var uiCamera GetUICamera();var popup tf.GetComponent<RectTransform>();//获取鼠标位置Vector2 mousePos Input.mousePosition;float screenWidt…

【C++基础-关键字】:extern

深入理解 C++ 关键字 extern 在 C++ 编程中,extern 关键字扮演着重要角色,主要用于声明全局变量或函数,使其在多个源文件间共享。本文将详细探讨 extern 的用法及其在实际开发中的应用。 1. 什么是 extern? extern 关键字用于声明一个变量或函数的引用,表示该变量或函数…

我为女儿开发了一个游戏网站

大家好&#xff0c;我是星河。 自从协助妻子为女儿开发了算数射击游戏后&#xff0c;星河就一直有个想法&#xff1a;为女儿打造一个专属的学习游戏网站。之前的射击游戏虽然有趣&#xff0c;但缺乏难度分级&#xff0c;无法根据女儿的学习进度灵活调整。而且&#xff0c;仅仅…

基于 Python 卷积神经网络的新闻文本分类系统,附源码

大家好&#xff0c;我是徐师兄&#xff0c;一个有着7年大厂经验的程序员&#xff0c;也是一名热衷于分享干货的技术爱好者。平时我在 CSDN、掘金、华为云、阿里云和 InfoQ 等平台分享我的心得体会。今天我来跟大家聊聊一个用 Python 和 Django 打造的人脸识别考勤系统&#xff…

ngx_cycle_modules

Ubuntu 下 nginx-1.24.0 源码分析 - ngx_cycle_modules-CSDN博客 定义在 src/core/ngx_module.c ngx_int_t ngx_cycle_modules(ngx_cycle_t *cycle) {/** create a list of modules to be used for this cycle,* copy static modules to it*/cycle->modules ngx_pcalloc(…

AI 代码生成工具如何突破 Java 单元测试效能天花板?

一、传统单元测试的四大痛点 时间黑洞&#xff1a;根据 JetBrains 调研&#xff0c;Java 开发者平均花费 35% 时间编写测试代码覆盖盲区&#xff1a;手工测试覆盖率普遍低于 60%&#xff08;Jacoco 全球统计数据&#xff09;维护困境&#xff1a;业务代码变更导致 38% 的测试用…

【保姆级图解】插入排序 算法详解:直接插入排序、希尔排序

总体引入 在计算机科学的算法领域中&#xff0c;排序是一项基础且重要的操作。它旨在将一组无序的数据元素重新排列为有序序列&#xff0c;以满足特定的顺序要求&#xff0c;如升序或降序。常见的排序算法可分为不同类别&#xff0c;像插入排序&#xff0c;包含直接插入排序和…

为什么ChatGPT选择SSE而非WebSocket?

为什么ChatGPT选择SSE而非WebSocket&#xff1f; 一、ChatGPT回答问题的技术逻辑 ChatGPT的响应生成基于Transformer架构和自注意力机制&#xff0c;其核心是通过概率预测逐词生成文本。当用户输入问题后&#xff0c;模型会先解析上下文&#xff0c;再通过预训练的庞大语料库…

Android 手机指纹传感器无法工作,如何恢复数据?

天津鸿萌科贸发展有限公司从事数据安全服务二十余年&#xff0c;致力于为各领域客户提供专业的数据恢复、数据清除、数据备份、数据取证、数据迁移解决方案&#xff0c;并针对企业面临的数据安全风险&#xff0c;提供专业的相关数据安全培训。 天津鸿萌科贸发展有限公司是众多国…

DeepSeek 在金融领域的应用解决方案

DeepSeek 在金融领域的应用解决方案 一、背景 随着人工智能技术的快速发展&#xff0c;DeepSeek 作为一款国产大模型&#xff0c;凭借其强大的语义理解、逻辑推理和多模态处理能力&#xff0c;在金融行业迅速崭露头角。金融行业作为经济的核心&#xff0c;面临着激烈的市场竞…

织光五载 焕新启航

成都时尚产业协会5周年 以创新为笔&#xff0c;续写国际时尚之都的璀璨篇章 【一场跨越时空的时尚对话】 五年前&#xff0c;一颗名为"成都时尚产业协会"的种子在蓉城落地生根&#xff1b;五年后&#xff0c;这棵新芽已成长为枝繁叶茂的生态之树&#xff0c;用交织…

scala集合

一、数组&#xff08;Array&#xff09; 1.数组转换 不可变转可变&#xff1a;arr1.toBuffer&#xff0c;arr1本身没有变化 可变转不可变&#xff1a;arr2.toArray&#xff0c;arr2本身没有变化 2.多维数组 创建&#xff1a;val arr Array.ofDim[Int](3, 4)&#xff08;3 …

常用 Excel VBA 技巧,简单好学易上手

在日常办公中&#xff0c;我们常常会遇到各种繁琐的数据处理任务&#xff0c;而 Excel VBA&#xff08;Visual Basic for Applications&#xff09;作为一款强大的自动化工具&#xff0c;能够帮助我们轻松应对这些挑战。本文将介绍一些常用且简单好学的 Excel VBA 技巧&#xf…

Java 基础 - 反射(1)

文章目录 引入类加载过程1. 通过 new 创建对象2. 通过反射创建对象2.1 触发加载但不初始化2.2 按需触发初始化2.3 选择性初始化控制 核心用法示例1. 通过无参构造函数创建实例对象2. 通过有参构造函数创建实例对象3. 反射通过私有构造函数创建对象&#xff0c; 破坏单例模式4. …

如何在React中集成 PDF.js?构建支持打印下载的PDF阅读器详解

本文深入解析基于 React 和 PDF.js 构建 PDF 查看器的实现方案&#xff0c;该组件支持 PDF 渲染、图片打印和下载功能&#xff0c;并包含完整的加载状态与错误处理机制。 完整代码在最后 一个PDF 文件&#xff1a; https://mozilla.github.io/pdf.js/web/compressed.tracemo…

数据结构与算法-动态规划-线性动态规划,0-1背包,多重背包,完全背包,有依赖的背包,分组背包,背包计数,背包路径

动态规划原理 动态规划这玩意儿&#xff0c;就好比是在拓扑图上玩跳格子游戏。在图论中&#xff0c;咱们是从特定的节点跳到其他节点&#xff1b;而在动态规划里呢&#xff0c;我们是从一个状态 “嗖” 地转移到另一个状态。状态一般用数组来表示&#xff0c;就像 f [i][j]&am…