pokemon-showdown-client/build-tools/update
Guangcong Luo 7a2b323eaf Deploy New Replays
The idea is to eventually move all client parts to their domain name
subdirectory, for clarity and better organization. New Replays is
just first.

Anyway, yeah, minor updates to New Replays, but otherwise it's just
getting deployed as-is, straight to

https://replay.pokemonshowdown.com/

The old URLs are getting taken down; they were only used for
development anyway.
2023-11-05 23:37:14 +00:00

215 lines
7.5 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 ***/';
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 (e) {}
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', {encoding: '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... ");
let compileStartTime = process.hrtime();
let compiledFiles = 0;
// Babel can't find babelrc if we try to compile stuff in data/pokemon-showdown/ fsr
let compileOpts = Object.assign(eval('(' + fs.readFileSync('.babelrc') + ')'), {
babelrc: false,
incremental: true,
ignore: ['src/battle-animations.js', 'src/battle-animations-moves.js'],
});
if (process.argv[2] === 'full') {
delete compileOpts.ignore;
compiler.compileToDir(
['data/pokemon-showdown/server/chat-formatter.ts'],
'js/server/',
compileOpts
);
} else {
try {
fs.statSync('data/graphics.js');
// graphics.js exists, recompile it
delete compileOpts.ignore;
} catch (e) {}
}
compiledFiles += compiler.compileToDir(`src`, `js`, compileOpts);
compiledFiles += compiler.compileToDir(`replay.pokemonshowdown.com/src`, `replay.pokemonshowdown.com/js`, compileOpts);
compiledFiles += compiler.compileToFile(
['src/battle-dex.ts', 'src/battle-dex-data.ts', 'src/battle-log.ts', 'src/battle-log-misc.js', 'data/pokemon-showdown/server/chat-formatter.ts', 'data/text.js', 'src/battle-text-parser.ts'],
'js/battledata.js',
compileOpts
);
if (!compileOpts.ignore) {
compiledFiles += compiler.compileToFile(
['src/battle-animations.ts', 'src/battle-animations-moves.ts'],
'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 updateURL(a, b, c, d) {
c = c.replace('/replay.pokemonshowdown.com/', '/' + routes.replays + '/');
c = c.replace('/dex.pokemonshowdown.com/', '/' + routes.dex + '/');
c = c.replace('/play.pokemonshowdown.com/', '/' + routes.client + '/');
c = c.replace('/pokemonshowdown.com/', '/' + routes.root + '/');
if (d) {
if (c.startsWith('/')) {
let hash = Math.random(); // just in case creating the hash fails
try {
const filename = c.slice(1).replace('/' + routes.client + '/', '');
const fstr = fs.readFileSync(filename, {encoding: 'utf8'});
hash = crypto.createHash('md5').update(fstr).digest('hex').substr(0, 8);
} catch (e) {}
return b + '="' + c + '?' + hash + '"';
} else {
// hardcoded to Replays rn; TODO: generalize
let hash;
try {
const fstr = fs.readFileSync('replay.pokemonshowdown.com/' + c, {encoding: 'utf8'});
hash = crypto.createHash('md5').update(fstr).digest('hex').substr(0, 8);
} catch (e) {}
return b + '="' + c + '?' + (hash || 'v1') + '"';
}
} else {
return b + '="' + c + '"';
}
}
function writeFiles(indexContents, preactIndexContents, crossprotocolContents, replayEmbedContents) {
fs.writeFileSync('index.html', indexContents);
fs.writeFileSync('preactalpha.html', preactIndexContents);
fs.writeFileSync('crossprotocol.html', crossprotocolContents);
fs.writeFileSync('js/replay-embed.js', replayEmbedContents);
let replaysContents = fs.readFileSync('replay.pokemonshowdown.com/index.template.html', {encoding: 'utf8'});
replaysContents = replaysContents.replace(URL_REGEX, updateURL);
fs.writeFileSync('replay.pokemonshowdown.com/index.html', replaysContents);
console.log("DONE");
}
function updateFiles() {
// add hashes to js and css files and rewrite URLs
let indexContents = fs.readFileSync('index.template.html', {encoding: 'utf8'});
indexContents = indexContents.replace(URL_REGEX, updateURL);
let preactIndexContents = fs.readFileSync('preactalpha.template.html', {encoding: 'utf8'});
preactIndexContents = preactIndexContents.replace(URL_REGEX, updateURL);
let crossprotocolContents = fs.readFileSync('crossprotocol.template.html', {encoding: 'utf8'});
crossprotocolContents = crossprotocolContents.replace(URL_REGEX, updateURL);
let replayEmbedContents = fs.readFileSync('js/replay-embed.template.js', {encoding: 'utf8'});
replayEmbedContents = replayEmbedContents.replace(/play\.pokemonshowdown\.com/g, routes.client);
// add news, only if it's actually likely to exist
if (__dirname.endsWith('play.pokemonshowdown.com/build-tools')) {
process.stdout.write("and news... ");
child_process.exec('php ' + path.resolve(thisDir, 'news-data.php'), function (error, stdout, stderr) {
let newsData = [0, '[failed to retrieve news]'];
if (!error && !stderr) {
try {
newsData = JSON.parse(stdout);
} catch (e) {
console.log("git hook failed to retrieve news (parsing JSON failed):\n" + e.stack);
}
} else {
console.log("git hook failed to retrieve news (exec command failed):\n" + (error + stderr + stdout));
}
indexContents = indexContents.replace(/<!-- newsid -->/g, newsData[0]);
indexContents = indexContents.replace(/<!-- news -->/g, newsData[1]);
indexContents = indexContents.replace(/<!-- head custom -->/g, '' + fs.readFileSync('config/head-custom.html'));
writeFiles(indexContents, preactIndexContents, crossprotocolContents, replayEmbedContents);
});
} else {
writeFiles(indexContents, preactIndexContents, crossprotocolContents, replayEmbedContents);
}
}
updateFiles();