[source code reading | 04] update notifier check package update

1. Scene

  used to convert the current package Compare the package in JSON with the package management tool library (such as npm). If there is a package that can be updated, you will be prompted.

2. Use

1) Installation

$ npm install update-notifier

2) Use

const updateNotifier = require('update-notifier');
const pkg = require('./package.json');

const notifier = updateNotifier({pkg});
notifier.notify();

console.log(notifier.update);
/*
{
	latest: '1.0.1',
	current: '1.0.0',
	type: 'patch', // Possible values: latest, major, minor, patch, prerelease, build
	name: 'pageres'
}
*/

3. Source code

1) Read readme md

Source address: https://github.com/yeoman/update-notifier

You can learn the following from readme

  • You can run example. In the root directory JS for debugging

  • For the sake of user experience, update notifier does not check every time it runs, but after a certain time interval

  • The update notifier check will start a child process, even though process. Is called Exit exits the process and does not affect the check

2) Clone source repository

# clone
git clone https://github.com/yeoman/update-notifier.git

# Installation dependency
cd update-notifier
npm install

3) Operation debugging

1) Via package JSON, you can see that the debugging command is as follows

npm run test

2) View debugging results

   the information will not be printed during the first run, and the update information will be prompted only after the second run.

    this is because the old version cannot be found for comparison during the first startup. Therefore, the first information will be stored persistently, and the last stored information will be compared with the results of this operation during the second execution.

4) Source code interpretation

example.js

'use strict';
const updateNotifier = require('.');

// Pass a package information into
// The first run will the package 0.9 Version 2 stored
// The second run will get 0.9 2 comparison with the latest version in the existing library
updateNotifier({
	pkg: {
		name: 'public-ip',
		version: '0.9.2'
	},
  // Check interval
	updateCheckInterval: 0
}).notify();

check.js

/* eslint-disable unicorn/no-process-exit */
'use strict';
let updateNotifier = require('.');

const options = JSON.parse(process.argv[2]);

updateNotifier = new updateNotifier.UpdateNotifier(options);

(async () => {
  // If the run times out, exit the process
	setTimeout(process.exit, 1000 * 30);

  // Get the package version information obtained from the update check
	const update = await updateNotifier.fetchInfo();

  // Take the current time as the last check update time
	updateNotifier.config.set('lastUpdateCheck', Date.now());

  // If the current package is not the latest version, a prompt is thrown
	if (update.type && update.type !== 'latest') {
		updateNotifier.config.set('update', update);
	}

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

index.js

Note: index There are many JS source codes. The following will be divided into multiple code blocks for interpretation

The overall code flow is understood as:

  1. Run for the first time and extract the current package The version information of all packages stored in JSON, generate files and store them persistently, and record the current time
  2. In the next run, compare the current time with the time of the last generated file. If the check interval is exceeded, perform udpate check. Check and compare the npm library. If you find that the package version can be updated, you will be prompted on the console

Follow the process to read the source code

1) Introduction Kit

'use strict';
const {spawn} = require('child_process');
const path = require('path');
const {format} = require('util');
// Lazy loading module: it can be imported only when the corresponding package is called
const importLazy = require('import-lazy')(require);

const configstore = importLazy('configstore');
const chalk = importLazy('chalk');
const semver = importLazy('semver');
const semverDiff = importLazy('semver-diff');
const latestVersion = importLazy('latest-version');
const isNpm = importLazy('is-npm');
const isInstalledGlobally = importLazy('is-installed-globally');
const isYarnGlobal = importLazy('is-yarn-global');
const hasYarn = importLazy('has-yarn');
const boxen = importLazy('boxen');
const xdgBasedir = importLazy('xdg-basedir');
const isCi = importLazy('is-ci');
const pupa = importLazy('pupa');

const ONE_DAY = 1000 * 60 * 60 * 24;

2) The UpdateNotifier class is declared. There is no special description later. The scope is in the class

class UpdateNotifier {
	... 
}

3) Declare the constructor of the class

