recommended CLI startup / bootstrap changes

This commit is contained in:
Peter Jaszkowiak
2022-12-30 19:31:29 -07:00
parent 0ba54a2b37
commit bfdf2a7b79
17 changed files with 407 additions and 377 deletions

View File

@@ -139,6 +139,7 @@
"timeago": "1.6.7",
"tinycon": "0.6.8",
"toobusy-js": "0.5.1",
"typescript": "4.9.4",
"validator": "13.7.0",
"webpack": "5.75.0",
"webpack-merge": "5.8.0",

2
nodebb
View File

@@ -3,4 +3,4 @@
'use strict';
console.log('SERIOUSLY?');
require('./build/src/cli');
require('./src/cli/bootstrap');

View File

@@ -4,7 +4,6 @@
// this allows plugins to use `require.main.require` to reference NodeBB modules
// without worrying about multiple parent modules
if (require.main !== module) {
//@ts-ignore
require.main.require = function (path) {
return require(path);
};

94
src/cli/bootstrap.js vendored Normal file
View File

@@ -0,0 +1,94 @@
/* eslint-disable import/order */
'use strict';
const fs = require('fs');
const path = require('path');
require('../../require-main');
const packageInstall = require('./package-install');
const { paths } = require('../constants');
try {
fs.accessSync(paths.currentPackage, fs.constants.R_OK); // throw on missing package.json
try { // handle missing node_modules/ directory
fs.accessSync(paths.nodeModules, fs.constants.R_OK);
} catch (e) {
if (e.code === 'ENOENT') {
// run package installation just to sync up node_modules/ with existing package.json
packageInstall.installAll();
} else {
throw e;
}
}
fs.accessSync(path.join(paths.nodeModules, 'semver/package.json'), fs.constants.R_OK);
const semver = require('semver');
const defaultPackage = require('../../install/package.json');
const checkVersion = function (packageName) {
if (defaultPackage.dependencies[packageName] == null) {
const e = new TypeError(`Attempt to \`checkVersion('${packageName}')\`, but no "${packageName}" dependency entry in 'install/package.json'.`);
e.code = 'DEP_NOT_DEFINED';
throw e;
}
const { version } = JSON.parse(fs.readFileSync(path.join(paths.nodeModules, packageName, 'package.json'), 'utf8'));
if (!semver.satisfies(version, defaultPackage.dependencies[packageName])) {
const e = new TypeError(`Incorrect dependency version: ${packageName}`);
e.code = 'DEP_WRONG_VERSION';
throw e;
}
};
checkVersion('nconf');
checkVersion('async');
checkVersion('commander');
checkVersion('chalk');
checkVersion('lodash');
checkVersion('lru-cache');
checkVersion('typescript');
} catch (e) {
if (['ENOENT', 'DEP_WRONG_VERSION', 'MODULE_NOT_FOUND'].includes(e.code)) {
console.info(e);
console.warn('Dependencies outdated or not yet installed.');
console.log('Installing them now...\n');
packageInstall.updatePackageFile();
packageInstall.preserveExtraneousPlugins();
packageInstall.installAll();
const chalk = require('chalk');
console.log(`${chalk.green('OK')}\n`);
} else {
throw e;
}
}
console.log('Running typescript to transpile files...');
const child_process = require('child_process');
const chalk = require('chalk');
const tsc = path.join(paths.nodeModules, '.bin/tsc');
try {
child_process.execFileSync(tsc, [
'--skipLibCheck',
'--noResolve',
'--noLib',
], {
cwd: paths.baseDir,
stdio: 'ignore',
});
} catch (e) {
if (e.status == null) {
throw e;
}
}
console.log(`${chalk.green('OK')}\n`);
require('../../build/src/cli');

View File

@@ -2,93 +2,22 @@
'use strict';
import * as prestart from '../prestart';
import yargs from 'yargs';
import fs from 'fs';
import path from 'path';
import { paths } from '../constants';
import nconf from 'nconf';
const opts = yargs((process as any).argv.slice(2)).help(false).exitProcess(false);
nconf.argv(opts).env({
separator: '__',
});
(process as any).env.NODE_ENV = (process as any).env.NODE_ENV || 'production';
(global as any).env = (process as any).env.NODE_ENV || 'production';
prestart.setupWinston();
// Alternate configuration file support
const configFile = path.resolve(paths.baseDir, nconf.get('config') || 'config.json');
const configExists = fs.existsSync(configFile) || (nconf.get('url') && nconf.get('secret') && nconf.get('database'));
prestart.loadConfig(configFile);
prestart.versionCheck();
// used for calling the file
require('../../require-main');
import packageInstall from './package-install';
import semver from 'semver';
//@ts-ignore
import defaultPackage from '../../install/package.json';
import chalk from 'chalk';
import nconf from 'nconf';
import { program } from 'commander';
import manage from './manage';
import yargs from 'yargs';
try {
fs.accessSync(paths.currentPackage, fs.constants.R_OK); // throw on missing package.json
try { // handle missing node_modules/ directory
fs.accessSync(paths.nodeModules, fs.constants.R_OK);
} catch (e: any) { if (e.code === 'ENOENT') {
// run package installation just to sync up node_modules/ with existing package.json
packageInstall.installAll();
} else {
throw e;
}
}
fs.accessSync(path.join(paths.nodeModules, 'semver/package.json'), fs.constants.R_OK);
const checkVersion = function (packageName) {
const { version } = JSON.parse(fs.readFileSync(path.join(paths.nodeModules, packageName, 'package.json'), 'utf8'));
if (!semver.satisfies(version, defaultPackage.dependencies[packageName])) {
const e: any = new TypeError(`Incorrect dependency version: ${packageName}`);
e.code = 'DEP_WRONG_VERSION';
throw e;
}
};
checkVersion('nconf');
checkVersion('async');
checkVersion('commander');
checkVersion('chalk');
checkVersion('lodash');
checkVersion('lru-cache');
} catch (e: any) {
if (['ENOENT', 'DEP_WRONG_VERSION', 'MODULE_NOT_FOUND'].includes(e.code)) {
console.warn('Dependencies outdated or not yet installed.');
console.log('Installing them now...\n');
packageInstall.updatePackageFile();
packageInstall.preserveExtraneousPlugins();
packageInstall.installAll();
console.log(`${chalk.green('OK')}\n`);
} else {
throw e;
}
}
import pkg from '../../../install/package.json';
import file from '../file';
import * as prestart from '../prestart';
program.configureHelp(require('./colors'));
program
.name('./nodebb')
.description('Welcome to NodeBB')
.version(defaultPackage.version)
.version(pkg.version)
.option('--json-logging', 'Output to logs in JSON format', false)
.option('--log-level <level>', 'Default logging level to use', 'info')
.option('--config <value>', 'Specify a config file', 'config.json')
@@ -98,15 +27,29 @@ program
// provide a yargs object ourselves
// otherwise yargs will consume `--help` or `help`
// and `nconf` will exit with useless usage info
const opts = yargs(process.argv.slice(2)).help(false).exitProcess(false);
nconf.argv(opts).env({
separator: '__',
});
process.env.NODE_ENV = process.env.NODE_ENV || 'production';
global.env = process.env.NODE_ENV || 'production';
if (!configExists && (process as any).argv[2] !== 'setup') {
prestart.setupWinston();
// Alternate configuration file support
const configFile = path.resolve(paths.baseDir, nconf.get('config') || 'config.json');
const 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();
// @ts-ignore
return;
}
(process as any).env.CONFIG = configFile;
process.env.CONFIG = configFile;
// running commands
program
@@ -129,8 +72,8 @@ program
})
.description('Start NodeBB in verbose development mode')
.action(() => {
(process as any).env.NODE_ENV = 'development';
(global as any).env = 'development';
process.env.NODE_ENV = 'development';
global.env = 'development';
require('./running').start({ ...program.opts(), dev: true });
});
program
@@ -183,7 +126,7 @@ program
.option('-f, --force', 'Force plugin installation even if it may be incompatible with currently installed NodeBB version')
.action((plugin, options) => {
if (plugin) {
manage.install(plugin);
require('./manage').install(plugin, options);
} else {
require('./setup').webInstall();
}
@@ -197,10 +140,10 @@ program
.action((targets, options) => {
console.log('HEYYYYY!!!!!!!!!!!');
if (program.opts().dev) {
(process as any).env.NODE_ENV = 'development';
(global as any).env = 'development';
process.env.NODE_ENV = 'development';
global.env = 'development';
}
manage.buildWrapper(targets.length ? targets : true, options);
require('./manage').build(targets.length ? targets : true, options);
})
.on('--help', () => {
require('../meta/aliases').buildTargets();
@@ -209,25 +152,25 @@ program
.command('activate [plugin]')
.description('Activate a plugin for the next startup of NodeBB (nodebb-plugin- prefix is optional)')
.action((plugin) => {
manage.activate(plugin);
require('./manage').activate(plugin);
});
program
.command('plugins')
.action(() => {
manage.listPlugins();
require('./manage').listPlugins();
})
.description('List all installed plugins');
program
.command('events [count]')
.description('Outputs the most recent administrative events recorded by NodeBB')
.action((count) => {
manage.listEvents(count);
require('./manage').listEvents(count);
});
program
.command('info')
.description('Outputs various system info')
.action(() => {
manage.info();
require('./manage').info();
});
// reset
@@ -249,10 +192,10 @@ resetCommand
require('./reset').reset(options, (err) => {
if (err) {
return (process as any).exit(1);
return process.exit(1);
}
(process as any).exit(0);
process.exit(0);
});
});
@@ -280,8 +223,8 @@ program
})
.action((scripts, options) => {
if (program.opts().dev) {
(process as any).env.NODE_ENV = 'development';
(global as any).env = 'development';
process.env.NODE_ENV = 'development';
global.env = 'development';
}
require('./upgrade').upgrade(scripts.length ? scripts : true, options);
});
@@ -298,7 +241,7 @@ program
throw err;
}
console.log(chalk.green('OK'));
(process as any).exit();
process.exit();
});
});
@@ -310,7 +253,7 @@ program
return program.help();
}
const command = program.commands.find(command => (command as any)._name === name);
const command = program.commands.find(command => command._name === name);
if (command) {
command.help();
} else {
@@ -319,10 +262,10 @@ program
}
});
if ((process as any).argv.length === 2) {
if (process.argv.length === 2) {
program.help();
}
(program as any).executables = false;
program.executables = false;
program.parse();

View File

@@ -5,18 +5,17 @@ import child from 'child_process';
import CliGraph from 'cli-graph';
import chalk from 'chalk';
import nconf from 'nconf';
import { build } from '../meta/build';
import { build as metaBuild } from '../meta/build';
import db from '../database';
import plugins from '../plugins';
import events from '../events';
import analytics from '../analytics';
import * as reset from './reset';
import { pluginNamePattern, themeNamePattern, paths } from '../constants';
//@ts-ignore
import { version } from '../../package.json';
// @ts-ignore
import { version } from '../../../package.json';
async function install(plugin, options?) {
export async function install(plugin, options?) {
if (!options) {
options = {};
}
@@ -45,19 +44,19 @@ async function install(plugin, options?) {
winston.info('Installing Plugin `%s@%s`', plugin, suggested.version);
await plugins.toggleInstall(plugin, suggested.version);
(process as any).exit(0);
process.exit(0);
} catch (err: any) {
winston.error(`An error occurred during plugin installation\n${err.stack}`);
(process as any).exit(1);
process.exit(1);
}
}
async function activate(plugin) {
export async function activate(plugin) {
if (themeNamePattern.test(plugin)) {
await reset.reset({
theme: plugin,
});
(process as any).exit();
process.exit();
}
try {
await db.init();
@@ -75,11 +74,11 @@ async function activate(plugin) {
const isActive = await plugins.isActive(plugin);
if (isActive) {
winston.info('Plugin `%s` already active', plugin);
(process as any).exit(0);
process.exit(0);
}
if (nconf.get('plugins:active')) {
winston.error('Cannot activate plugins while plugin state configuration is set, please change your active configuration (config.json, environmental variables or terminal arguments) instead');
(process as any).exit(1);
process.exit(1);
}
const numPlugins = await db.sortedSetCard('plugins:active');
winston.info('Activating plugin `%s`', plugin);
@@ -89,14 +88,14 @@ async function activate(plugin) {
text: plugin,
});
(process as any).exit(0);
process.exit(0);
} catch (err: any) {
winston.error(`An error occurred during plugin activation\n${err.stack}`);
(process as any).exit(1);
process.exit(1);
}
}
async function listPlugins() {
export async function listPlugins() {
await db.init();
const installed = await plugins.showInstalled();
const installedList = installed.map(plugin => plugin.name);
@@ -118,35 +117,35 @@ async function listPlugins() {
combined.sort((a, b) => (a.id > b.id ? 1 : -1));
// Pretty output
(process as any).stdout.write('Active plugins:\n');
process.stdout.write('Active plugins:\n');
combined.forEach((plugin) => {
(process as any).stdout.write(`\t* ${plugin.id}${plugin.version ? `@${plugin.version}` : ''} (`);
(process as any).stdout.write(plugin.installed ? chalk.green('installed') : chalk.red('not installed'));
(process as any).stdout.write(', ');
(process as any).stdout.write(plugin.active ? chalk.green('enabled') : chalk.yellow('disabled'));
(process as any).stdout.write(')\n');
process.stdout.write(`\t* ${plugin.id}${plugin.version ? `@${plugin.version}` : ''} (`);
process.stdout.write(plugin.installed ? chalk.green('installed') : chalk.red('not installed'));
process.stdout.write(', ');
process.stdout.write(plugin.active ? chalk.green('enabled') : chalk.yellow('disabled'));
process.stdout.write(')\n');
});
(process as any).exit();
process.exit();
}
async function listEvents(count = 10) {
export async function listEvents(count = 10) {
await db.init();
const eventData = await events.getEvents('', 0, count - 1);
console.log(chalk.bold(`\nDisplaying last ${count} administrative events...`));
eventData.forEach((event) => {
console.log(` * ${chalk.green(String(event.timestampISO))} ${chalk.yellow(String(event.type))}${event.text ? ` ${event.text}` : ''} (uid: ${event.uid ? event.uid : 0})`);
});
(process as any).exit();
process.exit();
}
async function info() {
export async function info() {
console.log('');
console.log(` version: ${version}`);
console.log(` Node ver: ${(process as any).version}`);
console.log(` Node ver: ${process.version}`);
// @ts-ignore
const hash = child(process as any).execSync('git rev-parse HEAD');
const hash = childprocess.execSync('git rev-parse HEAD');
console.log(` git hash: ${hash}`);
console.log(` database: ${nconf.get('database')}`);
@@ -190,24 +189,15 @@ async function info() {
console.log('');
console.log(graph.toString());
console.log(`Pageviews, last 24h (min: ${min} max: ${max})`);
(process as any).exit();
process.exit();
}
async function buildWrapper(targets, options?) {
export async function build(targets, options?) {
try {
await build(targets, options);
(process as any).exit(0);
await metaBuild(targets, options);
process.exit(0);
} catch (err: any) {
winston.error(err.stack);
(process as any).exit(1);
process.exit(1);
}
}
export default {
buildWrapper,
install,
activate,
listPlugins,
listEvents,
info
};

174
src/cli/package-install.js Normal file
View File

@@ -0,0 +1,174 @@
'use strict';
const path = require('path');
const fs = require('fs');
const cproc = require('child_process');
const { paths, pluginNamePattern } = require('../constants');
const pkgInstall = module.exports;
function sortDependencies(dependencies) {
return Object.entries(dependencies)
.sort((a, b) => (a < b ? -1 : 1))
.reduce((memo, pkg) => {
memo[pkg[0]] = pkg[1];
return memo;
}, {});
}
pkgInstall.updatePackageFile = () => {
let oldPackageContents;
try {
oldPackageContents = JSON.parse(fs.readFileSync(paths.currentPackage, 'utf8'));
} catch (e) {
if (e.code !== 'ENOENT') {
throw e;
} else {
// No local package.json, copy from install/package.json
fs.copyFileSync(paths.installPackage, paths.currentPackage);
return;
}
}
const _ = require('lodash');
const defaultPackageContents = JSON.parse(fs.readFileSync(paths.installPackage, 'utf8'));
let dependencies = {};
Object.entries(oldPackageContents.dependencies || {}).forEach(([dep, version]) => {
if (pluginNamePattern.test(dep)) {
dependencies[dep] = version;
}
});
const { devDependencies } = defaultPackageContents;
// Sort dependencies alphabetically
dependencies = sortDependencies({ ...dependencies, ...defaultPackageContents.dependencies });
const packageContents = { ..._.merge(oldPackageContents, defaultPackageContents), dependencies, devDependencies };
fs.writeFileSync(paths.currentPackage, JSON.stringify(packageContents, null, 4));
};
pkgInstall.supportedPackageManager = [
'npm',
'cnpm',
'pnpm',
'yarn',
];
pkgInstall.getPackageManager = () => {
try {
const packageContents = require(paths.currentPackage);
// This regex technically allows invalid values:
// cnpm isn't supported by corepack and it doesn't enforce a version string being present
const pmRegex = new RegExp(`^(?<packageManager>${pkgInstall.supportedPackageManager.join('|')})@?[\\d\\w\\.\\-]*$`);
const packageManager = packageContents.packageManager ? packageContents.packageManager.match(pmRegex) : false;
if (packageManager) {
return packageManager.groups.packageManager;
}
fs.accessSync(path.join(paths.nodeModules, 'nconf/package.json'), fs.constants.R_OK);
const nconf = require('nconf');
if (!Object.keys(nconf.stores).length) {
// Quick & dirty nconf setup for when you cannot rely on nconf having been required already
const configFile = path.resolve(__dirname, '../../', nconf.any(['config', 'CONFIG']) || 'config.json');
nconf.env().file({ // not sure why adding .argv() causes the process to terminate
file: configFile,
});
}
if (nconf.get('package_manager') && !pkgInstall.supportedPackageManager.includes(nconf.get('package_manager'))) {
nconf.clear('package_manager');
}
if (!nconf.get('package_manager')) {
nconf.set('package_manager', getPackageManagerByLockfile());
}
return nconf.get('package_manager') || 'npm';
} catch (e) {
// nconf not installed or other unexpected error/exception
return getPackageManagerByLockfile() || 'npm';
}
};
function getPackageManagerByLockfile() {
for (const [packageManager, lockfile] of Object.entries({ npm: 'package-lock.json', yarn: 'yarn.lock', pnpm: 'pnpm-lock.yaml' })) {
try {
fs.accessSync(path.resolve(__dirname, `../../${lockfile}`), fs.constants.R_OK);
return packageManager;
} catch (e) {}
}
}
pkgInstall.installAll = () => {
const prod = process.env.NODE_ENV !== 'development';
let command = 'npm install';
const supportedPackageManagerList = exports.supportedPackageManager; // load config from src/cli/package-install.js
const packageManager = pkgInstall.getPackageManager();
if (supportedPackageManagerList.indexOf(packageManager) >= 0) {
switch (packageManager) {
case 'yarn':
command = `yarn${prod ? ' --production' : ''}`;
break;
case 'pnpm':
command = 'pnpm install'; // pnpm checks NODE_ENV
break;
case 'cnpm':
command = `cnpm install ${prod ? ' --production' : ''}`;
break;
default:
command += prod ? ' --omit=dev' : '';
break;
}
}
try {
cproc.execSync(command, {
cwd: paths.baseDir,
stdio: 'inherit',
});
} catch (e) {
console.log('Error installing dependencies!');
console.log(`message: ${e.message}`);
console.log(`stdout: ${e.stdout}`);
console.log(`stderr: ${e.stderr}`);
throw e;
}
};
pkgInstall.preserveExtraneousPlugins = () => {
// Skip if `node_modules/` is not found or inaccessible
try {
fs.accessSync(paths.nodeModules, fs.constants.R_OK);
} catch (e) {
return;
}
const packages = fs.readdirSync(paths.nodeModules)
.filter(pkgName => pluginNamePattern.test(pkgName));
const packageContents = JSON.parse(fs.readFileSync(paths.currentPackage, 'utf8'));
const extraneous = packages
// only extraneous plugins (ones not in package.json) which are not links
.filter((pkgName) => {
const extraneous = !packageContents.dependencies.hasOwnProperty(pkgName);
const isLink = fs.lstatSync(path.join(paths.nodeModules, pkgName)).isSymbolicLink();
return extraneous && !isLink;
})
// reduce to a map of package names to package versions
.reduce((map, pkgName) => {
const pkgConfig = JSON.parse(fs.readFileSync(path.join(paths.nodeModules, pkgName, 'package.json'), 'utf8'));
map[pkgName] = pkgConfig.version;
return map;
}, {});
// Add those packages to package.json
packageContents.dependencies = sortDependencies({ ...packageContents.dependencies, ...extraneous });
fs.writeFileSync(paths.currentPackage, JSON.stringify(packageContents, null, 4));
};

View File

@@ -1,173 +1 @@
'use strict';
import path from 'path';
import fs from 'fs';
import cproc from 'child_process';
import { paths, pluginNamePattern } from '../constants';
import nconf from 'nconf';
import _ from 'lodash';
const pkgInstall = {} as any;
function sortDependencies(dependencies) {
return Object.entries(dependencies)
.sort((a, b) => (a < b ? -1 : 1))
.reduce((memo, pkg) => {
memo[pkg[0]] = pkg[1];
return memo;
}, {});
}
pkgInstall.updatePackageFile = () => {
let oldPackageContents;
try {
oldPackageContents = JSON.parse(fs.readFileSync(paths.currentPackage, 'utf8'));
} catch (e: any) { if (e.code !== 'ENOENT') {
throw e;
} else {
// No local package.json, copy from install/package.json
fs.copyFileSync(paths.installPackage, paths.currentPackage);
return;
}
}
const defaultPackageContents = JSON.parse(fs.readFileSync(paths.installPackage, 'utf8'));
let dependencies = {};
Object.entries(oldPackageContents.dependencies || {}).forEach(([dep, version]) => {
if (pluginNamePattern.test(dep)) {
dependencies[dep] = version;
}
});
const { devDependencies } = defaultPackageContents;
// Sort dependencies alphabetically
dependencies = sortDependencies({ ...dependencies, ...defaultPackageContents.dependencies });
const packageContents = { ..._.merge(oldPackageContents, defaultPackageContents), dependencies, devDependencies };
fs.writeFileSync(paths.currentPackage, JSON.stringify(packageContents, null, 4));
};
pkgInstall.supportedPackageManager = [
'npm',
'cnpm',
'pnpm',
'yarn',
];
pkgInstall.getPackageManager = () => {
try {
const packageContents = require(paths.currentPackage);
// This regex technically allows invalid values:
// cnpm isn't supported by corepack and it doesn't enforce a version string being present
const pmRegex = new RegExp(`^(?<packageManager>${pkgInstall.supportedPackageManager.join('|')})@?[\\d\\w\\.\\-]*$`);
const packageManager = packageContents.packageManager ? packageContents.packageManager.match(pmRegex) : false;
if (packageManager) {
return packageManager.groups.packageManager;
}
fs.accessSync(path.join(paths.nodeModules, 'nconf/package.json'), fs.constants.R_OK);
if (!Object.keys(nconf.stores).length) {
// Quick & dirty nconf setup for when you cannot rely on nconf having been required already
const configFile = path.resolve(__dirname, '../../', nconf.any(['config', 'CONFIG']) || 'config.json');
nconf.env().file({ // not sure why adding .argv() causes the process to terminate
file: configFile,
});
}
if (nconf.get('package_manager') && !pkgInstall.supportedPackageManager.includes(nconf.get('package_manager'))) {
nconf.clear('package_manager');
}
if (!nconf.get('package_manager')) {
nconf.set('package_manager', getPackageManagerByLockfile());
}
return nconf.get('package_manager') || 'npm';
} catch (e: any) { // nconf not installed or other unexpected error/exception
return getPackageManagerByLockfile() || 'npm';
}
};
function getPackageManagerByLockfile() {
for (const [packageManager, lockfile] of Object.entries({ npm: 'package-lock.json', yarn: 'yarn.lock', pnpm: 'pnpm-lock.yaml' })) {
try {
fs.accessSync(path.resolve(__dirname, `../../${lockfile}`), fs.constants.R_OK);
return packageManager;
} catch (e) {}
}
}
pkgInstall.installAll = () => {
const prod = (process as any).env.NODE_ENV !== 'development';
let command = 'npm install';
const supportedPackageManagerList = pkgInstall.supportedPackageManager; // load config from src/cli/package-install.js
const packageManager = pkgInstall.getPackageManager();
if (supportedPackageManagerList.indexOf(packageManager) >= 0) {
switch (packageManager) {
case 'yarn':
command = `yarn${prod ? ' --production' : ''}`;
break;
case 'pnpm':
command = 'pnpm install'; // pnpm checks NODE_ENV
break;
case 'cnpm':
command = `cnpm install ${prod ? ' --production' : ''}`;
break;
default:
command += prod ? ' --omit=dev' : '';
break;
}
}
try {
cproc.execSync(command, {
cwd: path.join(__dirname, '../../'),
stdio: [0, 1, 2],
});
} catch (e: any) {
console.log('Error installing dependencies!');
console.log(`message: ${e.message}`);
console.log(`stdout: ${e.stdout}`);
console.log(`stderr: ${e.stderr}`);
throw e;
}
};
pkgInstall.preserveExtraneousPlugins = () => {
// Skip if `node_modules/` is not found or inaccessible
try {
fs.accessSync(paths.nodeModules, fs.constants.R_OK);
} catch (e: any) { return;
}
const packages = fs.readdirSync(paths.nodeModules)
.filter(pkgName => pluginNamePattern.test(pkgName));
const packageContents = JSON.parse(fs.readFileSync(paths.currentPackage, 'utf8'));
const extraneous = packages
// only extraneous plugins (ones not in package.json) which are not links
.filter((pkgName) => {
const extraneous = !packageContents.dependencies.hasOwnProperty(pkgName);
const isLink = fs.lstatSync(path.join(paths.nodeModules, pkgName)).isSymbolicLink();
return extraneous && !isLink;
})
// reduce to a map of package names to package versions
.reduce((map, pkgName) => {
const pkgConfig = JSON.parse(fs.readFileSync(path.join(paths.nodeModules, pkgName, 'package.json'), 'utf8'));
map[pkgName] = pkgConfig.version;
return map;
}, {});
// Add those packages to package.json
packageContents.dependencies = sortDependencies({ ...packageContents.dependencies, ...extraneous });
fs.writeFileSync(paths.currentPackage, JSON.stringify(packageContents, null, 4));
};
export default pkgInstall;
export * from '../../../src/cli/package-install';

View File

@@ -70,7 +70,7 @@ export const reset = async function (options) {
' Prefix is optional, e.g. ./nodebb reset -p markdown, ./nodebb reset -t harmony',
].join('\n'));
(process as any).exit(0);
process.exit(0);
}
try {
@@ -80,10 +80,10 @@ export const reset = async function (options) {
await task();
}
winston.info('[reset] Reset complete. Please run `./nodebb build` to rebuild assets.');
(process as any).exit(0);
process.exit(0);
} catch (err: any) {
winston.error(`[reset] Errors were encountered during reset -- ${err.message}`);
(process as any).exit(1);
process.exit(1);
}
};
@@ -120,7 +120,7 @@ async function resetPlugin(pluginId?) {
try {
if (nconf.get('plugins:active')) {
winston.error('Cannot reset plugins while plugin state is set in the configuration (config.json, environmental variables or terminal arguments), please modify the configuration instead');
(process as any).exit(1);
process.exit(1);
}
const isActive = await db.isSortedSetMember('plugins:active', pluginId);
if (isActive) {
@@ -143,7 +143,7 @@ async function resetPlugin(pluginId?) {
async function resetPlugins() {
if (nconf.get('plugins:active')) {
winston.error('Cannot reset plugins while plugin state is set in the configuration (config.json, environmental variables or terminal arguments), please modify the configuration instead');
(process as any).exit(1);
process.exit(1);
}
await db.delete('plugins:active');
winston.info('[reset] All Plugins De-activated');

View File

@@ -18,7 +18,7 @@ export function getRunningPid(callback) {
pid = parseInt(pid as string, 10);
try {
(process as any).kill(pid, 0);
process.kill(pid, 0);
callback(null, pid);
} catch (e: any) { callback(e);
}
@@ -27,9 +27,9 @@ export function getRunningPid(callback) {
export function start(options?) {
if (options.dev) {
(process as any).env.NODE_ENV = 'development';
process.env.NODE_ENV = 'development';
fork(paths.loader, ['--no-daemon', '--no-silent'], {
env: (process as any).env,
env: process.env,
stdio: 'inherit',
cwd,
});
@@ -52,13 +52,13 @@ export function start(options?) {
}
// Spawn a new NodeBB process
const child = fork(paths.loader, (process as any).argv.slice(3), {
env: (process as any).env,
const child = fork(paths.loader, process.argv.slice(3), {
env: process.env,
cwd,
});
if (options.log) {
// @ts-ignore
child(process as any).spawn('tail', ['-F', './logs/output.log'], {
childprocess.spawn('tail', ['-F', './logs/output.log'], {
stdio: 'inherit',
cwd,
});
@@ -70,7 +70,7 @@ export function start(options?) {
export function stop() {
getRunningPid((err, pid) => {
if (!err) {
(process as any).kill(pid, 'SIGTERM');
process.kill(pid, 'SIGTERM');
console.log('Stopping NodeBB. Goodbye!');
} else {
console.log('NodeBB is already stopped.');
@@ -82,7 +82,7 @@ function restart(options) {
getRunningPid((err, pid) => {
if (!err) {
console.log(chalk.bold('\nRestarting NodeBB'));
(process as any).kill(pid, 'SIGTERM');
process.kill(pid, 'SIGTERM');
options.silent = true;
start(options);
@@ -110,7 +110,7 @@ export function status() {
export function log() {
console.log(`${chalk.red('\nHit ') + chalk.bold('Ctrl-C ') + chalk.red('to exit\n')}\n`);
// child(process as any).spawn('tail', ['-F', './logs/output.log'], {
// childprocess.spawn('tail', ['-F', './logs/output.log'], {
// stdio: 'inherit',
// cwd,
// });

View File

@@ -34,8 +34,8 @@ async function setup(initConfig?) {
}
let separator = ' ';
if ((process as any).stdout.columns > 10) {
for (let x = 0, cols = (process as any).stdout.columns - 10; x < cols; x += 1) {
if (process.stdout.columns > 10) {
for (let x = 0, cols = process.stdout.columns - 10; x < cols; x += 1) {
separator += '=';
}
}
@@ -51,10 +51,10 @@ async function setup(initConfig?) {
// 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 as any).send) {
(process as any).send(data);
if (process.send) {
process.send(data);
}
(process as any).exit();
process.exit();
}
export default {

View File

@@ -16,7 +16,7 @@ const packageManager = pkgInstall.getPackageManager();
let packageManagerExecutable = packageManager;
const packageManagerInstallArgs = packageManager === 'yarn' ? ['add'] : ['install', '--save'];
if ((process as any).platform === 'win32') {
if (process.platform === 'win32') {
packageManagerExecutable += '.cmd';
}
@@ -85,7 +85,7 @@ async function getSuggestedModules(nbbVersion, toCheck) {
}
async function checkPlugins() {
(process as any).stdout.write('Checking installed plugins and themes for updates... ');
process.stdout.write('Checking installed plugins and themes for updates... ');
const [plugins, nbbVersion] = await Promise.all([
getInstalledPlugins(),
getCurrentVersion(),
@@ -93,11 +93,11 @@ async function checkPlugins() {
const toCheck = Object.keys(plugins);
if (!toCheck.length) {
(process as any).stdout.write(chalk.green(' OK'));
process.stdout.write(chalk.green(' OK'));
return []; // no extraneous plugins installed
}
const suggestedModules = await getSuggestedModules(nbbVersion, toCheck);
(process as any).stdout.write(chalk.green(' OK'));
process.stdout.write(chalk.green(' OK'));
let current;
let suggested;
@@ -122,9 +122,9 @@ export const upgradePlugins = async function() {
try {
const found = await checkPlugins();
if (found && found.length) {
(process as any).stdout.write(`\n\nA total of ${chalk.bold(String(found.length))} package(s) can be upgraded:\n\n`);
process.stdout.write(`\n\nA total of ${chalk.bold(String(found.length))} package(s) can be upgraded:\n\n`);
found.forEach((suggestObj) => {
(process as any).stdout.write(`${chalk.yellow(' * ') + suggestObj.name} (${chalk.yellow(suggestObj.current)} -> ${chalk.green(suggestObj.suggested)})\n`);
process.stdout.write(`${chalk.yellow(' * ') + suggestObj.name} (${chalk.yellow(suggestObj.current)} -> ${chalk.green(suggestObj.suggested)})\n`);
});
} else {
console.log(chalk.green('\nAll packages up-to-date!'));

View File

@@ -3,7 +3,7 @@
import nconf from 'nconf';
import chalk from 'chalk';
import packageInstall from './package-install';
import { upgradePlugins } from './upgrade-plugins';
import { upgradePlugins } from './upgrade-plugins';
import db from '../database';
import meta from '../meta';
import Upgrade from '../upgrade';
@@ -15,13 +15,13 @@ const steps = {
handler: function () {
packageInstall.updatePackageFile();
packageInstall.preserveExtraneousPlugins();
(process as any).stdout.write(chalk.green(' OK\n'));
process.stdout.write(chalk.green(' OK\n'));
},
},
install: {
message: 'Bringing base dependencies up to date...',
handler: function () {
(process as any).stdout.write(chalk.green(' started\n'));
process.stdout.write(chalk.green(' started\n'));
packageInstall.installAll();
},
},
@@ -53,7 +53,7 @@ async function runSteps(tasks) {
for (let i = 0; i < tasks.length; i++) {
const step = steps[tasks[i]];
if (step && step.message && step.handler) {
(process as any).stdout.write(`\n${chalk.bold(`${i + 1}. `)}${chalk.yellow(step.message)}`);
process.stdout.write(`\n${chalk.bold(`${i + 1}. `)}${chalk.yellow(step.message)}`);
/* eslint-disable-next-line */
await step.handler();
}
@@ -61,12 +61,12 @@ async function runSteps(tasks) {
const message = 'NodeBB Upgrade Complete!';
// some consoles will return undefined/zero columns,
// so just use 2 spaces in upgrade script if we can't get our column count
const { columns } = (process as any).stdout;
const { columns } = process.stdout;
const spaces = columns ? new Array(Math.floor(columns / 2) - (message.length / 2) + 1).join(' ') : ' ';
console.log(`\n\n${spaces}${chalk.green.bold(message)}\n`);
(process as any).exit();
process.exit();
} catch (err: any) {
console.error(`Error occurred during upgrade: ${err.stack}`);
throw err;
@@ -92,7 +92,7 @@ async function runUpgrade(upgrades, options?) {
db.init();
meta.configs.init();
Upgrade.runParticular(upgrades);
(process as any).exit(0);
process.exit(0);
}
export const upgrade = runUpgrade;

View File

@@ -91,10 +91,10 @@ async function execute(cmd, args) {
} catch (err: any) {
const userError = err.name === 'UserError';
winston.error(`[userCmd/${cmd.name}] ${userError ? `${err.message}` : 'Command failed.'}`, userError ? '' : err);
(process as any).exit(1);
process.exit(1);
}
(process as any).exit();
process.exit();
}
function UserCmdHelpers() {

26
src/constants.js Normal file
View File

@@ -0,0 +1,26 @@
'use strict';
const path = require('path');
const baseDir = path.join(__dirname, '../');
const loader = path.join(baseDir, 'loader.js');
const app = path.join(baseDir, 'app.js');
const pidfile = path.join(baseDir, 'pidfile');
const config = path.join(baseDir, 'config.json');
const currentPackage = path.join(baseDir, 'package.json');
const installPackage = path.join(baseDir, 'install/package.json');
const nodeModules = path.join(baseDir, 'node_modules');
exports.paths = {
baseDir,
loader,
app,
pidfile,
config,
currentPackage,
installPackage,
nodeModules,
};
exports.pluginNamePattern = /^(@[\w-]+\/)?nodebb-(theme|plugin|widget|rewards)-[\w-]+$/;
exports.themeNamePattern = /^(@[\w-]+\/)?nodebb-theme-[\w-]+$/;

View File

@@ -1,26 +1 @@
'use strict';
import path from 'path';
const baseDir = path.join(__dirname, '../');
const loader = path.join(baseDir, '../loader.js');
const app = path.join(baseDir, 'app.js');
const pidfile = path.join(baseDir, 'pidfile');
const config = path.join(baseDir, '../config.json');
const currentPackage = path.join(baseDir, '../package.json');
const installPackage = path.join(baseDir, 'install/package.json');
const nodeModules = path.join(baseDir, '../node_modules');
export const paths = {
baseDir,
loader,
app,
pidfile,
config,
currentPackage,
installPackage,
nodeModules,
};
export const pluginNamePattern = /^(@[\w-]+\/)?nodebb-(theme|plugin|widget|rewards)-[\w-]+$/;
export const themeNamePattern = /^(@[\w-]+\/)?nodebb-theme-[\w-]+$/;
export * from '../../src/constants';

View File

@@ -11,7 +11,7 @@
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"target": "ES2019", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
@@ -78,13 +78,13 @@
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
"noImplicitAny": false, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
"strictNullChecks": false, /* When type checking, take into account 'null' and 'undefined'. */
"strictNullChecks": false, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
"noImplicitThis": false, /* Enable error reporting when 'this' is given the type 'any'. */
"noImplicitThis": false, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
"alwaysStrict": false, /* Ensure 'use strict' is always emitted. */
"alwaysStrict": false, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */