diff --git a/.eslintignore b/.eslintignore index 2de8d7c7f6..8559eb10fd 100644 --- a/.eslintignore +++ b/.eslintignore @@ -2,4 +2,4 @@ server/tournaments/lib/ logs/ dev-tools/globals.js node_modules/ -.sim-dist/ +.*-dist/ diff --git a/.gitignore b/.gitignore index 7a65552477..4ce45a2588 100644 --- a/.gitignore +++ b/.gitignore @@ -10,7 +10,7 @@ npm-debug.log package-lock.json # Typescript build artifacts -.sim-dist/ +.*-dist/ # visual studio live share .vs diff --git a/.lib-dist/README.md b/.lib-dist/README.md new file mode 100644 index 0000000000..edb4a4ecc7 --- /dev/null +++ b/.lib-dist/README.md @@ -0,0 +1,3 @@ +**NOTE**: This folder contains the compiled output of the `lib/` directory. +You should be editing the `.ts` files there and then running `npm run build` or +`./pokemon-showdown` to force these `.js` files to be recreated. diff --git a/.sim-dist/README.md b/.sim-dist/README.md index 50f23d0fb1..9da980889f 100644 --- a/.sim-dist/README.md +++ b/.sim-dist/README.md @@ -1,3 +1,3 @@ **NOTE**: This folder contains the compiled output of the `sim/` directory. -You should be editting the `.ts` files there and then running `npm run build` or +You should be editing the `.ts` files there and then running `npm run build` or `./pokemon-showdown` to force these `.js` files to be recreated. diff --git a/build b/build index 45c2fcb1a2..25163ac2b1 100755 --- a/build +++ b/build @@ -12,6 +12,9 @@ var fs = require('fs'); var path = require('path'); function shell(cmd) { child_process.execSync(cmd, {stdio: 'inherit', cwd: __dirname}); } +function sucrase(src, out) { + shell(`npx sucrase -q ${src} -d ${out} --transforms typescript,imports --enable-legacy-typescript-module-interop`); +} try { require.resolve('sucrase'); @@ -20,7 +23,9 @@ try { shell('npm install'); } -shell('npx sucrase -q ./sim -d ./.sim-dist --transforms typescript,imports --enable-legacy-typescript-module-interop'); +sucrase('./sim', './.sim-dist'); +sucrase('./lib', './.lib-dist'); +shell('npx replace --silent \'(require\\\(.*?)(lib)(.*?\\\))\' \'$1.lib-dist$3\' .sim-dist/*'); // Make sure config.js exists. If not, copy it over synchronously from // config-example.js, since it's needed before we can start the server diff --git a/data/mods/gen3/random-teams.js b/data/mods/gen3/random-teams.js index bcbd056eca..9ce92f17fa 100644 --- a/data/mods/gen3/random-teams.js +++ b/data/mods/gen3/random-teams.js @@ -17,7 +17,7 @@ class RandomGen3Teams extends RandomGen4Teams { template = this.getTemplate('unown'); let err = new Error('Template incompatible with random battles: ' + species); - require('../../../lib/crashlogger')(err, 'The gen 3 randbat set generator'); + Monitor.crashlog(err, 'The gen 3 randbat set generator'); } if (template.battleOnly) species = template.baseSpecies; diff --git a/data/mods/gen4/random-teams.js b/data/mods/gen4/random-teams.js index e15d6abc87..ab2dddea2d 100644 --- a/data/mods/gen4/random-teams.js +++ b/data/mods/gen4/random-teams.js @@ -18,7 +18,7 @@ class RandomGen4Teams extends RandomGen5Teams { template = this.getTemplate('unown'); let err = new Error('Template incompatible with random battles: ' + species); - require('../../../lib/crashlogger')(err, 'The gen 4 randbat set generator'); + Monitor.crashlog(err, 'The gen 4 randbat set generator'); } if (template.battleOnly) species = template.baseSpecies; diff --git a/data/mods/gen5/random-teams.js b/data/mods/gen5/random-teams.js index eb3851d7f3..ea5408138c 100644 --- a/data/mods/gen5/random-teams.js +++ b/data/mods/gen5/random-teams.js @@ -19,7 +19,7 @@ class RandomGen5Teams extends RandomGen6Teams { template = this.getTemplate('unown'); let err = new Error('Template incompatible with random battles: ' + species); - require('../../../lib/crashlogger')(err, 'The gen 5 randbat set generator'); + Monitor.crashlog(err, 'The gen 5 randbat set generator'); } if (template.battleOnly) { diff --git a/data/mods/gen6/random-teams.js b/data/mods/gen6/random-teams.js index 697db8b560..183b595115 100644 --- a/data/mods/gen6/random-teams.js +++ b/data/mods/gen6/random-teams.js @@ -30,7 +30,7 @@ class RandomGen6Teams extends RandomTeams { template = this.getTemplate('unown'); let err = new Error('Template incompatible with random battles: ' + species); - require('../../../lib/crashlogger')(err, 'The gen 6 randbat set generator'); + Monitor.crashlog(err, 'The gen 6 randbat set generator'); } if (template.battleOnly) { diff --git a/data/mods/letsgo/random-teams.js b/data/mods/letsgo/random-teams.js index df91ccfb2a..61e135ba1f 100644 --- a/data/mods/letsgo/random-teams.js +++ b/data/mods/letsgo/random-teams.js @@ -17,7 +17,7 @@ class RandomLetsGoTeams extends RandomTeams { template = this.getTemplate('bulbasaur'); let err = new Error('Template incompatible with random battles: ' + species); - require('../../../lib/crashlogger')(err, 'The Let\'s Go randbat set generator'); + Monitor.crashlog(err, 'The Let\'s Go randbat set generator'); } if (template.battleOnly) { diff --git a/data/random-teams.js b/data/random-teams.js index cc52379690..ef78e93aea 100644 --- a/data/random-teams.js +++ b/data/random-teams.js @@ -611,7 +611,7 @@ class RandomTeams extends Dex.ModdedDex { template = this.getTemplate('unown'); let err = new Error('Template incompatible with random battles: ' + species); - require('../lib/crashlogger')(err, 'The randbat set generator'); + Monitor.crashlog(err, 'The randbat set generator'); } if (template.battleOnly) { diff --git a/dev-tools/global.d.ts b/dev-tools/global.d.ts index c5042dc34f..17a481aded 100644 --- a/dev-tools/global.d.ts +++ b/dev-tools/global.d.ts @@ -18,7 +18,7 @@ import UsersType = require('./../server/users'); import PunishmentsType = require('./../server/punishments'); import ChatType = require('./../server/chat'); -import StreamsType = require('./../lib/streams'); +import * as StreamsType from './../lib/streams'; declare global { // modules diff --git a/lib/crashlogger.js b/lib/crashlogger.ts similarity index 79% rename from lib/crashlogger.js rename to lib/crashlogger.ts index b3223aeed1..e17b1e1ff4 100644 --- a/lib/crashlogger.js +++ b/lib/crashlogger.ts @@ -8,45 +8,38 @@ * @license MIT */ -'use strict'; - -const fs = require('fs'); -const path = require('path'); +import * as fs from 'fs'; +import * as path from 'path'; const CRASH_EMAIL_THROTTLE = 5 * 60 * 1000; // 5 minutes const LOCKDOWN_PERIOD = 30 * 60 * 1000; // 30 minutes const logPath = path.resolve(__dirname, '../logs/errors.txt'); let lastCrashLog = 0; -/** @type {any} */ -let transport; +let transport: any; /** * Logs when a crash happens to console, then e-mails those who are configured * to receive them. - * - * @param {Error | string} err - * @param {string} description - * @param {?Object} [data = null] - * @return {?string} */ -module.exports = function crashlogger(err, description, data = null) { +export = function crashlogger(error: Error | string, description: string, data: object | null = null): string | null { const datenow = Date.now(); - let stack = typeof err === 'string' ? err : err.stack; + let stack = typeof error === 'string' ? error : error.stack; if (data) { stack += `\n\nAdditional information:\n`; for (let k in data) { + // @ts-ignore stack += ` ${k} = ${data[k]}\n`; } } console.error(`\nCRASH: ${stack}\n`); - let out = fs.createWriteStream(logPath, {'flags': 'a'}); + let out = fs.createWriteStream(logPath, {flags: 'a'}); out.on('open', () => { out.write(`\n${stack}\n`); out.end(); - }).on('error', /** @param {Error} err */ err => { + }).on('error', (err: Error) => { console.error(`\nSUBCRASH: ${err.stack}\n`); }); @@ -69,6 +62,7 @@ module.exports = function crashlogger(err, description, data = null) { text += `again with this stack trace:\n${stack}`; } else { try { + // tslint:disable-next-line:no-implicit-dependencies transport = require('nodemailer').createTransport(Config.crashguardemail.options); } catch (e) { throw new Error("Failed to start nodemailer; are you sure you've configured Config.crashguardemail correctly?"); @@ -82,7 +76,7 @@ module.exports = function crashlogger(err, description, data = null) { to: Config.crashguardemail.to, subject: Config.crashguardemail.subject, text, - }, /** @param {?Error} err */ err => { + }, (err: Error | null) => { if (err) console.error(`Error sending email: ${err}`); }); } diff --git a/lib/dashycode.js b/lib/dashycode.ts similarity index 86% rename from lib/dashycode.js rename to lib/dashycode.ts index e2cb78fd70..02242fda70 100644 --- a/lib/dashycode.js +++ b/lib/dashycode.ts @@ -12,8 +12,6 @@ * @license MIT */ -'use strict'; - const CODE_MAP = "23456789abcdefghijkmnpqrstuvwxyz"; const UNSAFE_MAP = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; @@ -31,19 +29,14 @@ const UNSAFE_MAP = "!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~"; * An object representing a Dashycode bitstream. * The stream can be either a read stream or a write stream, but not * both simultaneously. - * - * @typedef {Object} DashyStream - * @property {string} codeBuf - * @property {number} buf - * @property {number} bufLength */ +interface DashyStream { + codeBuf: string; + buf: number; + bufLength: number; +} -/** - * @param {DashyStream} stream - * @param {number} writeBufLength in bits - * @param {number} writeBuf - */ -function streamWrite(stream, writeBufLength, writeBuf) { +function streamWrite(stream: DashyStream, writeBufLength: number, writeBuf: number) { stream.buf += (writeBuf << stream.bufLength); stream.bufLength += writeBufLength; while (stream.bufLength >= 5) { @@ -52,10 +45,8 @@ function streamWrite(stream, writeBufLength, writeBuf) { stream.bufLength -= 5; } } -/** - * @param {DashyStream} stream - */ -function streamGetCode(stream) { + +function streamGetCode(stream: DashyStream) { const buf = stream.codeBuf + CODE_MAP.charAt(stream.buf); // truncate trailing `2`s (0b00000 chunks) @@ -64,12 +55,7 @@ function streamGetCode(stream) { return end2Len ? buf.slice(0, -end2Len) : buf; } -/** - * @param {DashyStream} stream - * @param {number} readLength - * @param {number} readMask passed for "perf" - */ -function streamPeek(stream, readLength, readMask = 0xFFFF >> (16 - readLength)) { +function streamPeek(stream: DashyStream, readLength: number, readMask: number = 0xFFFF >> (16 - readLength)) { while (stream.bufLength < readLength && stream.codeBuf.length) { const next5Bits = CODE_MAP.indexOf(stream.codeBuf.charAt(0)); if (next5Bits < 0) throw new Error("Invalid character in coded buffer"); @@ -79,12 +65,8 @@ function streamPeek(stream, readLength, readMask = 0xFFFF >> (16 - readLength)) } return stream.buf & readMask; } -/** - * @param {DashyStream} stream - * @param {number} readLength - * @param {number} readMask passed for "perf" - */ -function streamRead(stream, readLength, readMask = 0xFFFF >> (16 - readLength)) { + +function streamRead(stream: DashyStream, readLength: number, readMask: number = 0xFFFF >> (16 - readLength)) { const output = streamPeek(stream, readLength, readMask); // Note: bufLength can go negative! Streams have infinite trailing 0s stream.buf >>= readLength; @@ -92,17 +74,14 @@ function streamRead(stream, readLength, readMask = 0xFFFF >> (16 - readLength)) return output; } -/** - * @param {string} str - */ -function encode(str, allowCaps = false) { +export function encode(str: string, allowCaps: boolean = false) { if (!str) return '0--0'; let safePart = ''; - let unsafeStream = /** @type {DashyStream} */ ({ + let unsafeStream: DashyStream = { codeBuf: '', buf: 0x0, bufLength: 0, - }); + }; let isSafe = true; let alphaIndex = 0; let capBuffer = 0x0; @@ -169,6 +148,7 @@ function encode(str, allowCaps = false) { streamWrite(unsafeStream, 2, 0x0); } else if (curCharCode === 32) { // space streamWrite(unsafeStream, 3, 0x3); + // tslint:disable-next-line:no-conditional-assignment } else if ((unsafeMapIndex = UNSAFE_MAP.indexOf(str.charAt(i))) >= 0) { curCharCode = (unsafeMapIndex << 2) + 0x2; streamWrite(unsafeStream, 7, curCharCode); @@ -194,10 +174,7 @@ function encode(str, allowCaps = false) { return safePart + '--' + unsafePart; } -/** - * @param {string} codedStr - */ -function decode(codedStr) { +export function decode(codedStr: string) { let str = ''; let lastDashIndex = codedStr.lastIndexOf('--'); if (lastDashIndex < 0) { @@ -216,11 +193,11 @@ function decode(codedStr) { codedStr = '-' + codedStr.slice(0, -1); lastDashIndex += 1; } - let unsafeStream = /** @type {DashyStream} */ ({ + let unsafeStream: DashyStream = { codeBuf: codedStr.slice(lastDashIndex + 2), buf: 0x0, bufLength: 0, - }); + }; /** * Status: * 1 : awaiting next read @@ -289,10 +266,7 @@ function decode(codedStr) { return str; } -/** - * @param {string} codeBuf - */ -function vizStream(codeBuf, translate = true) { +export function vizStream(codeBuf: string, translate: boolean = true) { let spacedStream = ''; if (codeBuf.charAt(0) === '0') { codeBuf = codeBuf.slice(1); @@ -302,16 +276,18 @@ function vizStream(codeBuf, translate = true) { codeBuf = codeBuf.slice(0, -1); spacedStream = ' [start unsafe]' + spacedStream; } - let stream = /** @type {DashyStream} */ ({ + let stream: DashyStream = { codeBuf, buf: 0x0, bufLength: 0, - }); - function vizBlock(/** @type {DashyStream} */ stream, /** @type {number} */ bufLen) { - const buf = streamRead(stream, bufLen); + }; + + function vizBlock(s: DashyStream, bufLen: number) { + const buf = streamRead(s, bufLen); // @ts-ignore return buf.toString(2).padStart(bufLen, '0'); } + while (stream.bufLength > 0 || stream.codeBuf) { switch (streamRead(stream, 2)) { case 0x0: @@ -338,9 +314,3 @@ function vizStream(codeBuf, translate = true) { } return spacedStream; } - -module.exports = { - encode, - decode, - vizStream, -}; diff --git a/lib/fs.js b/lib/fs.ts similarity index 68% rename from lib/fs.js rename to lib/fs.ts index 43571c758d..31c5cde85e 100644 --- a/lib/fs.js +++ b/lib/fs.ts @@ -23,71 +23,64 @@ 'use strict'; -const pathModule = require('path'); -const fs = require('fs'); -const Streams = require('./streams'); -const WriteStream = Streams.WriteStream; +import * as fs from 'fs'; +import * as pathModule from 'path'; +import { ReadStream, WriteStream } from './streams'; const ROOT_PATH = pathModule.resolve(__dirname, '..'); -/*eslint no-unused-expressions: ["error", { "allowTernary": true }]*/ +interface PendingUpdate { + isWriting: boolean; // true: waiting on a call to FS.write, false: waiting on a throttle + pendingDataFetcher: (() => string | Buffer) | null; + pendingOptions: object | null; + throttleTime: number; // throttling until time (0 for no throttle) + throttleTimer: NodeJS.Timer | null; +} + +const pendingUpdates: Map = new Map(); class FSPath { - /** - * @param {string} path - */ - constructor(path) { + path: string; + + constructor(path: string) { this.path = pathModule.resolve(ROOT_PATH, path); } + parentDir() { return new FSPath(pathModule.dirname(this.path)); } - /** - * @param {AnyObject | string} [options] - * @return {Promise} - */ - read(options = 'utf8') { + + read(options: AnyObject | string = 'utf8'): Promise { if (typeof options !== 'string' && options.encoding === undefined) { options.encoding = 'utf8'; } return new Promise((resolve, reject) => { fs.readFile(this.path, options, (err, data) => { - err ? reject(err) : resolve(/** @type {string} */ (data)); + err ? reject(err) : resolve(data as string); }); }); } - /** - * @param {AnyObject | string} [options] - * @return {string} - */ - readSync(/** @type {AnyObject | string} */ options = 'utf8') { + + readSync(options: AnyObject | string = 'utf8'): string { if (typeof options !== 'string' && options.encoding === undefined) { options.encoding = 'utf8'; } - return /** @type {string} */ (fs.readFileSync(this.path, options)); + return fs.readFileSync(this.path, options) as string; } - /** - * @param {AnyObject | string} [options] - * @return {Promise} - */ - readBuffer(/** @type {AnyObject | string} */ options = {}) { + + readBuffer(options: AnyObject | string = {}): Promise { return new Promise((resolve, reject) => { fs.readFile(this.path, options, (err, data) => { - err ? reject(err) : resolve(/** @type {Buffer} */ (data)); + err ? reject(err) : resolve(data as Buffer); }); }); } - /** - * @param {AnyObject | string} [options] - * @return {Buffer} - */ - readBufferSync(/** @type {AnyObject | string} */ options = {}) { - return /** @type {Buffer} */ (fs.readFileSync(this.path, options)); + + readBufferSync(options: AnyObject | string = {}) { + return fs.readFileSync(this.path, options) as Buffer; } - /** - * @return {Promise} - */ - readIfExists() { + + readIfExists(): Promise { return new Promise((resolve, reject) => { fs.readFile(this.path, 'utf8', (err, data) => { if (err && err.code === 'ENOENT') return resolve(''); @@ -95,6 +88,7 @@ class FSPath { }); }); } + readIfExistsSync() { try { return fs.readFileSync(this.path, 'utf8'); @@ -103,11 +97,8 @@ class FSPath { } return ''; } - /** - * @param {string | Buffer} data - * @param {Object} options - */ - write(data, options = {}) { + + write(data: string | Buffer, options: object = {}) { if (Config.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.writeFile(this.path, data, options, err => { @@ -115,44 +106,34 @@ class FSPath { }); }); } - /** - * @param {string | Buffer} data - * @param {Object} options - */ - writeSync(data, options = {}) { + + writeSync(data: string | Buffer, options: object = {}) { if (Config.nofswriting) return; return fs.writeFileSync(this.path, data, options); } + /** * Writes to a new file before renaming to replace an old file. If * the process crashes while writing, the old file won't be lost. * Does not protect against simultaneous writing; use writeUpdate * for that. - * - * @param {string | Buffer} data - * @param {Object} options */ - async safeWrite(data, options = {}) { + async safeWrite(data: string | Buffer, options: object = {}) { await FS(this.path + '.NEW').write(data, options); await FS(this.path + '.NEW').rename(this.path); } - /** - * @param {string | Buffer} data - * @param {Object} options - */ - safeWriteSync(data, options = {}) { + + safeWriteSync(data: string | Buffer, options: object = {}) { FS(this.path + '.NEW').writeSync(data, options); FS(this.path + '.NEW').renameSync(this.path); } - /** - * @param {number} time - * @return {Promise} - */ - waitUntil(time) { + + waitUntil(time: number): Promise { return new Promise(resolve => { setTimeout(() => resolve(), time - Date.now()); }); } + /** * Safest way to update a file with in-memory state. Pass a callback * that fetches the data to be written. It will write an update, @@ -164,15 +145,12 @@ class FSPath { * * No synchronous version because there's no risk of race conditions * with synchronous code; just use `safeWriteSync`. - * - * @param {() => string | Buffer} dataFetcher - * @param {Object} options */ - writeUpdate(dataFetcher, options = {}) { + writeUpdate(dataFetcher: () => string | Buffer, options: object = {}) { if (Config.nofswriting) return; - /** @type {PendingUpdate | undefined} */ - let pendingUpdate = FS.pendingUpdates.get(this.path); + let pendingUpdate: PendingUpdate | undefined = pendingUpdates.get(this.path); + // @ts-ignore const throttleTime = options.throttle ? Date.now() + options.throttle : 0; if (pendingUpdate) { @@ -188,38 +166,36 @@ class FSPath { this.writeUpdateNow(dataFetcher, options); } - /** - * @param {() => string | Buffer} dataFetcher - * @param {Object} options - */ - writeUpdateNow(dataFetcher, options) { + + writeUpdateNow(dataFetcher: () => string | Buffer, options: object) { + // @ts-ignore const throttleTime = options.throttle ? Date.now() + options.throttle : 0; const update = { isWriting: true, pendingDataFetcher: null, pendingOptions: null, - throttleTime: throttleTime, + throttleTime, throttleTimer: null, }; - FS.pendingUpdates.set(this.path, update); + pendingUpdates.set(this.path, update); this.safeWrite(dataFetcher(), options).then(() => this.finishUpdate()); } checkNextUpdate() { - let pendingUpdate = FS.pendingUpdates.get(this.path); + let pendingUpdate = pendingUpdates.get(this.path); if (!pendingUpdate) throw new Error(`FS: Pending update not found`); if (pendingUpdate.isWriting) throw new Error(`FS: Conflicting update`); const {pendingDataFetcher: dataFetcher, pendingOptions: options} = pendingUpdate; if (!dataFetcher || !options) { // no pending update - FS.pendingUpdates.delete(this.path); + pendingUpdates.delete(this.path); return; } this.writeUpdateNow(dataFetcher, options); } finishUpdate() { - let pendingUpdate = FS.pendingUpdates.get(this.path); + let pendingUpdate = pendingUpdates.get(this.path); if (!pendingUpdate) throw new Error(`FS: Pending update not found`); if (!pendingUpdate.isWriting) throw new Error(`FS: Conflicting update`); @@ -232,11 +208,8 @@ class FSPath { pendingUpdate.throttleTimer = setTimeout(() => this.checkNextUpdate(), throttleTime - Date.now()); } - /** - * @param {string | Buffer} data - * @param {Object} options - */ - append(data, options = {}) { + + append(data: string | Buffer, options: object = {}) { if (Config.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.appendFile(this.path, data, options, err => { @@ -244,18 +217,13 @@ class FSPath { }); }); } - /** - * @param {string | Buffer} data - * @param {Object} options - */ - appendSync(data, options = {}) { + + appendSync(data: string | Buffer, options: object = {}) { if (Config.nofswriting) return; return fs.appendFileSync(this.path, data, options); } - /** - * @param {string} target - */ - symlinkTo(target) { + + symlinkTo(target: string) { if (Config.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.symlink(target, this.path, err => { @@ -263,17 +231,13 @@ class FSPath { }); }); } - /** - * @param {string} target - */ - symlinkToSync(target) { + + symlinkToSync(target: string) { if (Config.nofswriting) return; return fs.symlinkSync(target, this.path); } - /** - * @param {string} target - */ - rename(target) { + + rename(target: string) { if (Config.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.rename(this.path, target, err => { @@ -281,30 +245,29 @@ class FSPath { }); }); } - /** - * @param {string} target - */ - renameSync(target) { + + renameSync(target: string) { if (Config.nofswriting) return; return fs.renameSync(this.path, target); } - readdir() { + + readdir(): Promise { return new Promise((resolve, reject) => { fs.readdir(this.path, (err, data) => { err ? reject(err) : resolve(data); }); }); } + readdirSync() { return fs.readdirSync(this.path); } + createReadStream() { return new FileReadStream(this.path); } - /** - * @return {WriteStream} - */ - createWriteStream(options = {}) { + + createWriteStream(options = {}): WriteStream { if (Config.nofswriting) { // @ts-ignore return new WriteStream({write() {}}); @@ -312,10 +275,8 @@ class FSPath { // @ts-ignore return new WriteStream(fs.createWriteStream(this.path, options)); } - /** - * @return {WriteStream} - */ - createAppendStream(options = {}) { + + createAppendStream(options = {}): WriteStream { if (Config.nofswriting) { // @ts-ignore return new WriteStream({write() {}}); @@ -325,6 +286,7 @@ class FSPath { // @ts-ignore return new WriteStream(fs.createWriteStream(this.path, options)); } + unlinkIfExists() { if (Config.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { @@ -334,6 +296,7 @@ class FSPath { }); }); } + unlinkIfExistsSync() { if (Config.nofswriting) return; try { @@ -342,10 +305,8 @@ class FSPath { if (err.code !== 'ENOENT') throw err; } } - /** - * @param {string | number} mode - */ - mkdir(mode = 0o755) { + + mkdir(mode: string | number = 0o755) { if (Config.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.mkdir(this.path, mode, err => { @@ -353,17 +314,13 @@ class FSPath { }); }); } - /** - * @param {string | number} mode - */ - mkdirSync(mode = 0o755) { + + mkdirSync(mode: string | number = 0o755) { if (Config.nofswriting) return; return fs.mkdirSync(this.path, mode); } - /** - * @param {string | number} mode - */ - mkdirIfNonexistent(mode = 0o755) { + + mkdirIfNonexistent(mode: string | number = 0o755) { if (Config.nofswriting) return Promise.resolve(); return new Promise((resolve, reject) => { fs.mkdir(this.path, mode, err => { @@ -372,10 +329,8 @@ class FSPath { }); }); } - /** - * @param {string | number} mode - */ - mkdirIfNonexistentSync(mode = 0o755) { + + mkdirIfNonexistentSync(mode: string | number = 0o755) { if (Config.nofswriting) return; try { fs.mkdirSync(this.path, mode); @@ -383,12 +338,12 @@ class FSPath { if (err.code !== 'EEXIST') throw err; } } + /** * Creates the directory (and any parent directories if necessary). * Does not throw if the directory already exists. - * @param {string | number} mode */ - async mkdirp(mode = 0o755) { + async mkdirp(mode: string | number = 0o755) { try { await this.mkdirIfNonexistent(mode); } catch (err) { @@ -397,12 +352,12 @@ class FSPath { await this.mkdirIfNonexistent(mode); } } + /** * Creates the directory (and any parent directories if necessary). * Does not throw if the directory already exists. Synchronous. - * @param {string | number} mode */ - mkdirpSync(mode = 0o755) { + mkdirpSync(mode: string | number = 0o755) { try { this.mkdirIfNonexistentSync(mode); } catch (err) { @@ -411,36 +366,33 @@ class FSPath { this.mkdirIfNonexistentSync(mode); } } - /** - * Calls the callback if the file is modified. - * @param {function (): void} callback - */ - onModify(callback) { + + /** Calls the callback if the file is modified. */ + onModify(callback: () => void) { fs.watchFile(this.path, (curr, prev) => { if (curr.mtime > prev.mtime) return callback(); }); } - /** - * Clears callbacks added with onModify() - */ + + /** Clears callbacks added with onModify(). */ unwatch() { fs.unwatchFile(this.path); } } -class FileReadStream extends Streams.ReadStream { - /** - * @param {string} file - */ - constructor(file) { +class FileReadStream extends ReadStream { + fd: Promise; + + constructor(file: string) { super(); - /** @type {Promise} */ this.fd = new Promise((resolve, reject) => { fs.open(file, 'r', (err, fd) => err ? reject(err) : resolve(fd)); }); this.atEOF = false; } - _read(size = 16384) { + + // @ts-ignore + _read(size: number = 16384) { return new Promise((resolve, reject) => { if (this.atEOF) return resolve(false); this.ensureCapacity(size); @@ -460,38 +412,20 @@ class FileReadStream extends Streams.ReadStream { }); }); } + _destroy() { - return /** @type {Promise} */ (new Promise(resolve => { + return new Promise(resolve => { this.fd.then(fd => { fs.close(fd, () => resolve()); }); - })); + }); } } -/** - * @param {string} path - */ -function getFs(path) { +function getFs(path: string) { return new FSPath(path); } -/** - * @typedef {object} PendingUpdate - * @property {boolean} isWriting true: waiting on a call to FS.write, false: waiting on a throttle - * @property {(() => string | Buffer)?} pendingDataFetcher - * @property {Object?} pendingOptions - * @property {number} throttleTime throttling until time (0 for no throttle) - * @property {NodeJS.Timer?} throttleTimer - */ - -const FS = Object.assign(getFs, { +export const FS = Object.assign(getFs, { FileReadStream, - - /** - * @type {Map} - */ - pendingUpdates: new Map(), }); - -module.exports = FS; diff --git a/lib/process-manager.js b/lib/process-manager.ts similarity index 73% rename from lib/process-manager.js rename to lib/process-manager.ts index 28dca4de8f..b350df093f 100644 --- a/lib/process-manager.js +++ b/lib/process-manager.ts @@ -9,25 +9,23 @@ * @license MIT */ -'use strict'; +import * as child_process from 'child_process'; +import * as path from 'path'; +import * as Streams from './streams'; -const path = require('path'); -const child_process = require('child_process'); -const Streams = require('./streams'); const ROOT_DIR = path.resolve(__dirname, '..'); +export const processManagers: ProcessManager[] = []; +export const disabled = false; + class SubprocessStream extends Streams.ObjectReadWriteStream { - /** - * @param {ChildProcess} process - * @param {number} taskId - */ - constructor(process, taskId) { + constructor(public process: ChildProcess, public taskId: number) { super(); this.process = process; this.taskId = taskId; this.process.send(`${taskId}\nNEW`); } - _write(/** @type {string} */ message) { + _write(message: string) { if (!this.process.connected) return; this.process.send(`${this.taskId}\nWRITE\n${message}`); // responses are handled in ProcessWrapper @@ -38,27 +36,27 @@ class SubprocessStream extends Streams.ObjectReadWriteStream { } } -/** - * @typedef {Object} ProcessWrapper - * @property {number} load - * @property {() => Promise} release - */ +interface ProcessWrapper { + load: number; + release: () => Promise; +} -/** - * Wraps the process object in the PARENT process - */ -class QueryProcessWrapper { - constructor(/** @type {string} */ file) { +/** Wraps the process object in the PARENT process. */ +export class QueryProcessWrapper { + process: ChildProcess; + taskId: number; + pendingTasks: Map void>; + pendingRelease: Promise | null; + resolveRelease: (() => void) | null; + + constructor(file: string) { this.process = child_process.fork(file, [], {cwd: ROOT_DIR}); this.taskId = 0; - /** @type {Map void>} */ this.pendingTasks = new Map(); - /** @type {Promise?} */ this.pendingRelease = null; - /** @type {(() => void)?} */ this.resolveRelease = null; - this.process.on('message', /** @param {string} message */ message => { + this.process.on('message', (message: string) => { const nlLoc = message.indexOf('\n'); if (nlLoc <= 0) throw new Error(`Invalid response ${message}`); if (message.slice(0, nlLoc) === 'THROW') { @@ -67,7 +65,7 @@ class QueryProcessWrapper { throw error; } - const taskId = parseInt(message.slice(0, nlLoc)); + const taskId = parseInt(message.slice(0, nlLoc), 10); const resolve = this.pendingTasks.get(taskId); if (!resolve) throw new Error(`Invalid taskId ${message.slice(0, nlLoc)}`); this.pendingTasks.delete(taskId); @@ -84,11 +82,7 @@ class QueryProcessWrapper { return this.pendingTasks.size; } - /** - * @param {any} input - * @return {Promise} - */ - query(input) { + query(input: any): Promise { this.taskId++; const taskId = this.taskId; this.process.send(`${taskId}\n${JSON.stringify(input)}`); @@ -97,10 +91,7 @@ class QueryProcessWrapper { }); } - /** - * @return {Promise} - */ - release() { + release(): Promise { if (this.pendingRelease) return this.pendingRelease; if (!this.load) { this.destroy(); @@ -109,7 +100,7 @@ class QueryProcessWrapper { this.resolveRelease = resolve; }); } - return /** @type {Promise} */ (this.pendingRelease); + return this.pendingRelease as Promise; } destroy() { @@ -132,21 +123,22 @@ class QueryProcessWrapper { } } -/** - * Wraps the process object in the PARENT process - */ -class StreamProcessWrapper { - constructor(/** @type {string} */ file) { +/** Wraps the process object in the PARENT process. */ +export class StreamProcessWrapper { + process: ChildProcess; + taskId: number; + activeStreams: Map; + pendingRelease: Promise | null; + resolveRelease: (() => void) | null; + + constructor(file: string) { this.process = child_process.fork(file, [], {cwd: ROOT_DIR}); this.taskId = 0; - /** @type {Map} */ this.activeStreams = new Map(); - /** @type {Promise?} */ this.pendingRelease = null; - /** @type {(() => void)?} */ this.resolveRelease = null; - this.process.on('message', /** @param {string} message */ message => { + this.process.on('message', (message: string) => { let nlLoc = message.indexOf('\n'); if (nlLoc <= 0) throw new Error(`Invalid response ${message}`); if (message.slice(0, nlLoc) === 'THROW') { @@ -155,7 +147,7 @@ class StreamProcessWrapper { throw error; } - const taskId = parseInt(message.slice(0, nlLoc)); + const taskId = parseInt(message.slice(0, nlLoc), 10); const stream = this.activeStreams.get(taskId); if (!stream) throw new Error(`Invalid taskId ${message.slice(0, nlLoc)}`); @@ -183,7 +175,7 @@ class StreamProcessWrapper { }); } - deleteStream(/** @type {number} */ taskId) { + deleteStream(taskId: number) { this.activeStreams.delete(taskId); // try to release if (this.resolveRelease && !this.load) this.destroy(); @@ -193,10 +185,7 @@ class StreamProcessWrapper { return this.activeStreams.size; } - /** - * @return {SubprocessStream} - */ - createStream() { + createStream(): SubprocessStream { this.taskId++; const taskId = this.taskId; const stream = new SubprocessStream(this.process, taskId); @@ -204,10 +193,7 @@ class StreamProcessWrapper { return stream; } - /** - * @return {Promise} - */ - release() { + release(): Promise { if (this.pendingRelease) return this.pendingRelease; if (!this.load) { this.destroy(); @@ -216,7 +202,7 @@ class StreamProcessWrapper { this.resolveRelease = resolve; }); } - return /** @type {Promise} */ (this.pendingRelease); + return this.pendingRelease as Promise; } destroy() { @@ -242,19 +228,22 @@ class StreamProcessWrapper { * A ProcessManager wraps a query function: A function that takes a * string and returns a string or Promise. */ -class ProcessManager { - /** - * @param {NodeJS.Module} module - */ - constructor(module) { - /** @type {ProcessWrapper[]} */ +export class ProcessManager { + processes: ProcessWrapper[]; + releasingProcesses: ProcessWrapper[]; + // @ts-ignore + module: NodeJs.Module; + filename: string; + basename: string; + isParentProcess: boolean; + + // @ts-ignore + constructor(module: NodeJs.Module) { this.processes = []; - /** @type {ProcessWrapper[]} */ this.releasingProcesses = []; this.module = module; this.filename = module.filename; this.basename = path.basename(module.filename); - this.isParentProcess = (process.mainModule !== module || !process.send); this.listen(); @@ -285,48 +274,41 @@ class ProcessManager { } spawn(count = 1) { if (!this.isParentProcess) return; - if (PMLib.disabled) return; + if (disabled) return; while (this.processes.length < count) { this.processes.push(this.createProcess()); } } - respawn(/** @type {number?} */ count = null) { + respawn(count: number | null = null) { if (count === null) count = this.processes.length; this.unspawn(); this.spawn(count); } - /** - * @return {ProcessWrapper} - */ - createProcess() { + createProcess(): ProcessWrapper { throw new Error(`implemented by subclass`); } listen() { throw new Error(`implemented by subclass`); } destroy() { - const index = PMLib.processManagers.indexOf(this); - if (index) PMLib.processManagers.splice(index, 1); + const index = processManagers.indexOf(this); + if (index) processManagers.splice(index, 1); this.unspawn(); } } -class QueryProcessManager extends ProcessManager { - /** - * @param {NodeJS.Module} module - * @param {(input: any) => any} query - */ - constructor(module, query) { +export class QueryProcessManager extends ProcessManager { + // tslint:disable-next-line:variable-name + _query: (input: any) => any; + + constructor(module: NodeJS.Module, query: (input: any) => any) { super(module); this._query = query; - PMLib.processManagers.push(this); + processManagers.push(this); } - /** - * @param {any} input - */ - query(input) { - const process = /** @type {QueryProcessWrapper} */ (this.acquire()); + query(input: any) { + const process = this.acquire() as QueryProcessWrapper; if (!process) return Promise.resolve(this._query(input)); return process.query(input); } @@ -336,15 +318,17 @@ class QueryProcessManager extends ProcessManager { listen() { if (this.isParentProcess) return; // child process - process.on('message', async (/** @type {string} */ message) => { + process.on('message', async (message: string) => { let nlLoc = message.indexOf('\n'); if (nlLoc <= 0) throw new Error(`Invalid response ${message}`); const taskId = message.slice(0, nlLoc); message = message.slice(nlLoc + 1); if (taskId.startsWith('EVAL')) { + /* tslint:disable:no-eval */ // @ts-ignore guaranteed to be defined here process.send(`${taskId}\n` + eval(message)); + /* tslint:enable:no-eval */ return; } @@ -357,37 +341,36 @@ class QueryProcessManager extends ProcessManager { }); } } -class StreamProcessManager extends ProcessManager { - /** - * @param {NodeJS.Module} module - * @param {() => ObjectReadWriteStream} createStream - */ - constructor(module, createStream) { + +export class StreamProcessManager extends ProcessManager { + /* taskid: stream used only in child process */ + activeStreams: Map; + // tslint:disable-next-line:variable-name + _createStream: () => Streams.ObjectReadWriteStream; + + constructor(module: NodeJS.Module, createStream: () => Streams.ObjectReadWriteStream) { super(module); - /** @type {Map} taskid: stream used only in child process */ this.activeStreams = new Map(); this._createStream = createStream; - PMLib.processManagers.push(this); + processManagers.push(this); } createStream() { - const process = /** @type {StreamProcessWrapper} */ (this.acquire()); + const process = this.acquire() as StreamProcessWrapper; if (!process) return this._createStream(); return process.createStream(); } createProcess() { return new StreamProcessWrapper(this.filename); } - /** - * @param {string} taskId - * @param {ObjectReadStream} stream - */ - async pipeStream(taskId, stream) { + async pipeStream(taskId: string, stream: Streams.ObjectReadStream) { + /* tslint:disable */ let value, done; while (({value, done} = await stream.next(), !done)) { // @ts-ignore Guaranteed to be a child process process.send(`${taskId}\nPUSH\n${value}`); } + /* tslint:enable */ // @ts-ignore Guaranteed to be a child process process.send(`${taskId}\nEND`); this.activeStreams.delete(taskId); @@ -395,7 +378,7 @@ class StreamProcessManager extends ProcessManager { listen() { if (this.isParentProcess) return; // child process - process.on('message', async (/** @type {string} */ message) => { + process.on('message', async (message: string) => { let nlLoc = message.indexOf('\n'); if (nlLoc <= 0) throw new Error(`Invalid request ${message}`); const taskId = message.slice(0, nlLoc); @@ -408,8 +391,10 @@ class StreamProcessManager extends ProcessManager { message = message.slice(nlLoc + 1); if (taskId.startsWith('EVAL')) { + /* tslint:disable:no-eval */ // @ts-ignore guaranteed to be a child process process.send(`${taskId}\n` + eval(message)); + /* tslint:enable:no-eval */ return; } @@ -434,17 +419,3 @@ class StreamProcessManager extends ProcessManager { }); } } - -const PMLib = { - QueryProcessWrapper, - StreamProcessWrapper, - ProcessManager, - QueryProcessManager, - StreamProcessManager, - - /** @type {ProcessManager[]} */ - processManagers: [], - disabled: false, -}; -// @ts-ignore Typescript bug -module.exports = PMLib; diff --git a/lib/repl.js b/lib/repl.ts similarity index 80% rename from lib/repl.js rename to lib/repl.ts index dd57be02ed..f744b18b01 100644 --- a/lib/repl.js +++ b/lib/repl.ts @@ -8,21 +8,18 @@ * @license MIT */ -'use strict'; +import * as fs from 'fs'; +import * as net from 'net'; +import * as path from 'path'; +import * as repl from 'repl'; -const fs = require('fs'); -const path = require('path'); -const net = require('net'); -const repl = require('repl'); - -const Repl = { +export const Repl = new class { /** * Contains the pathnames of all active REPL sockets. - * @type {Set} */ - socketPathnames: new Set(), + socketPathnames: Set = new Set(); - listenersSetup: false, + listenersSetup: boolean = false; setupListeners() { if (Repl.listenersSetup) return; @@ -44,17 +41,14 @@ const Repl = { if (!process.listeners('SIGINT').length) { process.once('SIGINT', () => process.exit(128 + 2)); } - }, + } /** * Starts a REPL server, using a UNIX socket for IPC. The eval function * parametre is passed in because there is no other way to access a file's * non-global context. - * - * @param {string} filename - * @param {(input: string) => any} evalFunction */ - start(filename, evalFunction) { + start(filename: string, evalFunction: (input: string) => any) { if ('repl' in Config && !Config.repl) return; // TODO: Windows does support the REPL when using named pipes. For now, @@ -84,14 +78,8 @@ const Repl = { repl.start({ input: socket, output: socket, - /** - * @param {string} cmd - * @param {any} context - * @param {string} filename - * @param {Function} callback - * @return {any} - */ - eval(cmd, context, filename, callback) { + // tslint:disable-next-line:ban-types + eval(cmd: string, context: any, unusedFilename: string, callback: Function): any { try { return callback(null, evalFunction(cmd)); } catch (e) { @@ -108,8 +96,9 @@ const Repl = { Repl.socketPathnames.add(pathname); }); - server.once('error', /** @param {NodeJS.ErrnoException} err */ err => { + server.once('error', (err: NodeJS.ErrnoException) => { if (err.code === "EADDRINUSE") { + // tslint:disable-next-line:variable-name fs.unlink(pathname, _err => { if (_err && _err.code !== "ENOENT") { require('./crashlogger')(_err, `REPL: ${filename}`); @@ -126,7 +115,5 @@ const Repl = { Repl.socketPathnames.delete(pathname); Repl.start(filename, evalFunction); }); - }, + } }; - -module.exports = Repl; diff --git a/lib/streams.js b/lib/streams.ts similarity index 64% rename from lib/streams.js rename to lib/streams.ts index 9f47aeefd3..708aaf2a9a 100644 --- a/lib/streams.js +++ b/lib/streams.ts @@ -10,33 +10,35 @@ * @license MIT */ -'use strict'; - const BUF_SIZE = 65536 * 4; -class ReadStream { - /** @param {{[k: string]: any} | NodeJS.ReadableStream | string | Buffer} optionsOrStreamLike */ - constructor(optionsOrStreamLike = {}) { +export class ReadStream { + buf: Buffer; + bufStart: number; + bufEnd: number; + bufCapacity: number; + readSize: number; + atEOF: boolean; + encoding: string; + isReadable: boolean; + isWritable: boolean; + nodeReadableStream: NodeJS.ReadableStream | null; + nextPushResolver: (() => void) | null; + nextPush: Promise; + awaitingPush: boolean; + + constructor(optionsOrStreamLike: {[k: string]: any} | NodeJS.ReadableStream | string | Buffer = {}) { this.buf = Buffer.allocUnsafe(BUF_SIZE); this.bufStart = 0; this.bufEnd = 0; this.bufCapacity = BUF_SIZE; - // TypeScript bug: can't infer type - /** @type {number} */ this.readSize = 0; this.atEOF = false; this.encoding = 'utf8'; - - /** @type {true} */ this.isReadable = true; this.isWritable = false; - - /** @type {NodeJS.ReadableStream?} */ this.nodeReadableStream = null; - - /** @type {(() => void)?} */ this.nextPushResolver = null; - /** @type {Promise} */ this.nextPush = new Promise(resolve => { this.nextPushResolver = resolve; }); @@ -47,13 +49,13 @@ class ReadStream { options = {buffer: optionsOrStreamLike}; } else if (optionsOrStreamLike instanceof Buffer) { options = {buffer: optionsOrStreamLike}; - } else if (typeof /** @type {any} */ (optionsOrStreamLike)._readableState === 'object') { - options = {nodeStream: /** @type {NodeJS.ReadableStream} */ (optionsOrStreamLike)}; + } else if (typeof (optionsOrStreamLike as any)._readableState === 'object') { + options = {nodeStream: optionsOrStreamLike as NodeJS.ReadableStream}; } else { options = optionsOrStreamLike; } if (options.nodeStream) { - const nodeStream = /** @type {NodeJS.ReadableStream} */ (options.nodeStream); + const nodeStream: NodeJS.ReadableStream = options.nodeStream; this.nodeReadableStream = nodeStream; nodeStream.on('data', data => { this.push(data); @@ -61,19 +63,13 @@ class ReadStream { nodeStream.on('end', () => { this.push(null); }); - /** - * @this {ReadStream} - * @param {number} bytes - */ - options.read = function (bytes) { - this.nodeReadableStream.resume(); + + options.read = function (this: ReadStream, unusedBytes: number) { + this.nodeReadableStream!.resume(); }; - /** - * @this {ReadStream} - * @param {number} bytes - */ - options.pause = function (bytes) { - this.nodeReadableStream.pause(); + + options.pause = function (this: ReadStream, unusedBytes: number) { + this.nodeReadableStream!.pause(); }; } @@ -86,9 +82,11 @@ class ReadStream { this.push(null); } } + get bufSize() { return this.bufEnd - this.bufStart; } + moveBuf() { if (this.bufStart !== this.bufEnd) { this.buf.copy(this.buf, 0, this.bufStart, this.bufEnd); @@ -96,6 +94,7 @@ class ReadStream { this.bufEnd -= this.bufStart; this.bufStart = 0; } + expandBuf(newCapacity = this.bufCapacity * 2) { const newBuf = Buffer.allocUnsafe(newCapacity); this.buf.copy(newBuf, 0, this.bufStart, this.bufEnd); @@ -103,10 +102,8 @@ class ReadStream { this.bufStart = 0; this.buf = newBuf; } - /** - * @param {number} additionalCapacity - */ - ensureCapacity(additionalCapacity) { + + ensureCapacity(additionalCapacity: number) { if (this.bufEnd + additionalCapacity <= this.bufCapacity) return; const capacity = this.bufEnd - this.bufStart + additionalCapacity; if (capacity <= this.bufCapacity) { @@ -116,10 +113,8 @@ class ReadStream { while (newCapacity < capacity) newCapacity *= 2; this.expandBuf(newCapacity); } - /** - * @param {Buffer | string | null} buf - */ - push(buf, encoding = this.encoding) { + + push(buf: Buffer | string | null, encoding: string = this.encoding) { let size; if (this.atEOF) return; if (buf === null) { @@ -139,6 +134,7 @@ class ReadStream { if (this.bufSize > this.readSize && size * 2 < this.bufSize) this._pause(); this.resolvePush(); } + resolvePush() { if (!this.nextPushResolver) throw new Error(`Push after end of read stream`); this.nextPushResolver(); @@ -150,23 +146,18 @@ class ReadStream { this.nextPushResolver = resolve; }); } - /** - * @param {number} [size] - * @return {void | Promise} - */ - _read(size = 0) { + + _read(size: number = 0): void | Promise { throw new Error(`ReadStream needs to be subclassed and the _read function needs to be implemented.`); } + _destroy() {} _pause() {} - /** - * @param {number?} byteCount - */ - async loadIntoBuffer(byteCount = null) { + + async loadIntoBuffer(byteCount: number | null = null) { if (byteCount === 0) return; this.readSize = Math.max(byteCount || (this.bufSize + 1), this.readSize); - /** @type {number?} */ - let bytes = this.readSize - this.bufSize; + let bytes: number | null = this.readSize - this.bufSize; if (bytes === Infinity || byteCount === null) bytes = null; while (!this.atEOF && this.bufSize < this.readSize) { if (bytes) this._read(bytes); @@ -174,10 +165,8 @@ class ReadStream { await this.nextPush; } } - /** - * @param {number?} byteCount - */ - async peek(byteCount = null, encoding = this.encoding) { + + async peek(byteCount: number | null = null, encoding: string = this.encoding) { if (byteCount === null && this.bufSize) return this.buf.toString(encoding, this.bufStart, this.bufEnd); await this.loadIntoBuffer(byteCount); if (byteCount === null) return this.buf.toString(encoding, this.bufStart, this.bufEnd); @@ -185,10 +174,8 @@ class ReadStream { if (!this.bufSize) return null; return this.buf.toString(encoding, this.bufStart, this.bufStart + byteCount); } - /** - * @param {number?} byteCount - */ - async peekBuffer(byteCount = null) { + + async peekBuffer(byteCount: number | null = null) { if (byteCount === null && this.bufSize) return this.buf.slice(this.bufStart, this.bufEnd); await this.loadIntoBuffer(byteCount); if (byteCount === null) return this.buf.slice(this.bufStart, this.bufEnd); @@ -196,10 +183,8 @@ class ReadStream { if (!this.bufSize) return null; return this.buf.slice(this.bufStart, this.bufStart + byteCount); } - /** - * @param {number? | string} byteCount - */ - async read(byteCount = null, encoding = this.encoding) { + + async read(byteCount: number | string | null = null, encoding = this.encoding) { if (typeof byteCount === 'string') { encoding = byteCount; byteCount = null; @@ -213,10 +198,8 @@ class ReadStream { } return out; } - /** - * @param {number?} byteCount - */ - async readBuffer(byteCount = null) { + + async readBuffer(byteCount: number | null = null) { const out = await this.peekBuffer(byteCount); if (byteCount === null || byteCount >= this.bufSize) { this.bufStart = 0; @@ -226,10 +209,8 @@ class ReadStream { } return out; } - /** - * @param {string} symbol - */ - async indexOf(symbol, encoding = this.encoding) { + + async indexOf(symbol: string, encoding: string = this.encoding) { let idx = this.buf.indexOf(symbol, this.bufStart, encoding); while (!this.atEOF && (idx >= this.bufEnd || idx < 0)) { await this.loadIntoBuffer(); @@ -238,16 +219,16 @@ class ReadStream { if (idx >= this.bufEnd) return -1; return idx - this.bufStart; } + async readAll(encoding = this.encoding) { return (await this.read(Infinity, encoding)) || ''; } + peekAll(encoding = this.encoding) { return this.peek(Infinity, encoding); } - /** - * @param {string} symbol - */ - async readDelimitedBy(symbol, encoding = this.encoding) { + + async readDelimitedBy(symbol: string, encoding: string = this.encoding) { if (this.atEOF && !this.bufSize) return null; const idx = await this.indexOf(symbol, encoding); if (idx < 0) { @@ -258,12 +239,14 @@ class ReadStream { return out; } } + async readLine(encoding = this.encoding) { if (!encoding) throw new Error(`readLine must have an encoding`); let line = await this.readDelimitedBy('\n', encoding); if (line && line.endsWith('\r')) line = line.slice(0, -1); return line; } + async destroy() { this.atEOF = true; this.bufStart = 0; @@ -271,54 +254,52 @@ class ReadStream { if (this.nextPushResolver) this.resolvePush(); return this._destroy(); } - /** - * @param {string | number | null} [byteCount] - */ - async next(byteCount = null) { + + async next(byteCount: string | number | null = null) { const value = await this.read(byteCount); return {value, done: value === null}; } - /** - * @param {WriteStream} outStream - * @param {{noEnd?: boolean}} [options] - */ - async pipeTo(outStream, options = {}) { + + async pipeTo(outStream: WriteStream, options: {noEnd?: boolean} = {}) { + /* tslint:disable */ let value, done; while (({value, done} = await this.next(), !done)) { await outStream.write(value); } + /* tslint:enable */ if (!options.noEnd) outStream.end(); } } -class WriteStream { - /** - * @param {{_writableState?: any, nodeStream?: NodeJS.ReadableStream, write?: (this: WriteStream, data: string | Buffer) => (Promise | undefined), end?: () => Promise}} options - */ - constructor(options = {}) { +interface WriteStreamOptions { + writableState?: any; + nodeStream?: NodeJS.ReadableStream; + write?: (this: WriteStream, data: string | Buffer) => (Promise | undefined); + end?: () => Promise; +} + +export class WriteStream { + isReadable: boolean; + isWritable: true; + encoding: string; + nodeWritableStream: NodeJS.ReadableStream | null; + drainListeners: (() => void)[]; + + constructor(options: WriteStreamOptions = {}) { this.isReadable = false; - /** @type {true} */ this.isWritable = true; this.encoding = 'utf8'; - - /** @type {NodeJS.ReadableStream?} */ this.nodeWritableStream = null; - /** @type {(() => void)[]} */ this.drainListeners = []; - if (options._writableState) { + if ((options as any)._writableState) { // @ts-ignore options = {nodeStream: options}; } if (options.nodeStream) { - const nodeStream = /** @type {NodeJS.ReadableStream} */ (options.nodeStream); + const nodeStream: NodeJS.ReadableStream = options.nodeStream; this.nodeWritableStream = nodeStream; - /** - * @this {WriteStream} - * @param {string | Buffer} data - */ - // @ts-ignore TypeScript bug - options.write = function (data) { + options.write = function (this: WriteStream, data: string | Buffer) { // @ts-ignore const result = this.nodeWritableStream.write(data); if (result !== false) return undefined; @@ -344,11 +325,8 @@ class WriteStream { if (options.write) this._write = options.write; if (options.end) this._end = options.end; } - /** - * @param {Buffer | string | null} chunk - * @return {Promise} - */ - async write(chunk) { + + async write(chunk: Buffer | string | null): Promise { if (chunk === null) { await this.end(); return false; @@ -356,31 +334,22 @@ class WriteStream { await this._write(chunk); return true; } - /** - * @param {string | null} chunk - * @return {Promise} - */ - async writeLine(chunk) { + + async writeLine(chunk: string | null): Promise { if (chunk === null) { await this.end(); return false; } return this.write(chunk + '\n'); } - /** - * @param {Buffer | string} chunk - * @return {void | Promise} - */ - _write(chunk) { + + _write(chunk: Buffer | string): void | Promise { throw new Error(`WriteStream needs to be subclassed and the _write function needs to be implemented.`); } - /** @return {void | Promise} */ - _end() {} - /** - * @param {string | null} chunk - * @return {Promise} - */ - async end(chunk = null) { + + _end(): void | Promise {} + + async end(chunk: string | null = null): Promise { if (chunk) { await this.write(chunk); } @@ -388,68 +357,54 @@ class WriteStream { } } -class ReadWriteStream extends ReadStream { +export class ReadWriteStream extends ReadStream { constructor(options = {}) { super(options); - /** @type {true} */ this.isReadable = true; - /** @type {true} */ this.isWritable = true; } - /** - * @param {Buffer | string} chunk - * @return {Promise | void} - */ - write(chunk) { + + write(chunk: Buffer | string): Promise | void { return this._write(chunk); } - /** - * @param {string} chunk - * @return {Promise | void} - */ - writeLine(chunk) { + + writeLine(chunk: string): Promise | void { return this.write(chunk + '\n'); } - /** - * @param {Buffer | string} chunk - * @return {Promise | void} - */ - _write(chunk) { + + _write(chunk: Buffer | string): Promise | void { throw new Error(`WriteStream needs to be subclassed and the _write function needs to be implemented.`); } - /** - * In a ReadWriteStream, _read does not need to be implemented - */ + + /** In a ReadWriteStream, _read does not need to be implemented. */ _read() {} - /** @return {void | Promise} */ - _end() {} + + _end(): void | Promise {} + async end() { return this._end(); } } -class ObjectReadStream { - /** - * @param {{[k: string]: any} | NodeJS.ReadableStream | any[]} optionsOrStreamLike - */ - constructor(optionsOrStreamLike = {}) { - /** @type {any[]} */ +export class ObjectReadStream { + buf: any[]; + readSize: number; + atEOF: boolean; + isReadable: boolean; + isWritable: boolean; + nodeReadableStream: NodeJS.ReadableStream | null; + nextPushResolver: (() => void) | null; + nextPush: Promise; + awaitingPush: boolean; + + constructor(optionsOrStreamLike: {[k: string]: any} | NodeJS.ReadableStream | any[] = {}) { this.buf = []; - // TypeScript bug: can't infer type - /** @type {number} */ this.readSize = 0; this.atEOF = false; - - /** @type {true} */ this.isReadable = true; this.isWritable = false; - - /** @type {NodeJS.ReadableStream?} */ this.nodeReadableStream = null; - - /** @type {(() => void)?} */ this.nextPushResolver = null; - /** @type {Promise} */ this.nextPush = new Promise(resolve => { this.nextPushResolver = resolve; }); @@ -458,13 +413,13 @@ class ObjectReadStream { let options; if (Array.isArray(optionsOrStreamLike)) { options = {buffer: optionsOrStreamLike}; - } else if (typeof /** @type {any} */ (optionsOrStreamLike)._readableState === 'object') { - options = {nodeStream: /** @type {NodeJS.ReadableStream} */ (optionsOrStreamLike)}; + } else if (typeof (optionsOrStreamLike as any)._readableState === 'object') { + options = {nodeStream: optionsOrStreamLike as NodeJS.ReadableStream}; } else { options = optionsOrStreamLike; } if (options.nodeStream) { - const nodeStream = /** @type {NodeJS.ReadableStream} */ (options.nodeStream); + const nodeStream: NodeJS.ReadableStream = options.nodeStream; this.nodeReadableStream = nodeStream; nodeStream.on('data', data => { this.push(data); @@ -472,19 +427,13 @@ class ObjectReadStream { nodeStream.on('end', () => { this.push(null); }); - /** - * @this {ReadStream} - * @param {number} bytes - */ - options.read = function (bytes) { - this.nodeReadableStream.resume(); + + options.read = function (this: ReadStream, unusedBytes: number) { + this.nodeReadableStream!.resume(); }; - /** - * @this {ReadStream} - * @param {number} bytes - */ - options.pause = function (bytes) { - this.nodeReadableStream.pause(); + + options.pause = function (this: ReadStream, unusedBytes: number) { + this.nodeReadableStream!.pause(); }; } @@ -496,10 +445,8 @@ class ObjectReadStream { this.push(null); } } - /** - * @param {any} elem - */ - push(elem) { + + push(elem: any) { if (this.atEOF) return; if (elem === null) { this.atEOF = true; @@ -511,6 +458,7 @@ class ObjectReadStream { if (this.buf.length > this.readSize && this.buf.length >= 16) this._pause(); this.resolvePush(); } + resolvePush() { if (!this.nextPushResolver) throw new Error(`Push after end of read stream`); this.nextPushResolver(); @@ -522,19 +470,15 @@ class ObjectReadStream { this.nextPushResolver = resolve; }); } - /** - * @param {number} [size] - * @return {void | Promise} - */ - _read(size = 0) { + + _read(size: number = 0): void | Promise { throw new Error(`ReadStream needs to be subclassed and the _read function needs to be implemented.`); } + _destroy() {} _pause() {} - /** - * @param {number} count - */ - async loadIntoBuffer(count = 1) { + + async loadIntoBuffer(count: number = 1) { if (this.buf.length >= count) return; this.readSize = Math.max(count, this.readSize); while (!this.atEOF && this.buf.length < this.readSize) { @@ -547,81 +491,84 @@ class ObjectReadStream { } } } + async peek() { if (this.buf.length) return this.buf[0]; await this.loadIntoBuffer(); return this.buf[0]; } + async read() { if (this.buf.length) return this.buf.shift(); await this.loadIntoBuffer(); if (!this.buf.length) return null; return this.buf.shift(); } - /** - * @param {number?} [count] - */ - async peekArray(count = null) { + + async peekArray(count: number | null = null) { await this.loadIntoBuffer(count || 1); if (count === null || count === Infinity) { return this.buf.slice(); } return this.buf.slice(0, count); } - /** - * @param {number?} [count] - */ - async readArray(count = null) { + + async readArray(count: number | null = null) { let out = await this.peekArray(count); this.buf = this.buf.slice(out.length); return out; } + async readAll() { await this.loadIntoBuffer(Infinity); let out = this.buf; this.buf = []; return out; } + async peekAll() { await this.loadIntoBuffer(Infinity); return this.buf.slice(); } + async destroy() { this.atEOF = true; this.buf = []; this.resolvePush(); return this._destroy(); } + async next() { const value = await this.read(); return {value, done: value === null}; } - /** - * @param {WriteStream} outStream - * @param {{noEnd?: boolean}} options - */ - async pipeTo(outStream, options = {}) { + + async pipeTo(outStream: WriteStream, options: {noEnd?: boolean} = {}) { + /* tslint:disable */ let value, done; while (({value, done} = await this.next(), !done)) { await outStream.write(value); } + /* tslint:enable */ if (!options.noEnd) outStream.end(); } } -/** - * @template T - */ -class ObjectWriteStream { - /** - * @param {{_writableState?: any, nodeStream?: NodeJS.ReadableStream, write?: (this: WriteStream, data: T) => Promise | undefined, end?: () => Promise}} options - */ - constructor(options = {}) { - this.isReadable = false; - /** @type {true} */ - this.isWritable = true; +interface ObjectWriteStreamOptions { + _writableState?: any; + nodeStream?: NodeJS.ReadableStream; + write?: (this: WriteStream, data: T) => Promise | undefined; + end?: () => Promise; +} - /** @type {NodeJS.ReadableStream?} */ +export class ObjectWriteStream { + isReadable: boolean; + isWritable: true; + nodeWritableStream: NodeJS.ReadableStream | null; + + constructor(options: ObjectWriteStreamOptions = {}) { + this.isReadable = false; + this.isWritable = true; this.nodeWritableStream = null; if (options._writableState) { @@ -629,15 +576,12 @@ class ObjectWriteStream { options = {nodeStream: options}; } if (options.nodeStream) { - const nodeStream = /** @type {NodeJS.ReadableStream} */ (options.nodeStream); + const nodeStream: NodeJS.ReadableStream = options.nodeStream; this.nodeWritableStream = nodeStream; - /** - * @this {WriteStream} - * @param {T} data - */ - options.write = function (data) { + + options.write = function (this: WriteStream, data: T) { // @ts-ignore - const result = this.nodeWritableStream.write(data); + const result = this.nodeWritableStream!.write(data); if (result === false) { return new Promise(resolve => { // @ts-ignore @@ -647,6 +591,7 @@ class ObjectWriteStream { }); } }; + options.end = function () { return new Promise(resolve => { // @ts-ignore @@ -658,11 +603,8 @@ class ObjectWriteStream { if (options.write) this._write = options.write; if (options.end) this._end = options.end; } - /** - * @param {T?} elem - * @return {Promise} - */ - async write(elem) { + + async write(elem: T | null): Promise { if (elem === null) { await this.end(); return false; @@ -670,20 +612,14 @@ class ObjectWriteStream { await this._write(elem); return true; } - /** - * @param {T} elem - * @return {void | Promise} - */ - _write(elem) { + + _write(elem: T): void | Promise { throw new Error(`WriteStream needs to be subclassed and the _write function needs to be implemented.`); } - /** @return {void | Promise} */ - _end() {} - /** - * @param {T?} elem - * @return {Promise} - */ - async end(elem = null) { + + _end(): void | Promise {} + + async end(elem: T | null = null): Promise { if (elem) { await this.write(elem); } @@ -691,57 +627,40 @@ class ObjectWriteStream { } } -class ObjectReadWriteStream extends ObjectReadStream { - /** - * @param {{write?: (this: WriteStream, data: string) => Promise | undefined | void, end?: () => Promise | undefined | void}} options - */ - constructor(options = {}) { +interface ObjectReadWriteStreamOptions { + write?: (this: WriteStream, data: string) => Promise | undefined | void; + end?: () => Promise | undefined | void; +} + +export class ObjectReadWriteStream extends ObjectReadStream { + isReadable: true; + isWritable: true; + + constructor(options: ObjectReadWriteStreamOptions = {}) { super(options); - /** @type {true} */ this.isReadable = true; - /** @type {true} */ this.isWritable = true; if (options.write) this._write = options.write; if (options.end) this._end = options.end; } - /** - * @param {any} elem - * @return {void | Promise} - */ - write(elem) { + + write(elem: any): void | Promise { return this._write(elem); } - /** - * @param {any} elem - * @return {void | Promise} - */ - _write(elem) { + + _write(elem: any): void | Promise { throw new Error(`WriteStream needs to be subclassed and the _write function needs to be implemented.`); } - /** - * In a ReadWriteStream, _read does not need to be implemented - */ + /** In a ReadWriteStream, _read does not need to be implemented. */ _read() {} - /** @return {void | Promise} */ - _end() {} + + _end(): void | Promise {} + async end() { return this._end(); } } -module.exports = { - ReadStream, - WriteStream, - ReadWriteStream, - ObjectReadStream, - ObjectWriteStream, - ObjectReadWriteStream, - - /** - * @param {NodeJS.ReadableStream} nodeStream - * @param {*} encoding - */ - readAll(nodeStream, encoding = undefined) { - return new ReadStream(nodeStream).readAll(encoding); - }, -}; +export function readAll(nodeStream: NodeJS.ReadableStream, encoding?: any) { + return new ReadStream(nodeStream).readAll(encoding); +} diff --git a/package.json b/package.json index 135facb975..54b425248d 100644 --- a/package.json +++ b/package.json @@ -5,6 +5,7 @@ "version": "0.11.2", "dependencies": { "probe-image-size": "^4.0.0", + "replace": "^1.0.1", "sockjs": "0.3.18", "sucrase": "^3.9.5" }, diff --git a/pokemon-showdown b/pokemon-showdown index fce4408f3c..03d404a58c 100755 --- a/pokemon-showdown +++ b/pokemon-showdown @@ -75,7 +75,7 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) { var Dex = require('./.sim-dist/dex'); var TeamValidator = require('./.sim-dist/team-validator'); var validator = TeamValidator(process.argv[3]); - var Streams = require('./lib/streams'); + var Streams = require('./.lib-dist/streams'); var stdin = new Streams.ReadStream(process.stdin); stdin.readLine().then(function (textTeam) { @@ -97,7 +97,7 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) { case 'simulate-battle': { var BattleTextStream = require('./.sim-dist/battle-stream').BattleTextStream; - var Streams = require('./lib/streams'); + var Streams = require('./.lib-dist/streams'); var stdin = new Streams.ReadStream(process.stdin); var stdout = new Streams.WriteStream(process.stdout); @@ -111,7 +111,7 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) { case 'unpack-team': { var Dex = require('./.sim-dist/dex'); - var Streams = require('./lib/streams'); + var Streams = require('./.lib-dist/streams'); var stdin = new Streams.ReadStream(process.stdin); stdin.readLine().then(function (packedTeam) { @@ -129,7 +129,7 @@ if (!process.argv[2] || /^[0-9]+$/.test(process.argv[2])) { case 'pack-team': { var Dex = require('./.sim-dist/dex'); - var Streams = require('./lib/streams'); + var Streams = require('./.lib-dist/streams'); var stdin = new Streams.ReadStream(process.stdin); stdin.readLine().then(function (unpackedTeam) { diff --git a/server/chat-commands.js b/server/chat-commands.js index 88a463bb6f..f397c86967 100644 --- a/server/chat-commands.js +++ b/server/chat-commands.js @@ -18,7 +18,7 @@ /* eslint no-else-return: "error" */ const crypto = require('crypto'); -const FS = require('../lib/fs'); +const FS = require('../.lib-dist/fs').FS; const MAX_REASON_LENGTH = 300; const MUTE_LENGTH = 7 * 60 * 1000; @@ -3005,7 +3005,7 @@ const commands = { Chat.destroy(); - const processManagers = require('../lib/process-manager').processManagers; + const processManagers = require('../.lib-dist/process-manager').processManagers; for (let manager of processManagers.slice()) { if (manager.filename.startsWith(FS('server/chat-plugins').path)) { manager.destroy(); diff --git a/server/chat-plugins/chat-monitor.js b/server/chat-plugins/chat-monitor.js index 592e514cc1..aab6b9fae3 100644 --- a/server/chat-plugins/chat-monitor.js +++ b/server/chat-plugins/chat-monitor.js @@ -1,6 +1,7 @@ 'use strict'; -const FS = require('../../lib/fs'); +/** @type {typeof import('../../lib/fs').FS} */ +const FS = require(/** @type {any} */('../../.lib-dist/fs')).FS; const MONITOR_FILE = 'config/chat-plugins/chat-monitor.tsv'; const WRITE_THROTTLE_TIME = 5 * 60 * 1000; diff --git a/server/chat-plugins/daily-spotlight.js b/server/chat-plugins/daily-spotlight.js index 8809f53353..a694483900 100644 --- a/server/chat-plugins/daily-spotlight.js +++ b/server/chat-plugins/daily-spotlight.js @@ -1,6 +1,7 @@ 'use strict'; -const FS = require('../../lib/fs'); +/** @type {typeof import('../../lib/fs').FS} */ +const FS = require(/** @type {any} */('../../.lib-dist/fs')).FS; const DAY = 24 * 60 * 60 * 1000; const SPOTLIGHT_FILE = 'config/chat-plugins/spotlights.json'; diff --git a/server/chat-plugins/datasearch.js b/server/chat-plugins/datasearch.js index 067e8e3015..e8f71509f8 100644 --- a/server/chat-plugins/datasearch.js +++ b/server/chat-plugins/datasearch.js @@ -1580,7 +1580,7 @@ function runSearch(query) { * Process manager *********************************************************/ -const QueryProcessManager = require('../../lib/process-manager').QueryProcessManager; +const QueryProcessManager = require('../../.lib-dist/process-manager').QueryProcessManager; const PM = new QueryProcessManager(module, async query => { try { @@ -1599,7 +1599,7 @@ const PM = new QueryProcessManager(module, async query => { return null; } } catch (err) { - require('../../lib/crashlogger')(err, 'A search query', query); + Monitor.crashlog(err, 'A search query', query); } return {error: "Sorry! Our search engine crashed on your query. We've been automatically notified and will fix this crash."}; }); @@ -1607,10 +1607,22 @@ const PM = new QueryProcessManager(module, async query => { if (!PM.isParentProcess) { // This is a child process! global.Config = require('../../config/config'); - + // @ts-ignore ??? + global.Monitor = { + /** + * @param {Error} error + * @param {string} source + * @param {{}?} details + */ + crashlog(error, source = 'A datasearch 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 => { - require('../../lib/crashlogger')(err, 'A dexsearch process'); + Monitor.crashlog(err, 'A dexsearch process'); }); } @@ -1619,7 +1631,7 @@ if (!PM.isParentProcess) { Dex.includeData(); global.TeamValidator = require('../../.sim-dist/team-validator').TeamValidator; - require('../../lib/repl').start('dexsearch', cmd => eval(cmd)); + require('../../.lib-dist/repl').Repl.start('dexsearch', cmd => eval(cmd)); } else { PM.spawn(MAX_PROCESSES); } diff --git a/server/chat-plugins/helptickets.js b/server/chat-plugins/helptickets.js index dd21cef844..18e3686286 100644 --- a/server/chat-plugins/helptickets.js +++ b/server/chat-plugins/helptickets.js @@ -1,6 +1,7 @@ 'use strict'; -const FS = require('../../lib/fs'); +/** @type {typeof import('../../lib/fs').FS} */ +const FS = require(/** @type {any} */('../../.lib-dist/fs')).FS; const TICKET_FILE = 'config/tickets.json'; const TICKET_CACHE_TIME = 24 * 60 * 60 * 1000; // 24 hours const TICKET_BAN_DURATION = 48 * 60 * 60 * 1000; // 48 hours diff --git a/server/chat-plugins/info.js b/server/chat-plugins/info.js index ec27cc8308..b8159e321d 100644 --- a/server/chat-plugins/info.js +++ b/server/chat-plugins/info.js @@ -1749,8 +1749,8 @@ const commands = { for (const worker of Sockets.workers.values()) { buf += `${worker.pid || worker.process.pid} - Sockets ${worker.id}
`; } - - const processManagers = require('../../lib/process-manager').processManagers; + /** @type {typeof import('../../lib/process-manager').processManagers} */ + const processManagers = require(/** @type {any} */('../../.lib-dist/process-manager')).processManagers; for (const manager of processManagers) { for (const [i, process] of manager.processes.entries()) { buf += `${process.process.pid} - ${manager.basename} ${i} (load ${process.load})
`; diff --git a/server/chat-plugins/mafia.js b/server/chat-plugins/mafia.js index 3d4586e762..2c3d7a94b0 100644 --- a/server/chat-plugins/mafia.js +++ b/server/chat-plugins/mafia.js @@ -49,7 +49,8 @@ * @property {Object} picks */ -const FS = require('../../lib/fs'); +/** @type {typeof import('../../lib/fs').FS} */ +const FS = require(/** @type {any} */('../../.lib-dist/fs')).FS; const LOGS_FILE = 'config/chat-plugins/mafia-logs.json'; const BANS_FILE = 'config/chat-plugins/mafia-bans.json'; const MafiaData = require('./mafia-data.js'); diff --git a/server/chat-plugins/modlog.js b/server/chat-plugins/modlog.js index 18a1fe3dbd..9d5bcc65f2 100644 --- a/server/chat-plugins/modlog.js +++ b/server/chat-plugins/modlog.js @@ -15,9 +15,9 @@ 'use strict'; -const FS = require('../../lib/fs'); +const FS = require('../../.lib-dist/fs').FS; const path = require('path'); -const Dashycode = require('../../lib/dashycode'); +const Dashycode = require('../../.lib-dist/dashycode'); const util = require('util'); const execFile = util.promisify(require('child_process').execFile); @@ -559,7 +559,7 @@ exports.commands = { * Process manager *********************************************************/ -const QueryProcessManager = require('../../lib/process-manager').QueryProcessManager; +const QueryProcessManager = require('../../.lib-dist/process-manager').QueryProcessManager; const PM = new QueryProcessManager(module, async data => { switch (data.cmd) { @@ -568,7 +568,7 @@ const PM = new QueryProcessManager(module, async data => { try { return await runModlog(roomidList, searchString, exactSearch, maxLines); } catch (err) { - require('../../lib/crashlogger')(err, 'A modlog query', { + Monitor.crashlog(err, 'A modlog query', { roomidList, searchString, exactSearch, @@ -581,7 +581,7 @@ const PM = new QueryProcessManager(module, async data => { try { return await runBattleSearch(userid, turnLimit, month, tierid, date); } catch (err) { - require('../../lib/crashlogger')(err, 'A battle search query', { + Monitor.crashlog(err, 'A battle search query', { userid, turnLimit, month, @@ -597,15 +597,28 @@ const PM = new QueryProcessManager(module, async data => { if (!PM.isParentProcess) { // This is a child process! global.Config = require('../../config/config'); + // @ts-ignore ??? + global.Monitor = { + /** + * @param {Error} error + * @param {string} source + * @param {{}?} details + */ + crashlog(error, source = 'A modlog process', details = null) { + const repr = JSON.stringify([error.name, error.message, source, details]); + // @ts-ignore + process.send(`THROW\n@!!@${repr}\n${error.stack}`); + }, + }; process.on('uncaughtException', err => { if (Config.crashguard) { - require('../../lib/crashlogger')(err, 'A modlog child process'); + Monitor.crashlog(err, 'A modlog child process'); } }); global.Dex = require('../../.sim-dist/dex'); global.toId = Dex.getId; - require('../../lib/repl').start('modlog', cmd => eval(cmd)); + require('../../.lib-dist/repl').Repl.start('modlog', cmd => eval(cmd)); } else { PM.spawn(MAX_PROCESSES); } diff --git a/server/chat-plugins/room-faqs.js b/server/chat-plugins/room-faqs.js index 93559c668e..356c857e41 100644 --- a/server/chat-plugins/room-faqs.js +++ b/server/chat-plugins/room-faqs.js @@ -1,6 +1,7 @@ 'use strict'; -const FS = require('../../lib/fs'); +/** @type {typeof import('../../lib/fs').FS} */ +const FS = require(/** @type {any} */('../../.lib-dist/fs')).FS; const ROOMFAQ_FILE = 'config/chat-plugins/faqs.json'; diff --git a/server/chat-plugins/scavengers.js b/server/chat-plugins/scavengers.js index bfe928dd80..fd915499a4 100644 --- a/server/chat-plugins/scavengers.js +++ b/server/chat-plugins/scavengers.js @@ -10,7 +10,7 @@ 'use strict'; -const FS = require('../../lib/fs'); +const FS = require('../../.lib-dist/fs').FS; const RATED_TYPES = ['official', 'regular', 'mini']; const DEFAULT_POINTS = { diff --git a/server/chat-plugins/thecafe.js b/server/chat-plugins/thecafe.js index 3169d4eb5e..8d79e720bc 100644 --- a/server/chat-plugins/thecafe.js +++ b/server/chat-plugins/thecafe.js @@ -1,6 +1,7 @@ 'use strict'; -const FS = require('../../lib/fs'); +/** @type {typeof import('../../lib/fs').FS} */ +const FS = require(/** @type {any} */('../../.lib-dist/fs')).FS; const DISHES_FILE = 'config/chat-plugins/thecafe-foodfight.json'; const FOODFIGHT_COOLDOWN = 5 * 60 * 1000; diff --git a/server/chat-plugins/thing-of-the-day.js b/server/chat-plugins/thing-of-the-day.js index 02764b8d25..76221d09bd 100644 --- a/server/chat-plugins/thing-of-the-day.js +++ b/server/chat-plugins/thing-of-the-day.js @@ -1,6 +1,7 @@ 'use strict'; -const FS = require('../../lib/fs'); +/** @type {typeof import('../../lib/fs').FS} */ +const FS = require(/** @type {any} */('../../.lib-dist/fs')).FS; const MINUTE = 60 * 1000; const ROOMIDS = ['thestudio', 'jubilifetvfilms', 'youtube', 'thelibrary', 'prowrestling']; diff --git a/server/chat-plugins/trivia.js b/server/chat-plugins/trivia.js index 2664630e60..693a50222d 100644 --- a/server/chat-plugins/trivia.js +++ b/server/chat-plugins/trivia.js @@ -5,7 +5,8 @@ 'use strict'; -const FS = require('../../lib/fs'); +/** @type {typeof import('../../lib/fs').FS} */ +const FS = require(/** @type {any} */('../../.lib-dist/fs')).FS; const MAIN_CATEGORIES = { ae: 'Arts and Entertainment', diff --git a/server/chat-plugins/wifi.js b/server/chat-plugins/wifi.js index aefad08a98..5b5285eaa7 100644 --- a/server/chat-plugins/wifi.js +++ b/server/chat-plugins/wifi.js @@ -6,7 +6,8 @@ 'use strict'; -const FS = require('../../lib/fs'); +/** @type {typeof import('../../lib/fs').FS} */ +const FS = require(/** @type {any} */('../../.lib-dist/fs')).FS; Punishments.roomPunishmentTypes.set('GIVEAWAYBAN', 'banned from giveaways'); diff --git a/server/chat.js b/server/chat.js index 69f76b2b43..55c5498223 100644 --- a/server/chat.js +++ b/server/chat.js @@ -55,7 +55,8 @@ const BROADCAST_TOKEN = '!'; const TRANSLATION_DIRECTORY = 'translations/'; -const FS = require('../lib/fs'); +/** @type {typeof import('../lib/fs').FS} */ +const FS = require(/** @type {any} */('../.lib-dist/fs')).FS; /** @type {(url: string) => Promise<{width: number, height: number}>} */ // @ts-ignore ignoring until there is a ts typedef available for this module. diff --git a/server/dnsbl.js b/server/dnsbl.js index 871684beb2..a884e0b8ae 100644 --- a/server/dnsbl.js +++ b/server/dnsbl.js @@ -19,7 +19,8 @@ const BLOCKLISTS = ['sbl.spamhaus.org', 'rbl.efnetrbl.org']; const dns = require('dns'); -const FS = require('../lib/fs'); +/** @type {typeof import('../lib/fs').FS} */ +const FS = require(/** @type {any} */('../.lib-dist/fs')).FS; let Dnsbl = module.exports; diff --git a/server/index.js b/server/index.js index eb07c0623e..86aed133ee 100644 --- a/server/index.js +++ b/server/index.js @@ -60,7 +60,7 @@ try { throw new Error("Dependencies are unmet; run `node build` before launching Pokemon Showdown again."); } -const FS = require('../lib/fs'); +const FS = require('../.lib-dist/fs').FS; /********************************************************* * Load configuration @@ -151,4 +151,4 @@ TeamValidatorAsync.PM.spawn(); * Start up the REPL server *********************************************************/ -require('../lib/repl').start('app', cmd => eval(cmd)); +require('../.lib-dist/repl').Repl.start('app', cmd => eval(cmd)); diff --git a/server/ladders-local.js b/server/ladders-local.js index fd115ca946..f52df9414c 100644 --- a/server/ladders-local.js +++ b/server/ladders-local.js @@ -15,7 +15,8 @@ 'use strict'; -const FS = require('../lib/fs'); +/** @type {typeof import('../lib/fs').FS} */ +const FS = require(/** @type {any} */('../.lib-dist/fs')).FS; // ladderCaches = {formatid: ladder OR Promise(ladder)} // Use Ladders(formatid).ladder to guarantee a Promise(ladder). diff --git a/server/loginserver.js b/server/loginserver.js index c8396497cc..79841f5476 100644 --- a/server/loginserver.js +++ b/server/loginserver.js @@ -15,8 +15,10 @@ const LOGIN_SERVER_BATCH_TIME = 1000; const http = Config.loginserver.startsWith('http:') ? require("http") : require("https"); const url = require('url'); -const FS = require('../lib/fs'); -const Streams = require('../lib/streams'); +/** @type {typeof import('../lib/fs').FS} */ +const FS = require(/** @type {any} */('../.lib-dist/fs')).FS; +/** @type {typeof import('../lib/streams')} */ +const Streams = require(/** @type {any} */('../.lib-dist/streams')); /** * A custom error type used when requests to the login server take too long. diff --git a/server/monitor.js b/server/monitor.js index f485300e27..1cbfdbb9df 100644 --- a/server/monitor.js +++ b/server/monitor.js @@ -8,7 +8,8 @@ */ 'use strict'; -const FS = require('../lib/fs'); +/** @type {typeof import('../lib/fs').FS} */ +const FS = require(/** @type {any} */('../.lib-dist/fs')).FS; const MONITOR_CLEAN_TIMEOUT = 2 * 60 * 60 * 1000; @@ -54,7 +55,8 @@ if (('Config' in global) && Config.loglevel = 2; } -let crashlogger = require('../lib/crashlogger'); +/** @type {typeof import('../lib/crashlogger')} */ +let crashlogger = require(/** @type {any} */('../.lib-dist/crashlogger')); const Monitor = module.exports = { /********************************************************* diff --git a/server/punishments.js b/server/punishments.js index 7f75c0987a..085386fc2b 100644 --- a/server/punishments.js +++ b/server/punishments.js @@ -15,7 +15,8 @@ let Punishments = module.exports; -const FS = require('../lib/fs'); +/** @type {typeof import('../lib/fs').FS} */ +const FS = require(/** @type {any} */('../.lib-dist/fs')).FS; const PUNISHMENT_FILE = 'config/punishments.tsv'; const ROOM_PUNISHMENT_FILE = 'config/room-punishments.tsv'; diff --git a/server/room-battle.js b/server/room-battle.js index 857adb6fed..d726c3d85f 100644 --- a/server/room-battle.js +++ b/server/room-battle.js @@ -13,7 +13,8 @@ 'use strict'; -const FS = require('../lib/fs'); +/** @type {typeof import('../lib/fs').FS} */ +const FS = require(/** @type {any} */('../.lib-dist/fs')).FS; /** 5 seconds */ const TICK_TIME = 5; @@ -1022,7 +1023,8 @@ exports.RoomBattle = Battle; * Process manager *********************************************************/ -const StreamProcessManager = require('../lib/process-manager').StreamProcessManager; +/** @type {typeof import('../lib/process-manager').StreamProcessManager} */ +const StreamProcessManager = require(/** @type {any} */('../.lib-dist/process-manager')).StreamProcessManager; const PM = new StreamProcessManager(module, () => { /** @type {typeof import('../sim/battle-stream').BattleStream} */ @@ -1067,7 +1069,9 @@ if (!PM.isParentProcess) { }); } - require('../lib/repl').start(`sim-${process.pid}`, cmd => eval(cmd)); + /** @type {typeof import('../lib/repl').Repl} */ + const Repl = require(/** @type {any} */('../.lib-dist/repl')).Repl; + Repl.start(`sim-${process.pid}`, cmd => eval(cmd)); } else { PM.spawn(global.Config ? Config.simulatorprocesses : 1); } diff --git a/server/roomlogs.js b/server/roomlogs.js index 24487d4bbf..ed843b82c3 100644 --- a/server/roomlogs.js +++ b/server/roomlogs.js @@ -9,7 +9,8 @@ 'use strict'; -const FS = require('../lib/fs'); +/** @type {typeof import('../lib/fs').FS} */ +const FS = require(/** @type {any} */('../.lib-dist/fs')).FS; /** * Most rooms have three logs: diff --git a/server/rooms.js b/server/rooms.js index 99daa7e777..63155feb55 100644 --- a/server/rooms.js +++ b/server/rooms.js @@ -22,7 +22,8 @@ const LAST_BATTLE_WRITE_THROTTLE = 10; /** @type {null} */ const RETRY_AFTER_LOGIN = null; -const FS = require('../lib/fs'); +/** @type {typeof import('../lib/fs').FS} */ +const FS = require(/** @type {any} */('../.lib-dist/fs')).FS; const Roomlogs = require('./roomlogs'); /********************************************************* @@ -483,7 +484,7 @@ class GlobalRoom extends BasicRoom { // Prevent there from being two possible hidden classes an instance // of GlobalRoom can have. // @ts-ignore - this.ladderIpLog = new (require('../lib/streams')).WriteStream({write() {}}); + this.ladderIpLog = new (require(/** @type {any} */('../.lib-dist/streams'))).WriteStream({write() {}}); } let lastBattle; diff --git a/server/sockets.js b/server/sockets.js index 5bdbe3c6a8..b58878a0e4 100644 --- a/server/sockets.js +++ b/server/sockets.js @@ -17,7 +17,12 @@ const MINUTES = 60 * 1000; const cluster = require('cluster'); const fs = require('fs'); -const FS = require('../lib/fs'); +/** @type {typeof import('../lib/fs').FS} */ +const FS = require(/** @type {any} */('../.lib-dist/fs')).FS; + +const Monitor = { + crashlog: require(/** @type {any} */('../.lib-dist/crashlogger')), +}; if (cluster.isMaster) { cluster.setupMaster({ @@ -77,7 +82,7 @@ if (cluster.isMaster) { workers.delete(worker.id); } else if (code > 0) { // Worker was killed abnormally, likely because of a crash. - require('../lib/crashlogger')(new Error(`Worker ${worker.id} abruptly died with code ${code} and signal ${signal}`), "The main process"); + Monitor.crashlog(new Error(`Worker ${worker.id} abruptly died with code ${code} and signal ${signal}`), "The main process"); // Don't delete the worker so it can be inspected if need be. } @@ -281,7 +286,7 @@ if (cluster.isMaster) { if (Config.crashguard) { // graceful crash process.on('uncaughtException', err => { - require('../lib/crashlogger')(err, `Socket process ${cluster.worker.id} (${process.pid})`); + Monitor.crashlog(err, `Socket process ${cluster.worker.id} (${process.pid})`); }); } @@ -296,7 +301,7 @@ if (cluster.isMaster) { try { key = fs.readFileSync(key); } catch (e) { - require('../lib/crashlogger')(new Error(`Failed to read the configured SSL private key PEM file:\n${e.stack}`), `Socket process ${cluster.worker.id} (${process.pid})`); + Monitor.crashlog(new Error(`Failed to read the configured SSL private key PEM file:\n${e.stack}`), `Socket process ${cluster.worker.id} (${process.pid})`); } } catch (e) { console.warn('SSL private key config values will not support HTTPS server option values in the future. Please set it to use the absolute path of its PEM file.'); @@ -310,7 +315,7 @@ if (cluster.isMaster) { try { cert = fs.readFileSync(cert); } catch (e) { - require('../lib/crashlogger')(new Error(`Failed to read the configured SSL certificate PEM file:\n${e.stack}`), `Socket process ${cluster.worker.id} (${process.pid})`); + Monitor.crashlog(new Error(`Failed to read the configured SSL certificate PEM file:\n${e.stack}`), `Socket process ${cluster.worker.id} (${process.pid})`); } } catch (e) { console.warn('SSL certificate config values will not support HTTPS server option values in the future. Please set it to use the absolute path of its PEM file.'); @@ -322,7 +327,7 @@ if (cluster.isMaster) { // In case there are additional SSL config settings besides the key and cert... appssl = require('https').createServer(Object.assign({}, Config.ssl.options, {key, cert})); } catch (e) { - require('../lib/crashlogger')(`The SSL settings are misconfigured:\n${e.stack}`, `Socket process ${cluster.worker.id} (${process.pid})`); + Monitor.crashlog(new Error(`The SSL settings are misconfigured:\n${e.stack}`), `Socket process ${cluster.worker.id} (${process.pid})`); } } } @@ -404,7 +409,7 @@ if (cluster.isMaster) { // @ts-ignore options.faye_server_options = {extensions: [deflate]}; } catch (e) { - require('../lib/crashlogger')(new Error("Dependency permessage-deflate is not installed or is otherwise unaccessable. No message compression will take place until server restart."), "Sockets"); + Monitor.crashlog(new Error("Dependency permessage-deflate is not installed or is otherwise unaccessable. No message compression will take place until server restart."), "Sockets"); } } @@ -707,5 +712,7 @@ if (cluster.isMaster) { console.log(`Test your server at http://${Config.bindaddress === '0.0.0.0' ? 'localhost' : Config.bindaddress}:${Config.port}`); - require('../lib/repl').start(`sockets-${cluster.worker.id}-${process.pid}`, cmd => eval(cmd)); + /** @type {typeof import('../lib/repl').Repl} */ + const Repl = require(/** @type {any} */('../.lib-dist/repl')).Repl; + Repl.start(`sockets-${cluster.worker.id}-${process.pid}`, cmd => eval(cmd)); } diff --git a/server/team-validator-async.js b/server/team-validator-async.js index 424d6b9070..e9936ea2f4 100644 --- a/server/team-validator-async.js +++ b/server/team-validator-async.js @@ -32,9 +32,10 @@ class ValidatorAsync { * Process manager *********************************************************/ -const QueryProcessManager = require('../lib/process-manager').QueryProcessManager; +/** @type {typeof import('../lib/process-manager').QueryProcessManager} */ +const QueryProcessManager = require(/** @type {any} */('../.lib-dist/process-manager')).QueryProcessManager; -/**@type {QueryProcessManager} */ +/** @type {QueryProcessManager} */ // @ts-ignore const PM = new QueryProcessManager(module, async message => { let {formatid, removeNicknames, team} = message; @@ -44,7 +45,7 @@ const PM = new QueryProcessManager(module, async message => { try { problems = TeamValidator(formatid).validateTeam(parsedTeam, removeNicknames); } catch (err) { - require('../lib/crashlogger')(err, 'A team validation', { + require(/** @type {any} */('../.lib-dist/crashlogger'))(err, 'A team validation', { formatid: formatid, team: team, }); @@ -93,7 +94,9 @@ if (!PM.isParentProcess) { global.toId = Dex.getId; global.Chat = require('./chat'); - require('../lib/repl').start(`team-validator-${process.pid}`, cmd => eval(cmd)); + /** @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); } diff --git a/server/users.js b/server/users.js index 79955f1655..196fce5335 100644 --- a/server/users.js +++ b/server/users.js @@ -38,7 +38,8 @@ const PERMALOCK_CACHE_TIME = 30 * 24 * 60 * 60 * 1000; const DEFAULT_TRAINER_SPRITES = [1, 2, 101, 102, 169, 170, 265, 266]; -const FS = require('../lib/fs'); +/** @type {typeof import('../lib/fs').FS} */ +const FS = require(/** @type {any} */('../.lib-dist/fs')).FS; /********************************************************* * Utility functions diff --git a/server/verifier.js b/server/verifier.js index d325e6dbef..88d68a9141 100644 --- a/server/verifier.js +++ b/server/verifier.js @@ -20,7 +20,8 @@ const crypto = require('crypto'); * Process manager *********************************************************/ -const QueryProcessManager = require('../lib/process-manager').QueryProcessManager; +/** @type {typeof import('../lib/process-manager').QueryProcessManager} */ +const QueryProcessManager = require(/** @type {any} */('../.lib-dist/process-manager')).QueryProcessManager; /**@type {QueryProcessManager} */ // @ts-ignore @@ -39,7 +40,9 @@ if (!PM.isParentProcess) { // This is a child process! // @ts-ignore This file doesn't exist on the repository, so Travis checks fail if this isn't ignored global.Config = require('../config/config'); - require('../lib/repl').start('verifier', /** @param {string} cmd */ cmd => eval(cmd)); + /** @type {typeof import('../lib/repl').Repl} */ + const Repl = require(/** @type {any} */('../.lib-dist/repl')).Repl; + Repl.start('verifier', /** @param {string} cmd */ cmd => eval(cmd)); } else { PM.spawn(global.Config ? Config.verifierprocesses : 1); } diff --git a/test/main.js b/test/main.js index 493295f118..5b6ec53f5e 100644 --- a/test/main.js +++ b/test/main.js @@ -26,7 +26,7 @@ before('initialization', function () { } finally { config = require('../config/config'); } - require('./../lib/process-manager').disabled = true; + require('./../.lib-dist/process-manager').disabled = true; Object.assign(config, require('../config/config-example')); // Actually crash if we crash @@ -39,7 +39,7 @@ before('initialization', function () { config.fakeladder = false; // Don't create a REPL - require('../lib/repl').start = noop; + require('../.lib-dist/repl').Repl.start = noop; // Start the server. require('../server'); diff --git a/tsconfig.json b/tsconfig.json index d605829a85..f3484f6ec6 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -11,7 +11,7 @@ }, "types": ["node"], "exclude": [ - "./.sim-dist/*", + "./.*-dist/*", "./server/index.js", "./server/chat-commands.js" ], diff --git a/tslint.json b/tslint.json index ced93f9f8e..8a558e09be 100644 --- a/tslint.json +++ b/tslint.json @@ -54,6 +54,6 @@ }, "rulesDirectory": [], "linterOptions": { - "exclude": ["node_modules/**/*", "**/*.js", "dev-tools/*", ".sim-dist/*"] + "exclude": ["node_modules/**/*", "**/*.js", "dev-tools/*", ".*-dist/*"] } }