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'. */