diff --git a/package.json b/package.json index 8771a5b5ec..4664b53ec0 100644 --- a/package.json +++ b/package.json @@ -62,7 +62,7 @@ "license": "MIT", "devDependencies": { "@types/cloud-env": "^0.2.0", - "@types/node": "^12.7.11", + "@types/node": "^12.12.6", "@types/node-static": "^0.7.3", "@types/nodemailer": "^6.2.1", "@types/sockjs": "^0.3.31", diff --git a/pokemon-showdown b/pokemon-showdown index 54493699cd..35dc359277 100755 --- a/pokemon-showdown +++ b/pokemon-showdown @@ -38,7 +38,7 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) { // $ ./pokemon-showdown 9000 if (!built) build(); - require('module')._load('./server', module, true); + require('module')._load('./.server-dist', module, true); } else switch (process.argv[2]) { case 'help': case 'h': @@ -87,7 +87,7 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) { { process.argv[2] = process.argv[3]; if (!built) build(); - require('module')._load('./server', module, true); + require('module')._load('./.server-dist', module, true); break; } case 'generate-team': diff --git a/server/index.js b/server/index.ts similarity index 71% rename from server/index.js rename to server/index.ts index 4f966c50ae..3470a12aa6 100644 --- a/server/index.js +++ b/server/index.ts @@ -40,8 +40,6 @@ * @license MIT */ -'use strict'; - // NOTE: This file intentionally doesn't use too many modern JavaScript // features, so that it doesn't crash old versions of Node.js, so we // can successfully print the "We require Node.js 8+" message. @@ -54,24 +52,29 @@ try { } catch (e) { throw new Error("We require Node.js version 10 or later; you're using " + process.version); } + try { require.resolve('../.sim-dist/index'); } catch (e) { throw new Error("Dependencies are unmet; run `node build` before launching Pokemon Showdown again."); } -const FS = require('../.lib-dist/fs').FS; +import {FS} from '../lib/fs'; /********************************************************* * Load configuration *********************************************************/ -const ConfigLoader = require('../.server-dist/config-loader'); +// global becomes much easier to use if declared as an object +declare const global: any; + +import * as ConfigLoader from './config-loader'; global.Config = ConfigLoader.Config; -global.Monitor = require('../.server-dist/monitor').Monitor; +import {Monitor} from './monitor'; +global.Monitor = Monitor; global.__version = {head: ''}; -Monitor.version().then(function (hash) { +void Monitor.version().then((hash: any) => { global.__version.tree = hash; }); @@ -91,35 +94,48 @@ if (Config.watchconfig) { * Set up most of our globals *********************************************************/ -global.Dex = require('../.sim-dist/dex').Dex; -global.toID = Dex.getId; +import {Dex} from '../sim/dex'; +global.Dex = Dex; +global.toId = Dex.getId; -global.LoginServer = require('../.server-dist/loginserver').LoginServer; +import {LoginServer} from './loginserver'; +global.LoginServer = LoginServer; -global.Ladders = require('../.server-dist/ladders').Ladders; +import {Ladders} from './ladders'; +global.Ladders = Ladders; -global.Chat = require('../.server-dist/chat').Chat; +import {Chat} from './chat'; +global.Chat = Chat; -global.Users = require('../.server-dist/users').Users; +import {Users} from './users'; +global.Users = Users; -global.Punishments = require('../.server-dist/punishments').Punishments; +import {Punishments} from './punishments'; +global.Punishments = Punishments; -global.Rooms = require('../.server-dist/rooms').Rooms; +import {Rooms} from './rooms'; +global.Rooms = Rooms; -global.Verifier = require('../.server-dist/verifier'); +import * as Verifier from './verifier'; +global.Verifier = Verifier; Verifier.PM.spawn(); -global.Tournaments = require('../.server-dist/tournaments').Tournaments; +import {Tournaments} from './tournaments'; +global.Tournaments = Tournaments; -global.IPTools = require('../.server-dist/ip-tools').IPTools; -IPTools.loadDatacenters(); +import {IPTools} from './ip-tools'; +global.IPTools = IPTools; +void IPTools.loadDatacenters(); if (Config.crashguard) { // graceful crash - allow current battles to finish before restarting - process.on('uncaughtException', err => { + process.on('uncaughtException', (err: Error) => { Monitor.crashlog(err, 'The main process'); }); - process.on('unhandledRejection', err => { + + // Typescript doesn't like this call + // @ts-ignore + process.on('unhandledRejection', (err: Error, promise: Promise) => { Monitor.crashlog(err, 'A main process Promise'); }); } @@ -128,11 +144,12 @@ if (Config.crashguard) { * Start networking processes to be connected to *********************************************************/ -global.Sockets = require('./sockets'); +import * as Sockets from '../server/sockets'; +global.Sockets = Sockets; -exports.listen = function (port, bindAddress, workerCount) { +export function listen(port: number, bindAddress: string, workerCount: number) { Sockets.listen(port, bindAddress, workerCount); -}; +} if (require.main === module) { // Launch the server directly when app.js is the main module. Otherwise, @@ -147,11 +164,14 @@ if (require.main === module) { * Set up our last global *********************************************************/ -global.TeamValidatorAsync = require('./team-validator-async'); +import * as TeamValidatorAsync from './team-validator-async'; +global.TeamValidatorAsync = TeamValidatorAsync; TeamValidatorAsync.PM.spawn(); /********************************************************* * Start up the REPL server *********************************************************/ -require('../.lib-dist/repl').Repl.start('app', cmd => eval(cmd)); +import {Repl} from '../lib/repl'; +// tslint:disable-next-line: no-eval +Repl.start('app', cmd => eval(cmd)); diff --git a/server/team-validator-async.js b/server/team-validator-async.js deleted file mode 100644 index e88159d725..0000000000 --- a/server/team-validator-async.js +++ /dev/null @@ -1,119 +0,0 @@ -/** - * Team Validator - * Pokemon Showdown - http://pokemonshowdown.com/ - * - * Spawns a child process to validate teams. - * - * @license MIT - */ - -'use strict'; - -/** @type {typeof import('../lib/crashlogger').crashlogger} */ -let crashlogger = require(/** @type {any} */('../.lib-dist/crashlogger')).crashlogger; - -class TeamValidatorAsync { - /** - * @param {string} format - */ - constructor(format) { - this.format = Dex.getFormat(format); - } - - /** - * @param {string} team - * @param {boolean} [removeNicknames] - */ - validateTeam(team, removeNicknames = false) { - let formatid = this.format.id; - if (this.format.customRules) formatid += '@@@' + this.format.customRules.join(','); - return PM.query({formatid, removeNicknames, team}); - } - - /** - * @param {string} format - */ - static get(format) { - return new TeamValidatorAsync(format); - } -} - -/********************************************************* - * Process manager - *********************************************************/ - -/** @type {typeof import('../lib/process-manager').QueryProcessManager} */ -const QueryProcessManager = require(/** @type {any} */('../.lib-dist/process-manager')).QueryProcessManager; - -/** @type {import('../lib/process-manager').QueryProcessManager} */ -// @ts-ignore -const PM = new QueryProcessManager(module, async message => { - let {formatid, removeNicknames, team} = message; - let parsedTeam = Dex.fastUnpackTeam(team); - - let problems; - try { - problems = TeamValidator.get(formatid).validateTeam(parsedTeam, removeNicknames); - } catch (err) { - crashlogger(err, 'A team validation', { - formatid: formatid, - team: team, - }); - problems = [`Your team crashed the team validator. We've been automatically notified and will fix this crash, but you should use a different team for now.`]; - } - - if (problems && problems.length) { - return '0' + problems.join('\n'); - } - let packedTeam = Dex.packTeam(parsedTeam); - // console.log('FROM: ' + message.substr(pipeIndex2 + 1)); - // console.log('TO: ' + packedTeam); - return '1' + packedTeam; -}); - -if (!PM.isParentProcess) { - // This is a child process! - global.Config = require(/** @type {any} */('../.server-dist/config-loader')).Config; - - global.TeamValidator = require(/** @type {any} */ ('../.sim-dist/team-validator')).TeamValidator; - // @ts-ignore ??? - global.Monitor = { - /** - * @param {Error} error - * @param {string} source - * @param {{}?} details - */ - crashlog(error, source = 'A team validator process', details = null) { - const repr = JSON.stringify([error.name, error.message, source, details]); - // @ts-ignore - process.send(`THROW\n@!!@${repr}\n${error.stack}`); - }, - }; - - if (Config.crashguard) { - process.on('uncaughtException', err => { - Monitor.crashlog(err, `A team validator process`); - }); - process.on('unhandledRejection', err => { - if (err instanceof Error) { - Monitor.crashlog(err, 'A team validator process Promise'); - } - }); - } - - global.Dex = require(/** @type {any} */ ('../.sim-dist/dex')).Dex.includeData(); - global.toID = Dex.getId; - global.Chat = require(/** @type {any} */('../.server-dist/chat')).Chat; - - /** @type {typeof import('../lib/repl').Repl} */ - const Repl = require(/** @type {any} */('../.lib-dist/repl')).Repl; - Repl.start(`team-validator-${process.pid}`, cmd => eval(cmd)); -} else { - PM.spawn(global.Config ? Config.validatorprocesses : 1); -} - -/********************************************************* - * Exports - *********************************************************/ - -module.exports = {get: TeamValidatorAsync.get, TeamValidatorAsync, PM}; diff --git a/server/team-validator-async.ts b/server/team-validator-async.ts new file mode 100644 index 0000000000..07717f8282 --- /dev/null +++ b/server/team-validator-async.ts @@ -0,0 +1,105 @@ +/** + * Team Validator + * Pokemon Showdown - http://pokemonshowdown.com/ + * + * Spawns a child process to validate teams. + * + * @license MIT + */ + +import {crashlogger} from '../lib/crashlogger'; + +declare var global: any; + +export class TeamValidatorAsync { + format: Format; + + constructor(format: string) { + this.format = Dex.getFormat(format); + } + + validateTeam(team: string, removeNicknames: boolean = false) { + let formatid = this.format.id; + if (this.format.customRules) formatid += '@@@' + this.format.customRules.join(','); + return PM.query({formatid, removeNicknames, team}); + } + + static get(format: string) { + return new TeamValidatorAsync(format); + } +} + +export const get = TeamValidatorAsync.get; + +/********************************************************* + * Process manager + *********************************************************/ + +import {QueryProcessManager} from '../lib/process-manager'; + +export const PM = new QueryProcessManager(module, async message => { + const {formatid, removeNicknames, team} = message; + const parsedTeam = Dex.fastUnpackTeam(team); + + let problems; + try { + problems = TeamValidator.get(formatid).validateTeam(parsedTeam, removeNicknames); + } catch (err) { + crashlogger(err, 'A team validation', { + formatid, + team, + }); + problems = [`Your team crashed the team validator. We've been automatically notified and will fix this crash, but you should use a different team for now.`]; + } + + if (problems && problems.length) { + return '0' + problems.join('\n'); + } + const packedTeam = Dex.packTeam(parsedTeam); + // console.log('FROM: ' + message.substr(pipeIndex2 + 1)); + // console.log('TO: ' + packedTeam); + return '1' + packedTeam; +}); + +import {Repl} from '../lib/repl'; +import {Dex as importedDex} from '../sim/dex'; +import {TeamValidator} from '../sim/team-validator'; +import {Chat} from './chat'; +import {Config} from './config-loader'; + +if (!PM.isParentProcess) { + // This is a child process! + global.Config = Config; + + global.TeamValidator = TeamValidator; + + // @ts-ignore ??? + global.Monitor = { + crashlog(error: Error, source: string = 'A team validator process', details: any = null) { + const repr = JSON.stringify([error.name, error.message, source, details]); + // @ts-ignore + process.send(`THROW\n@!!@${repr}\n${error.stack}`); + }, + }; + + if (Config.crashguard) { + process.on('uncaughtException', (err: Error) => { + Monitor.crashlog(err, `A team validator process`); + }); + // Typescript doesn't like this call + // @ts-ignore + process.on('unhandledRejection', (err: Error, promise: Promise) => { + if (err instanceof Error) { + Monitor.crashlog(err, 'A team validator process Promise'); + } + }); + } + + global.Dex = importedDex.includeData(); + global.toId = Dex.getId; + + // tslint:disable-next-line: no-eval + Repl.start(`team-validator-${process.pid}`, cmd => eval(cmd)); +} else { + PM.spawn(global.Config ? Config.validatorprocesses : 1); +} diff --git a/test/main.js b/test/main.js index 24c8938c51..98f12ec971 100644 --- a/test/main.js +++ b/test/main.js @@ -43,7 +43,8 @@ before('initialization', function () { require('../.lib-dist/repl').Repl.start = noop; // Start the server. - require('../server'); + // NOTE: This used "server" before when we needed ".server-dist" + require('../.server-dist'); LoginServer.disabled = true;