Learning objectives
- Understand the role and usage scenario of update notifier
- Application scenario:
Check whether npm packages are updated, such as component library updates or other npm package updates,
Data preparation
Link: https://github.com/yeoman/update-notifier
What is an update notifier
Prompt package needs to be updated
The essence is to open the child_process runs in the background by comparing the name and version in package.json with the latest version in the current npm registry. If an updated version is found available, the result will be saved in the. Update attribute to mark whether an updated version is available. If so, update information will be output and the updated version is available
preparation in advance
git clone https://github.com/yeoman/update-notifier cd update-notifier npm i
First knowledge - Official Documents
- usage
- 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' } */
- Options and custom messages
const notifier = updateNotifier({ pkg, updateCheckInterval: 1000 * 60 * 60 * 24 * 7 // 1 week }); if (notifier.update) { console.log(`Update available: ${notifier.update.latest}`); }
-
HOW
When the user runs the application for the first time, it will check whether there are updates. Even if there are updates available, it will wait for the specified updateCheckInterval before notifying the user. This is done so as not to disturb the user.
-
API
- notifier = updateNotifier(options)
Check for updates available. Accept the options defined below. If an update is available, an instance with the. Update property is returned; otherwise, an undefined instance is returned.options: { pkg: {}, // object type name: '', //Required, string type version: '', //Required, string type updateCheckInterval: '',//number type, the default value is one day (1000 * 60 * 60 * 24) shouldNotifyInNpmScript: false,//Boolean value. The default value is false. Whether to allow notifications to be displayed when running as npm scripts distTag: 'latest'//String, the default value is latest }
- notifier.fetchInfo()
Check the update information and return an object with the following contents{ latest: '' ,//String, latest version current: '',//String, current version type: '',//String, the type of current update. The possible values are: latest, major, minor, patch, prerelease, build name: '' // String, package name }
- notifier.notify(options?)
A convenient way to display notification messages, only when there are updates and the process is TTY. options:{ defer: true, // Boolean value, the default is true, and the notification will be delayed after the process exits message: '', // string, the message that will be displayed when the update is available isGlobal:true/false, // Boolean value, including the - g parameter in the npm i recommendation of the default message. boxenOptions: object,// An object, the parameter passed to box }
- notifier = updateNotifier(options)
Interpreting index.js
Reference package section
- const {spawn} = require('child_process');
- Kill the executing child process in node.js
- const {spawn} = require('child_process'); And const spawn = require('child_process'); Differences between:
- {x} = v equals {x:x} = v
- {x:y} = v is equivalent to y = v.x
- const {spawn} = require('child_process'); Equivalent to const_= require('child_process'); const spawn = _. resolve;
- It is equivalent to the deconstructed thing. With this thing in the current file, it has been obtained and can be used directly. There is no need to get the corresponding variables or methods through spawn.xxxx
- const path = require('path');
node.js deals with the problem of file path link
- const {format} = require('util');
- util is a common function set, which is used to make up for the lack of too concise core JS functions, in order to meet the requirements of the internal api of nide.js
- util.format(format[, ...])
- The util.form() function takes a formatted string format as the first parameter and returns the formatted string.
- The format parameter is a string that can contain zero or more placeholders. Each placeholder starts with a% character and is eventually replaced by the string value converted by the corresponding parameter
- const importLazy = require('import-lazy')(require); usage
const importLazy = require('import-lazy')(require);//Module lazy loading const configstore = importLazy('configstore');// Easily load and persist configurations regardless of where and how they are loaded. const chalk = importLazy('chalk'); //Color font notification const semver = importLazy('semver'); //Semantic versioner of npm const semverDiff = importLazy('semver-diff');// Obtain the difference types of two semver versions / / for example: 0.0.1, 0.0.2 → patch const latestVersion = importLazy('latest-version');// Get the latest version of npm package const isNpm = importLazy('is-npm');//Check if the code is running as an npm script const isInstalledGlobally = importLazy('is-installed-globally'); //Check if the package is installed globally through npm const isYarnGlobal = importLazy('is-yarn-global'); // Check if it is installed globally by yarn without any fs calls const hasYarn = importLazy('has-yarn'); //Check if the project is using yarn const boxen = importLazy('boxen');//Create box at terminal const xdgBasedir = importLazy('xdg-basedir');//Get XDG base directory path const isCi = importLazy('is-ci');//Returns true if the current environment is a continuous integration server const pupa = importLazy('pupa');//Simple micro template const ONE_DAY = 1000 * 60 * 60 * 24; //24 hours 60 minutes 60 seconds 1000 milliseconds
UpdateNotifier
class UpdateNotifier { constructor(options = {}) { } check(){} async fetchInfo() {} notify(options) {} }
consructor initialization phase
class UpdateNotifier { constructor(options = {}) { // Give package.json to options this.options = options; // fault tolerant 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 }; //Necessary field detection: an error is reported if the name and version do not exist if (!options.pkg.name || !options.pkg.version) { throw new Error('pkg.name and pkg.version required'); } //Package name this.packageName = options.pkg.name; //edition this.packageVersion = options.pkg.version; /** * By reading the official document, you can know that the updateCheckInterval defaults to one day (the default is in digital format). The inspection cycle here is in ms * const ONE_DAY = 1000 * 60 * 60 * 24; //24 Hours, 60 minutes, 60 seconds, 1000 milliseconds */ this.updateCheckInterval = typeof options.updateCheckInterval === 'number' ? options.updateCheckInterval : ONE_DAY; /** * isCi() Returns true if the current environment is a continuous integration server * Disable status detection: disable update notifications */ this.disabled = 'NO_UPDATE_NOTIFIER' in process.env || process.env.NODE_ENV === 'test' || process.argv.includes('--no-update-notifier') || isCi(); /** * shouldNotifyInNpmScript Allow notifications to be displayed when running as npm scripts */ this.shouldNotifyInNpmScript = options.shouldNotifyInNpmScript; if (!this.disabled) { // If update notifications are not disabled try { //Set configuration object const ConfigStore = configstore(); // Use the configstore to obtain the local storage of the package related information. It is not available for the first time 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 // Setting the current time as the starting point of the inspection interval does not affect the user. Here is the reason why the user is not notified when starting for the first time lastUpdateCheck: Date.now() }); } catch { // Expecting error code EACCES or EPERM // Prompt error message 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() { /** * If the local storage of the package related information cannot be obtained 𞓜 optOut is true | the check is stopped when the update notification is prohibited */ if (!this.config || this.config.get('optOut') || this.disabled) { return; } //Get the update information of related packages. For the first time, it is undefined this.update = this.config.get('update'); if (this.update) { // Use the real latest version instead of the cached one //If the latest package information is obtained, the actual value is used instead of the cache value this.update.current = this.packageVersion; // Clear cached information clear cached information this.config.delete('update'); } // Only check for updates on a set interval //Check the update only within the set time interval. If it is still in the cycle, return and stop the check if (Date.now() - this.config.get('lastUpdateCheck') < this.updateCheckInterval) { return; } // Spawn a detached process, passing the options as an environment property /** * Generate separate processes and pass options as environment properties * path.join Merge multiple parameter values into one path */ spawn(process.execPath, [path.join(__dirname, 'check.js'), JSON.stringify(this.options)], { //Let the child process run independently of its parent process. The child process can continue to run after the parent process exits, whether they are separated or not. [link]( http://nodejs.cn/api/child_process/options_detached.html) detached: true, // Ignore the input and output of the console and ignore the termination of the parent process stdio: 'ignore' }).unref();// The unref method is used to break the relationship so that the "parent" process can exit independently (without causing the child process to exit with it) }
check.js
/* eslint-disable unicorn/no-process-exit */ 'use strict'; // Get the content exported by index.js let updateNotifier = require('.'); // Get options const options = JSON.parse(process.argv[2]); //Instantiate updateNotifier updateNotifier = new updateNotifier.UpdateNotifier(options); //Execute a function (async () => { // Exit process when offline setTimeout(process.exit, 1000 * 30); // Call the fetchInfo method of updateNotifier to get the update information const update = await updateNotifier.fetchInfo(); // Only update the last update check time on success updateNotifier.config.set('lastUpdateCheck', Date.now()); // Set the update field as long as it is not the latest package if (update.type && update.type !== 'latest') { updateNotifier.config.set('update', update); } // The obtained information, if the version is updated, is saved to config // Call process exit explicitly to terminate the child process, // otherwise the child process will run forever, according to the Node.js docs // Explicitly call the process exit to terminate the child process. According to the Node.js document, otherwise the child process will run forever process.exit(); })().catch(error => { console.error(error); process.exit(1); });
fetchInfo gets information about the package
//Get information about the package async fetchInfo() { const {distTag} = this.options; // latestVersion gets the latest version of the npm package // Because it is lazy loading, you need to execute latestVersion() first // Note that latestVersion is an asynchronous request const latest = await latestVersion()(this.packageName, {version: distTag}); // Returns the object information after assembly return { latest, current: this.packageVersion, type: semverDiff()(this.packageVersion, latest) || distTag, name: this.packageName }; } notify(options) {}
notify notification
notify(options) { // Support NPM scripts calls const suppressForNpm = !this.shouldNotifyInNpmScript && isNpm().isNpmOrYarn; /** * !process.stdout.isTTY node Not running on terminal * suppressForNpm NPM scripts calls are not supported * !this.update No updates have been checked locally * !semver().gt(this.update.latest, this.update.current) Is the current version behind the latest version * No notification is required when the above conditions are met (no notification when there is no update) */ if (!process.stdout.isTTY || suppressForNpm || !this.update || !semver().gt(this.update.latest, this.update.current)) { return this; } options = { // Check if the package is installed globally through npm isGlobal: isInstalledGlobally(), // Is it installed globally through yarn // There is a flaw: the premise is that the user does not change the default global installation directory of yarn isYarnGlobal: isYarnGlobal()(), ...options }; // Corresponding update command prompt let installCommand; if (options.isYarnGlobal) { // Is it a global installation through yarn installCommand = `yarn global add ${this.packageName}`; } else if (options.isGlobal) { //Is it a global installation through npm installCommand = `npm i -g ${this.packageName}`; } else if (hasYarn()()) {// hasYarn() because hasYarn is a lazy module, you need to perform a partial installation of hasYarn first installCommand = `yarn add ${this.packageName}`; } else {// npm local installation installCommand = `npm i ${this.packageName}`; } const defaultTemplate = 'Update available ' + chalk().dim('{currentVersion}') + chalk().reset(' → ') + chalk().green('{latestVersion}') + ' \nRun ' + chalk().cyan('{updateCommand}') + ' to update'; //Prompt template const template = options.message || defaultTemplate; options.boxenOptions = options.boxenOptions || { padding: 1, margin: 1, align: 'center', borderColor: 'yellow', borderStyle: 'round' }; /** * boxen Create box at terminal * pupa Simple micro template */ const message = boxen()( pupa()(template, { // Complete the following information into the template // this.update is populated in the check phase packageName: this.packageName, currentVersion: this.update.current, latestVersion: this.update.latest, updateCommand: installCommand }), options.boxenOptions ); //The notification is displayed without delay after the process exits if (options.defer === false) { console.error(message); } else { // Display prompt when exiting the process process.on('exit', () => { console.error(message); }); // Handle unexpected exit process.on('SIGINT', () => { console.error(''); process.exit(); }); } // Return the purpose of this: the option configuration has been changed in order to chain call return this; }
Summary process
● const updateNotifier = new UpdateNotifier({})
○ initialize constuctor
○ updateNotifier.check()
● judge whether to execute check.js. If yes:
○ updateNotifier.fetchInfo()
○ set('lastUpdateCheck')
○ set('update')
● notify()
summary
1. Deepen the understanding of deconstruction assignment
2. Know more about the api of node.js according to the source code
3. We also know whether the update notifier update is the tool library of the latest version of the package, which can be added to the actual use
4. It's also a good sequential process to follow the official website, especially when collecting the notes of all the leaders, it will be easier to understand, full of harvest and make persistent efforts!