如何自動檢測使用的組件庫有更新

在這里插入圖片描述

🤖 作者簡介:水煮白菜王,一位前端勸退師 👻
👀 文章專欄: 前端專欄 ,記錄一下平時在博客寫作中,總結出的一些開發技巧和知識歸納總結?。
感謝支持💕💕💕

目錄

  • 實現流程分析
  • 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.pswp.cn/web/75307.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/75307.shtml
英文地址,請注明出處:http://en.pswp.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…