const fs = require('fs').promises; const fsConstants = require('fs').constants; const buildCommon = require('./build/utils/build-common.js'); const PullClassifier = require('./build/utils/publish-classifier.js') async function publishData() { const classifier = new PullClassifier(); try { console.log("[*] Copying pre-generated data files."); const sourcePath = "./data"; const targetPath = "./out/data"; await buildCommon.ensureDir(targetPath); const pathStat = await fs.stat(sourcePath); if (!pathStat.isDirectory()) { return; } let file_count = 0; const sourceFiles = await fs.readdir(sourcePath); for (let sourceName of sourceFiles) { if (sourceName === "." || sourceName === ".." || !sourceName.endsWith(".json")) { continue; } const entryPath = `${sourcePath}/${sourceName}`; const entryStat = await fs.stat(entryPath); if (!entryStat.isFile()) { continue; } // Resave the data entry without extra spaces as a way to compress it a bit. // We also do some pre-processing to speed up processing on the front end at // a cost of a slightly bigger download. await fs.access(entryPath, fsConstants.R_OK); const entryContent = await fs.readFile(entryPath, { encoding: 'utf-8' }); const entry = JSON.parse(entryContent); // Classify and clean-up pull requests. Object.keys(entry.pulls).forEach((pullKey) => { const pull = entry.pulls[pullKey]; pull.group_key = classifier.determineGroup(pull); pull.group_name = classifier.humanizeGroup(pull.group_key); pull.title = classifier.cleanupTitle(pull); }); const copyPath = `${targetPath}/${sourceName}`; await fs.writeFile(copyPath, JSON.stringify(entry), { encoding: 'utf-8' }); console.log(` Published data from ${sourceName}.`); file_count++; } console.log(` Published ${file_count} data files.`); } catch (err) { console.error(` Error publishing data files: ` + err); process.exitCode = buildCommon.ExitCodes.IOFailure; return; } } async function publishConfigs() { try { console.log("[*] Reading existing version configs."); const sourcePath = "./configs"; const pathStat = await fs.stat(sourcePath); if (!pathStat.isDirectory()) { return; } // Read all configs and store them in a collection. let loadedConfigs = {}; let config_count = 0; const sourceFiles = await fs.readdir(sourcePath); for (let sourceName of sourceFiles) { if (sourceName === "." || sourceName === ".." || !sourceName.endsWith(".json")) { continue; } const nameBits = sourceName.substring(0, sourceName.length - 5).split(".", 3); if (nameBits.length !== 3) { continue; } const configPath = `${sourcePath}/${sourceName}`; const entryStat = await fs.stat(configPath); if (!entryStat.isFile()) { continue; } await fs.access(configPath, fsConstants.R_OK); const configContent = await fs.readFile(configPath, { encoding: 'utf-8' }); const config = JSON.parse(configContent); const [ config_owner, config_repo ] = nameBits; if (typeof loadedConfigs[config_owner] === "undefined") { loadedConfigs[config_owner] = {}; } if (typeof loadedConfigs[config_owner][config_repo] === "undefined") { loadedConfigs[config_owner][config_repo] = {}; } loadedConfigs[config_owner][config_repo][config.name] = config; config_count++; } console.log(` Found ${config_count} config files.`); console.log("[*] Saving unified version configs."); const targetPath = "./out/data"; await buildCommon.ensureDir(targetPath); // Comparing strings is kind of meh, so we turn them into numeric values. const envalueVersion = (versionString) => { let value = 0; const versionBits = versionString.split("."); while (versionBits.length < 4) { // Make sure we have exactly 4 values in the version. Normally Godot // versions are either X.Y or X.Y.Z, but there are exceptions, so we // normalize the size to match them. versionBits.push("0"); } let i = 0; while (versionBits.length > 0) { value += parseInt(versionBits.pop(), 10) * Math.pow(100, i); i++; } return value; }; // Iterate through loaded configs, sort them, and write them to a unified // file. for (let config_owner in loadedConfigs) { for (let config_repo in loadedConfigs[config_owner]) { const versions = loadedConfigs[config_owner][config_repo]; const versionNumbers = Object.keys(versions); versionNumbers.sort((a,b) => { const av = envalueVersion(a); const bv = envalueVersion(b); if (av > bv) return -1; if (bv > av) return 1; return 0; }); let configBundle = []; for (let versionNumber of versionNumbers) { versions[versionNumber].releases.reverse(); configBundle.push(versions[versionNumber]); } await fs.writeFile(`./out/data/${config_owner}.${config_repo}.versions.json`, JSON.stringify(configBundle), {encoding: "utf-8"}) console.log(` Published version config for "${config_owner}/${config_repo}".`); } } } catch (err) { console.error(` Error publishing version configs: ` + err); process.exitCode = buildCommon.ExitCodes.IOFailure; return; } } async function main() { // Internal utility methods. const checkForExit = () => { if (process.exitCode > 0) { process.exit(); } }; console.log("[*] Publishing local commit and pull request database."); await buildCommon.ensureDir("./out"); // First, we copy all data files that are available to the output directory. await publishData(); checkForExit(); // Second, we load every version config, group them by the data source and // publish a unified single-file version for each owner and repo. await publishConfigs(); checkForExit(); console.log("[*] Database published."); } main();