mirror of
https://github.com/smogon/pokemon-showdown.git
synced 2026-04-25 07:22:09 -05:00
ESLint has a whole new config format, so I figure it's a good time to make the config system saner. - First, we no longer have separate eslint-no-types configs. Lint performance shouldn't be enough of a problem to justify the relevant maintenance complexity. - Second, our base config should work out-of-the-box now. `npx eslint` will work as expected, without any CLI flags. You should still use `npm run lint` which adds the `--cached` flag for performance. - Third, whatever updates I did fixed style linting, which apparently has been bugged for quite some time, considering all the obvious mixed-tabs-and-spaces issues I found in the upgrade. Also here are some changes to our style rules. In particular: - Curly brackets (for objects etc) now have spaces inside them. Sorry for the huge change. ESLint doesn't support our old style, and most projects use Prettier style, so we might as well match them in this way. See https://github.com/eslint-stylistic/eslint-stylistic/issues/415 - String + number concatenation is no longer allowed. We now consistently use template strings for this.
168 lines
5.1 KiB
TypeScript
168 lines
5.1 KiB
TypeScript
/**
|
|
* Config loader
|
|
* Pokemon Showdown - http://pokemonshowdown.com/
|
|
*
|
|
* @license MIT
|
|
*/
|
|
|
|
import * as defaults from '../config/config-example';
|
|
import type { GroupInfo, EffectiveGroupSymbol } from './user-groups';
|
|
import { ProcessManager, FS } from '../lib';
|
|
|
|
export type ConfigType = typeof defaults & {
|
|
groups: { [symbol: string]: GroupInfo },
|
|
groupsranking: EffectiveGroupSymbol[],
|
|
greatergroupscache: { [combo: string]: GroupSymbol },
|
|
[k: string]: any,
|
|
};
|
|
/** Map<process flag, config settings for it to turn on> */
|
|
const FLAG_PRESETS = new Map([
|
|
['--no-security', ['nothrottle', 'noguestsecurity', 'noipchecks']],
|
|
]);
|
|
|
|
const CONFIG_PATH = FS('./config/config.js').path;
|
|
|
|
export function load(invalidate = false) {
|
|
if (invalidate) delete require.cache[CONFIG_PATH];
|
|
const config = ({ ...defaults, ...require(CONFIG_PATH) }) as ConfigType;
|
|
// config.routes is nested - we need to ensure values are set for its keys as well.
|
|
config.routes = { ...defaults.routes, ...config.routes };
|
|
|
|
// Automatically stop startup if better-sqlite3 isn't installed and SQLite is enabled
|
|
if (config.usesqlite) {
|
|
try {
|
|
require('better-sqlite3');
|
|
} catch {
|
|
throw new Error(`better-sqlite3 is not installed or could not be loaded, but Config.usesqlite is enabled.`);
|
|
}
|
|
}
|
|
|
|
for (const [preset, values] of FLAG_PRESETS) {
|
|
if (process.argv.includes(preset)) {
|
|
for (const value of values) config[value] = true;
|
|
}
|
|
}
|
|
|
|
cacheGroupData(config);
|
|
return config;
|
|
}
|
|
|
|
export function cacheGroupData(config: ConfigType) {
|
|
if (config.groups) {
|
|
// Support for old config groups format.
|
|
// Should be removed soon.
|
|
reportError(
|
|
`You are using a deprecated version of user group specification in config.\n` +
|
|
`Support for this will be removed soon.\n` +
|
|
`Please ensure that you update your config.js to the new format (see config-example.js, line 457).\n`
|
|
);
|
|
} else {
|
|
config.punishgroups = Object.create(null);
|
|
config.groups = Object.create(null);
|
|
config.groupsranking = [];
|
|
config.greatergroupscache = Object.create(null);
|
|
}
|
|
|
|
const groups = config.groups;
|
|
const punishgroups = config.punishgroups;
|
|
const cachedGroups: { [k: string]: 'processing' | true } = {};
|
|
|
|
function isPermission(key: string) {
|
|
return !['symbol', 'id', 'name', 'rank', 'globalGroupInPersonalRoom'].includes(key);
|
|
}
|
|
function cacheGroup(symbol: string, groupData: GroupInfo) {
|
|
if (cachedGroups[symbol] === 'processing') {
|
|
throw new Error(`Cyclic inheritance in group config for symbol "${symbol}"`);
|
|
}
|
|
if (cachedGroups[symbol] === true) return;
|
|
|
|
for (const key in groupData) {
|
|
if (isPermission(key)) {
|
|
const jurisdiction = groupData[key as 'jurisdiction'];
|
|
if (typeof jurisdiction === 'string' && jurisdiction.includes('s')) {
|
|
reportError(`Outdated jurisdiction for permission "${key}" of group "${symbol}": 's' is no longer a supported jurisdiction; we now use 'ipself' and 'altsself'`);
|
|
delete groupData[key as 'jurisdiction'];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (groupData['inherit']) {
|
|
cachedGroups[symbol] = 'processing';
|
|
const inheritGroup = groups[groupData['inherit']];
|
|
cacheGroup(groupData['inherit'], inheritGroup);
|
|
// Add lower group permissions to higher ranked groups,
|
|
// preserving permissions specifically declared for the higher group.
|
|
for (const key in inheritGroup) {
|
|
if (key in groupData) continue;
|
|
if (!isPermission(key)) continue;
|
|
(groupData as any)[key] = (inheritGroup as any)[key];
|
|
}
|
|
delete groupData['inherit'];
|
|
}
|
|
cachedGroups[symbol] = true;
|
|
}
|
|
|
|
if (config.grouplist) { // Using new groups format.
|
|
const grouplist = config.grouplist as any;
|
|
const numGroups = grouplist.length;
|
|
for (let i = 0; i < numGroups; i++) {
|
|
const groupData = grouplist[i];
|
|
|
|
// punish groups
|
|
if (groupData.punishgroup) {
|
|
punishgroups[groupData.id] = groupData;
|
|
continue;
|
|
}
|
|
|
|
groupData.rank = numGroups - i - 1;
|
|
groups[groupData.symbol] = groupData;
|
|
config.groupsranking.unshift(groupData.symbol);
|
|
}
|
|
}
|
|
|
|
for (const sym in groups) {
|
|
const groupData = groups[sym];
|
|
cacheGroup(sym, groupData);
|
|
}
|
|
|
|
// hardcode default punishgroups.
|
|
if (!punishgroups.locked) {
|
|
punishgroups.locked = {
|
|
name: 'Locked',
|
|
id: 'locked',
|
|
symbol: '\u203d',
|
|
};
|
|
}
|
|
if (!punishgroups.muted) {
|
|
punishgroups.muted = {
|
|
name: 'Muted',
|
|
id: 'muted',
|
|
symbol: '!',
|
|
};
|
|
}
|
|
}
|
|
|
|
export function checkRipgrepAvailability() {
|
|
if (Config.ripgrepmodlog === undefined) {
|
|
const cwd = FS.ROOT_PATH;
|
|
Config.ripgrepmodlog = (async () => {
|
|
try {
|
|
await ProcessManager.exec(['rg', '--version'], { cwd });
|
|
await ProcessManager.exec(['tac', '--version'], { cwd });
|
|
return true;
|
|
} catch {
|
|
return false;
|
|
}
|
|
})();
|
|
}
|
|
return Config.ripgrepmodlog;
|
|
}
|
|
|
|
function reportError(msg: string) {
|
|
// This module generally loads before Monitor, so we put this in a setImmediate to wait for it to load.
|
|
// Most child processes don't have Monitor.error, but the main process should always have them, and Config
|
|
// errors should always be the same across processes, so this is a neat way to avoid unnecessary logging.
|
|
setImmediate(() => global.Monitor?.error?.(msg));
|
|
}
|
|
export const Config = load();
|