mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 08:36:12 +01:00
refactor: rewrite src/upgrade.js with async/await
This commit is contained in:
@@ -101,7 +101,9 @@ function runUpgrade(upgrades, options) {
|
||||
async.series([
|
||||
db.init,
|
||||
require('../meta').configs.init,
|
||||
async.apply(upgrade.runParticular, upgrades),
|
||||
async function () {
|
||||
await upgrade.runParticular(upgrades);
|
||||
},
|
||||
], function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
|
||||
305
src/upgrade.js
305
src/upgrade.js
@@ -1,94 +1,79 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var path = require('path');
|
||||
var util = require('util');
|
||||
var semver = require('semver');
|
||||
var readline = require('readline');
|
||||
var winston = require('winston');
|
||||
const path = require('path');
|
||||
const util = require('util');
|
||||
const semver = require('semver');
|
||||
const readline = require('readline');
|
||||
const winston = require('winston');
|
||||
|
||||
var db = require('./database');
|
||||
var file = require('./file');
|
||||
const db = require('./database');
|
||||
const file = require('./file');
|
||||
|
||||
/*
|
||||
* 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)
|
||||
* and timestamp (don't forget the timestamp!)
|
||||
* 3. Add your script under the "method" property
|
||||
*/
|
||||
|
||||
var Upgrade = module.exports;
|
||||
const Upgrade = module.exports;
|
||||
|
||||
Upgrade.getAll = function (callback) {
|
||||
async.waterfall([
|
||||
async.apply(file.walk, path.join(__dirname, './upgrades')),
|
||||
function (files, next) {
|
||||
// Sort the upgrade scripts based on version
|
||||
var versionA;
|
||||
var versionB;
|
||||
setImmediate(next, null, files.filter(file => path.basename(file) !== 'TEMPLATE').sort(function (a, b) {
|
||||
versionA = path.dirname(a).split(path.sep).pop();
|
||||
versionB = path.dirname(b).split(path.sep).pop();
|
||||
var semverCompare = semver.compare(versionA, versionB);
|
||||
if (semverCompare) {
|
||||
return semverCompare;
|
||||
}
|
||||
var timestampA = require(a).timestamp;
|
||||
var timestampB = require(b).timestamp;
|
||||
return timestampA - timestampB;
|
||||
}));
|
||||
},
|
||||
async.apply(Upgrade.appendPluginScripts),
|
||||
function (files, next) {
|
||||
// check duplicates and error
|
||||
const seen = {};
|
||||
const dupes = [];
|
||||
files.forEach((file) => {
|
||||
if (seen[file]) {
|
||||
dupes.push(file);
|
||||
} else {
|
||||
seen[file] = true;
|
||||
}
|
||||
});
|
||||
if (dupes.length) {
|
||||
winston.error('Found duplicate upgrade scripts\n' + dupes);
|
||||
return next(new Error('[[error:duplicate-upgrade-scripts]]'));
|
||||
}
|
||||
next(null, files);
|
||||
},
|
||||
], callback);
|
||||
Upgrade.getAll = async function () {
|
||||
let files = await file.walk(path.join(__dirname, './upgrades'));
|
||||
|
||||
// Sort the upgrade scripts based on version
|
||||
files = files.filter(file => path.basename(file) !== 'TEMPLATE').sort(function (a, b) {
|
||||
const versionA = path.dirname(a).split(path.sep).pop();
|
||||
const versionB = path.dirname(b).split(path.sep).pop();
|
||||
const semverCompare = semver.compare(versionA, versionB);
|
||||
if (semverCompare) {
|
||||
return semverCompare;
|
||||
}
|
||||
const timestampA = require(a).timestamp;
|
||||
const timestampB = require(b).timestamp;
|
||||
return timestampA - timestampB;
|
||||
});
|
||||
|
||||
await Upgrade.appendPluginScripts(files);
|
||||
|
||||
// check duplicates and error
|
||||
const seen = {};
|
||||
const dupes = [];
|
||||
files.forEach((file) => {
|
||||
if (seen[file]) {
|
||||
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]]');
|
||||
}
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
Upgrade.appendPluginScripts = function (files, callback) {
|
||||
async.waterfall([
|
||||
// Find all active plugins
|
||||
async.apply(db.getSortedSetRange.bind(db), 'plugins:active', 0, -1),
|
||||
|
||||
// Read plugin.json and check for upgrade scripts
|
||||
function (plugins, next) {
|
||||
async.each(plugins, function (plugin, next) {
|
||||
var configPath = path.join(__dirname, '../node_modules', plugin, 'plugin.json');
|
||||
try {
|
||||
var pluginConfig = require(configPath);
|
||||
if (pluginConfig.hasOwnProperty('upgrades') && Array.isArray(pluginConfig.upgrades)) {
|
||||
pluginConfig.upgrades.forEach(function (script) {
|
||||
files.push(path.join(path.dirname(configPath), script));
|
||||
});
|
||||
}
|
||||
|
||||
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.appendPluginScripts = async function (files) {
|
||||
// Find all active plugins
|
||||
const plugins = await db.getSortedSetRange('plugins:active', 0, -1);
|
||||
plugins.forEach((plugin) => {
|
||||
const configPath = path.join(__dirname, '../node_modules', plugin, 'plugin.json');
|
||||
try {
|
||||
const pluginConfig = require(configPath);
|
||||
if (pluginConfig.hasOwnProperty('upgrades') && Array.isArray(pluginConfig.upgrades)) {
|
||||
pluginConfig.upgrades.forEach(function (script) {
|
||||
files.push(path.join(path.dirname(configPath), script));
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
winston.warn('[upgrade/appendPluginScripts] Unable to read plugin.json for plugin `' + plugin + '`. Skipping.');
|
||||
}
|
||||
});
|
||||
return files;
|
||||
};
|
||||
|
||||
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... ');
|
||||
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);
|
||||
const [completed, available] = await Promise.all([
|
||||
db.getSortedSetRange('schemaLog', 0, -1),
|
||||
Upgrade.getAll(),
|
||||
]);
|
||||
|
||||
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 = async function (names) {
|
||||
console.log('\nParsing upgrade scripts... ');
|
||||
const files = await file.walk(path.join(__dirname, './upgrades'));
|
||||
await Upgrade.appendPluginScripts(files);
|
||||
const upgrades = files.filter(file => names.includes(path.basename(file, '.js')));
|
||||
await Upgrade.process(upgrades, 0);
|
||||
};
|
||||
|
||||
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 : ''));
|
||||
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;
|
||||
}
|
||||
|
||||
queue = data.available.reduce(function (memo, cur) {
|
||||
if (!data.completed.includes(path.basename(cur, '.js'))) {
|
||||
memo.push(cur);
|
||||
} else {
|
||||
skipped += 1;
|
||||
}
|
||||
// Promisify method if necessary
|
||||
if (scriptExport.method.constructor && scriptExport.method.constructor.name !== 'AsyncFunction') {
|
||||
scriptExport.method = util.promisify(scriptExport.method);
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, queue);
|
||||
// Do the upgrade...
|
||||
try {
|
||||
await scriptExport.method.bind({
|
||||
progress: progress,
|
||||
})();
|
||||
} catch (err) {
|
||||
console.error('Error occurred');
|
||||
throw err;
|
||||
}
|
||||
|
||||
Upgrade.process(queue, skipped, callback);
|
||||
});
|
||||
};
|
||||
process.stdout.write(' OK\n'.green);
|
||||
|
||||
Upgrade.runParticular = function (names, callback) {
|
||||
console.log('\nParsing upgrade scripts... ');
|
||||
// Record success in schemaLog
|
||||
await db.sortedSetAdd('schemaLog', Date.now(), path.basename(file, '.js'));
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
file.walk(path.join(__dirname, './upgrades'), next);
|
||||
},
|
||||
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) {
|
||||
console.log('OK'.green + ' | '.reset + String(files.length).cyan + ' script(s) found'.cyan + (skipCount > 0 ? ', '.cyan + String(skipCount).cyan + ' skipped'.cyan : ''));
|
||||
|
||||
async.waterfall([
|
||||
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);
|
||||
console.log('Schema update complete!\n'.green);
|
||||
};
|
||||
|
||||
Upgrade.incrementProgress = function (value) {
|
||||
|
||||
Reference in New Issue
Block a user