UNPKG

@fontoxml/fontoxml-development-tools

Version:
164 lines (140 loc) 5.48 kB
'use strict'; const compareVersions = require('compare-versions'); const path = require('path'); const request = require('request'); const CHECK_STATUS_FAILED = Symbol('check failed'); const CHECK_STATUS_FOUND_NEW = Symbol('check found new'); const CHECK_STATUS_NOT_CONNECTED = Symbol('check not connected'); const CHECK_STATUS_OK = Symbol('check ok'); const MESSAGE_TIMEOUT = 5000; const REQUEST_TIMEOUT = 10000; const CHECK_RATE = 7 * 24 * 60 * 60 * 1000; /* istanbul ignore next */ function checkIfUpdateCheckIsDue (updateCheckConfig) { try { if (updateCheckConfig && updateCheckConfig.timestamp) { const now = Date.now(); if (now - updateCheckConfig.timestamp <= CHECK_RATE) { return false; } } } catch (_error) { // Just check for updates when the file does not exist or is corrupted. } return true; } /* istanbul ignore next */ function storeLastCheckDate (app, updateCheckConfig) { try { updateCheckConfig.timestamp = Date.now(); app.config.save(); } catch (_error) { // Do nothing, and try again on next run. } } /* istanbul ignore next */ function checkPackageForUpdates (packagePath) { return new Promise((resolve, reject) => { const currentPackageInfo = require(path.join(packagePath, 'package.json')); const currentName = currentPackageInfo.name; const currentVersion = currentPackageInfo.version; request({ method: 'GET', url: `https://registry.npmjs.org/${encodeURIComponent(currentName)}/x`, timeout: REQUEST_TIMEOUT }, (error, response, body) => { if (error || response.statusCode !== 200) { return reject(error); } const packageInfo = JSON.parse(body); const latestVersion = packageInfo.version; const isLatest = compareVersions(currentVersion, latestVersion) >= 0; resolve({ name: currentName, status: isLatest ? CHECK_STATUS_OK : CHECK_STATUS_FOUND_NEW, currentVersion, latestVersion }); }); }).catch(error => { return { name: path.basename(packagePath), status: error && error.code === 'ENOTFOUND' ? CHECK_STATUS_NOT_CONNECTED : CHECK_STATUS_FAILED }; }); } /* istanbul ignore next */ function createDelayPromise (timeout = 1000) { return new Promise((resolve, _reject) => { setTimeout(resolve, timeout); }); } /* istanbul ignore next */ module.exports = function addCheckForUpdates (app) { const updateCheckConfig = app.config.registerConfig('fdtUpdateCheck', { timestamp: null }); app.cli.addPreController((_request, response) => { let stopSpinner = null; // Always return a Promise, this also catches any error so normal operation is always possible. return new Promise((mainResolve) => { // Don't check for updates when we already checked recently. We sould play nice with the repository. if (!checkIfUpdateCheckIsDue(updateCheckConfig)) { return mainResolve(); } stopSpinner = response.spinner('Doing periodic check for updates...'); const appPackageInfo = require(path.join(__dirname, '../package.json')); // Get all modules from the development tools package.json dependencies which match the tools's package name. const packagePathsToCheck = [ path.resolve(path.join(__dirname, '..')), ...Object.keys(appPackageInfo.dependencies || {}) .filter(packageName => { return packageName.indexOf(`${appPackageInfo.name}-module-`) === 0; }) .map(packageName => path.dirname(require.resolve(packageName))) ]; const updateCheckPromises = Promise.all(packagePathsToCheck.map(checkPackageForUpdates)); mainResolve(updateCheckPromises.then(updateCheckResults => { const packagesWithUpdates = updateCheckResults.filter(r => r.status === CHECK_STATUS_FOUND_NEW); const packagesWithFails = updateCheckResults.filter(r => r.status === CHECK_STATUS_FAILED); const packagesWithNotConnected = updateCheckResults.filter(r => r.status === CHECK_STATUS_NOT_CONNECTED); // Store last check timestamp, but only when there were no connection problems. if (!packagesWithNotConnected.length) { storeLastCheckDate(app, updateCheckConfig); } stopSpinner(); stopSpinner = null; if (packagesWithUpdates.length) { response.notice(`Found ${packagesWithUpdates.length} updates for this tool.`); response.notice(`You can run \`npm update -g ${appPackageInfo.name}\` to install updates...`); return createDelayPromise(MESSAGE_TIMEOUT); } else if (packagesWithFails.length) { response.notice(`Could not check for updates on ${packagesWithFails.length}/${packagePathsToCheck.length} packages...`); response.notice(`If this message keeps showing, you can run \`npm update -g ${appPackageInfo.name}\` to try and install updates...`); return createDelayPromise(MESSAGE_TIMEOUT); } else if (packagesWithNotConnected.length) { response.log('Could not connect to the packages repository, will try again next time.'); } else { response.log(`Checked all ${packagePathsToCheck.length} packages for updates, everything is up to date.`); } })); }).catch(_error => { // Catch any error. Update checking should never stop you from using this tool. try { if (stopSpinner) { stopSpinner(); } response.notice('Could not check for updates...'); response.notice('If this message keeps showing, run a global update of this tool using `npm update -g <packageName>` to try and install updates...'); } catch (_error) { // Do nothing } }); }); };