From bfdf2a7b7907c7708a2b947c6fbaa8bfd192d345 Mon Sep 17 00:00:00 2001
From: Peter Jaszkowiak
Date: Fri, 30 Dec 2022 19:31:29 -0700
Subject: [PATCH] recommended CLI startup / bootstrap changes
---
install/package.json | 1 +
nodebb | 2 +-
require-main.ts => require-main.js | 1 -
src/cli/bootstrap.js | 94 ++++++++++++++++
src/cli/index.ts | 139 +++++++----------------
src/cli/manage.ts | 70 +++++-------
src/cli/package-install.js | 174 +++++++++++++++++++++++++++++
src/cli/package-install.ts | 174 +----------------------------
src/cli/reset.ts | 10 +-
src/cli/running.ts | 18 +--
src/cli/setup.ts | 10 +-
src/cli/upgrade-plugins.ts | 12 +-
src/cli/upgrade.ts | 14 +--
src/cli/user.ts | 4 +-
src/constants.js | 26 +++++
src/constants.ts | 27 +----
tsconfig.json | 8 +-
17 files changed, 407 insertions(+), 377 deletions(-)
rename require-main.ts => require-main.js (95%)
create mode 100644 src/cli/bootstrap.js
create mode 100644 src/cli/package-install.js
create mode 100644 src/constants.js
diff --git a/install/package.json b/install/package.json
index 462fa7e65f..1dbee30548 100644
--- a/install/package.json
+++ b/install/package.json
@@ -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",
diff --git a/nodebb b/nodebb
index 602a5f3e28..de2b49b145 100755
--- a/nodebb
+++ b/nodebb
@@ -3,4 +3,4 @@
'use strict';
console.log('SERIOUSLY?');
-require('./build/src/cli');
+require('./src/cli/bootstrap');
diff --git a/require-main.ts b/require-main.js
similarity index 95%
rename from require-main.ts
rename to require-main.js
index 9c48762570..ed26ca1a0a 100644
--- a/require-main.ts
+++ b/require-main.js
@@ -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);
};
diff --git a/src/cli/bootstrap.js b/src/cli/bootstrap.js
new file mode 100644
index 0000000000..81409ece56
--- /dev/null
+++ b/src/cli/bootstrap.js
@@ -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');
diff --git a/src/cli/index.ts b/src/cli/index.ts
index d2744d5d8e..bb1bc52fb4 100644
--- a/src/cli/index.ts
+++ b/src/cli/index.ts
@@ -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 ', 'Default logging level to use', 'info')
.option('--config ', '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();
diff --git a/src/cli/manage.ts b/src/cli/manage.ts
index ab61b9a0f5..1f27e261ed 100644
--- a/src/cli/manage.ts
+++ b/src/cli/manage.ts
@@ -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
-};
\ No newline at end of file
diff --git a/src/cli/package-install.js b/src/cli/package-install.js
new file mode 100644
index 0000000000..c4380725b4
--- /dev/null
+++ b/src/cli/package-install.js
@@ -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(`^(?${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));
+};
diff --git a/src/cli/package-install.ts b/src/cli/package-install.ts
index 8df2b181af..22219b769a 100644
--- a/src/cli/package-install.ts
+++ b/src/cli/package-install.ts
@@ -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(`^(?${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;
\ No newline at end of file
+export * from '../../../src/cli/package-install';
diff --git a/src/cli/reset.ts b/src/cli/reset.ts
index 398b14772f..708c05521b 100644
--- a/src/cli/reset.ts
+++ b/src/cli/reset.ts
@@ -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');
diff --git a/src/cli/running.ts b/src/cli/running.ts
index 3d6ff57a0b..93c99ea037 100644
--- a/src/cli/running.ts
+++ b/src/cli/running.ts
@@ -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,
// });
diff --git a/src/cli/setup.ts b/src/cli/setup.ts
index 0f45e78577..14c75a60be 100644
--- a/src/cli/setup.ts
+++ b/src/cli/setup.ts
@@ -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 {
diff --git a/src/cli/upgrade-plugins.ts b/src/cli/upgrade-plugins.ts
index fa18e0e6a8..a8c6f4f348 100644
--- a/src/cli/upgrade-plugins.ts
+++ b/src/cli/upgrade-plugins.ts
@@ -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!'));
diff --git a/src/cli/upgrade.ts b/src/cli/upgrade.ts
index ade8516f55..ba559ec34a 100644
--- a/src/cli/upgrade.ts
+++ b/src/cli/upgrade.ts
@@ -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;
diff --git a/src/cli/user.ts b/src/cli/user.ts
index 657c4fcfbe..613dcdd06c 100644
--- a/src/cli/user.ts
+++ b/src/cli/user.ts
@@ -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() {
diff --git a/src/constants.js b/src/constants.js
new file mode 100644
index 0000000000..cc563f1353
--- /dev/null
+++ b/src/constants.js
@@ -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-]+$/;
diff --git a/src/constants.ts b/src/constants.ts
index 45543a045e..1f45be5ecf 100644
--- a/src/constants.ts
+++ b/src/constants.ts
@@ -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';
diff --git a/tsconfig.json b/tsconfig.json
index 97f43004ae..9d26489116 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -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'. */