diff --git a/src/cli/package-install.js b/src/cli/package-install.js index 4f391c18c1..caad43162a 100644 --- a/src/cli/package-install.js +++ b/src/cli/package-install.js @@ -66,7 +66,7 @@ function installAll() { } } } catch (e) { - // ignore + console.error(e); } try { cproc.execSync(command + (prod ? ' --production' : ''), { diff --git a/src/cli/upgrade-plugins.js b/src/cli/upgrade-plugins.js index 8ec46f7a62..2359bcc767 100644 --- a/src/cli/upgrade-plugins.js +++ b/src/cli/upgrade-plugins.js @@ -1,6 +1,5 @@ 'use strict'; -const async = require('async'); const prompt = require('prompt'); const request = require('request'); const cproc = require('child_process'); @@ -8,6 +7,7 @@ const semver = require('semver'); const fs = require('fs'); const path = require('path'); const nconf = require('nconf'); +const util = require('util'); const { paths, pluginNamePattern } = require('../constants'); @@ -22,190 +22,147 @@ if (process.platform === 'win32') { packageManagerExecutable += '.cmd'; } -function getModuleVersions(modules, callback) { +async function getModuleVersions(modules) { const versionHash = {}; - - async.eachLimit(modules, 50, (module, next) => { - fs.readFile(path.join(paths.nodeModules, module, 'package.json'), { encoding: 'utf-8' }, (err, pkg) => { - if (err) { - return next(err); - } - - try { - pkg = JSON.parse(pkg); - versionHash[module] = pkg.version; - next(); - } catch (err) { - next(err); - } - }); - }, (err) => { - callback(err, versionHash); + const batch = require('../batch'); + await batch.processArray(modules, async (moduleNames) => { + await Promise.all(moduleNames.map(async (module) => { + let pkg = await fs.promises.readFile( + path.join(paths.nodeModules, module, 'package.json'), { encoding: 'utf-8' } + ); + pkg = JSON.parse(pkg); + versionHash[module] = pkg.version; + })); + }, { + batch: 50, }); + + return versionHash; } -function getInstalledPlugins(callback) { - async.parallel({ - files: async.apply(fs.readdir, paths.nodeModules), - deps: async.apply(fs.readFile, paths.currentPackage, { encoding: 'utf-8' }), - bundled: async.apply(fs.readFile, paths.installPackage, { encoding: 'utf-8' }), - }, (err, payload) => { - if (err) { - return callback(err); - } +async function getInstalledPlugins() { + let [deps, bundled] = await Promise.all([ + fs.promises.readFile(paths.currentPackage, { encoding: 'utf-8' }), + fs.promises.readFile(paths.installPackage, { encoding: 'utf-8' }), + ]); + + deps = Object.keys(JSON.parse(deps).dependencies) + .filter(pkgName => pluginNamePattern.test(pkgName)); + bundled = Object.keys(JSON.parse(bundled).dependencies) + .filter(pkgName => pluginNamePattern.test(pkgName)); + - payload.files = payload.files.filter(file => pluginNamePattern.test(file)); + // Whittle down deps to send back only extraneously installed plugins/themes/etc + const checklist = deps.filter((pkgName) => { + if (bundled.includes(pkgName)) { + return false; + } + // Ignore git repositories try { - payload.deps = Object.keys(JSON.parse(payload.deps).dependencies); - payload.bundled = Object.keys(JSON.parse(payload.bundled).dependencies); - } catch (err) { - return callback(err); + fs.accessSync(path.join(paths.nodeModules, pkgName, '.git')); + return false; + } catch (e) { + return true; } + }); - payload.bundled = payload.bundled.filter(pkgName => pluginNamePattern.test(pkgName)); - payload.deps = payload.deps.filter(pkgName => pluginNamePattern.test(pkgName)); - - // Whittle down deps to send back only extraneously installed plugins/themes/etc - const checklist = payload.deps.filter((pkgName) => { - if (payload.bundled.includes(pkgName)) { - return false; - } - - // Ignore git repositories - try { - fs.accessSync(path.join(paths.nodeModules, pkgName, '.git')); - return false; - } catch (e) { - return true; - } - }); + return await getModuleVersions(checklist); +} - getModuleVersions(checklist, callback); - }); +async function getCurrentVersion() { + let pkg = await fs.promises.readFile(paths.installPackage, { encoding: 'utf-8' }); + pkg = JSON.parse(pkg); + return pkg.version; } -function getCurrentVersion(callback) { - fs.readFile(paths.installPackage, { encoding: 'utf-8' }, (err, pkg) => { +const getSuggestedModules = util.promisify((nbbVersion, toCheck, cb) => { + request({ + method: 'GET', + url: `https://packages.nodebb.org/api/v1/suggest?version=${nbbVersion}&package[]=${toCheck.join('&package[]=')}`, + json: true, + }, (err, res, body) => { if (err) { - return callback(err); + process.stdout.write('error'.red + ''.reset); + return cb(err); } - - try { - pkg = JSON.parse(pkg); - } catch (err) { - return callback(err); + if (!Array.isArray(body) && toCheck.length === 1) { + body = [body]; } - callback(null, pkg.version); + cb(null, body); }); -} - -function checkPlugins(standalone, callback) { - if (standalone) { - process.stdout.write('Checking installed plugins and themes for updates... '); +}); + +async function checkPlugins() { + process.stdout.write('Checking installed plugins and themes for updates... '); + const [plugins, nbbVersion] = await Promise.all([ + getInstalledPlugins, + getCurrentVersion, + ]); + + const toCheck = Object.keys(plugins); + if (!toCheck.length) { + process.stdout.write(' OK'.green + ''.reset); + return []; // no extraneous plugins installed } + const suggestedModules = await getSuggestedModules(nbbVersion, toCheck); + process.stdout.write(' OK'.green + ''.reset); + + let current; + let suggested; + const upgradable = suggestedModules.map((suggestObj) => { + current = plugins[suggestObj.package]; + suggested = suggestObj.version; + + if (suggestObj.code === 'match-found' && semver.gt(suggested, current)) { + return { + name: suggestObj.package, + current: current, + suggested: suggested, + }; + } + return null; + }).filter(Boolean); - async.waterfall([ - async.apply(async.parallel, { - plugins: getInstalledPlugins, - version: getCurrentVersion, - }), - function (payload, next) { - const toCheck = Object.keys(payload.plugins); - - if (!toCheck.length) { - process.stdout.write(' OK'.green + ''.reset); - return next(null, []); // no extraneous plugins installed - } - - request({ - method: 'GET', - url: `https://packages.nodebb.org/api/v1/suggest?version=${payload.version}&package[]=${toCheck.join('&package[]=')}`, - json: true, - }, (err, res, body) => { - if (err) { - process.stdout.write('error'.red + ''.reset); - return next(err); - } - process.stdout.write(' OK'.green + ''.reset); - - if (!Array.isArray(body) && toCheck.length === 1) { - body = [body]; - } - - let current; - let suggested; - const upgradable = body.map((suggestObj) => { - current = payload.plugins[suggestObj.package]; - suggested = suggestObj.version; - - if (suggestObj.code === 'match-found' && semver.gt(suggested, current)) { - return { - name: suggestObj.package, - current: current, - suggested: suggested, - }; - } - return null; - }).filter(Boolean); - - next(null, upgradable); - }); - }, - ], callback); + return upgradable; } -function upgradePlugins(callback) { - let standalone = false; - if (typeof callback !== 'function') { - callback = function () {}; - standalone = true; - } - - checkPlugins(standalone, (err, found) => { - if (err) { - console.log('Warning'.yellow + ': An unexpected error occured when attempting to verify plugin upgradability'.reset); - return callback(err); - } - +async function upgradePlugins() { + try { + const found = await checkPlugins(); if (found && found.length) { process.stdout.write(`\n\nA total of ${String(found.length).bold} package(s) can be upgraded:\n\n`); found.forEach((suggestObj) => { process.stdout.write(`${' * '.yellow + suggestObj.name.reset} (${suggestObj.current.yellow}${' -> '.reset}${suggestObj.suggested.green}${')\n'.reset}`); }); } else { - if (standalone) { - console.log('\nAll packages up-to-date!'.green + ''.reset); - } - return callback(); + console.log('\nAll packages up-to-date!'.green + ''.reset); + return; } prompt.message = ''; prompt.delimiter = ''; + const promptGet = util.promisify((schema, callback) => prompt.get(schema, callback)); prompt.start(); - prompt.get({ + const result = await promptGet({ name: 'upgrade', description: '\nProceed with upgrade (y|n)?'.reset, type: 'string', - }, (err, result) => { - if (err) { - return callback(err); - } - - if (['y', 'Y', 'yes', 'YES'].includes(result.upgrade)) { - console.log('\nUpgrading packages...'); - const args = packageManagerInstallArgs.concat(found.map(suggestObj => `${suggestObj.name}@${suggestObj.suggested}`)); - - cproc.execFile(packageManagerExecutable, args, { stdio: 'ignore' }, (err) => { - callback(err, false); - }); - } else { - console.log('Package upgrades skipped'.yellow + '. Check for upgrades at any time by running "'.reset + './nodebb upgrade -p'.green + '".'.reset); - callback(); - } }); - }); + + if (['y', 'Y', 'yes', 'YES'].includes(result.upgrade)) { + console.log('\nUpgrading packages...'); + const args = packageManagerInstallArgs.concat(found.map(suggestObj => `${suggestObj.name}@${suggestObj.suggested}`)); + + cproc.execFileSync(packageManagerExecutable, args, { stdio: 'ignore' }); + } else { + console.log('Package upgrades skipped'.yellow + '. Check for upgrades at any time by running "'.reset + './nodebb upgrade -p'.green + '".'.reset); + } + } catch (err) { + console.log('Warning'.yellow + ': An unexpected error occured when attempting to verify plugin upgradability'.reset); + throw err; + } } exports.upgradePlugins = upgradePlugins; diff --git a/src/cli/upgrade.js b/src/cli/upgrade.js index 1210c23ef2..2198203b53 100644 --- a/src/cli/upgrade.js +++ b/src/cli/upgrade.js @@ -1,6 +1,5 @@ 'use strict'; -const async = require('async'); const nconf = require('nconf'); const packageInstall = require('./package-install'); @@ -9,61 +8,52 @@ const { upgradePlugins } = require('./upgrade-plugins'); const steps = { package: { message: 'Updating package.json file with defaults...', - handler: function (next) { + handler: function () { packageInstall.updatePackageFile(); packageInstall.preserveExtraneousPlugins(); process.stdout.write(' OK\n'.green); - next(); }, }, install: { message: 'Bringing base dependencies up to date...', - handler: function (next) { + handler: function () { process.stdout.write(' started\n'.green); packageInstall.installAll(); - next(); }, }, plugins: { message: 'Checking installed plugins for updates...', - handler: function (next) { - async.series([ - require('../database').init, - upgradePlugins, - ], next); + handler: async function () { + await require('../database').init(); + await upgradePlugins(); }, }, schema: { message: 'Updating NodeBB data store schema...', - handler: function (next) { - async.series([ - require('../database').init, - require('../meta').configs.init, - require('../upgrade').run, - ], next); + handler: async function () { + await require('../database').init(); + await require('../meta').configs.init(); + await require('../upgrade').run(); }, }, build: { message: 'Rebuilding assets...', - handler: require('../meta/build').buildAll, + handler: async function () { + await require('../meta/build').buildAll(); + }, }, }; -function runSteps(tasks) { - tasks = tasks.map((key, i) => function (next) { - process.stdout.write(`\n${(`${i + 1}. `).bold}${steps[key].message.yellow}`); - return steps[key].handler((err) => { - if (err) { return next(err); } - next(); - }); - }); - - async.series(tasks, (err) => { - if (err) { - console.error(`Error occurred during upgrade: ${err.stack}`); - throw err; +async function runSteps(tasks) { + try { + for (let i = 0; i < tasks.length; i++) { + const step = steps[tasks[i]]; + if (step && step.message && step.handler) { + process.stdout.write(`\n${(`${i + 1}. `).bold}${step.message.yellow}`); + /* eslint-disable-next-line */ + await step.handler(); + } } - const message = 'NodeBB Upgrade Complete!'; // some consoles will return undefined/zero columns, // so just use 2 spaces in upgrade script if we can't get our column count @@ -73,10 +63,13 @@ function runSteps(tasks) { console.log(`\n\n${spaces}${message.green.bold}${'\n'.reset}`); process.exit(); - }); + } catch (err) { + console.error(`Error occurred during upgrade: ${err.stack}`); + throw err; + } } -function runUpgrade(upgrades, options) { +async function runUpgrade(upgrades, options) { console.log('\nUpdating NodeBB...'.cyan); options = options || {}; // disable mongo timeouts during upgrade @@ -88,23 +81,14 @@ function runUpgrade(upgrades, options) { options.plugins || options.schema || options.build) { tasks = tasks.filter(key => options[key]); } - runSteps(tasks); + await runSteps(tasks); return; } - async.series([ - require('../database').init, - require('../meta').configs.init, - async function () { - await require('../upgrade').runParticular(upgrades); - }, - ], (err) => { - if (err) { - throw err; - } - - process.exit(0); - }); + await require('../database').init(); + await require('../meta').configs.init(); + await require('../upgrade').runParticular(upgrades); + process.exit(0); } exports.upgrade = runUpgrade;