class UpdateNotifier {
	constructor(options = {}) {
		this.options = options;
		options.pkg = options.pkg || {};
		options.distTag = options.distTag || 'latest';

    // Read package name and version number information
		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;
    // Update check interval, default: 1 day
		this.updateCheckInterval = typeof options.updateCheckInterval === 'number' ? options.updateCheckInterval : ONE_DAY;
		this.disabled = 'NO_UPDATE_NOTIFIER' in process.env ||
			process.env.NODE_ENV === 'test' ||
      // process.argv: Node. Command line parameters passed in during JS process
			process.argv.includes('--no-update-notifier') ||
      // CI continuous integration environment
			isCi();
		this.shouldNotifyInNpmScript = options.shouldNotifyInNpmScript;

		if (!this.disabled) {
      // Generate file storage version information
			try {
				const ConfigStore = configstore();
				this.config = new ConfigStore(`update-notifier-${this.packageName}`, {
					optOut: false,
          // When generating a file, record the current time to facilitate the next comparison of whether it exceeds the inspection interval
					lastUpdateCheck: Date.now()
				});
			} catch {
				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'}));
				});
			}
		}
	}
  ...
}

4) Declare check method

class UpdateNotifier {
  ...
	check() {
		if (
			!this.config ||
			this.config.get('optOut') ||
			this.disabled
		) {
			return;
		}

    // Read the version information obtained from this check
		this.update = this.config.get('update');

		if (this.update) {
      // Use the latest version instead of the cached version
			this.update.current = this.packageVersion;

      // Clear information from cache
			this.config.delete('update');
		}

		// Only check for updates on a set interval
    // Check only within the inspection interval, for example, set no repeat inspection within 1 week
		if (Date.now() - this.config.get('lastUpdateCheck') < this.updateCheckInterval) {
			return;
		}

    // Use child processes to perform tasks
		spawn(process.execPath, [path.join(__dirname, 'check.js'), JSON.stringify(this.options)], {
			detached: true,
			stdio: 'ignore'
		}).unref();
	}
}

5) Get version information

class UpdateNotifier {
  ...
	// Get version information
	async fetchInfo() {
		const {distTag} = this.options;
		const latest = await latestVersion()(this.packageName, {version: distTag});

		return {
			latest,
			current: this.packageVersion,
      // Check whether the package is the latest version and contact check JS type === 'latest'
			type: semverDiff()(this.packageVersion, latest) || distTag,
			name: this.packageName
		};
	}
}

6) Send notification reminder

class UpdateNotifier {
  ...
	notify(options) {
		const suppressForNpm = !this.shouldNotifyInNpmScript && isNpm().isNpmOrYarn;
		if (!process.stdout.isTTY || suppressForNpm || !this.update || !semver().gt(this.update.latest, this.update.current)) {
			return this;
		}

		options = {
			isGlobal: isInstalledGlobally(),
			isYarnGlobal: isYarnGlobal()(),
			...options
		};

    // Message reminder template
		let installCommand;
		if (options.isYarnGlobal) {
			installCommand = `yarn global add ${this.packageName}`;
		} else if (options.isGlobal) {
			installCommand = `npm i -g ${this.packageName}`;
		} else if (hasYarn()()) {
			installCommand = `yarn add ${this.packageName}`;
		} else {
			installCommand = `npm i ${this.packageName}`;
		}

    // Message reminder template
		const defaultTemplate = 'Update available ' +
			chalk().dim('{currentVersion}') +
			chalk().reset(' → ') +
			chalk().green('{latestVersion}') +
			' \nRun ' + chalk().cyan('{updateCommand}') + ' to update';

		const template = options.message || defaultTemplate;

    // Message reminder border style
		options.boxenOptions = options.boxenOptions || {
			padding: 1,
			margin: 1,
			align: 'center',
			borderColor: 'yellow',
			borderStyle: 'round'
		};

    // Splicing reminder message
		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;
	}
}

Keywords: Javascript Front-end npm source code

Added by ijmccoy on Thu, 16 Dec 2021 16:06:06 +0200