refactor: rewrite src/upgrade.js with async/await

v1.18.x
Barış Soner Uşaklı 5 years ago
parent 231d34d0aa
commit 33c5988c34

@ -101,7 +101,9 @@ function runUpgrade(upgrades, options) {
async.series([ async.series([
db.init, db.init,
require('../meta').configs.init, require('../meta').configs.init,
async.apply(upgrade.runParticular, upgrades), async function () {
await upgrade.runParticular(upgrades);
},
], function (err) { ], function (err) {
if (err) { if (err) {
throw err; throw err;

@ -1,94 +1,79 @@
'use strict'; 'use strict';
var async = require('async'); const path = require('path');
var path = require('path'); const util = require('util');
var util = require('util'); const semver = require('semver');
var semver = require('semver'); const readline = require('readline');
var readline = require('readline'); const winston = require('winston');
var winston = require('winston');
var db = require('./database'); const db = require('./database');
var file = require('./file'); const file = require('./file');
/* /*
* Need to write an upgrade script for NodeBB? Cool. * Need to write an upgrade script for NodeBB? Cool.
* *
* 1. Copy TEMPLATE to a file name of your choice. Try to be succinct. * 1. Copy TEMPLATE to a unique file name of your choice. Try to be succinct.
* 2. Open up that file and change the user-friendly name (can be longer/more descriptive than the file name) * 2. Open up that file and change the user-friendly name (can be longer/more descriptive than the file name)
* and timestamp (don't forget the timestamp!) * and timestamp (don't forget the timestamp!)
* 3. Add your script under the "method" property * 3. Add your script under the "method" property
*/ */
var Upgrade = module.exports; const Upgrade = module.exports;
Upgrade.getAll = function (callback) { Upgrade.getAll = async function () {
async.waterfall([ let files = await file.walk(path.join(__dirname, './upgrades'));
async.apply(file.walk, path.join(__dirname, './upgrades')),
function (files, next) { // Sort the upgrade scripts based on version
// Sort the upgrade scripts based on version files = files.filter(file => path.basename(file) !== 'TEMPLATE').sort(function (a, b) {
var versionA; const versionA = path.dirname(a).split(path.sep).pop();
var versionB; const versionB = path.dirname(b).split(path.sep).pop();
setImmediate(next, null, files.filter(file => path.basename(file) !== 'TEMPLATE').sort(function (a, b) { const semverCompare = semver.compare(versionA, versionB);
versionA = path.dirname(a).split(path.sep).pop(); if (semverCompare) {
versionB = path.dirname(b).split(path.sep).pop(); return semverCompare;
var semverCompare = semver.compare(versionA, versionB); }
if (semverCompare) { const timestampA = require(a).timestamp;
return semverCompare; const timestampB = require(b).timestamp;
} return timestampA - timestampB;
var timestampA = require(a).timestamp; });
var timestampB = require(b).timestamp;
return timestampA - timestampB; await Upgrade.appendPluginScripts(files);
}));
}, // check duplicates and error
async.apply(Upgrade.appendPluginScripts), const seen = {};
function (files, next) { const dupes = [];
// check duplicates and error files.forEach((file) => {
const seen = {}; if (seen[file]) {
const dupes = []; dupes.push(file);
files.forEach((file) => { } else {
if (seen[file]) { seen[file] = true;
dupes.push(file); }
} else { });
seen[file] = true; if (dupes.length) {
} winston.error('Found duplicate upgrade scripts\n' + dupes);
}); throw new Error('[[error:duplicate-upgrade-scripts]]');
if (dupes.length) { }
winston.error('Found duplicate upgrade scripts\n' + dupes);
return next(new Error('[[error:duplicate-upgrade-scripts]]')); return files;
}
next(null, files);
},
], callback);
}; };
Upgrade.appendPluginScripts = function (files, callback) { Upgrade.appendPluginScripts = async function (files) {
async.waterfall([ // Find all active plugins
// Find all active plugins const plugins = await db.getSortedSetRange('plugins:active', 0, -1);
async.apply(db.getSortedSetRange.bind(db), 'plugins:active', 0, -1), plugins.forEach((plugin) => {
const configPath = path.join(__dirname, '../node_modules', plugin, 'plugin.json');
// Read plugin.json and check for upgrade scripts try {
function (plugins, next) { const pluginConfig = require(configPath);
async.each(plugins, function (plugin, next) { if (pluginConfig.hasOwnProperty('upgrades') && Array.isArray(pluginConfig.upgrades)) {
var configPath = path.join(__dirname, '../node_modules', plugin, 'plugin.json'); pluginConfig.upgrades.forEach(function (script) {
try { files.push(path.join(path.dirname(configPath), script));
var pluginConfig = require(configPath); });
if (pluginConfig.hasOwnProperty('upgrades') && Array.isArray(pluginConfig.upgrades)) { }
pluginConfig.upgrades.forEach(function (script) { } catch (e) {
files.push(path.join(path.dirname(configPath), script)); winston.warn('[upgrade/appendPluginScripts] Unable to read plugin.json for plugin `' + plugin + '`. Skipping.');
}); }
} });
return files;
next();
} catch (e) {
winston.warn('[upgrade/appendPluginScripts] Unable to read plugin.json for plugin `' + plugin + '`. Skipping.');
process.nextTick(next);
}
}, function (err) {
// Return list of upgrade scripts for continued processing
next(err, files);
});
},
], callback);
}; };
Upgrade.check = async function () { Upgrade.check = async function () {
@ -101,110 +86,86 @@ Upgrade.check = async function () {
} }
}; };
Upgrade.run = function (callback) { Upgrade.run = async function () {
console.log('\nParsing upgrade scripts... '); console.log('\nParsing upgrade scripts... ');
var queue = [];
var skipped = 0;
async.parallel({
// Retrieve list of upgrades that have already been run
completed: async.apply(db.getSortedSetRange, 'schemaLog', 0, -1),
// ... and those available to be run
available: Upgrade.getAll,
}, function (err, data) {
if (err) {
return callback(err);
}
queue = data.available.reduce(function (memo, cur) {
if (!data.completed.includes(path.basename(cur, '.js'))) {
memo.push(cur);
} else {
skipped += 1;
}
return memo; const [completed, available] = await Promise.all([
}, queue); db.getSortedSetRange('schemaLog', 0, -1),
Upgrade.getAll(),
]);
Upgrade.process(queue, skipped, callback); let skipped = 0;
const queue = available.filter(function (cur) {
const upgradeRan = completed.includes(path.basename(cur, '.js'));
if (upgradeRan) {
skipped += 1;
}
return !upgradeRan;
}); });
await Upgrade.process(queue, skipped);
}; };
Upgrade.runParticular = function (names, callback) { Upgrade.runParticular = async function (names) {
console.log('\nParsing upgrade scripts... '); console.log('\nParsing upgrade scripts... ');
const files = await file.walk(path.join(__dirname, './upgrades'));
async.waterfall([ await Upgrade.appendPluginScripts(files);
function (next) { const upgrades = files.filter(file => names.includes(path.basename(file, '.js')));
file.walk(path.join(__dirname, './upgrades'), next); await Upgrade.process(upgrades, 0);
},
function (files, next) {
Upgrade.appendPluginScripts(files, next);
},
function (files, next) {
const upgrades = files.filter(file => names.includes(path.basename(file, '.js')));
Upgrade.process(upgrades, 0, next);
},
], callback);
}; };
Upgrade.process = function (files, skipCount, callback) { Upgrade.process = async function (files, skipCount) {
console.log('OK'.green + ' | '.reset + String(files.length).cyan + ' script(s) found'.cyan + (skipCount > 0 ? ', '.cyan + String(skipCount).cyan + ' skipped'.cyan : '')); console.log('OK'.green + ' | '.reset + String(files.length).cyan + ' script(s) found'.cyan + (skipCount > 0 ? ', '.cyan + String(skipCount).cyan + ' skipped'.cyan : ''));
const [schemaDate, schemaLogCount] = await Promise.all([
db.get('schemaDate'),
db.sortedSetCard('schemaLog'),
]);
for (const file of files) {
/* eslint-disable no-await-in-loop */
const scriptExport = require(file);
const date = new Date(scriptExport.timestamp);
const version = path.dirname(file).split('/').pop();
const progress = {
current: 0,
total: 0,
incr: Upgrade.incrementProgress,
script: scriptExport,
date: date,
};
process.stdout.write(' → '.white + String('[' + [date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()].join('/') + '] ').gray + String(scriptExport.name).reset + '...');
// For backwards compatibility, cross-reference with schemaDate (if found). If a script's date is older, skip it
if ((!schemaDate && !schemaLogCount) || (scriptExport.timestamp <= schemaDate && semver.lt(version, '1.5.0'))) {
process.stdout.write(' skipped\n'.grey);
await db.sortedSetAdd('schemaLog', Date.now(), path.basename(file, '.js'));
return;
}
// Promisify method if necessary
if (scriptExport.method.constructor && scriptExport.method.constructor.name !== 'AsyncFunction') {
scriptExport.method = util.promisify(scriptExport.method);
}
// Do the upgrade...
try {
await scriptExport.method.bind({
progress: progress,
})();
} catch (err) {
console.error('Error occurred');
throw err;
}
process.stdout.write(' OK\n'.green);
// Record success in schemaLog
await db.sortedSetAdd('schemaLog', Date.now(), path.basename(file, '.js'));
}
async.waterfall([ console.log('Schema update complete!\n'.green);
function (next) {
async.parallel({
schemaDate: async.apply(db.get, 'schemaDate'),
schemaLogCount: async.apply(db.sortedSetCard, 'schemaLog'),
}, next);
},
function (results, next) {
async.eachSeries(files, async (file) => {
var scriptExport = require(file);
var date = new Date(scriptExport.timestamp);
var version = path.dirname(file).split('/').pop();
var progress = {
current: 0,
total: 0,
incr: Upgrade.incrementProgress,
script: scriptExport,
date: date,
};
process.stdout.write(' → '.white + String('[' + [date.getUTCFullYear(), date.getUTCMonth() + 1, date.getUTCDate()].join('/') + '] ').gray + String(scriptExport.name).reset + '...');
// For backwards compatibility, cross-reference with schemaDate (if found). If a script's date is older, skip it
if ((!results.schemaDate && !results.schemaLogCount) || (scriptExport.timestamp <= results.schemaDate && semver.lt(version, '1.5.0'))) {
process.stdout.write(' skipped\n'.grey);
await db.sortedSetAdd('schemaLog', Date.now(), path.basename(file, '.js'));
return;
}
// Promisify method if necessary
if (scriptExport.method.constructor && scriptExport.method.constructor.name !== 'AsyncFunction') {
scriptExport.method = util.promisify(scriptExport.method);
}
// Do the upgrade...
try {
await scriptExport.method.bind({
progress: progress,
})();
} catch (err) {
console.error('Error occurred');
throw err;
}
process.stdout.write(' OK\n'.green);
// Record success in schemaLog
await db.sortedSetAdd('schemaLog', Date.now(), path.basename(file, '.js'));
}, next);
},
function (next) {
console.log('Schema update complete!\n'.green);
setImmediate(next);
},
], callback);
}; };
Upgrade.incrementProgress = function (value) { Upgrade.incrementProgress = function (value) {

Loading…
Cancel
Save