mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-07-12 22:29:10 -05:00
The version number is now generated from package.json instead of needing to be manually updated in multiple places.
260 lines
9.6 KiB
JavaScript
Executable File
260 lines
9.6 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.
|
|
*
|
|
* On the live web server, this script is set as the following git hooks:
|
|
* post-commit, post-checkout, post-merge, post-rewrite
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
const crypto = require('crypto');
|
|
const exec = require('child_process').exec;
|
|
|
|
const thisFile = __filename;
|
|
const thisDir = __dirname;
|
|
const rootDir = path.resolve(thisDir, '..');
|
|
|
|
const Tools = require('../data/Pokemon-Showdown/tools').includeData();
|
|
const toId = Tools.getId;
|
|
|
|
// update version
|
|
process.stdout.write("Updating version... ");
|
|
|
|
let version = require('../package.json').version;
|
|
|
|
let configBuf = fs.readFileSync(path.resolve(rootDir, 'config/config.js'), {encoding: 'utf8'});
|
|
configBuf = configBuf.replace(/\/\* version \*\/[^;\n]*;/, `/* version */ Config.version = ${JSON.stringify(version)};`);
|
|
|
|
fs.writeFileSync(path.resolve(rootDir, 'config/config.js'), configBuf);
|
|
console.log("DONE");
|
|
|
|
function updateIndex() {
|
|
let indexContents = fs.readFileSync(path.resolve(rootDir, 'index.template.html'), {encoding: 'utf8'});
|
|
|
|
// add hashes to js and css files
|
|
process.stdout.write("Updating hashes... ");
|
|
indexContents = indexContents.replace(/(src|href)="\/(.*?)\?[a-z0-9]*?"/g, function (a, b, c) {
|
|
let hash = Math.random(); // just in case creating the hash fails
|
|
try {
|
|
const filename = c.replace('/play.pokemonshowdown.com/', '');
|
|
const fstr = fs.readFileSync(path.resolve(rootDir, filename), {encoding: 'utf8'});
|
|
hash = crypto.createHash('md5').update(fstr).digest('hex').substr(0, 8);
|
|
} catch (e) {}
|
|
|
|
return b + '="/' + c + '?' + hash + '"';
|
|
});
|
|
console.log("DONE");
|
|
|
|
// add news
|
|
process.stdout.write("Updating news... ");
|
|
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));
|
|
}
|
|
|
|
indexContents = indexContents.replace(/<!-- newsid -->/g, newsData[0]);
|
|
indexContents = indexContents.replace(/<!-- news -->/g, newsData[1]);
|
|
console.log("DONE");
|
|
|
|
process.stdout.write("Writing new `index.html` file... ");
|
|
fs.writeFileSync(path.resolve(rootDir, 'index.html'), indexContents);
|
|
console.log("DONE");
|
|
});
|
|
}
|
|
|
|
function updateLearnsets(callback) {
|
|
const reservedKeywords = ['return']; // `return` is the only ES3+ reserved keyword that (currently) raises conflicts
|
|
const numberRegExp = new RegExp('^[0-9]*');
|
|
const reservedRegExp = new RegExp('^(' + reservedKeywords.join('|') + '|\\d.*)$', 'g');
|
|
const alphabetize = (a, b) => a.localeCompare(b);
|
|
const getLsetGen = lset => lset.charAt(0);
|
|
|
|
const padNumString = function (str) {
|
|
switch (str.length) {
|
|
case 1: return '00' + str;
|
|
case 2: return '0' + str;
|
|
case 3: return '' + str;
|
|
}
|
|
};
|
|
const inLearnset = function (lset, learnset) {
|
|
const secondChar = lset.charAt(1);
|
|
if (secondChar !== 'L') return learnset.indexOf(lset) >= 0;
|
|
|
|
const firstFragment = lset.substr(0, 2);
|
|
const levelFragment = lset.substring(2, 5);
|
|
const paddedLevel = padNumString(levelFragment);
|
|
for (let i = 0, len = learnset.length; i < len; i++) {
|
|
if (learnset[i].substring(0, 2) !== firstFragment) continue;
|
|
// ignore PSdex starter moves sorting data
|
|
if (paddedLevel === padNumString(learnset[i].substring(2, 5))) return true;
|
|
}
|
|
return false;
|
|
};
|
|
const formatLset = function (lset) {
|
|
const secondChar = lset.charAt(1);
|
|
if (secondChar !== 'L') return lset;
|
|
const firstFragment = lset.substr(0, 2);
|
|
const levelFragment = lset.substr(2).match(numberRegExp)[0];
|
|
const sortFragment = lset.substr(2 + levelFragment.length);
|
|
return firstFragment + padNumString(levelFragment) + sortFragment;
|
|
};
|
|
|
|
const Pokedex = Tools.data.Pokedex;
|
|
const Learnsets = Tools.data.Learnsets;
|
|
const newLearnsetsG6 = {};
|
|
|
|
let oldLearnsetsG6;
|
|
try {
|
|
oldLearnsetsG6 = require(path.join(rootDir, 'data', 'learnsets-g6.js')).BattleLearnsets;
|
|
} catch (err) {
|
|
if (err.code !== 'MODULE_NOT_FOUND') return callback(err);
|
|
oldLearnsetsG6 = {};
|
|
}
|
|
|
|
for (const speciesid in Learnsets) {
|
|
if (!oldLearnsetsG6[speciesid] || !oldLearnsetsG6[speciesid].learnset) {
|
|
console.log("NEW ENTRY at learnsets-g6.js: " + Pokedex[speciesid].species + ".");
|
|
oldLearnsetsG6[speciesid] = {learnset: {}};
|
|
}
|
|
}
|
|
for (const speciesid in oldLearnsetsG6) {
|
|
if (!oldLearnsetsG6[speciesid] || !oldLearnsetsG6[speciesid].learnset) return callback(new TypeError("Invalid `learnsets-g6.js` entry for " + speciesid + "."));
|
|
if (!Learnsets[speciesid]) {
|
|
console.log("REMOVED ENTRY at learnsets-g6.js: " + (Pokedex[speciesid] ? Pokedex[speciesid].species : speciesid) + ".");
|
|
continue;
|
|
}
|
|
const newLearnset = {};
|
|
const oldLearnset = oldLearnsetsG6[speciesid].learnset;
|
|
const fullLearnset = Learnsets[speciesid].learnset;
|
|
if (!fullLearnset) return callback(new TypeError("Invalid data at `learnsets.js` for " + speciesid + "."));
|
|
|
|
// copy, but ignore moves removed in main file
|
|
for (const moveid in oldLearnset) {
|
|
if (!Array.isArray(oldLearnset[moveid])) return callback(new TypeError("Invalid data at `learnsets-g6.js` for " + speciesid + ":" + moveid + "."));
|
|
if (!fullLearnset[moveid]) continue;
|
|
newLearnset[moveid] = [];
|
|
for (let i = 0, len = oldLearnset[moveid].length; i < len; i++) {
|
|
if (!inLearnset(oldLearnset[moveid][i], fullLearnset[moveid])) continue;
|
|
newLearnset[moveid].push(oldLearnset[moveid][i]);
|
|
}
|
|
}
|
|
|
|
for (const moveid in fullLearnset) {
|
|
if (!Array.isArray(fullLearnset[moveid])) return callback(new TypeError("Invalid data at `learnsets.js` for " + speciesid + ":" + moveid + "."));
|
|
|
|
if (!newLearnset[moveid]) newLearnset[moveid] = [];
|
|
newLearnset[moveid] = newLearnset[moveid].map(formatLset);
|
|
for (let i = 0, len = fullLearnset[moveid].length; i < len; i++) {
|
|
if (getLsetGen(fullLearnset[moveid][i]) !== '7') continue;
|
|
if (inLearnset(fullLearnset[moveid][i], newLearnset[moveid])) continue;
|
|
newLearnset[moveid].push(formatLset(fullLearnset[moveid][i]));
|
|
}
|
|
newLearnset[moveid].sort(alphabetize);
|
|
}
|
|
|
|
newLearnsetsG6[speciesid] = {learnset: newLearnset};
|
|
}
|
|
|
|
const buf = [];
|
|
const pokemonList = Object.keys(Pokedex).map(speciesId => Pokedex[speciesId]).sort(function (a, b) {
|
|
// Missingno. goes first (zeroth); afterwards, CAP in descending dex order (increasingly negative)
|
|
// Finally, standard Pokémon in ascending dex order
|
|
if (a.num <= 0 && b.num > 0) return -1;
|
|
if (b.num <= 0 && a.num > 0) return 1;
|
|
if (a.num <= 0 && b.num <= 0) return b.num - a.num;
|
|
return a.num - b.num;
|
|
}).map(template => toId(template.species));
|
|
|
|
for (let i = 0, len = pokemonList.length; i < len; i++) {
|
|
const entry = newLearnsetsG6[pokemonList[i]];
|
|
if (!entry || !entry.learnset) continue;
|
|
const lsetSerialized = '{' + Object.keys(entry.learnset).sort(alphabetize).map(function (moveid) {
|
|
return moveid.replace(reservedRegExp, '"$1"') + ':' + JSON.stringify(entry.learnset[moveid]);
|
|
}).join(',') + '}';
|
|
buf.push(pokemonList[i] + ':{learnset:' + lsetSerialized + '}');
|
|
}
|
|
|
|
const writeStream = fs.createWriteStream(path.join(rootDir, 'data', 'learnsets-g6.js')).on('error', callback);
|
|
writeStream.write('exports.BattleLearnsets = {\n\t' + buf.join(',\n\t') + '\n};\n');
|
|
writeStream.end(callback);
|
|
}
|
|
|
|
let pendingFiles = 0;
|
|
let indexStats, updateStats, indexMTime, updateMTime;
|
|
try {
|
|
indexStats = fs.statSync(path.join(rootDir, 'index.html'));
|
|
indexMTime = indexStats.mtime.getTime();
|
|
} catch (err) {
|
|
if (err.code !== 'ENOENT') throw err;
|
|
// It doesn't exist currently, but it will by the end of the script execution.
|
|
// Any other error is unacceptable and will throw.
|
|
}
|
|
try {
|
|
updateStats = fs.statSync(thisFile);
|
|
updateMTime = updateStats.mtime.getTime();
|
|
} catch (err) {
|
|
throw err; // !!
|
|
}
|
|
|
|
// update learnsets-g6
|
|
process.stdout.write("Updating file `data/learnsets-g6`... ");
|
|
|
|
pendingFiles++;
|
|
let learnsetsStats;
|
|
let learnsetsG6Stats;
|
|
let learnsetsG6ToUpdate = true;
|
|
|
|
try {
|
|
learnsetsStats = fs.statSync(path.join(rootDir, 'data', 'learnsets.js'));
|
|
} catch (err) {
|
|
// Couldn't find learnsets.js, but that's not the end of the world: skip to next task.
|
|
console.error("Couldn't find `data/learnsets.js`. Task aborted.");
|
|
learnsetsG6ToUpdate = false;
|
|
if (!--pendingFiles) updateIndex();
|
|
}
|
|
if (learnsetsG6ToUpdate) {
|
|
try {
|
|
learnsetsG6Stats = fs.statSync(path.join(rootDir, 'data', 'learnsets-g6.js'));
|
|
} catch (err) {
|
|
if (err.code === 'ENOENT') {
|
|
// It doesn't exist currently, but it will by the end of the script execution.
|
|
} else {
|
|
// Any other error is unacceptable for learnsets-g6.js update: skip to next task.
|
|
console.error("Failed to read `data/learnsets-g6.js`. Task aborted.");
|
|
learnsetsG6ToUpdate = false;
|
|
if (!--pendingFiles) updateIndex();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (learnsetsG6ToUpdate && (!indexStats || !learnsetsG6Stats || indexMTime < updateMTime || indexMTime < learnsetsStats.mtime.getTime() || indexMTime < learnsetsG6Stats.mtime.getTime())) {
|
|
// Only merge learnsets.js with learnsets-g6.js if any of those files, or this one, have been modified recently (or if we don't know what "recently" means)
|
|
|
|
updateLearnsets(function (err) {
|
|
if (err) {
|
|
let stack = err.stack || '';
|
|
stack = "File `data/learnsets-g6` failed to update.\n" + stack;
|
|
console.error(stack);
|
|
} else {
|
|
console.log("DONE");
|
|
}
|
|
if (!--pendingFiles) updateIndex();
|
|
});
|
|
} else {
|
|
console.log("CACHED");
|
|
if (!--pendingFiles) updateIndex();
|
|
}
|