@fontoxml/fontoxml-development-tools
Version:
Development tools for FontoXML.
164 lines (140 loc) • 5.48 kB
JavaScript
;
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
}
});
});
};