mirror of
https://github.com/smogon/pokemon-showdown-client.git
synced 2026-05-18 19:15:38 -05:00
- Its execution will be aborted if no relevant files have been modified since last index update. - It will no longer require learnsets-g6.js to already exist. - It will no longer error if entries of learnsets.js are deleted. - It will no longer throw if there is an error, allowing the rest of the script to get executed.
248 lines
9.1 KiB
JavaScript
Executable File
248 lines
9.1 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
|
|
*/
|
|
|
|
var path = require('path');
|
|
var fs = require('fs');
|
|
var crypto = require('crypto');
|
|
var exec = require('child_process').exec;
|
|
var sugar = require('sugar');
|
|
|
|
var thisFile = __filename;
|
|
var thisDir = __dirname;
|
|
var rootDir = path.resolve(thisDir, '..');
|
|
|
|
function toId (text) {
|
|
if (text && text.id) text = text.id;
|
|
return ('' + text).toLowerCase().replace(/[^a-z0-9]+/g, '');
|
|
}
|
|
|
|
function updateIndex () {
|
|
var indexContents = fs.readFileSync(path.resolve(rootDir, 'index.template.html'), { encoding: 'utf8' });
|
|
|
|
// add hashes to js and css files
|
|
console.log("Updating hashes...");
|
|
indexContents = indexContents.replace(/(src|href)="\/(.*?)\?[a-z0-9]*?"/g, function (a, b, c) {
|
|
var hash = Math.random(); // just in case creating the hash fails
|
|
try {
|
|
var filename = c.replace('/play.pokemonshowdown.com/', '');
|
|
var 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("Hashes updated.");
|
|
|
|
// add news
|
|
console.log("Updating news...");
|
|
exec('php ' + path.resolve(thisDir, 'news-data.php'), function (error, stdout, stderr) {
|
|
var 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("News updated.");
|
|
|
|
console.log("Writing new `index.html` file...");
|
|
fs.writeFileSync(path.resolve(rootDir, 'index.html'), indexContents);
|
|
console.log("File `index.html` successfully updated.");
|
|
});
|
|
}
|
|
|
|
function updateLearnsets (callback) {
|
|
var reservedKeywords = ['return']; // `return` is the only ES3+ reserved keyword that (currently) raises conflicts
|
|
var numberRegExp = new RegExp('^[0-9]*');
|
|
var reservedRegExp = new RegExp('^(' + reservedKeywords.join('|') + ')$', 'g');
|
|
var alphabetize = function (a, b) {return a.localeCompare(b);};
|
|
var getLsetGen = function (lset) {return lset.charAt(0);};
|
|
|
|
var padNumString = function (str) {
|
|
switch (str.length) {
|
|
case 1: return '00' + str;
|
|
case 2: return '0' + str;
|
|
case 3: return '' + str;
|
|
}
|
|
};
|
|
var inLearnset = function (lset, learnset) {
|
|
var secondChar = lset.charAt(1);
|
|
if (secondChar !== 'L') return learnset.indexOf(lset) >= 0;
|
|
|
|
var firstFragment = lset.substr(0, 2);
|
|
var levelFragment = lset.substr(2);
|
|
var paddedLevel = padNumString(levelFragment);
|
|
for (var i = 0, len = learnset.length; i < len; i++) {
|
|
if (learnset[i].substring(0, 2) !== firstFragment) continue;
|
|
// ignore PSdex starter moves sorting data
|
|
if (learnset[i].substring(2, 5) === paddedLevel) return true;
|
|
}
|
|
return false;
|
|
};
|
|
var formatLset = function (lset) {
|
|
var secondChar = lset.charAt(1);
|
|
if (secondChar !== 'L') return lset;
|
|
var firstFragment = lset.substr(0, 2);
|
|
var levelFragment = lset.substr(2).match(numberRegExp)[0];
|
|
var sortFragment = lset.substr(2 + levelFragment.length);
|
|
return firstFragment + padNumString(levelFragment) + sortFragment;
|
|
};
|
|
|
|
var Pokedex, Learnsets, oldLearnsetsG6, newLearnsetsG6;
|
|
newLearnsetsG6 = {};
|
|
try {
|
|
Pokedex = require(path.join(rootDir, 'data', 'pokedex.js')).BattlePokedex;
|
|
Learnsets = require(path.join(rootDir, 'data', 'learnsets.js')).BattleLearnsets;
|
|
} catch (err) {
|
|
return callback(err);
|
|
}
|
|
try {
|
|
oldLearnsetsG6 = require(path.join(rootDir, 'data', 'learnsets-g6.js')).BattleLearnsets;
|
|
} catch (err) {
|
|
if (err.code !== 'MODULE_NOT_FOUND') return callback(err);
|
|
oldLearnsetsG6 = {};
|
|
}
|
|
|
|
for (var speciesid in Learnsets) {
|
|
if (!oldLearnsetsG6[speciesid] || !oldLearnsetsG6[speciesid].learnset) {
|
|
console.log("NEW ENTRY at learnsets-g6.js: " + Pokedex[speciesid].species + ".");
|
|
oldLearnsetsG6[speciesid] = {learnset: {}};
|
|
}
|
|
}
|
|
for (var 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].species + ".");
|
|
continue;
|
|
}
|
|
var newLearnset = {};
|
|
var oldLearnset = oldLearnsetsG6[speciesid].learnset;
|
|
var fullLearnset = Learnsets[speciesid].learnset;
|
|
if (!fullLearnset) return callback(new TypeError("Invalid data at `learnsets.js` for " + speciesid + "."));
|
|
|
|
// clone
|
|
for (var moveid in oldLearnset) {
|
|
if (!Array.isArray(oldLearnset[moveid])) return callback(new TypeError("Invalid data at `learnsets-g6.js` for " + speciesid + ":" + moveid + "."));
|
|
newLearnset[moveid] = oldLearnset[moveid].slice();
|
|
}
|
|
|
|
for (var 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 (var i = 0, len = fullLearnset[moveid].length; i < len; i++) {
|
|
if (getLsetGen(fullLearnset[moveid][i]) !== '6') continue;
|
|
if (inLearnset(fullLearnset[moveid][i], newLearnset[moveid])) continue;
|
|
newLearnset[moveid].push(formatLset(fullLearnset[moveid][i]));
|
|
}
|
|
newLearnset[moveid].sort(alphabetize);
|
|
}
|
|
|
|
newLearnsetsG6[speciesid] = {learnset: newLearnset};
|
|
}
|
|
|
|
var buf = [];
|
|
var pokemonList = Object.values(Pokedex).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('species').map(toId);
|
|
|
|
for (var i = 0, len = pokemonList.length; i < len; i++) {
|
|
var entry = newLearnsetsG6[pokemonList[i]];
|
|
if (!entry || !entry.learnset) continue;
|
|
var 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 + '}');
|
|
}
|
|
|
|
var 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);
|
|
}
|
|
|
|
var pendingFiles = 0;
|
|
var 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 = indexStats.mtime.getTime();
|
|
} catch (err) {
|
|
throw err; // !!
|
|
}
|
|
|
|
// update learnsets-g6
|
|
console.log("Updating file `data/learnsets-g6`...");
|
|
|
|
pendingFiles++;
|
|
var learnsetsStats;
|
|
var learnsetsG6Stats;
|
|
var 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) {
|
|
var stack = err.stack || '';
|
|
stack = "File `data/learnsets-g6` failed to update.\n" + stack;
|
|
console.error(stack);
|
|
} else {
|
|
console.log("File `data/learnsets-g6` updated.");
|
|
}
|
|
if (!--pendingFiles) updateIndex();
|
|
});
|
|
} else {
|
|
console.log("Input files not modified. Task aborted.");
|
|
if (!--pendingFiles) updateIndex();
|
|
}
|