mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 01:26:12 +02:00
CLI refactor with commander (#6058)
* CLI refactor with commander - Modularized the functionality - All functionality done directly from `./nodebb` now (still available from `app` for backwards compatibility) - Moved all CLI code from `./nodebb` to `src/cli` - Fixed `nodebb.bat` to work from any location, like `./nodebb`, and also hides command output - Overwrite some commander methods to add CLI color support - Added `./nodebb info` for quick info including git hash, NodeBB version, node version, and some database info - Refactored `./nodebb reset` to allow multiple resets at once - Changed `./nodebb restart` to essentially stop and start, as Windows doesn't support signals - Added `-l, --log` option which works on `./nodebb start` and `./nodebb restart` to show logging, like `./nodebb slog` - Expanded `-d, --dev` option which works on them as well, like `./nodebb dev` - Improvements to self-help. `./nodebb build -h` will output all possible targets - `./nodebb reset` explains usage better * Fix some style inconsistencies * Fix prestart being required before modules installed * Fix travis failures * Fix `help` command to output help for subcommands * Pick steps of the upgrade process to run * Fix formatting for upgrade help * Fix web installer
This commit is contained in:
committed by
Barış Soner Uşaklı
parent
c731661a39
commit
ae24bca16e
266
app.js
266
app.js
@@ -30,47 +30,16 @@ nconf.argv().env({
|
||||
separator: '__',
|
||||
});
|
||||
|
||||
var url = require('url');
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
var path = require('path');
|
||||
var pkg = require('./package.json');
|
||||
|
||||
var file = require('./src/file');
|
||||
var debug = require('./src/meta/debugFork').debugging;
|
||||
|
||||
global.env = process.env.NODE_ENV || 'production';
|
||||
|
||||
winston.remove(winston.transports.Console);
|
||||
winston.add(winston.transports.Console, {
|
||||
colorize: true,
|
||||
timestamp: function () {
|
||||
var date = new Date();
|
||||
return nconf.get('json-logging') ? date.toJSON() : date.getDate() + '/' + (date.getMonth() + 1) + ' ' + date.toTimeString().substr(0, 8) + ' [' + global.process.pid + ']';
|
||||
},
|
||||
level: nconf.get('log-level') || (global.env === 'production' ? 'info' : 'verbose'),
|
||||
json: (!!nconf.get('json-logging')),
|
||||
stringify: (!!nconf.get('json-logging')),
|
||||
});
|
||||
|
||||
if (debug) {
|
||||
var winstonCommon = require('winston/lib/winston/common');
|
||||
// Override to use real console.log etc for VSCode debugger
|
||||
winston.transports.Console.prototype.log = function (level, message, meta, callback) {
|
||||
const output = winstonCommon.log(Object.assign({}, this, {
|
||||
level,
|
||||
message,
|
||||
meta,
|
||||
}));
|
||||
|
||||
console[level in console ? level : 'log'](output);
|
||||
|
||||
setImmediate(callback, null, true);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// Alternate configuration file support
|
||||
var configFile = path.join(__dirname, '/config.json');
|
||||
var configFile = path.join(__dirname, 'config.json');
|
||||
|
||||
if (nconf.get('config')) {
|
||||
configFile = path.resolve(__dirname, nconf.get('config'));
|
||||
@@ -78,8 +47,9 @@ if (nconf.get('config')) {
|
||||
|
||||
var configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database'));
|
||||
|
||||
loadConfig();
|
||||
versionCheck();
|
||||
var prestart = require('./src/prestart');
|
||||
prestart.loadConfig(configFile);
|
||||
prestart.versionCheck();
|
||||
|
||||
if (!process.send) {
|
||||
// If run using `node app`, log GNU copyright info along with server info
|
||||
@@ -89,224 +59,40 @@ if (!process.send) {
|
||||
winston.info('');
|
||||
}
|
||||
|
||||
|
||||
if (nconf.get('setup') || nconf.get('install')) {
|
||||
setup();
|
||||
require('./src/cli/setup').setup();
|
||||
} else if (!configExists) {
|
||||
require('./install/web').install(nconf.get('port'));
|
||||
} else if (nconf.get('upgrade')) {
|
||||
upgrade();
|
||||
require('./src/cli/upgrade').upgrade(true);
|
||||
} else if (nconf.get('reset')) {
|
||||
async.waterfall([
|
||||
async.apply(require('./src/reset').reset),
|
||||
async.apply(require('./src/meta/build').buildAll),
|
||||
var options = {
|
||||
theme: nconf.get('t'),
|
||||
plugin: nconf.get('p'),
|
||||
widgets: nconf.get('w'),
|
||||
settings: nconf.get('s'),
|
||||
all: nconf.get('a'),
|
||||
};
|
||||
|
||||
async.series([
|
||||
async.apply(require('./src/cli/reset').reset, options),
|
||||
require('./src/meta/build').buildAll,
|
||||
], function (err) {
|
||||
process.exit(err ? 1 : 0);
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
} else if (nconf.get('activate')) {
|
||||
activate();
|
||||
require('./src/cli/manage').activate(nconf.get('activate'));
|
||||
} else if (nconf.get('plugins')) {
|
||||
listPlugins();
|
||||
require('./src/cli/manage').listPlugins();
|
||||
} else if (nconf.get('build')) {
|
||||
require('./src/meta/build').build(nconf.get('build'));
|
||||
} else if (nconf.get('events')) {
|
||||
async.series([
|
||||
async.apply(require('./src/database').init),
|
||||
async.apply(require('./src/events').output),
|
||||
]);
|
||||
require('./src/cli/manage').listEvents();
|
||||
} else {
|
||||
require('./src/start').start();
|
||||
}
|
||||
|
||||
function loadConfig(callback) {
|
||||
winston.verbose('* using configuration stored in: %s', configFile);
|
||||
|
||||
nconf.file({
|
||||
file: configFile,
|
||||
});
|
||||
|
||||
nconf.defaults({
|
||||
base_dir: __dirname,
|
||||
themes_path: path.join(__dirname, 'node_modules'),
|
||||
upload_path: 'public/uploads',
|
||||
views_dir: path.join(__dirname, 'build/public/templates'),
|
||||
version: pkg.version,
|
||||
});
|
||||
|
||||
if (!nconf.get('isCluster')) {
|
||||
nconf.set('isPrimary', 'true');
|
||||
nconf.set('isCluster', 'false');
|
||||
}
|
||||
|
||||
// Ensure themes_path is a full filepath
|
||||
nconf.set('themes_path', path.resolve(__dirname, nconf.get('themes_path')));
|
||||
nconf.set('core_templates_path', path.join(__dirname, 'src/views'));
|
||||
nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-persona/templates'));
|
||||
|
||||
nconf.set('upload_path', path.resolve(nconf.get('base_dir'), nconf.get('upload_path')));
|
||||
|
||||
if (nconf.get('url')) {
|
||||
nconf.set('url_parsed', url.parse(nconf.get('url')));
|
||||
}
|
||||
|
||||
// Explicitly cast 'jobsDisabled' as Bool
|
||||
var castAsBool = ['jobsDisabled'];
|
||||
nconf.stores.env.readOnly = false;
|
||||
castAsBool.forEach(function (prop) {
|
||||
var value = nconf.get(prop);
|
||||
if (value) {
|
||||
nconf.set(prop, typeof value === 'boolean' ? value : String(value).toLowerCase() === 'true');
|
||||
}
|
||||
});
|
||||
nconf.stores.env.readOnly = true;
|
||||
|
||||
if (typeof callback === 'function') {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
function setup() {
|
||||
winston.info('NodeBB Setup Triggered via Command Line');
|
||||
|
||||
var install = require('./src/install');
|
||||
var build = require('./src/meta/build');
|
||||
|
||||
process.stdout.write('\nWelcome to NodeBB!\n');
|
||||
process.stdout.write('\nThis looks like a new installation, so you\'ll have to answer a few questions about your environment before we can proceed.\n');
|
||||
process.stdout.write('Press enter to accept the default setting (shown in brackets).\n');
|
||||
|
||||
async.series([
|
||||
async.apply(install.setup),
|
||||
async.apply(loadConfig),
|
||||
async.apply(build.buildAll),
|
||||
], function (err, data) {
|
||||
// Disregard build step data
|
||||
data = data[0];
|
||||
|
||||
var separator = ' ';
|
||||
if (process.stdout.columns > 10) {
|
||||
for (var x = 0, cols = process.stdout.columns - 10; x < cols; x += 1) {
|
||||
separator += '=';
|
||||
}
|
||||
}
|
||||
process.stdout.write('\n' + separator + '\n\n');
|
||||
|
||||
if (err) {
|
||||
winston.error('There was a problem completing NodeBB setup', err);
|
||||
throw err;
|
||||
} else {
|
||||
if (data.hasOwnProperty('password')) {
|
||||
process.stdout.write('An administrative user was automatically created for you:\n');
|
||||
process.stdout.write(' Username: ' + data.username + '\n');
|
||||
process.stdout.write(' Password: ' + data.password + '\n');
|
||||
process.stdout.write('\n');
|
||||
}
|
||||
process.stdout.write('NodeBB Setup Completed. Run \'./nodebb start\' to manually start your NodeBB server.\n');
|
||||
|
||||
// If I am a child process, notify the parent of the returned data before exiting (useful for notifying
|
||||
// hosts of auto-generated username/password during headless setups)
|
||||
if (process.send) {
|
||||
process.send(data);
|
||||
}
|
||||
}
|
||||
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
|
||||
function upgrade() {
|
||||
var db = require('./src/database');
|
||||
var meta = require('./src/meta');
|
||||
var upgrade = require('./src/upgrade');
|
||||
var build = require('./src/meta/build');
|
||||
var tasks = [db.init, meta.configs.init];
|
||||
|
||||
if (nconf.get('upgrade') !== true) {
|
||||
// Likely an upgrade script name passed in
|
||||
tasks.push(async.apply(upgrade.runParticular, nconf.get('upgrade').split(',')));
|
||||
} else {
|
||||
tasks.push(upgrade.run, build.buildAll);
|
||||
}
|
||||
// disable mongo timeouts during upgrade
|
||||
nconf.set('mongo:options:socketTimeoutMS', 0);
|
||||
async.series(tasks, function (err) {
|
||||
if (err) {
|
||||
winston.error(err.stack);
|
||||
process.exit(1);
|
||||
} else {
|
||||
process.exit(0);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function activate() {
|
||||
var db = require('./src/database');
|
||||
var plugins = require('./src/plugins');
|
||||
var events = require('./src/events');
|
||||
var plugin = nconf.get('activate');
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.init(next);
|
||||
},
|
||||
function (next) {
|
||||
if (plugin.indexOf('nodebb-') !== 0) {
|
||||
// Allow omission of `nodebb-plugin-`
|
||||
plugin = 'nodebb-plugin-' + plugin;
|
||||
}
|
||||
plugins.isInstalled(plugin, next);
|
||||
},
|
||||
function (isInstalled, next) {
|
||||
if (!isInstalled) {
|
||||
return next(new Error('plugin not installed'));
|
||||
}
|
||||
|
||||
winston.info('Activating plugin `%s`', plugin);
|
||||
db.sortedSetAdd('plugins:active', 0, plugin, next);
|
||||
},
|
||||
function (next) {
|
||||
events.log({
|
||||
type: 'plugin-activate',
|
||||
text: plugin,
|
||||
}, next);
|
||||
},
|
||||
], function (err) {
|
||||
if (err) {
|
||||
winston.error('An error occurred during plugin activation', err);
|
||||
throw err;
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
function listPlugins() {
|
||||
require('./src/database').init(function (err) {
|
||||
if (err) {
|
||||
winston.error(err.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
var db = require('./src/database');
|
||||
|
||||
db.getSortedSetRange('plugins:active', 0, -1, function (err, plugins) {
|
||||
if (err) {
|
||||
winston.error(err.stack);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
winston.info('Active plugins: \n\t - ' + plugins.join('\n\t - '));
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
function versionCheck() {
|
||||
var version = process.version.slice(1);
|
||||
var range = pkg.engines.node;
|
||||
var semver = require('semver');
|
||||
var compatible = semver.satisfies(version, range);
|
||||
|
||||
if (!compatible) {
|
||||
winston.warn('Your version of Node.js is too outdated for NodeBB. Please update your version of Node.js.');
|
||||
winston.warn('Recommended ' + range.green + ', '.reset + version.yellow + ' provided\n'.reset);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,6 +27,7 @@
|
||||
"chart.js": "^2.7.0",
|
||||
"colors": "^1.1.2",
|
||||
"compression": "^1.7.1",
|
||||
"commander": "^2.11.0",
|
||||
"connect-ensure-login": "^0.1.1",
|
||||
"connect-flash": "^0.1.1",
|
||||
"connect-mongo": "2.0.0",
|
||||
@@ -52,7 +53,6 @@
|
||||
"logrotate-stream": "^0.2.5",
|
||||
"lru-cache": "4.1.1",
|
||||
"mime": "^2.0.3",
|
||||
"minimist": "^1.2.0",
|
||||
"mkdirp": "^0.5.1",
|
||||
"mongodb": "2.2.33",
|
||||
"morgan": "^1.9.0",
|
||||
|
||||
547
nodebb
547
nodebb
@@ -2,549 +2,4 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var cproc = require('child_process');
|
||||
|
||||
var packageInstall = require('./src/meta/package-install');
|
||||
|
||||
// check to make sure dependencies are installed
|
||||
try {
|
||||
fs.readFileSync(path.join(__dirname, './package.json'));
|
||||
fs.readFileSync(path.join(__dirname, 'node_modules/async/package.json'));
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOENT') {
|
||||
process.stdout.write('Dependencies not yet installed.\n');
|
||||
process.stdout.write('Installing them now...\n\n');
|
||||
|
||||
packageInstall.updatePackageFile();
|
||||
packageInstall.preserveExtraneousPlugins();
|
||||
packageInstall.npmInstallProduction();
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
var minimist;
|
||||
var request;
|
||||
var semver;
|
||||
var prompt;
|
||||
var async;
|
||||
|
||||
try {
|
||||
require('colors');
|
||||
minimist = require('minimist');
|
||||
request = require('request');
|
||||
semver = require('semver');
|
||||
prompt = require('prompt');
|
||||
async = require('async');
|
||||
} catch (e) {
|
||||
process.stdout.write(
|
||||
'\x1b[31mNodeBB could not be initialised because there was an error while loading dependencies.\n' +
|
||||
'Please run "\x1b[33mnpm install --production\x1b[31m" and try again.\x1b[0m\n\n' +
|
||||
'For more information, please see: https://docs.nodebb.org/installing/os/\n\n'
|
||||
);
|
||||
|
||||
throw e;
|
||||
}
|
||||
|
||||
var args = minimist(process.argv.slice(2));
|
||||
|
||||
var loaderPath = path.join(__dirname, 'loader.js');
|
||||
var appPath = path.join(__dirname, 'app.js');
|
||||
|
||||
if (args.dev) {
|
||||
process.env.NODE_ENV = 'development';
|
||||
}
|
||||
|
||||
function getRunningPid(callback) {
|
||||
fs.readFile(path.join(__dirname, 'pidfile'), {
|
||||
encoding: 'utf-8',
|
||||
}, function (err, pid) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
try {
|
||||
process.kill(parseInt(pid, 10), 0);
|
||||
callback(null, parseInt(pid, 10));
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
function getCurrentVersion(callback) {
|
||||
fs.readFile(path.join(__dirname, 'package.json'), { encoding: 'utf-8' }, function (err, pkg) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
try {
|
||||
pkg = JSON.parse(pkg);
|
||||
return callback(null, pkg.version);
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
}
|
||||
});
|
||||
}
|
||||
function fork(args) {
|
||||
return cproc.fork(appPath, args, {
|
||||
cwd: __dirname,
|
||||
silent: false,
|
||||
});
|
||||
}
|
||||
function getInstalledPlugins(callback) {
|
||||
async.parallel({
|
||||
files: async.apply(fs.readdir, path.join(__dirname, 'node_modules')),
|
||||
deps: async.apply(fs.readFile, path.join(__dirname, 'package.json'), { encoding: 'utf-8' }),
|
||||
}, function (err, payload) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var isNbbModule = /^nodebb-(?:plugin|theme|widget|rewards)-[\w-]+$/;
|
||||
var moduleName;
|
||||
var isGitRepo;
|
||||
|
||||
payload.files = payload.files.filter(function (file) {
|
||||
return isNbbModule.test(file);
|
||||
});
|
||||
|
||||
try {
|
||||
payload.deps = JSON.parse(payload.deps).dependencies;
|
||||
payload.bundled = [];
|
||||
payload.installed = [];
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
for (moduleName in payload.deps) {
|
||||
if (isNbbModule.test(moduleName)) {
|
||||
payload.bundled.push(moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
// Whittle down deps to send back only extraneously installed plugins/themes/etc
|
||||
payload.files.forEach(function (moduleName) {
|
||||
try {
|
||||
fs.accessSync(path.join(__dirname, 'node_modules/' + moduleName, '.git'));
|
||||
isGitRepo = true;
|
||||
} catch (e) {
|
||||
isGitRepo = false;
|
||||
}
|
||||
|
||||
if (
|
||||
payload.files.indexOf(moduleName) !== -1 && // found in `node_modules/`
|
||||
payload.bundled.indexOf(moduleName) === -1 && // not found in `package.json`
|
||||
!fs.lstatSync(path.join(__dirname, 'node_modules/' + moduleName)).isSymbolicLink() && // is not a symlink
|
||||
!isGitRepo // .git/ does not exist, so it is not a git repository
|
||||
) {
|
||||
payload.installed.push(moduleName);
|
||||
}
|
||||
});
|
||||
|
||||
getModuleVersions(payload.installed, callback);
|
||||
});
|
||||
}
|
||||
function getModuleVersions(modules, callback) {
|
||||
var versionHash = {};
|
||||
|
||||
async.eachLimit(modules, 50, function (module, next) {
|
||||
fs.readFile(path.join(__dirname, 'node_modules/' + module + '/package.json'), { encoding: 'utf-8' }, function (err, pkg) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
try {
|
||||
pkg = JSON.parse(pkg);
|
||||
versionHash[module] = pkg.version;
|
||||
next();
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
}, function (err) {
|
||||
callback(err, versionHash);
|
||||
});
|
||||
}
|
||||
function checkPlugins(standalone, callback) {
|
||||
if (standalone) {
|
||||
process.stdout.write('Checking installed plugins and themes for updates... ');
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
async.apply(async.parallel, {
|
||||
plugins: async.apply(getInstalledPlugins),
|
||||
version: async.apply(getCurrentVersion),
|
||||
}),
|
||||
function (payload, next) {
|
||||
var toCheck = Object.keys(payload.plugins);
|
||||
|
||||
if (!toCheck.length) {
|
||||
process.stdout.write('OK'.green + '\n'.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,
|
||||
}, function (err, res, body) {
|
||||
if (err) {
|
||||
process.stdout.write('error'.red + '\n'.reset);
|
||||
return next(err);
|
||||
}
|
||||
process.stdout.write('OK'.green + '\n'.reset);
|
||||
|
||||
if (!Array.isArray(body) && toCheck.length === 1) {
|
||||
body = [body];
|
||||
}
|
||||
|
||||
var current;
|
||||
var suggested;
|
||||
var upgradable = body.map(function (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);
|
||||
}
|
||||
function upgradePlugins(callback) {
|
||||
var standalone = false;
|
||||
if (typeof callback !== 'function') {
|
||||
callback = function () {};
|
||||
standalone = true;
|
||||
}
|
||||
|
||||
checkPlugins(standalone, function (err, found) {
|
||||
if (err) {
|
||||
process.stdout.write('Warning'.yellow + ': An unexpected error occured when attempting to verify plugin upgradability\n'.reset);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (found && found.length) {
|
||||
process.stdout.write('\nA total of ' + String(found.length).bold + ' package(s) can be upgraded:\n');
|
||||
found.forEach(function (suggestObj) {
|
||||
process.stdout.write(' * '.yellow + suggestObj.name.reset + ' (' + suggestObj.current.yellow + ' -> '.reset + suggestObj.suggested.green + ')\n'.reset);
|
||||
});
|
||||
process.stdout.write('\n');
|
||||
} else {
|
||||
if (standalone) {
|
||||
process.stdout.write('\nAll packages up-to-date!'.green + '\n'.reset);
|
||||
}
|
||||
return callback();
|
||||
}
|
||||
|
||||
prompt.message = '';
|
||||
prompt.delimiter = '';
|
||||
|
||||
prompt.start();
|
||||
prompt.get({
|
||||
name: 'upgrade',
|
||||
description: 'Proceed with upgrade (y|n)?'.reset,
|
||||
type: 'string',
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (['y', 'Y', 'yes', 'YES'].indexOf(result.upgrade) !== -1) {
|
||||
process.stdout.write('\nUpgrading packages...');
|
||||
var args = ['i'];
|
||||
found.forEach(function (suggestObj) {
|
||||
args.push(suggestObj.name + '@' + suggestObj.suggested);
|
||||
});
|
||||
|
||||
cproc.execFile((process.platform === 'win32') ? 'npm.cmd' : 'npm', args, { stdio: 'ignore' }, function (err) {
|
||||
if (!err) {
|
||||
process.stdout.write(' OK\n'.green);
|
||||
}
|
||||
|
||||
callback(err);
|
||||
});
|
||||
} else {
|
||||
process.stdout.write('\nPackage upgrades skipped'.yellow + '. Check for upgrades at any time by running "'.reset + './nodebb upgrade-plugins'.green + '".\n'.reset);
|
||||
callback();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
var commands = {
|
||||
status: {
|
||||
description: 'View the status of the NodeBB server',
|
||||
usage: 'Usage: ' + './nodebb status'.yellow,
|
||||
handler: function () {
|
||||
getRunningPid(function (err, pid) {
|
||||
if (!err) {
|
||||
process.stdout.write('\nNodeBB Running '.bold + '(pid '.cyan + pid.toString().cyan + ')\n'.cyan);
|
||||
process.stdout.write('\t"' + './nodebb stop'.yellow + '" to stop the NodeBB server\n');
|
||||
process.stdout.write('\t"' + './nodebb log'.yellow + '" to view server output\n');
|
||||
process.stdout.write('\t"' + './nodebb restart'.yellow + '" to restart NodeBB\n\n');
|
||||
} else {
|
||||
process.stdout.write('\nNodeBB is not running\n'.bold);
|
||||
process.stdout.write('\t"' + './nodebb start'.yellow + '" to launch the NodeBB server\n\n'.reset);
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
start: {
|
||||
description: 'Start the NodeBB server',
|
||||
usage: 'Usage: ' + './nodebb start'.yellow,
|
||||
handler: function () {
|
||||
process.stdout.write('\nStarting NodeBB\n'.bold);
|
||||
process.stdout.write(' "' + './nodebb stop'.yellow + '" to stop the NodeBB server\n');
|
||||
process.stdout.write(' "' + './nodebb log'.yellow + '" to view server output\n');
|
||||
process.stdout.write(' "' + './nodebb restart'.yellow + '" to restart NodeBB\n\n'.reset);
|
||||
|
||||
// Spawn a new NodeBB process
|
||||
cproc.fork(loaderPath, process.argv.slice(3), {
|
||||
env: process.env,
|
||||
});
|
||||
},
|
||||
},
|
||||
stop: {
|
||||
description: 'Stop the NodeBB server',
|
||||
usage: 'Usage: ' + './nodebb stop'.yellow,
|
||||
handler: function () {
|
||||
getRunningPid(function (err, pid) {
|
||||
if (!err) {
|
||||
process.kill(pid, 'SIGTERM');
|
||||
process.stdout.write('Stopping NodeBB. Goodbye!\n');
|
||||
} else {
|
||||
process.stdout.write('NodeBB is already stopped.\n');
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
restart: {
|
||||
description: 'Restart the NodeBB server',
|
||||
usage: 'Usage: ' + './nodebb restart'.yellow,
|
||||
handler: function () {
|
||||
getRunningPid(function (err, pid) {
|
||||
if (!err) {
|
||||
process.kill(pid, 'SIGHUP');
|
||||
process.stdout.write('\nRestarting NodeBB\n'.bold);
|
||||
} else {
|
||||
process.stdout.write('NodeBB could not be restarted, as a running instance could not be found.\n');
|
||||
}
|
||||
});
|
||||
},
|
||||
},
|
||||
log: {
|
||||
description: 'Open the output log (useful for debugging)',
|
||||
usage: 'Usage: ' + './nodebb log'.yellow,
|
||||
handler: function () {
|
||||
process.stdout.write('\nHit '.red + 'Ctrl-C '.bold + 'to exit'.red);
|
||||
process.stdout.write('\n\n'.reset);
|
||||
cproc.spawn('tail', ['-F', './logs/output.log'], {
|
||||
cwd: __dirname,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
},
|
||||
},
|
||||
slog: {
|
||||
description: 'Start the NodeBB server and view the live output log',
|
||||
usage: 'Usage: ' + './nodebb slog'.yellow,
|
||||
handler: function () {
|
||||
process.stdout.write('\nStarting NodeBB with logging output\n'.bold);
|
||||
process.stdout.write('\nHit '.red + 'Ctrl-C '.bold + 'to exit'.red);
|
||||
process.stdout.write('\n\n'.reset);
|
||||
|
||||
// Spawn a new NodeBB process
|
||||
cproc.fork(loaderPath, {
|
||||
env: process.env,
|
||||
});
|
||||
cproc.spawn('tail', ['-F', './logs/output.log'], {
|
||||
cwd: __dirname,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
},
|
||||
},
|
||||
dev: {
|
||||
description: 'Start NodeBB in verbose development mode',
|
||||
usage: 'Usage: ' + './nodebb dev'.yellow,
|
||||
handler: function () {
|
||||
process.env.NODE_ENV = 'development';
|
||||
cproc.fork(loaderPath, ['--no-daemon', '--no-silent'], {
|
||||
env: process.env,
|
||||
});
|
||||
},
|
||||
},
|
||||
build: {
|
||||
description: 'Compile static assets (CSS, Javascript, etc)',
|
||||
usage: 'Usage: ' + './nodebb build'.yellow + ' [js,clientCSS,acpCSS,tpl,lang]'.red + '\n' +
|
||||
' e.g. ' + './nodebb build js,tpl'.yellow + '\tbuilds JS and templates\n' +
|
||||
' ' + './nodebb build'.yellow + '\t\tbuilds all targets\n',
|
||||
handler: function () {
|
||||
var arr = ['--build'].concat(process.argv.slice(3));
|
||||
fork(arr);
|
||||
},
|
||||
},
|
||||
setup: {
|
||||
description: 'Run the NodeBB setup script',
|
||||
usage: 'Usage: ' + './nodebb setup'.yellow,
|
||||
handler: function () {
|
||||
var arr = ['--setup'].concat(process.argv.slice(3));
|
||||
fork(arr);
|
||||
},
|
||||
},
|
||||
reset: {
|
||||
description: 'Disable plugins and restore the default theme',
|
||||
usage: 'Usage: ' + './nodebb reset '.yellow + '{-t|-p|-w|-s|-a}'.red + '\n' +
|
||||
' -t <theme>\tuse specified theme\n' +
|
||||
' -p <plugin>\tdisable specified plugin\n' +
|
||||
'\n' +
|
||||
' -t\t\tuse default theme\n' +
|
||||
' -p\t\tdisable all but core plugins\n' +
|
||||
' -w\t\twidgets\n' +
|
||||
' -s\t\tsettings\n' +
|
||||
' -a\t\tall of the above\n',
|
||||
handler: function () {
|
||||
var arr = ['--reset'].concat(process.argv.slice(3));
|
||||
fork(arr);
|
||||
},
|
||||
},
|
||||
activate: {
|
||||
description: 'Activate a plugin for the next startup of NodeBB',
|
||||
usage: 'Usage: ' + './nodebb activate <plugin>'.yellow,
|
||||
handler: function () {
|
||||
var name = args._[1];
|
||||
if (!name) {
|
||||
process.stdout.write(commands.activate.usage + '\n');
|
||||
process.exit();
|
||||
}
|
||||
if (name.startsWith('nodebb-theme')) {
|
||||
fork(['--reset', '-t', name]);
|
||||
return;
|
||||
}
|
||||
var arr = ['--activate=' + name].concat(process.argv.slice(4));
|
||||
fork(arr);
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
description: 'List all installed plugins',
|
||||
usage: 'Usage: ' + './nodebb plugins'.yellow,
|
||||
handler: function () {
|
||||
var arr = ['--plugins'].concat(process.argv.slice(3));
|
||||
fork(arr);
|
||||
},
|
||||
},
|
||||
upgrade: {
|
||||
description: 'Run NodeBB upgrade scripts, ensure packages are up-to-date',
|
||||
usage: 'Usage: ' + './nodebb upgrade'.yellow,
|
||||
handler: function () {
|
||||
if (process.argv[3]) {
|
||||
process.stdout.write('\nUpdating NodeBB data store schema...\n'.yellow);
|
||||
var arr = ['--upgrade'].concat(process.argv.slice(3));
|
||||
var upgradeProc = fork(arr);
|
||||
|
||||
return upgradeProc.on('close', function (err) {
|
||||
if (err) {
|
||||
process.stdout.write('Error occurred during upgrade');
|
||||
throw err;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async.series([
|
||||
function (next) {
|
||||
packageInstall.updatePackageFile();
|
||||
packageInstall.preserveExtraneousPlugins();
|
||||
next();
|
||||
},
|
||||
function (next) {
|
||||
process.stdout.write('1. '.bold + 'Bringing base dependencies up to date... \n'.yellow);
|
||||
packageInstall.npmInstallProduction();
|
||||
next();
|
||||
},
|
||||
function (next) {
|
||||
process.stdout.write('OK\n'.green);
|
||||
process.stdout.write('2. '.bold + 'Checking installed plugins for updates... '.yellow);
|
||||
upgradePlugins(next);
|
||||
},
|
||||
function (next) {
|
||||
process.stdout.write('3. '.bold + 'Updating NodeBB data store schema...\n'.yellow);
|
||||
var arr = ['--upgrade'].concat(process.argv.slice(3));
|
||||
var upgradeProc = fork(arr);
|
||||
|
||||
upgradeProc.on('close', next);
|
||||
upgradeProc.on('error', next);
|
||||
},
|
||||
], function (err) {
|
||||
if (err) {
|
||||
process.stdout.write('Error occurred during upgrade');
|
||||
throw err;
|
||||
}
|
||||
|
||||
var 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
|
||||
var columns = process.stdout.columns;
|
||||
var spaces = columns ? new Array(Math.floor(columns / 2) - (message.length / 2) + 1).join(' ') : ' ';
|
||||
|
||||
process.stdout.write('OK\n'.green);
|
||||
process.stdout.write('\n' + spaces + message.green.bold + '\n\n'.reset);
|
||||
});
|
||||
},
|
||||
},
|
||||
upgradePlugins: {
|
||||
hidden: true,
|
||||
description: '',
|
||||
handler: function () {
|
||||
upgradePlugins();
|
||||
},
|
||||
},
|
||||
events: {
|
||||
description: 'Outputs the last ten (10) administrative events recorded by NodeBB',
|
||||
usage: 'Usage: ' + './nodebb events'.yellow,
|
||||
handler: function () {
|
||||
fork(['--events']);
|
||||
},
|
||||
},
|
||||
help: {
|
||||
description: 'Display the help message for a given command',
|
||||
usage: 'Usage: ' + './nodebb help <command>'.yellow,
|
||||
handler: function () {
|
||||
var command = commands[args._[1]];
|
||||
if (command) {
|
||||
process.stdout.write(command.description + '\n'.reset);
|
||||
process.stdout.write(command.usage + '\n'.reset);
|
||||
|
||||
return;
|
||||
}
|
||||
var keys = Object.keys(commands).filter(function (key) {
|
||||
return !commands[key].hidden;
|
||||
});
|
||||
|
||||
process.stdout.write('\nWelcome to NodeBB\n\n'.bold);
|
||||
process.stdout.write('Usage: ./nodebb {' + keys.join('|') + '}\n\n');
|
||||
|
||||
var usage = keys.map(function (key) {
|
||||
var line = '\t' + key.yellow + (key.length < 8 ? '\t\t' : '\t');
|
||||
return line + commands[key].description;
|
||||
}).join('\n');
|
||||
|
||||
process.stdout.write(usage + '\n'.reset);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
commands['upgrade-plugins'] = commands.upgradePlugins;
|
||||
|
||||
if (!commands[args._[0]]) {
|
||||
commands.help.handler();
|
||||
} else {
|
||||
commands[args._[0]].handler();
|
||||
}
|
||||
require('./src/cli');
|
||||
|
||||
@@ -1 +1 @@
|
||||
node ./nodebb %*
|
||||
@echo off && cd %~dp0 && node ./src/cli %*
|
||||
|
||||
127
src/cli/colors.js
Normal file
127
src/cli/colors.js
Normal file
@@ -0,0 +1,127 @@
|
||||
'use strict';
|
||||
|
||||
|
||||
// override commander functions
|
||||
// to include color styling in the output
|
||||
// so the CLI looks nice
|
||||
|
||||
var Command = require('commander').Command;
|
||||
|
||||
var commandColor = 'yellow';
|
||||
var optionColor = 'cyan';
|
||||
var argColor = 'magenta';
|
||||
var subCommandColor = 'green';
|
||||
var subOptionColor = 'blue';
|
||||
var subArgColor = 'red';
|
||||
|
||||
Command.prototype.helpInformation = function () {
|
||||
var desc = [];
|
||||
if (this._description) {
|
||||
desc = [
|
||||
' ' + this._description,
|
||||
'',
|
||||
];
|
||||
}
|
||||
|
||||
var cmdName = this._name;
|
||||
if (this._alias) {
|
||||
cmdName = cmdName + ' | ' + this._alias;
|
||||
}
|
||||
var usage = [
|
||||
'',
|
||||
' Usage: ' + cmdName[commandColor] + ' '.reset + this.usage(),
|
||||
'',
|
||||
];
|
||||
|
||||
var cmds = [];
|
||||
var commandHelp = this.commandHelp();
|
||||
if (commandHelp) {
|
||||
cmds = [commandHelp];
|
||||
}
|
||||
|
||||
var options = [
|
||||
'',
|
||||
' Options:',
|
||||
'',
|
||||
'' + this.optionHelp().replace(/^/gm, ' '),
|
||||
'',
|
||||
];
|
||||
|
||||
return usage
|
||||
.concat(desc)
|
||||
.concat(options)
|
||||
.concat(cmds)
|
||||
.join('\n'.reset);
|
||||
};
|
||||
|
||||
function humanReadableArgName(arg) {
|
||||
var nameOutput = arg.name + (arg.variadic === true ? '...' : '');
|
||||
|
||||
return arg.required ? '<' + nameOutput + '>' : '[' + nameOutput + ']';
|
||||
}
|
||||
|
||||
Command.prototype.usage = function () {
|
||||
var args = this._args.map(function (arg) {
|
||||
return humanReadableArgName(arg);
|
||||
});
|
||||
|
||||
var usage = '[options]'[optionColor] +
|
||||
(this.commands.length ? ' [command]' : '')[subCommandColor] +
|
||||
(this._args.length ? ' ' + args.join(' ') : '')[argColor];
|
||||
|
||||
return usage;
|
||||
};
|
||||
|
||||
function pad(str, width) {
|
||||
var len = Math.max(0, width - str.length);
|
||||
return str + Array(len + 1).join(' ');
|
||||
}
|
||||
|
||||
Command.prototype.commandHelp = function () {
|
||||
if (!this.commands.length) {
|
||||
return '';
|
||||
}
|
||||
|
||||
var commands = this.commands.filter(function (cmd) {
|
||||
return !cmd._noHelp;
|
||||
}).map(function (cmd) {
|
||||
var args = cmd._args.map(function (arg) {
|
||||
return humanReadableArgName(arg);
|
||||
}).join(' ');
|
||||
|
||||
return [
|
||||
cmd._name[subCommandColor] +
|
||||
(cmd._alias ? ' | ' + cmd._alias : '')[subCommandColor] +
|
||||
(cmd.options.length ? ' [options]' : '')[subOptionColor] +
|
||||
' ' + args[subArgColor],
|
||||
cmd._description,
|
||||
];
|
||||
});
|
||||
|
||||
var width = commands.reduce(function (max, command) {
|
||||
return Math.max(max, command[0].length);
|
||||
}, 0);
|
||||
|
||||
return [
|
||||
'',
|
||||
' Commands:',
|
||||
'',
|
||||
commands.map(function (cmd) {
|
||||
var desc = cmd[1] ? ' ' + cmd[1] : '';
|
||||
return pad(cmd[0], width) + desc;
|
||||
}).join('\n').replace(/^/gm, ' '),
|
||||
'',
|
||||
].join('\n');
|
||||
};
|
||||
|
||||
Command.prototype.optionHelp = function () {
|
||||
var width = this.largestOptionLength();
|
||||
|
||||
// Append the help information
|
||||
return this.options
|
||||
.map(function (option) {
|
||||
return pad(option.flags, width)[optionColor] + ' ' + option.description;
|
||||
})
|
||||
.concat([pad('-h, --help', width)[optionColor] + ' output usage information'])
|
||||
.join('\n');
|
||||
};
|
||||
265
src/cli/index.js
Normal file
265
src/cli/index.js
Normal file
@@ -0,0 +1,265 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var packageInstall = require('../meta/package-install');
|
||||
var dirname = require('./paths').baseDir;
|
||||
|
||||
// check to make sure dependencies are installed
|
||||
try {
|
||||
fs.readFileSync(path.join(dirname, 'package.json'));
|
||||
fs.readFileSync(path.join(dirname, 'node_modules/async/package.json'));
|
||||
} catch (e) {
|
||||
if (e.code === 'ENOENT') {
|
||||
process.stdout.write('Dependencies not yet installed.\n');
|
||||
process.stdout.write('Installing them now...\n\n');
|
||||
|
||||
packageInstall.updatePackageFile();
|
||||
packageInstall.preserveExtraneousPlugins();
|
||||
packageInstall.npmInstallProduction();
|
||||
|
||||
require('colors');
|
||||
process.stdout.write('OK'.green + '\n'.reset);
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
require('colors');
|
||||
var nconf = require('nconf');
|
||||
var program = require('commander');
|
||||
|
||||
var pkg = require('../../package.json');
|
||||
var file = require('../file');
|
||||
var prestart = require('../prestart');
|
||||
|
||||
program
|
||||
.name('./nodebb')
|
||||
.description('Welcome to NodeBB')
|
||||
.version(pkg.version)
|
||||
.option('--json-logging', 'Output to logs in JSON format', false)
|
||||
.option('--log-level <level>', 'Default logging level to use', 'info')
|
||||
.option('-d, --dev', 'Development mode, including verbose logging', false)
|
||||
.option('-l, --log', 'Log subprocess output to console', false)
|
||||
.option('-c, --config <value>', 'Specify a config file', 'config.json')
|
||||
.parse(process.argv);
|
||||
|
||||
nconf.argv().env({
|
||||
separator: '__',
|
||||
});
|
||||
|
||||
var env = program.dev ? 'development' : (process.env.NODE_ENV || 'production');
|
||||
process.env.NODE_ENV = env;
|
||||
global.env = env;
|
||||
|
||||
prestart.setupWinston();
|
||||
|
||||
// Alternate configuration file support
|
||||
var configFile = path.resolve(dirname, program.config);
|
||||
var configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database'));
|
||||
|
||||
prestart.loadConfig(configFile);
|
||||
prestart.versionCheck();
|
||||
|
||||
if (!configExists && process.argv[2] !== 'setup') {
|
||||
require('./setup').webInstall();
|
||||
return;
|
||||
}
|
||||
|
||||
// running commands
|
||||
program
|
||||
.command('start')
|
||||
.description('Start the NodeBB server')
|
||||
.action(function () {
|
||||
require('./running').start(program);
|
||||
});
|
||||
program
|
||||
.command('slog', null, {
|
||||
noHelp: true,
|
||||
})
|
||||
.description('Start the NodeBB server and view the live output log')
|
||||
.action(function () {
|
||||
program.log = true;
|
||||
require('./running').start(program);
|
||||
});
|
||||
program
|
||||
.command('dev', null, {
|
||||
noHelp: true,
|
||||
})
|
||||
.description('Start NodeBB in verbose development mode')
|
||||
.action(function () {
|
||||
program.dev = true;
|
||||
process.env.NODE_ENV = 'development';
|
||||
global.env = 'development';
|
||||
require('./running').start(program);
|
||||
});
|
||||
program
|
||||
.command('stop')
|
||||
.description('Stop the NodeBB server')
|
||||
.action(function () {
|
||||
require('./running').stop(program);
|
||||
});
|
||||
program
|
||||
.command('restart')
|
||||
.description('Restart the NodeBB server')
|
||||
.action(function () {
|
||||
require('./running').restart(program);
|
||||
});
|
||||
program
|
||||
.command('status')
|
||||
.description('Check the running status of the NodeBB server')
|
||||
.action(function () {
|
||||
require('./running').status(program);
|
||||
});
|
||||
program
|
||||
.command('log')
|
||||
.description('Open the output log (useful for debugging)')
|
||||
.action(function () {
|
||||
require('./running').log(program);
|
||||
});
|
||||
|
||||
// management commands
|
||||
program
|
||||
.command('setup')
|
||||
.description('Run the NodeBB setup script')
|
||||
.action(function () {
|
||||
require('./setup').setup();
|
||||
});
|
||||
|
||||
program
|
||||
.command('install')
|
||||
.description('Launch the NodeBB web installer for configuration setup')
|
||||
.action(function () {
|
||||
require('./setup').webInstall();
|
||||
});
|
||||
program
|
||||
.command('build [targets...]')
|
||||
.description('Compile static assets ' + '(JS, CSS, templates, languages, sounds)'.red)
|
||||
.action(function (targets) {
|
||||
require('./manage').build(targets.length ? targets : true);
|
||||
})
|
||||
.on('--help', function () {
|
||||
require('./manage').buildTargets();
|
||||
});
|
||||
program
|
||||
.command('activate [plugin]')
|
||||
.description('Activate a plugin for the next startup of NodeBB (nodebb-plugin- prefix is optional)')
|
||||
.action(function (plugin) {
|
||||
require('./manage').activate(plugin);
|
||||
});
|
||||
program
|
||||
.command('plugins')
|
||||
.action(function () {
|
||||
require('./manage').listPlugins();
|
||||
})
|
||||
.description('List all installed plugins');
|
||||
program
|
||||
.command('events')
|
||||
.description('Outputs the last ten (10) administrative events recorded by NodeBB')
|
||||
.action(function () {
|
||||
require('./manage').listEvents();
|
||||
});
|
||||
program
|
||||
.command('info')
|
||||
.description('Outputs various system info')
|
||||
.action(function () {
|
||||
require('./manage').info();
|
||||
});
|
||||
|
||||
// reset
|
||||
var resetCommand = program.command('reset');
|
||||
|
||||
resetCommand
|
||||
.description('Reset plugins, themes, settings, etc')
|
||||
.option('-t, --theme [theme]', 'Reset to [theme] or to the default theme')
|
||||
.option('-p, --plugin [plugin]', 'Disable [plugin] or all plugins')
|
||||
.option('-w, --widgets', 'Disable all widgets')
|
||||
.option('-s, --settings', 'Reset settings to their default values')
|
||||
.option('-a, --all', 'All of the above')
|
||||
.action(function (options) {
|
||||
var valid = ['theme', 'plugin', 'widgets', 'settings', 'all'].some(function (x) {
|
||||
return options[x];
|
||||
});
|
||||
if (!valid) {
|
||||
process.stdout.write('\n No valid options passed in, so nothing was reset.\n'.red);
|
||||
resetCommand.help();
|
||||
}
|
||||
|
||||
require('./reset').reset(options, function (err) {
|
||||
if (err) { throw err; }
|
||||
require('../meta/build').buildAll(function (err) {
|
||||
if (err) { throw err; }
|
||||
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// upgrades
|
||||
program
|
||||
.command('upgrade [scripts...]')
|
||||
.description('Run NodeBB upgrade scripts and ensure packages are up-to-date, or run a particular upgrade script')
|
||||
.option('-m, --package', 'Update package.json from defaults', false)
|
||||
.option('-i, --install', 'Bringing base dependencies up to date', false)
|
||||
.option('-p, --plugins', 'Check installed plugins for updates', false)
|
||||
.option('-s, --schema', 'Update NodeBB data store schema', false)
|
||||
.option('-b, --build', 'Rebuild assets', false)
|
||||
.on('--help', function () {
|
||||
process.stdout.write(
|
||||
'\n' +
|
||||
'When running particular upgrade scripts, options are ignored.\n' +
|
||||
'By default all options are enabled. Passing any options disables that default.\n' +
|
||||
'Only package and dependency updates: ' + './nodebb upgrade -mi\n'.yellow +
|
||||
'Only database update: ' + './nodebb upgrade -d\n\n'.yellow
|
||||
);
|
||||
})
|
||||
.action(function (scripts, options) {
|
||||
require('./upgrade').upgrade(scripts.length ? scripts : true, options);
|
||||
});
|
||||
|
||||
program
|
||||
.command('upgrade-plugins', null, {
|
||||
noHelp: true,
|
||||
})
|
||||
.alias('upgradePlugins')
|
||||
.description('Upgrade plugins')
|
||||
.action(function () {
|
||||
require('./upgrade-plugins').upgradePlugins(function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
process.stdout.write('OK\n'.green);
|
||||
process.exit();
|
||||
});
|
||||
});
|
||||
|
||||
program
|
||||
.command('help [command]')
|
||||
.description('Display help for [command]')
|
||||
.action(function (name) {
|
||||
if (!name) {
|
||||
return program.help();
|
||||
}
|
||||
|
||||
var command = program.commands.find(function (command) { return command._name === name; });
|
||||
if (command) {
|
||||
command.help();
|
||||
} else {
|
||||
program.help();
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command('*', {}, {
|
||||
noHelp: true,
|
||||
})
|
||||
.action(function () {
|
||||
program.help();
|
||||
});
|
||||
|
||||
require('./colors');
|
||||
|
||||
program.executables = false;
|
||||
|
||||
program.parse(process.argv);
|
||||
143
src/cli/manage.js
Normal file
143
src/cli/manage.js
Normal file
@@ -0,0 +1,143 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var winston = require('winston');
|
||||
var childProcess = require('child_process');
|
||||
var _ = require('lodash');
|
||||
|
||||
var build = require('../meta/build');
|
||||
var db = require('../database');
|
||||
var plugins = require('../plugins');
|
||||
var events = require('../events');
|
||||
var reset = require('./reset');
|
||||
|
||||
function buildTargets() {
|
||||
var aliases = build.aliases;
|
||||
var length = 0;
|
||||
var output = Object.keys(aliases).map(function (name) {
|
||||
var arr = aliases[name];
|
||||
if (name.length > length) {
|
||||
length = name.length;
|
||||
}
|
||||
|
||||
return [name, arr.join(', ')];
|
||||
}).map(function (tuple) {
|
||||
return ' ' + _.padEnd('"' + tuple[0] + '"', length + 2).magenta + ' | ' + tuple[1];
|
||||
}).join('\n');
|
||||
process.stdout.write(
|
||||
'\n\n Build targets:\n' +
|
||||
('\n ' + _.padEnd('Target', length + 2) + ' | Aliases').green +
|
||||
'\n ------------------------------------------------------\n'.blue +
|
||||
output + '\n\n'
|
||||
);
|
||||
}
|
||||
|
||||
function activate(plugin) {
|
||||
if (plugin.startsWith('nodebb-theme-')) {
|
||||
reset.reset({
|
||||
theme: plugin,
|
||||
}, function (err) {
|
||||
if (err) { throw err; }
|
||||
process.exit();
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.init(next);
|
||||
},
|
||||
function (next) {
|
||||
if (!plugin.startsWith('nodebb-')) {
|
||||
// Allow omission of `nodebb-plugin-`
|
||||
plugin = 'nodebb-plugin-' + plugin;
|
||||
}
|
||||
plugins.isInstalled(plugin, next);
|
||||
},
|
||||
function (isInstalled, next) {
|
||||
if (!isInstalled) {
|
||||
return next(new Error('plugin not installed'));
|
||||
}
|
||||
|
||||
winston.info('Activating plugin `%s`', plugin);
|
||||
db.sortedSetAdd('plugins:active', 0, plugin, next);
|
||||
},
|
||||
function (next) {
|
||||
events.log({
|
||||
type: 'plugin-activate',
|
||||
text: plugin,
|
||||
}, next);
|
||||
},
|
||||
], function (err) {
|
||||
if (err) {
|
||||
winston.error('An error occurred during plugin activation', err);
|
||||
throw err;
|
||||
}
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
function listPlugins() {
|
||||
async.waterfall([
|
||||
db.init,
|
||||
function (next) {
|
||||
db.getSortedSetRange('plugins:active', 0, -1, next);
|
||||
},
|
||||
function (plugins) {
|
||||
winston.info('Active plugins: \n\t - ' + plugins.join('\n\t - '));
|
||||
process.exit();
|
||||
},
|
||||
], function (err) {
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
function listEvents() {
|
||||
async.series([
|
||||
db.init,
|
||||
events.output,
|
||||
]);
|
||||
}
|
||||
|
||||
function info() {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
var version = require('../../package.json').version;
|
||||
process.stdout.write('\n version: ' + version);
|
||||
|
||||
process.stdout.write('\n Node ver: ' + process.version);
|
||||
next();
|
||||
},
|
||||
function (next) {
|
||||
process.stdout.write('\n git hash: ');
|
||||
childProcess.execSync('git rev-parse HEAD', {
|
||||
stdio: 'inherit',
|
||||
});
|
||||
next();
|
||||
},
|
||||
function (next) {
|
||||
var config = require('../../config.json');
|
||||
process.stdout.write('\n database: ' + config.database);
|
||||
next();
|
||||
},
|
||||
db.init,
|
||||
function (next) {
|
||||
db.info(db.client, next);
|
||||
},
|
||||
function (info, next) {
|
||||
process.stdout.write('\n version: ' + info.version);
|
||||
process.stdout.write('\n engine: ' + info.storageEngine);
|
||||
next();
|
||||
},
|
||||
], function (err) {
|
||||
if (err) { throw err; }
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
|
||||
exports.build = build.build;
|
||||
exports.buildTargets = buildTargets;
|
||||
exports.activate = activate;
|
||||
exports.listPlugins = listPlugins;
|
||||
exports.listEvents = listEvents;
|
||||
exports.info = info;
|
||||
15
src/cli/paths.js
Normal file
15
src/cli/paths.js
Normal file
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
var path = require('path');
|
||||
|
||||
var baseDir = path.join(__dirname, '../../');
|
||||
var loader = path.join(baseDir, 'loader.js');
|
||||
var app = path.join(baseDir, 'app.js');
|
||||
var pidfile = path.join(baseDir, 'pidfile');
|
||||
|
||||
module.exports = {
|
||||
baseDir: baseDir,
|
||||
loader: loader,
|
||||
app: app,
|
||||
pidfile: pidfile,
|
||||
};
|
||||
@@ -3,79 +3,85 @@
|
||||
require('colors');
|
||||
var path = require('path');
|
||||
var winston = require('winston');
|
||||
var nconf = require('nconf');
|
||||
var async = require('async');
|
||||
var db = require('./database');
|
||||
var events = require('./events');
|
||||
var fs = require('fs');
|
||||
|
||||
var Reset = {};
|
||||
var db = require('../database');
|
||||
var events = require('../events');
|
||||
var meta = require('../meta');
|
||||
var plugins = require('../plugins');
|
||||
var widgets = require('../widgets');
|
||||
|
||||
Reset.reset = function (callback) {
|
||||
db.init(function (err) {
|
||||
if (err) {
|
||||
winston.error(err);
|
||||
throw err;
|
||||
}
|
||||
var dirname = require('./paths').baseDir;
|
||||
|
||||
if (nconf.get('t')) {
|
||||
var themeId = nconf.get('t');
|
||||
exports.reset = function (options, callback) {
|
||||
var map = {
|
||||
theme: function (next) {
|
||||
var themeId = options.theme;
|
||||
if (themeId === true) {
|
||||
resetThemes(callback);
|
||||
resetThemes(next);
|
||||
} else {
|
||||
if (themeId.indexOf('nodebb-') !== 0) {
|
||||
if (!themeId.startsWith('nodebb-theme-')) {
|
||||
// Allow omission of `nodebb-theme-`
|
||||
themeId = 'nodebb-theme-' + themeId;
|
||||
}
|
||||
|
||||
resetTheme(themeId, callback);
|
||||
resetTheme(themeId, next);
|
||||
}
|
||||
} else if (nconf.get('p')) {
|
||||
var pluginId = nconf.get('p');
|
||||
},
|
||||
plugin: function (next) {
|
||||
var pluginId = options.plugin;
|
||||
if (pluginId === true) {
|
||||
resetPlugins(callback);
|
||||
resetPlugins(next);
|
||||
} else {
|
||||
if (pluginId.indexOf('nodebb-') !== 0) {
|
||||
if (!pluginId.startsWith('nodebb-plugin-')) {
|
||||
// Allow omission of `nodebb-plugin-`
|
||||
pluginId = 'nodebb-plugin-' + pluginId;
|
||||
}
|
||||
|
||||
resetPlugin(pluginId, callback);
|
||||
resetPlugin(pluginId, next);
|
||||
}
|
||||
} else if (nconf.get('w')) {
|
||||
resetWidgets(callback);
|
||||
} else if (nconf.get('s')) {
|
||||
resetSettings(callback);
|
||||
} else if (nconf.get('a')) {
|
||||
require('async').series([resetWidgets, resetThemes, resetPlugins, resetSettings], function (err) {
|
||||
if (!err) {
|
||||
winston.info('[reset] Reset complete.');
|
||||
} else {
|
||||
winston.error('[reset] Errors were encountered while resetting your forum settings: %s', err);
|
||||
}
|
||||
},
|
||||
widgets: resetWidgets,
|
||||
settings: resetSettings,
|
||||
all: function (next) {
|
||||
async.series([resetWidgets, resetThemes, resetPlugins, resetSettings], next);
|
||||
},
|
||||
};
|
||||
|
||||
callback();
|
||||
});
|
||||
} else {
|
||||
process.stdout.write('\nNodeBB Reset\n'.bold);
|
||||
process.stdout.write('No arguments passed in, so nothing was reset.\n\n'.yellow);
|
||||
process.stdout.write('Use ./nodebb reset ' + '{-t|-p|-w|-s|-a}\n'.red);
|
||||
process.stdout.write(' -t\tthemes\n');
|
||||
process.stdout.write(' -p\tplugins\n');
|
||||
process.stdout.write(' -w\twidgets\n');
|
||||
process.stdout.write(' -s\tsettings\n');
|
||||
process.stdout.write(' -a\tall of the above\n');
|
||||
var tasks = Object.keys(map)
|
||||
.filter(function (x) { return options[x]; })
|
||||
.map(function (x) { return map[x]; });
|
||||
|
||||
process.stdout.write('\nPlugin and theme reset flags (-p & -t) can take a single argument\n');
|
||||
process.stdout.write(' e.g. ./nodebb reset -p nodebb-plugin-mentions, ./nodebb reset -t nodebb-theme-persona\n');
|
||||
process.stdout.write(' Prefix is optional, e.g. ./nodebb reset -p markdown, ./nodebb reset -t persona\n');
|
||||
if (!tasks.length) {
|
||||
process.stdout.write('\nNodeBB Reset\n'.bold);
|
||||
process.stdout.write('No arguments passed in, so nothing was reset.\n\n'.yellow);
|
||||
process.stdout.write('Use ./nodebb reset ' + '{-t|-p|-w|-s|-a}\n'.red);
|
||||
process.stdout.write(' -t\tthemes\n');
|
||||
process.stdout.write(' -p\tplugins\n');
|
||||
process.stdout.write(' -w\twidgets\n');
|
||||
process.stdout.write(' -s\tsettings\n');
|
||||
process.stdout.write(' -a\tall of the above\n');
|
||||
|
||||
process.exit(0);
|
||||
process.stdout.write('\nPlugin and theme reset flags (-p & -t) can take a single argument\n');
|
||||
process.stdout.write(' e.g. ./nodebb reset -p nodebb-plugin-mentions, ./nodebb reset -t nodebb-theme-persona\n');
|
||||
process.stdout.write(' Prefix is optional, e.g. ./nodebb reset -p markdown, ./nodebb reset -t persona\n');
|
||||
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
async.series([db.init].concat(tasks), function (err) {
|
||||
if (err) {
|
||||
winston.error('[reset] Errors were encountered during reset', err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
winston.info('[reset] Reset complete');
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
function resetSettings(callback) {
|
||||
var meta = require('./meta');
|
||||
meta.configs.set('allowLocalLogin', 1, function (err) {
|
||||
winston.info('[reset] Settings reset to default');
|
||||
callback(err);
|
||||
@@ -83,10 +89,7 @@ function resetSettings(callback) {
|
||||
}
|
||||
|
||||
function resetTheme(themeId, callback) {
|
||||
var meta = require('./meta');
|
||||
var fs = require('fs');
|
||||
|
||||
fs.access(path.join(__dirname, '../node_modules', themeId, 'package.json'), function (err) {
|
||||
fs.access(path.join(dirname, 'node_modules', themeId, 'package.json'), function (err) {
|
||||
if (err) {
|
||||
winston.warn('[reset] Theme `%s` is not installed on this forum', themeId);
|
||||
callback(new Error('theme-not-found'));
|
||||
@@ -108,8 +111,6 @@ function resetTheme(themeId, callback) {
|
||||
}
|
||||
|
||||
function resetThemes(callback) {
|
||||
var meta = require('./meta');
|
||||
|
||||
meta.themes.set({
|
||||
type: 'local',
|
||||
id: 'nodebb-theme-persona',
|
||||
@@ -163,13 +164,11 @@ function resetPlugins(callback) {
|
||||
|
||||
function resetWidgets(callback) {
|
||||
async.waterfall([
|
||||
require('./plugins').reload,
|
||||
require('./widgets').reset,
|
||||
plugins.reload,
|
||||
widgets.reset,
|
||||
function (next) {
|
||||
winston.info('[reset] All Widgets moved to Draft Zone');
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
module.exports = Reset;
|
||||
119
src/cli/running.js
Normal file
119
src/cli/running.js
Normal file
@@ -0,0 +1,119 @@
|
||||
'use strict';
|
||||
|
||||
var fs = require('fs');
|
||||
var childProcess = require('child_process');
|
||||
|
||||
var fork = require('../meta/debugFork');
|
||||
var paths = require('./paths');
|
||||
|
||||
var dirname = paths.baseDir;
|
||||
|
||||
function getRunningPid(callback) {
|
||||
fs.readFile(paths.pidfile, {
|
||||
encoding: 'utf-8',
|
||||
}, function (err, pid) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
pid = parseInt(pid, 10);
|
||||
|
||||
try {
|
||||
process.kill(pid, 0);
|
||||
callback(null, pid);
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function start(options) {
|
||||
if (options.dev) {
|
||||
process.env.NODE_ENV = 'development';
|
||||
fork(paths.loader, ['--no-daemon', '--no-silent'], {
|
||||
env: process.env,
|
||||
cwd: dirname,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (options.log) {
|
||||
process.stdout.write('\nStarting NodeBB with logging output\n'.bold);
|
||||
process.stdout.write('\nHit '.red + 'Ctrl-C '.bold + 'to exit'.red);
|
||||
|
||||
process.stdout.write('\nThe NodeBB process will continue to run in the background');
|
||||
process.stdout.write('\nUse "' + './nodebb stop'.yellow + '" to stop the NodeBB server\n');
|
||||
process.stdout.write('\n\n'.reset);
|
||||
} else if (!options.silent) {
|
||||
process.stdout.write('\nStarting NodeBB\n'.bold);
|
||||
process.stdout.write(' "' + './nodebb stop'.yellow + '" to stop the NodeBB server\n');
|
||||
process.stdout.write(' "' + './nodebb log'.yellow + '" to view server output\n');
|
||||
process.stdout.write(' "' + './nodebb restart'.yellow + '" to restart NodeBB\n\n'.reset);
|
||||
}
|
||||
|
||||
// Spawn a new NodeBB process
|
||||
fork(paths.loader, process.argv.slice(3), {
|
||||
env: process.env,
|
||||
cwd: dirname,
|
||||
});
|
||||
if (options.log) {
|
||||
childProcess.spawn('tail', ['-F', './logs/output.log'], {
|
||||
cwd: dirname,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function stop() {
|
||||
getRunningPid(function (err, pid) {
|
||||
if (!err) {
|
||||
process.kill(pid, 'SIGTERM');
|
||||
process.stdout.write('Stopping NodeBB. Goodbye!\n');
|
||||
} else {
|
||||
process.stdout.write('NodeBB is already stopped.\n');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function restart(options) {
|
||||
getRunningPid(function (err, pid) {
|
||||
if (!err) {
|
||||
process.stdout.write('\nRestarting NodeBB\n'.bold);
|
||||
process.kill(pid, 'SIGTERM');
|
||||
|
||||
options.silent = true;
|
||||
start(options);
|
||||
} else {
|
||||
process.stdout.write('NodeBB could not be restarted, as a running instance could not be found.\n');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function status() {
|
||||
getRunningPid(function (err, pid) {
|
||||
if (!err) {
|
||||
process.stdout.write('\nNodeBB Running '.bold + '(pid '.cyan + pid.toString().cyan + ')\n'.cyan);
|
||||
process.stdout.write('\t"' + './nodebb stop'.yellow + '" to stop the NodeBB server\n');
|
||||
process.stdout.write('\t"' + './nodebb log'.yellow + '" to view server output\n');
|
||||
process.stdout.write('\t"' + './nodebb restart'.yellow + '" to restart NodeBB\n\n');
|
||||
} else {
|
||||
process.stdout.write('\nNodeBB is not running\n'.bold);
|
||||
process.stdout.write('\t"' + './nodebb start'.yellow + '" to launch the NodeBB server\n\n'.reset);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function log() {
|
||||
process.stdout.write('\nHit '.red + 'Ctrl-C '.bold + 'to exit'.red);
|
||||
process.stdout.write('\n\n'.reset);
|
||||
childProcess.spawn('tail', ['-F', './logs/output.log'], {
|
||||
cwd: dirname,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
}
|
||||
|
||||
exports.start = start;
|
||||
exports.stop = stop;
|
||||
exports.restart = restart;
|
||||
exports.status = status;
|
||||
exports.log = log;
|
||||
59
src/cli/setup.js
Normal file
59
src/cli/setup.js
Normal file
@@ -0,0 +1,59 @@
|
||||
'use strict';
|
||||
|
||||
var winston = require('winston');
|
||||
var async = require('async');
|
||||
|
||||
var install = require('../../install/web').install;
|
||||
|
||||
function setup() {
|
||||
var install = require('../install');
|
||||
var build = require('../meta/build');
|
||||
var prestart = require('../prestart');
|
||||
|
||||
winston.info('NodeBB Setup Triggered via Command Line');
|
||||
|
||||
process.stdout.write('\nWelcome to NodeBB!\n');
|
||||
process.stdout.write('\nThis looks like a new installation, so you\'ll have to answer a few questions about your environment before we can proceed.\n');
|
||||
process.stdout.write('Press enter to accept the default setting (shown in brackets).\n');
|
||||
|
||||
async.series([
|
||||
install.setup,
|
||||
prestart.loadConfig,
|
||||
build.buildAll,
|
||||
], function (err, data) {
|
||||
// Disregard build step data
|
||||
data = data[0];
|
||||
|
||||
var separator = ' ';
|
||||
if (process.stdout.columns > 10) {
|
||||
for (var x = 0, cols = process.stdout.columns - 10; x < cols; x += 1) {
|
||||
separator += '=';
|
||||
}
|
||||
}
|
||||
process.stdout.write('\n' + separator + '\n\n');
|
||||
|
||||
if (err) {
|
||||
winston.error('There was a problem completing NodeBB setup', err);
|
||||
throw err;
|
||||
} else {
|
||||
if (data.hasOwnProperty('password')) {
|
||||
process.stdout.write('An administrative user was automatically created for you:\n');
|
||||
process.stdout.write(' Username: ' + data.username + '\n');
|
||||
process.stdout.write(' Password: ' + data.password + '\n');
|
||||
process.stdout.write('\n');
|
||||
}
|
||||
process.stdout.write('NodeBB Setup Completed. Run \'./nodebb start\' to manually start your NodeBB server.\n');
|
||||
|
||||
// If I am a child process, notify the parent of the returned data before exiting (useful for notifying
|
||||
// hosts of auto-generated username/password during headless setups)
|
||||
if (process.send) {
|
||||
process.send(data);
|
||||
}
|
||||
}
|
||||
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
|
||||
exports.setup = setup;
|
||||
exports.webInstall = install;
|
||||
216
src/cli/upgrade-plugins.js
Normal file
216
src/cli/upgrade-plugins.js
Normal file
@@ -0,0 +1,216 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var prompt = require('prompt');
|
||||
var request = require('request');
|
||||
var cproc = require('child_process');
|
||||
var semver = require('semver');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
|
||||
var paths = require('./paths');
|
||||
|
||||
var dirname = paths.baseDir;
|
||||
|
||||
function getModuleVersions(modules, callback) {
|
||||
var versionHash = {};
|
||||
|
||||
async.eachLimit(modules, 50, function (module, next) {
|
||||
fs.readFile(path.join(dirname, 'node_modules', module, 'package.json'), { encoding: 'utf-8' }, function (err, pkg) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
|
||||
try {
|
||||
pkg = JSON.parse(pkg);
|
||||
versionHash[module] = pkg.version;
|
||||
next();
|
||||
} catch (err) {
|
||||
next(err);
|
||||
}
|
||||
});
|
||||
}, function (err) {
|
||||
callback(err, versionHash);
|
||||
});
|
||||
}
|
||||
|
||||
function getInstalledPlugins(callback) {
|
||||
async.parallel({
|
||||
files: async.apply(fs.readdir, path.join(dirname, 'node_modules')),
|
||||
deps: async.apply(fs.readFile, path.join(dirname, 'package.json'), { encoding: 'utf-8' }),
|
||||
}, function (err, payload) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
var isNbbModule = /^nodebb-(?:plugin|theme|widget|rewards)-[\w-]+$/;
|
||||
var moduleName;
|
||||
var isGitRepo;
|
||||
|
||||
payload.files = payload.files.filter(function (file) {
|
||||
return isNbbModule.test(file);
|
||||
});
|
||||
|
||||
try {
|
||||
payload.deps = JSON.parse(payload.deps).dependencies;
|
||||
payload.bundled = [];
|
||||
payload.installed = [];
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
for (moduleName in payload.deps) {
|
||||
if (isNbbModule.test(moduleName)) {
|
||||
payload.bundled.push(moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
// Whittle down deps to send back only extraneously installed plugins/themes/etc
|
||||
payload.files.forEach(function (moduleName) {
|
||||
try {
|
||||
fs.accessSync(path.join(dirname, 'node_modules', moduleName, '.git'));
|
||||
isGitRepo = true;
|
||||
} catch (e) {
|
||||
isGitRepo = false;
|
||||
}
|
||||
|
||||
if (
|
||||
payload.files.indexOf(moduleName) !== -1 && // found in `node_modules/`
|
||||
payload.bundled.indexOf(moduleName) === -1 && // not found in `package.json`
|
||||
!fs.lstatSync(path.join(dirname, 'node_modules', moduleName)).isSymbolicLink() && // is not a symlink
|
||||
!isGitRepo // .git/ does not exist, so it is not a git repository
|
||||
) {
|
||||
payload.installed.push(moduleName);
|
||||
}
|
||||
});
|
||||
|
||||
getModuleVersions(payload.installed, callback);
|
||||
});
|
||||
}
|
||||
|
||||
function getCurrentVersion(callback) {
|
||||
fs.readFile(path.join(dirname, 'package.json'), { encoding: 'utf-8' }, function (err, pkg) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
try {
|
||||
pkg = JSON.parse(pkg);
|
||||
} catch (err) {
|
||||
return callback(err);
|
||||
}
|
||||
callback(null, pkg.version);
|
||||
});
|
||||
}
|
||||
|
||||
function checkPlugins(standalone, callback) {
|
||||
if (standalone) {
|
||||
process.stdout.write('Checking installed plugins and themes for updates... ');
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
async.apply(async.parallel, {
|
||||
plugins: async.apply(getInstalledPlugins),
|
||||
version: async.apply(getCurrentVersion),
|
||||
}),
|
||||
function (payload, next) {
|
||||
var toCheck = Object.keys(payload.plugins);
|
||||
|
||||
if (!toCheck.length) {
|
||||
process.stdout.write('OK'.green + '\n'.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,
|
||||
}, function (err, res, body) {
|
||||
if (err) {
|
||||
process.stdout.write('error'.red + '\n'.reset);
|
||||
return next(err);
|
||||
}
|
||||
process.stdout.write('OK'.green + '\n'.reset);
|
||||
|
||||
if (!Array.isArray(body) && toCheck.length === 1) {
|
||||
body = [body];
|
||||
}
|
||||
|
||||
var current;
|
||||
var suggested;
|
||||
var upgradable = body.map(function (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);
|
||||
}
|
||||
|
||||
function upgradePlugins(callback) {
|
||||
var standalone = false;
|
||||
if (typeof callback !== 'function') {
|
||||
callback = function () {};
|
||||
standalone = true;
|
||||
}
|
||||
|
||||
checkPlugins(standalone, function (err, found) {
|
||||
if (err) {
|
||||
process.stdout.write('Warning'.yellow + ': An unexpected error occured when attempting to verify plugin upgradability\n'.reset);
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (found && found.length) {
|
||||
process.stdout.write('\nA total of ' + String(found.length).bold + ' package(s) can be upgraded:\n');
|
||||
found.forEach(function (suggestObj) {
|
||||
process.stdout.write(' * '.yellow + suggestObj.name.reset + ' (' + suggestObj.current.yellow + ' -> '.reset + suggestObj.suggested.green + ')\n'.reset);
|
||||
});
|
||||
process.stdout.write('\n');
|
||||
} else {
|
||||
if (standalone) {
|
||||
process.stdout.write('\nAll packages up-to-date!'.green + '\n'.reset);
|
||||
}
|
||||
return callback();
|
||||
}
|
||||
|
||||
prompt.message = '';
|
||||
prompt.delimiter = '';
|
||||
|
||||
prompt.start();
|
||||
prompt.get({
|
||||
name: 'upgrade',
|
||||
description: 'Proceed with upgrade (y|n)?'.reset,
|
||||
type: 'string',
|
||||
}, function (err, result) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
|
||||
if (['y', 'Y', 'yes', 'YES'].indexOf(result.upgrade) !== -1) {
|
||||
process.stdout.write('\nUpgrading packages...');
|
||||
var args = ['i'];
|
||||
found.forEach(function (suggestObj) {
|
||||
args.push(suggestObj.name + '@' + suggestObj.suggested);
|
||||
});
|
||||
|
||||
cproc.execFile((process.platform === 'win32') ? 'npm.cmd' : 'npm', args, { stdio: 'ignore' }, callback);
|
||||
} else {
|
||||
process.stdout.write('\nPackage upgrades skipped'.yellow + '. Check for upgrades at any time by running "'.reset + './nodebb upgrade-plugins'.green + '".\n'.reset);
|
||||
callback();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
exports.upgradePlugins = upgradePlugins;
|
||||
117
src/cli/upgrade.js
Normal file
117
src/cli/upgrade.js
Normal file
@@ -0,0 +1,117 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var nconf = require('nconf');
|
||||
|
||||
var packageInstall = require('../meta/package-install');
|
||||
var upgrade = require('../upgrade');
|
||||
var build = require('../meta/build');
|
||||
var db = require('../database');
|
||||
var meta = require('../meta');
|
||||
var upgradePlugins = require('./upgrade-plugins').upgradePlugins;
|
||||
|
||||
var steps = {
|
||||
package: function (next) {
|
||||
process.stdout.write('Updating package.json file with defaults... \n'.yellow);
|
||||
packageInstall.updatePackageFile();
|
||||
packageInstall.preserveExtraneousPlugins();
|
||||
process.stdout.write('OK\n'.green);
|
||||
next();
|
||||
},
|
||||
install: function (next) {
|
||||
process.stdout.write('Bringing base dependencies up to date... \n'.yellow);
|
||||
packageInstall.npmInstallProduction();
|
||||
process.stdout.write('OK\n'.green);
|
||||
next();
|
||||
},
|
||||
plugins: function (next) {
|
||||
process.stdout.write('Checking installed plugins for updates... \n'.yellow);
|
||||
async.series([
|
||||
db.init,
|
||||
upgradePlugins,
|
||||
function (next) {
|
||||
process.stdout.write('OK\n'.green);
|
||||
next();
|
||||
},
|
||||
], next);
|
||||
},
|
||||
schema: function (next) {
|
||||
process.stdout.write('Updating NodeBB data store schema...\n'.yellow);
|
||||
async.series([
|
||||
db.init,
|
||||
upgrade.run,
|
||||
function (next) {
|
||||
process.stdout.write('OK\n'.green);
|
||||
next();
|
||||
},
|
||||
], next);
|
||||
},
|
||||
build: function (next) {
|
||||
process.stdout.write('Rebuilding assets...\n'.yellow);
|
||||
async.series([
|
||||
build.buildAll,
|
||||
function (next) {
|
||||
process.stdout.write('OK\n'.green);
|
||||
next();
|
||||
},
|
||||
], next);
|
||||
},
|
||||
};
|
||||
|
||||
function runSteps(tasks) {
|
||||
tasks = tasks.map(function (key, i) {
|
||||
return function (next) {
|
||||
process.stdout.write(((i + 1) + '. ').bold);
|
||||
return steps[key](next);
|
||||
};
|
||||
});
|
||||
|
||||
async.series(tasks, function (err) {
|
||||
if (err) {
|
||||
process.stdout.write('Error occurred during upgrade');
|
||||
throw err;
|
||||
}
|
||||
|
||||
var 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
|
||||
var columns = process.stdout.columns;
|
||||
var spaces = columns ? new Array(Math.floor(columns / 2) - (message.length / 2) + 1).join(' ') : ' ';
|
||||
|
||||
process.stdout.write('\n' + spaces + message.green.bold + '\n\n'.reset);
|
||||
|
||||
process.exit();
|
||||
});
|
||||
}
|
||||
|
||||
function runUpgrade(upgrades, options) {
|
||||
process.stdout.write('\nUpdating NodeBB...\n'.cyan);
|
||||
|
||||
// disable mongo timeouts during upgrade
|
||||
nconf.set('mongo:options:socketTimeoutMS', 0);
|
||||
|
||||
if (upgrades === true) {
|
||||
var tasks = Object.keys(steps);
|
||||
if (options.package || options.install ||
|
||||
options.plugins || options.schema || options.build) {
|
||||
tasks = tasks.filter(function (key) {
|
||||
return options[key];
|
||||
});
|
||||
}
|
||||
runSteps(tasks);
|
||||
return;
|
||||
}
|
||||
|
||||
async.series([
|
||||
db.init,
|
||||
meta.configs.init,
|
||||
async.apply(upgrade.runParticular, upgrades),
|
||||
], function (err) {
|
||||
if (err) {
|
||||
throw err;
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
exports.upgrade = runUpgrade;
|
||||
@@ -83,6 +83,8 @@ var aliases = {
|
||||
sounds: ['sound'],
|
||||
};
|
||||
|
||||
exports.aliases = aliases;
|
||||
|
||||
aliases = Object.keys(aliases).reduce(function (prev, key) {
|
||||
var arr = aliases[key];
|
||||
arr.forEach(function (alias) {
|
||||
|
||||
84
src/prestart.js
Normal file
84
src/prestart.js
Normal file
@@ -0,0 +1,84 @@
|
||||
'use strict';
|
||||
|
||||
var nconf = require('nconf');
|
||||
var url = require('url');
|
||||
var winston = require('winston');
|
||||
var path = require('path');
|
||||
|
||||
var pkg = require('../package.json');
|
||||
var dirname = require('./cli/paths').baseDir;
|
||||
|
||||
function setupWinston() {
|
||||
winston.remove(winston.transports.Console);
|
||||
winston.add(winston.transports.Console, {
|
||||
colorize: true,
|
||||
timestamp: function () {
|
||||
var date = new Date();
|
||||
return nconf.get('json-logging') ? date.toJSON() :
|
||||
date.getDate() + '/' + (date.getMonth() + 1) + ' ' +
|
||||
date.toTimeString().substr(0, 8) + ' [' + global.process.pid + ']';
|
||||
},
|
||||
level: nconf.get('log-level') || (global.env === 'production' ? 'info' : 'verbose'),
|
||||
json: !!nconf.get('json-logging'),
|
||||
stringify: !!nconf.get('json-logging'),
|
||||
});
|
||||
}
|
||||
|
||||
function loadConfig(configFile) {
|
||||
winston.verbose('* using configuration stored in: %s', configFile);
|
||||
|
||||
nconf.file({
|
||||
file: configFile,
|
||||
});
|
||||
|
||||
nconf.defaults({
|
||||
base_dir: dirname,
|
||||
themes_path: path.join(dirname, 'node_modules'),
|
||||
upload_path: 'public/uploads',
|
||||
views_dir: path.join(dirname, 'build/public/templates'),
|
||||
version: pkg.version,
|
||||
});
|
||||
|
||||
if (!nconf.get('isCluster')) {
|
||||
nconf.set('isPrimary', 'true');
|
||||
nconf.set('isCluster', 'false');
|
||||
}
|
||||
|
||||
// Ensure themes_path is a full filepath
|
||||
nconf.set('themes_path', path.resolve(dirname, nconf.get('themes_path')));
|
||||
nconf.set('core_templates_path', path.join(dirname, 'src/views'));
|
||||
nconf.set('base_templates_path', path.join(nconf.get('themes_path'), 'nodebb-theme-persona/templates'));
|
||||
|
||||
nconf.set('upload_path', path.resolve(nconf.get('base_dir'), nconf.get('upload_path')));
|
||||
|
||||
if (nconf.get('url')) {
|
||||
nconf.set('url_parsed', url.parse(nconf.get('url')));
|
||||
}
|
||||
|
||||
// Explicitly cast 'jobsDisabled' as Bool
|
||||
var castAsBool = ['jobsDisabled'];
|
||||
nconf.stores.env.readOnly = false;
|
||||
castAsBool.forEach(function (prop) {
|
||||
var value = nconf.get(prop);
|
||||
if (value) {
|
||||
nconf.set(prop, typeof value === 'boolean' ? value : String(value).toLowerCase() === 'true');
|
||||
}
|
||||
});
|
||||
nconf.stores.env.readOnly = true;
|
||||
}
|
||||
|
||||
function versionCheck() {
|
||||
var version = process.version.slice(1);
|
||||
var range = pkg.engines.node;
|
||||
var semver = require('semver');
|
||||
var compatible = semver.satisfies(version, range);
|
||||
|
||||
if (!compatible) {
|
||||
winston.warn('Your version of Node.js is too outdated for NodeBB. Please update your version of Node.js.');
|
||||
winston.warn('Recommended ' + range.green + ', '.reset + version.yellow + ' provided\n'.reset);
|
||||
}
|
||||
}
|
||||
|
||||
exports.setupWinston = setupWinston;
|
||||
exports.loadConfig = loadConfig;
|
||||
exports.versionCheck = versionCheck;
|
||||
@@ -18,7 +18,7 @@ var file = require('../src/file');
|
||||
* 3. Add your script under the "method" property
|
||||
*/
|
||||
|
||||
var Upgrade = {};
|
||||
var Upgrade = module.exports;
|
||||
|
||||
Upgrade.getAll = function (callback) {
|
||||
async.waterfall([
|
||||
@@ -212,4 +212,3 @@ Upgrade.incrementProgress = function (value) {
|
||||
process.stdout.write(' [' + (filled ? new Array(filled).join('#') : '') + new Array(unfilled).join(' ') + '] (' + this.current + '/' + (this.total || '??') + ') ' + percentage + ' ');
|
||||
};
|
||||
|
||||
module.exports = Upgrade;
|
||||
|
||||
Reference in New Issue
Block a user