mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 02:55:58 +01: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ı
						Barış Soner Uşaklı
					
				
			
			
				
	
			
			
			
						parent
						
							c731661a39
						
					
				
				
					commit
					ae24bca16e
				
			
							
								
								
									
										266
									
								
								app.js
									
									
									
									
									
								
							
							
						
						
									
										266
									
								
								app.js
									
									
									
									
									
								
							| @@ -30,47 +30,16 @@ nconf.argv().env({ | |||||||
| 	separator: '__', | 	separator: '__', | ||||||
| }); | }); | ||||||
|  |  | ||||||
| var url = require('url'); |  | ||||||
| var async = require('async'); | var async = require('async'); | ||||||
| var winston = require('winston'); | var winston = require('winston'); | ||||||
| var path = require('path'); | var path = require('path'); | ||||||
| var pkg = require('./package.json'); |  | ||||||
| var file = require('./src/file'); | var file = require('./src/file'); | ||||||
| var debug = require('./src/meta/debugFork').debugging; |  | ||||||
|  |  | ||||||
| global.env = process.env.NODE_ENV || 'production'; | 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 | // Alternate configuration file support | ||||||
| var	configFile = path.join(__dirname, '/config.json'); | var	configFile = path.join(__dirname, 'config.json'); | ||||||
|  |  | ||||||
| if (nconf.get('config')) { | if (nconf.get('config')) { | ||||||
| 	configFile = path.resolve(__dirname, 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')); | var configExists = file.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database')); | ||||||
|  |  | ||||||
| loadConfig(); | var prestart = require('./src/prestart'); | ||||||
| versionCheck(); | prestart.loadConfig(configFile); | ||||||
|  | prestart.versionCheck(); | ||||||
|  |  | ||||||
| if (!process.send) { | if (!process.send) { | ||||||
| 	// If run using `node app`, log GNU copyright info along with server info | 	// If run using `node app`, log GNU copyright info along with server info | ||||||
| @@ -89,224 +59,40 @@ if (!process.send) { | |||||||
| 	winston.info(''); | 	winston.info(''); | ||||||
| } | } | ||||||
|  |  | ||||||
|  |  | ||||||
| if (nconf.get('setup') || nconf.get('install')) { | if (nconf.get('setup') || nconf.get('install')) { | ||||||
| 	setup(); | 	require('./src/cli/setup').setup(); | ||||||
| } else if (!configExists) { | } else if (!configExists) { | ||||||
| 	require('./install/web').install(nconf.get('port')); | 	require('./install/web').install(nconf.get('port')); | ||||||
| } else if (nconf.get('upgrade')) { | } else if (nconf.get('upgrade')) { | ||||||
| 	upgrade(); | 	require('./src/cli/upgrade').upgrade(true); | ||||||
| } else if (nconf.get('reset')) { | } else if (nconf.get('reset')) { | ||||||
| 	async.waterfall([ | 	var options = { | ||||||
| 		async.apply(require('./src/reset').reset), | 		theme: nconf.get('t'), | ||||||
| 		async.apply(require('./src/meta/build').buildAll), | 		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) { | 	], function (err) { | ||||||
| 		process.exit(err ? 1 : 0); | 		if (err) { | ||||||
|  | 			throw err; | ||||||
|  | 		} | ||||||
|  |  | ||||||
|  | 		process.exit(0); | ||||||
| 	}); | 	}); | ||||||
| } else if (nconf.get('activate')) { | } else if (nconf.get('activate')) { | ||||||
| 	activate(); | 	require('./src/cli/manage').activate(nconf.get('activate')); | ||||||
| } else if (nconf.get('plugins')) { | } else if (nconf.get('plugins')) { | ||||||
| 	listPlugins(); | 	require('./src/cli/manage').listPlugins(); | ||||||
| } else if (nconf.get('build')) { | } else if (nconf.get('build')) { | ||||||
| 	require('./src/meta/build').build(nconf.get('build')); | 	require('./src/meta/build').build(nconf.get('build')); | ||||||
| } else if (nconf.get('events')) { | } else if (nconf.get('events')) { | ||||||
| 	async.series([ | 	require('./src/cli/manage').listEvents(); | ||||||
| 		async.apply(require('./src/database').init), |  | ||||||
| 		async.apply(require('./src/events').output), |  | ||||||
| 	]); |  | ||||||
| } else { | } else { | ||||||
| 	require('./src/start').start(); | 	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", |       "chart.js": "^2.7.0", | ||||||
|       "colors": "^1.1.2", |       "colors": "^1.1.2", | ||||||
|       "compression": "^1.7.1", |       "compression": "^1.7.1", | ||||||
|  |       "commander": "^2.11.0", | ||||||
|       "connect-ensure-login": "^0.1.1", |       "connect-ensure-login": "^0.1.1", | ||||||
|       "connect-flash": "^0.1.1", |       "connect-flash": "^0.1.1", | ||||||
|       "connect-mongo": "2.0.0", |       "connect-mongo": "2.0.0", | ||||||
| @@ -52,7 +53,6 @@ | |||||||
|       "logrotate-stream": "^0.2.5", |       "logrotate-stream": "^0.2.5", | ||||||
|       "lru-cache": "4.1.1", |       "lru-cache": "4.1.1", | ||||||
|       "mime": "^2.0.3", |       "mime": "^2.0.3", | ||||||
|       "minimist": "^1.2.0", |  | ||||||
|       "mkdirp": "^0.5.1", |       "mkdirp": "^0.5.1", | ||||||
|       "mongodb": "2.2.33", |       "mongodb": "2.2.33", | ||||||
|       "morgan": "^1.9.0", |       "morgan": "^1.9.0", | ||||||
|   | |||||||
							
								
								
									
										547
									
								
								nodebb
									
									
									
									
									
								
							
							
						
						
									
										547
									
								
								nodebb
									
									
									
									
									
								
							| @@ -2,549 +2,4 @@ | |||||||
|  |  | ||||||
| 'use strict'; | 'use strict'; | ||||||
|  |  | ||||||
| var fs = require('fs'); | require('./src/cli'); | ||||||
| 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(); |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -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'); | require('colors'); | ||||||
| var path = require('path'); | var path = require('path'); | ||||||
| var winston = require('winston'); | var winston = require('winston'); | ||||||
| var nconf = require('nconf'); |  | ||||||
| var async = require('async'); | var async = require('async'); | ||||||
| var db = require('./database'); | var fs = require('fs'); | ||||||
| var events = require('./events'); |  | ||||||
| 
 | 
 | ||||||
| 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) { | var dirname = require('./paths').baseDir; | ||||||
| 	db.init(function (err) { |  | ||||||
| 		if (err) { |  | ||||||
| 			winston.error(err); |  | ||||||
| 			throw err; |  | ||||||
| 		} |  | ||||||
| 
 | 
 | ||||||
| 		if (nconf.get('t')) { | exports.reset = function (options, callback) { | ||||||
| 			var themeId = nconf.get('t'); | 	var map = { | ||||||
|  | 		theme: function (next) { | ||||||
|  | 			var themeId = options.theme; | ||||||
| 			if (themeId === true) { | 			if (themeId === true) { | ||||||
| 				resetThemes(callback); | 				resetThemes(next); | ||||||
| 			} else { | 			} else { | ||||||
| 				if (themeId.indexOf('nodebb-') !== 0) { | 				if (!themeId.startsWith('nodebb-theme-')) { | ||||||
| 					// Allow omission of `nodebb-theme-`
 | 					// Allow omission of `nodebb-theme-`
 | ||||||
| 					themeId = 'nodebb-theme-' + themeId; | 					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) { | 			if (pluginId === true) { | ||||||
| 				resetPlugins(callback); | 				resetPlugins(next); | ||||||
| 			} else { | 			} else { | ||||||
| 				if (pluginId.indexOf('nodebb-') !== 0) { | 				if (!pluginId.startsWith('nodebb-plugin-')) { | ||||||
| 					// Allow omission of `nodebb-plugin-`
 | 					// Allow omission of `nodebb-plugin-`
 | ||||||
| 					pluginId = 'nodebb-plugin-' + pluginId; | 					pluginId = 'nodebb-plugin-' + pluginId; | ||||||
| 				} | 				} | ||||||
| 
 | 
 | ||||||
| 				resetPlugin(pluginId, callback); | 				resetPlugin(pluginId, next); | ||||||
| 			} | 			} | ||||||
| 		} else if (nconf.get('w')) { | 		}, | ||||||
| 			resetWidgets(callback); | 		widgets: resetWidgets, | ||||||
| 		} else if (nconf.get('s')) { | 		settings: resetSettings, | ||||||
| 			resetSettings(callback); | 		all: function (next) { | ||||||
| 		} else if (nconf.get('a')) { | 			async.series([resetWidgets, resetThemes, resetPlugins, resetSettings], next); | ||||||
| 			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); |  | ||||||
| 				} |  | ||||||
| 
 | 
 | ||||||
| 				callback(); | 	var tasks = Object.keys(map) | ||||||
| 			}); | 		.filter(function (x) { return options[x]; }) | ||||||
| 		} else { | 		.map(function (x) { return map[x]; }); | ||||||
| 			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.stdout.write('\nPlugin and theme reset flags (-p & -t) can take a single argument\n'); | 	if (!tasks.length) { | ||||||
| 			process.stdout.write('    e.g. ./nodebb reset -p nodebb-plugin-mentions, ./nodebb reset -t nodebb-theme-persona\n'); | 		process.stdout.write('\nNodeBB Reset\n'.bold); | ||||||
| 			process.stdout.write('         Prefix is optional, e.g. ./nodebb reset -p markdown, ./nodebb reset -t persona\n'); | 		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) { | function resetSettings(callback) { | ||||||
| 	var meta = require('./meta'); |  | ||||||
| 	meta.configs.set('allowLocalLogin', 1, function (err) { | 	meta.configs.set('allowLocalLogin', 1, function (err) { | ||||||
| 		winston.info('[reset] Settings reset to default'); | 		winston.info('[reset] Settings reset to default'); | ||||||
| 		callback(err); | 		callback(err); | ||||||
| @@ -83,10 +89,7 @@ function resetSettings(callback) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function resetTheme(themeId, callback) { | function resetTheme(themeId, callback) { | ||||||
| 	var meta = require('./meta'); | 	fs.access(path.join(dirname, 'node_modules', themeId, 'package.json'), function (err) { | ||||||
| 	var fs = require('fs'); |  | ||||||
| 
 |  | ||||||
| 	fs.access(path.join(__dirname, '../node_modules', themeId, 'package.json'), function (err) { |  | ||||||
| 		if (err) { | 		if (err) { | ||||||
| 			winston.warn('[reset] Theme `%s` is not installed on this forum', themeId); | 			winston.warn('[reset] Theme `%s` is not installed on this forum', themeId); | ||||||
| 			callback(new Error('theme-not-found')); | 			callback(new Error('theme-not-found')); | ||||||
| @@ -108,8 +111,6 @@ function resetTheme(themeId, callback) { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function resetThemes(callback) { | function resetThemes(callback) { | ||||||
| 	var meta = require('./meta'); |  | ||||||
| 
 |  | ||||||
| 	meta.themes.set({ | 	meta.themes.set({ | ||||||
| 		type: 'local', | 		type: 'local', | ||||||
| 		id: 'nodebb-theme-persona', | 		id: 'nodebb-theme-persona', | ||||||
| @@ -163,13 +164,11 @@ function resetPlugins(callback) { | |||||||
| 
 | 
 | ||||||
| function resetWidgets(callback) { | function resetWidgets(callback) { | ||||||
| 	async.waterfall([ | 	async.waterfall([ | ||||||
| 		require('./plugins').reload, | 		plugins.reload, | ||||||
| 		require('./widgets').reset, | 		widgets.reset, | ||||||
| 		function (next) { | 		function (next) { | ||||||
| 			winston.info('[reset] All Widgets moved to Draft Zone'); | 			winston.info('[reset] All Widgets moved to Draft Zone'); | ||||||
| 			next(); | 			next(); | ||||||
| 		}, | 		}, | ||||||
| 	], callback); | 	], 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'], | 	sounds: ['sound'], | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | exports.aliases = aliases; | ||||||
|  |  | ||||||
| aliases = Object.keys(aliases).reduce(function (prev, key) { | aliases = Object.keys(aliases).reduce(function (prev, key) { | ||||||
| 	var arr = aliases[key]; | 	var arr = aliases[key]; | ||||||
| 	arr.forEach(function (alias) { | 	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 |  * 3. Add your script under the "method" property | ||||||
|  */ |  */ | ||||||
|  |  | ||||||
| var Upgrade = {}; | var Upgrade = module.exports; | ||||||
|  |  | ||||||
| Upgrade.getAll = function (callback) { | Upgrade.getAll = function (callback) { | ||||||
| 	async.waterfall([ | 	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 + ' '); | 	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