pokemon-showdown-client/build-tools/update
Guangcong Luo a10821ab8b
Some checks are pending
Node.js CI / build (22.x) (push) Waiting to run
Update to ESLint 9 (#2326)
This finally removes the tslint dependency and switches to eslint.

There are a lot of other changes here, too, to bring the codebase up to
server standards. TSLint never had much in the way of indentation
enforcement.

Not very happy about eslint splitting itself up over 6 dependencies,
or its documentation over three websites, nor how poorly documented the
new flat config is, but I mean, eslint's gonna eslint. Customizing
would be even harder if we tried to use Biome or something. They mostly
seem to go full Prettier.

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 (except in ES3
  code). We otherwise now consistently use template strings for this.
2025-02-25 20:05:32 -08:00

223 lines
8.0 KiB
JavaScript
Executable File

#!/usr/bin/env node
/**
* This script parses index.html and sets the version query string of each
* resource to be the MD5 hash of that resource.
* It also updates news and the learnsets-g6.js file.
*/
"use strict";
const path = require('path');
const fs = require('fs');
const crypto = require('crypto');
const child_process = require('child_process');
const compiler = require('./compiler');
const thisDir = __dirname;
const rootDir = path.resolve(thisDir, '..');
process.chdir(rootDir);
const AUTOCONFIG_START = '/*** Begin automatically generated configuration ***/';
const AUTOCONFIG_END = '/*** End automatically generated configuration ***/';
const UTF8 = { encoding: 'utf8' };
function escapeRegex(string) {
return string.replace(/[/*.]/g, '\\$&');
}
/*********************************************************
* Update version number
*********************************************************/
process.stdout.write("Updating version... ");
let version = require('../package.json').version;
try {
let commit = child_process.execSync('git rev-parse HEAD', {
stdio: ['ignore', 'pipe', 'ignore'],
});
const head = ('' + commit).trim();
commit = child_process.execSync('git merge-base origin/master HEAD', {
stdio: ['ignore', 'pipe', 'ignore'],
});
const origin = ('' + commit).trim();
version += ` (${head.slice(0, 8)}${head !== origin ? `/${origin.slice(0, 8)}` : ''})`;
} catch {}
const routes = JSON.parse(fs.readFileSync('config/routes.json'));
const autoconfigRegex = new RegExp(`${escapeRegex(AUTOCONFIG_START)}[^]+${escapeRegex(AUTOCONFIG_END)}`);
const autoconfig = `${AUTOCONFIG_START}
Config.version = ${JSON.stringify(version)};
Config.routes = {
root: '${routes.root}',
client: '${routes.client}',
dex: '${routes.dex}',
replays: '${routes.replays}',
users: '${routes.users}',
};
${AUTOCONFIG_END}`;
// remove old automatically generated configuration and add the new one
let configBuf = fs.readFileSync('config/config.js', UTF8);
if (autoconfigRegex.test(configBuf)) {
configBuf = configBuf.replace(autoconfigRegex, autoconfig);
} else {
configBuf += autoconfig;
}
fs.writeFileSync('config/config.js', configBuf);
console.log("DONE");
/*********************************************************
* Compile TS files
*********************************************************/
process.stdout.write("Compiling TS files... ");
const compileStartTime = process.hrtime();
let compiledFiles = 0;
// Babel can't find babelrc if we try to compile stuff in caches/pokemon-showdown/ fsr
// eslint-disable-next-line no-eval
const compileOpts = Object.assign(eval('(' + fs.readFileSync('.babelrc') + ')'), {
babelrc: false,
incremental: true,
ignore: ['play.pokemonshowdown.com/src/battle-animations.js', 'play.pokemonshowdown.com/src/battle-animations-moves.js'],
});
if (process.argv[2] === 'full') {
delete compileOpts.ignore;
compiler.compileToDir(
['caches/pokemon-showdown/server/chat-formatter.ts'],
'play.pokemonshowdown.com/js/server/',
compileOpts
);
} else {
try {
fs.statSync('play.pokemonshowdown.com/data/graphics.js');
// graphics.js exists, recompile it
delete compileOpts.ignore;
} catch {}
}
compiledFiles += compiler.compileToDir(`play.pokemonshowdown.com/src`, `play.pokemonshowdown.com/js`, compileOpts);
compiledFiles += compiler.compileToDir(`replay.pokemonshowdown.com/src`, `replay.pokemonshowdown.com/js`, compileOpts);
compiledFiles += compiler.compileToFile(
[
'play.pokemonshowdown.com/src/battle-dex.ts',
'play.pokemonshowdown.com/src/battle-dex-data.ts',
'play.pokemonshowdown.com/src/battle-log.ts',
'play.pokemonshowdown.com/src/battle-log-misc.js',
'caches/pokemon-showdown/server/chat-formatter.ts',
'play.pokemonshowdown.com/data/text.js',
'play.pokemonshowdown.com/src/battle-text-parser.ts',
],
'play.pokemonshowdown.com/js/battledata.js',
compileOpts
);
if (!compileOpts.ignore) {
compiledFiles += compiler.compileToFile(
['play.pokemonshowdown.com/src/battle-animations.ts', 'play.pokemonshowdown.com/src/battle-animations-moves.ts'],
'play.pokemonshowdown.com/data/graphics.js',
compileOpts
);
}
const diff = process.hrtime(compileStartTime);
console.log(
`(${compiledFiles} ${compiledFiles !== 1 ? "files" : "file"} in ${diff[0] + Math.round(diff[1] / 1e6) / 1e3}s) DONE`
);
/*********************************************************
* Update cachebuster and News
*********************************************************/
process.stdout.write("Updating cachebuster and URLs... ");
const URL_REGEX = /(src|href)="(.*?)(\?[a-z0-9]*?)?"/g;
function addCachebuster(_, attr, url, urlQuery) {
url = url.replace('/replay.pokemonshowdown.com/', '/' + routes.replays + '/');
url = url.replace('/dex.pokemonshowdown.com/', '/' + routes.dex + '/');
url = url.replace('/play.pokemonshowdown.com/', '/' + routes.client + '/');
url = url.replace('/pokemonshowdown.com/', '/' + routes.root + '/');
if (urlQuery) {
if (url.startsWith('/')) {
let hash = Math.random(); // just in case creating the hash fails
try {
const filename = url.slice(1).replace('/' + routes.client + '/', '');
const fstr = fs.readFileSync(filename, UTF8);
hash = crypto.createHash('md5').update(fstr).digest('hex').substr(0, 8);
} catch {}
return attr + '="' + url + '?' + hash + '"';
} else {
// hardcoded to Replays rn; TODO: generalize
let hash;
try {
const fstr = fs.readFileSync('replay.pokemonshowdown.com/' + url, UTF8);
hash = crypto.createHash('md5').update(fstr).digest('hex').substr(0, 8);
} catch {}
return attr + '="' + url + '?' + (hash || 'v1') + '"';
}
} else {
return attr + '="' + url + '"';
}
}
// add hashes to js and css files and rewrite URLs
let indexContents = fs.readFileSync('play.pokemonshowdown.com/index.template.html', UTF8);
indexContents = indexContents.replace(URL_REGEX, addCachebuster);
let preactIndexContents = fs.readFileSync('play.pokemonshowdown.com/preactalpha.template.html', UTF8);
preactIndexContents = preactIndexContents.replace(URL_REGEX, addCachebuster);
let crossprotocolContents = fs.readFileSync('play.pokemonshowdown.com/crossprotocol.template.html', UTF8);
crossprotocolContents = crossprotocolContents.replace(URL_REGEX, addCachebuster);
let replayEmbedContents = fs.readFileSync('play.pokemonshowdown.com/js/replay-embed.template.js', UTF8);
replayEmbedContents = replayEmbedContents.replace(/play\.pokemonshowdown\.com/g, routes.client);
// add news, only if it's actually likely to exist
process.stdout.write("and news... ");
let stdout = '';
let newsid = 0;
let news = '[failed to retrieve news]';
try {
stdout = child_process.execSync('php ' + path.resolve(thisDir, 'news-embed.php'));
} catch (e) {
console.log("git hook failed to retrieve news (exec command failed):\n" + (e.error + e.stderr + e.stdout));
}
try {
if (stdout) [newsid, news] = JSON.parse(stdout);
} catch (e) {
console.log("git hook failed to retrieve news (parsing JSON failed):\n" + e.stack);
}
indexContents = indexContents.replace(/<!-- newsid -->/g, newsid);
indexContents = indexContents.replace(/<!-- news -->/g, news);
let indexContents2 = '';
try {
const indexContentsOld = indexContents;
indexContents = indexContents.replace(/<!-- head custom -->/g, '' + fs.readFileSync('config/head-custom.html'));
indexContents2 = indexContentsOld
.replace(/<!-- head custom -->/g, '' + fs.readFileSync('config/head-custom-test.html'));
} catch {}
fs.writeFileSync('play.pokemonshowdown.com/index.html', indexContents);
if (indexContents2) {
fs.writeFileSync('play.pokemonshowdown.com/index-test.html', indexContents2);
}
fs.writeFileSync('play.pokemonshowdown.com/preactalpha.html', preactIndexContents);
fs.writeFileSync('play.pokemonshowdown.com/crossprotocol.html', crossprotocolContents);
fs.writeFileSync('play.pokemonshowdown.com/js/replay-embed.js', replayEmbedContents);
let replaysContents = fs.readFileSync('replay.pokemonshowdown.com/index.template.php', UTF8);
replaysContents = replaysContents.replace(URL_REGEX, addCachebuster);
fs.writeFileSync('replay.pokemonshowdown.com/index.php', replaysContents);
console.log("DONE");