/** * Generator script which creates stub per-entity pages for SEO. */ import * as fs from "fs"; import "../js/parser.js"; import "../js/utils.js"; import "../js/utils-dataloader.js"; import "../js/render.js"; import "../js/render-dice.js"; import * as ut from "./util.js"; const IS_DEV_MODE = true; // !!process.env.VET_SEO_IS_DEV_MODE; // N.b.: disabled as all known deployments are "dev" const BASE_SITE_URL = process.env.VET_BASE_SITE_URL || "https://5e.tools/"; const isSkipUaEtc = !!process.env.VET_SEO_IS_SKIP_UA_ETC; const isOnlyVanilla = !!process.env.VET_SEO_IS_ONLY_VANILLA; const version = ut.readJson("package.json").version; const lastMod = (() => { const date = new Date(); return `${date.getFullYear()}-${`${date.getMonth() + 1}`.padStart(2, "0")}-${`${date.getDate()}`.padStart(2, "0")}`; })(); const baseSitemapData = (() => { const out = {}; // Scrape all the links from navigation.js -- avoid any unofficial HTML files which might exist const navText = fs.readFileSync("./js/navigation.js", "utf-8"); navText.replace(/(?:"([^"]+\.html)"|'([^']+)\.html'|`([^`]+)\.html`)/gi, (...m) => { const str = m[1] || m[2] || m[3]; if (str.includes("${")) return; out[str] = true; }); return out; })(); const getTemplate = (page, source, hash, textStyle, isFluff) => ` 5etools
Loading...
`; const getTemplateDev = (page, source, hash, textStyle, isFluff) => ` 5etools
Loading...
`; const toGenerate = [ { page: "spells", pGetEntries: () => { const index = ut.readJson(`data/spells/index.json`); const fileData = Object.entries(index) .filter(([source]) => !isSkipUaEtc || !SourceUtil.isNonstandardSourceWotc(source)) .filter(([source]) => !isOnlyVanilla || Parser.SOURCES_VANILLA.has(source)) .map(([_, filename]) => ut.readJson(`data/spells/${filename}`)); return fileData.map(it => MiscUtil.copy(it.spell)).reduce((a, b) => a.concat(b)); }, style: 1, isFluff: 1, }, { page: "bestiary", pGetEntries: () => { const index = ut.readJson(`data/bestiary/index.json`); const fileData = Object.entries(index) .filter(([source]) => !isSkipUaEtc || !SourceUtil.isNonstandardSourceWotc(source)) .filter(([source]) => !isOnlyVanilla || Parser.SOURCES_VANILLA.has(source)) .map(([source, filename]) => ({source: source, json: ut.readJson(`data/bestiary/${filename}`)})); // Filter to prevent duplicates from "otherSources" copies return fileData.map(it => MiscUtil.copy(it.json.monster.filter(mon => mon.source === it.source))).reduce((a, b) => a.concat(b)); }, style: 2, isFluff: 1, }, { page: "items", pGetEntries: async () => { const out = (await Renderer.item.pBuildList()).filter(it => !it._isItemGroup); return out .filter(it => !isSkipUaEtc || !SourceUtil.isNonstandardSourceWotc(it.source)) .filter(it => !isOnlyVanilla || Parser.SOURCES_VANILLA.has(it.source)); }, style: 1, isFluff: 1, }, // TODO expand this as required ]; const siteMapData = {}; async function main () { ut.patchLoadJson(); let total = 0; console.log(`Generating SEO pages...`); await Promise.all(toGenerate.map(async meta => { try { fs.mkdirSync(`./${meta.page}`, { recursive: true }); } catch (err) { if (err.code !== "EEXIST") throw err; } const entries = await meta.pGetEntries(); const builder = UrlUtil.URL_TO_HASH_BUILDER[`${meta.page}.html`]; entries.forEach(ent => { let offset = 0; let html; let path; while (true) { const hash = builder(ent); const sluggedHash = UrlUtil.getSluggedHash(hash); path = `${meta.page}/${sluggedHash}${offset ? `-${offset}` : ""}.html`; if (siteMapData[path]) { ++offset; continue; } html = (IS_DEV_MODE ? getTemplateDev : getTemplate)(meta.page, ent.source, hash, meta.style, meta.isFluff); siteMapData[path] = true; break; } if (offset > 0) console.warn(`\tDeduplicated URL using suffix: ${path}`); fs.writeFileSync(`./${path}`, html, "utf-8"); total++; if (total % 100 === 0) console.log(`Wrote ${total} files...`); }); })); console.log(`Wrote ${total} files.`); let sitemapLinkCount = 0; let sitemap = `\n`; sitemap += `\n`; sitemap += ` ${BASE_SITE_URL} ${lastMod} monthly \n`; sitemapLinkCount++; Object.keys(baseSitemapData).forEach(url => { sitemap += ` ${BASE_SITE_URL}${url} ${lastMod} monthly \n`; sitemapLinkCount++; }); Object.keys(siteMapData).forEach(url => { sitemap += ` ${BASE_SITE_URL}${url} ${lastMod} weekly \n`; sitemapLinkCount++; }); sitemap += `\n`; fs.writeFileSync("./sitemap.xml", sitemap, "utf-8"); console.log(`Wrote ${sitemapLinkCount.toLocaleString()} URL${sitemapLinkCount === 1 ? "" : "s"} to sitemap.xml`); ut.unpatchLoadJson(); } main().then(() => console.log(`SEO page generation complete.`)).catch(e => console.error(e));