From 33c5988c340a1d02793d70dd21464a643abd65c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 7 Aug 2020 19:52:04 -0400 Subject: [PATCH] refactor: rewrite src/upgrade.js with async/await --- src/cli/upgrade.js | 4 +- src/upgrade.js | 301 ++++++++++++++++++++------------------------- 2 files changed, 134 insertions(+), 171 deletions(-) diff --git a/src/cli/upgrade.js b/src/cli/upgrade.js index 618183111ef1..dd3410cf843d 100644 --- a/src/cli/upgrade.js +++ b/src/cli/upgrade.js @@ -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; diff --git a/src/upgrade.js b/src/upgrade.js index 87fb07c1203d..2dac78414be2 100644 --- a/src/upgrade.js +++ b/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; - -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); +const Upgrade = module.exports; + +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); - } - - queue = data.available.reduce(function (memo, cur) { - if (!data.completed.includes(path.basename(cur, '.js'))) { - memo.push(cur); - } else { - skipped += 1; - } - return memo; - }, queue); + const [completed, available] = await Promise.all([ + 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... '); - - 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); + 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 = 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 : '')); + 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([ - 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) {