mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-08-20 01:51:42 -05:00
Some checks failed
Node.js CI / build (22.x) (push) Has been cancelled
- Support new Team Preview choice protocol
- Fix contentEditable cursor reset on blur+refocus
- (Unfocusing and focusing would reset the textbox cursor position)
- Fix horizontal movement having precedence over selection and sliders
in mobile
- (You couldn't modify selections or drag sliders because it would
slide open the menu instead)
- Fix build error
- (Replays no longer has a custom build path)
- Don't apply "Zoom out forms" setting to import/export tab
285 lines
10 KiB
JavaScript
Executable File
285 lines
10 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 fs = require('node:fs');
|
|
const crypto = require('node:crypto');
|
|
const child_process = require('node:child_process');
|
|
const compiler = require('./compiler.mjs');
|
|
|
|
process.chdir(__dirname + '/..');
|
|
|
|
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, '\\$&');
|
|
}
|
|
|
|
function readOptionalFile(filename) {
|
|
try {
|
|
return fs.readFileSync(filename, UTF8);
|
|
} catch (error) {
|
|
if (error.code === 'ENOENT') return '';
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
/*********************************************************
|
|
* 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}',
|
|
teams: '${routes.teams}',
|
|
};
|
|
${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', 'caches/pokemon-showdown/sim/teams.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.compileToDir(`teams.pokemonshowdown.com/src`, `teams.pokemonshowdown.com/js`, compileOpts);
|
|
|
|
try {
|
|
fs.statSync('play.pokemonshowdown.com/data/text.js');
|
|
// text.js exists, we can compile battledata
|
|
compiledFiles += compiler.compileToFile(
|
|
[
|
|
'play.pokemonshowdown.com/src/battle-dex.ts',
|
|
'play.pokemonshowdown.com/src/battle-teams.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/data/text-afd.js',
|
|
'play.pokemonshowdown.com/src/battle-text-parser.ts',
|
|
],
|
|
'play.pokemonshowdown.com/js/battledata.js',
|
|
compileOpts
|
|
);
|
|
} catch {}
|
|
|
|
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] + diff[1] / 1e9).toFixed(3)}s) DONE`
|
|
);
|
|
|
|
/*********************************************************
|
|
* Copy old client files
|
|
*********************************************************/
|
|
|
|
process.stdout.write("Copying old client files... ");
|
|
fs.cpSync('play.pokemonshowdown.com/src/oldclient', 'play.pokemonshowdown.com/js/oldclient', {
|
|
recursive: true,
|
|
force: true,
|
|
});
|
|
console.log("DONE");
|
|
|
|
/*********************************************************
|
|
* Update cachebuster and News
|
|
*********************************************************/
|
|
|
|
process.stdout.write("Updating cachebuster and URLs... ");
|
|
|
|
const URL_REGEX = /(src=|href=|linkStyle\()"(.*?)(\?[a-z0-9]*?)?"/g;
|
|
|
|
const ROUTE_DIRS = [
|
|
['pokemonshowdown.com', routes.root],
|
|
['play.pokemonshowdown.com', routes.client],
|
|
['dex.pokemonshowdown.com', routes.dex],
|
|
['replay.pokemonshowdown.com', routes.replays],
|
|
['teams.pokemonshowdown.com', routes.teams],
|
|
];
|
|
|
|
function getCachebuster(url, sourceDir) {
|
|
let filename;
|
|
if (url.startsWith('//')) {
|
|
for (const [routeDir] of ROUTE_DIRS) {
|
|
if (url.startsWith(`//${routeDir}/`)) {
|
|
filename = url.slice(2);
|
|
break;
|
|
}
|
|
}
|
|
} else if (!url.includes('://')) {
|
|
filename = sourceDir + '/' + url.replace(/^\//, '');
|
|
}
|
|
|
|
if (filename) {
|
|
try {
|
|
const contents = fs.readFileSync(filename, UTF8);
|
|
return crypto.createHash('md5').update(contents).digest('hex').slice(0, 8);
|
|
} catch {}
|
|
}
|
|
|
|
const isMinidex = filename?.endsWith('/pokedex-mini.js') || filename?.endsWith('/pokedex-mini-bw.js');
|
|
if (process.argv[2] === 'full' && !isMinidex) {
|
|
console.error(`WARNING: Could not cachebust ${url} in ${sourceDir}; using random fallback.`);
|
|
process.exitCode = 2;
|
|
}
|
|
return `${Math.random()}`.slice(2, 10);
|
|
}
|
|
|
|
function cachebust(contents, sourceDir) {
|
|
return contents.replace(URL_REGEX, (_, attr, url, urlQuery) => {
|
|
if (urlQuery) url += '?' + getCachebuster(url, sourceDir);
|
|
|
|
for (const [routeDir, route] of ROUTE_DIRS) {
|
|
url = url.replace(`/${routeDir}/`, `/${route}/`);
|
|
}
|
|
|
|
return attr + '"' + url + '"';
|
|
});
|
|
}
|
|
|
|
// add hashes to js and css files and rewrite URLs
|
|
let indexContents = fs.readFileSync('play.pokemonshowdown.com/index-old.html', UTF8);
|
|
indexContents = cachebust(indexContents, 'play.pokemonshowdown.com');
|
|
let preactIndexContents = fs.readFileSync('play.pokemonshowdown.com/index-new.html', UTF8);
|
|
preactIndexContents = cachebust(preactIndexContents, 'play.pokemonshowdown.com');
|
|
let crossprotocolContents = fs.readFileSync('play.pokemonshowdown.com/crossprotocol.html', UTF8);
|
|
crossprotocolContents = cachebust(crossprotocolContents, 'play.pokemonshowdown.com');
|
|
let replayEmbedContents = fs.readFileSync('play.pokemonshowdown.com/js/replay-embed.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 build-tools/news-embed.php', {
|
|
stdio: 'pipe',
|
|
});
|
|
} 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) {
|
|
if (stdout.includes("config/news.inc.php): Failed to open stream: No such file or directory")) {
|
|
console.log("news.inc.php unavailable");
|
|
} else {
|
|
console.log("git hook failed to retrieve news (parsing JSON failed):\n\n" + e.message + '\n\n' + stdout);
|
|
}
|
|
}
|
|
|
|
indexContents = indexContents.replace(/<!-- newsid -->/g, newsid);
|
|
indexContents = indexContents.replace(/<!-- build-tools\/news-embed\.php -->/g, news);
|
|
preactIndexContents = preactIndexContents.replace(/<!-- newsid -->/g, newsid);
|
|
preactIndexContents = preactIndexContents.replace(/<!-- build-tools\/news-embed\.php -->/g, news);
|
|
|
|
let indexContents2 = '';
|
|
const customTestHead = readOptionalFile('config/head-custom-test.html');
|
|
if (customTestHead) {
|
|
indexContents2 = indexContents.replace(/<!-- config\/head-custom\.html -->/g, customTestHead);
|
|
}
|
|
|
|
const customHead = readOptionalFile('config/head-custom.html');
|
|
indexContents = indexContents.replace(/<!-- config\/head-custom\.html -->/g, customHead);
|
|
preactIndexContents = preactIndexContents.replace(/<!-- config\/head-custom\.html -->/g, customHead);
|
|
|
|
fs.writeFileSync('play.pokemonshowdown.com/caches/index-old.html', indexContents);
|
|
if (indexContents2) {
|
|
fs.writeFileSync('play.pokemonshowdown.com/caches/index-test.html', indexContents2);
|
|
} else {
|
|
fs.rmSync('play.pokemonshowdown.com/caches/index-test.html', { force: true });
|
|
}
|
|
fs.writeFileSync('play.pokemonshowdown.com/caches/index-new.html', preactIndexContents);
|
|
fs.writeFileSync('play.pokemonshowdown.com/caches/crossprotocol.html', crossprotocolContents);
|
|
fs.writeFileSync('play.pokemonshowdown.com/js/replay-embed.js', replayEmbedContents);
|
|
|
|
let replaysContents = fs.readFileSync('replay.pokemonshowdown.com/index.php', UTF8);
|
|
replaysContents = replaysContents.replace('<?php', "<?php\n\nchdir(dirname(__DIR__));");
|
|
replaysContents = cachebust(replaysContents, 'replay.pokemonshowdown.com');
|
|
fs.mkdirSync('replay.pokemonshowdown.com/caches', { recursive: true });
|
|
fs.writeFileSync('replay.pokemonshowdown.com/caches/index.php', replaysContents);
|
|
|
|
let teamsContents = fs.readFileSync('teams.pokemonshowdown.com/index.html', UTF8);
|
|
teamsContents = cachebust(teamsContents, 'teams.pokemonshowdown.com');
|
|
fs.mkdirSync('teams.pokemonshowdown.com/caches', { recursive: true });
|
|
fs.writeFileSync('teams.pokemonshowdown.com/caches/index.html', teamsContents);
|
|
|
|
console.log("DONE");
|