import fs from "node:fs"; import { APP_DESCRIPTIONS, APP_NAMES, AppKey, APPS } from "../src/config/apps.config"; import { ANSI } from "../src/config/apps/terminal.config"; import { NAME, TAG_LINE } from "../src/config/branding.config"; import { WALLPAPERS } from "../src/config/desktop.config"; import { BASE_URL, BUILD_DIR, DOMAIN } from "../src/config/deploy.config"; const PATHS = { sitemapXml: BUILD_DIR + "/sitemap.xml", robotsTxt: BUILD_DIR + "/robots.txt", indexHtml: BUILD_DIR + "/index.html", cname: BUILD_DIR + "/CNAME", }; function generateSitemapXml() { const date = new Date(); const lastModified = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`; const images = WALLPAPERS.map((path) => ` ${BASE_URL.slice(0, -1) + path} ` ); const pages = Object.values(APPS).map((appId) => ` ${BASE_URL + appId} ${lastModified} ` ); const sitemap = ` ${BASE_URL} ${lastModified} ${images.join("")} ${pages.join("")} `; return sitemap.trim(); } function generateRobotsTxt() { const sitemapUrl = BASE_URL + PATHS.sitemapXml.replace(BUILD_DIR + "/", ""); return `# https://www.robotstxt.org/robotstxt.html User-agent: * Disallow: Sitemap: ${sitemapUrl}`; } function generateCname() { return DOMAIN; } function generateTemplate(html: string) { const baseUrlRegex = /(?<=")https?:\/\/[a-z0-9-]+(\.)([a-zA-Z0-9-]+(\.))*[a-z]{2,3}\/(?=.*")/gi; html = html.replaceAll(baseUrlRegex, BASE_URL); const commentsRegex = //g; html = html.replaceAll(commentsRegex, ""); const emptyLinesRegex = /^\s*$\n/gm; html = html.replaceAll(emptyLinesRegex, ""); const path = `${BUILD_DIR}/index.html`; fs.writeFileSync(path, html, { flag: "w+" }); console.log(`- ${ANSI.fg.cyan}${path}${ANSI.reset}`); return html; } /** * To avoid GitHub pages rendering certain pages that are only defined by React router as a 404 page, * we copy the content of our index file to the 404 page, letting React router properly handle routing on every page */ function generate404Page(template: string) { const path = `${BUILD_DIR}/404.html`; fs.writeFileSync(path, template, { flag: "w+" }); console.log(`- ${ANSI.fg.cyan}${path}${ANSI.reset}`); } /** * Add an HTML file for every app page so they can be properly crawled and indexed */ function generateAppPages(template: string) { for (const [key, value] of Object.entries(APPS)) { const appId = value; const appName = key in APP_NAMES ? APP_NAMES[key as AppKey] as string : appId; const appDescription = Object.keys(APP_DESCRIPTIONS).includes(key) ? APP_DESCRIPTIONS[key as AppKey] as string : TAG_LINE; if (appId === "index") { console.log("Invalid app ID found: " + appId); return; } let html = template; const titleRegex = /(?<=(|<meta property="og:title" content="|<meta name="twitter:title" content="))(([a-zA-Z|-]|\s)+)(?=(<\/title>|"\/?>))/g; html = html.replaceAll(titleRegex, `${appName} | ${NAME}`); const descriptionRegex = /(?<=(<meta name="description" content="|<meta property="og:description" content="|<meta name="twitter:description" content="))(([a-zA-Z-.]|\s)+)(?=("\/?>))/g; html = html.replaceAll(descriptionRegex, appDescription); const canonicalRegex = /(?<=(<link rel="canonical" href="|<meta name="twitter:url" content="|<meta property="og:url" content="))(http(s)?:\/\/[a-zA-Z-.]+\/)(?=("\/?>))/g; html = html.replaceAll(canonicalRegex, BASE_URL + appId); const faqRegex = /<!-- FAQ -->.*?<script type="application\/ld\+json">.*?<\/script>/gs; html = html.replaceAll(faqRegex, ""); const path = `${BUILD_DIR}/${appId}.html`; fs.writeFileSync(path, html, { flag: "w+" }); console.log(`- ${ANSI.fg.cyan}${path}${ANSI.reset}`); } } function stage() { try { console.log(`${ANSI.fg.yellow}Staging build...${ANSI.reset}`); const files: [string, () => string][] = [ [PATHS.sitemapXml, generateSitemapXml], [PATHS.robotsTxt, generateRobotsTxt], [PATHS.cname, generateCname], ]; files.forEach(([path, generateContent]) => { const directory = path.substring(0, path.lastIndexOf("/")); if (directory != "" && !fs.existsSync(directory)){ fs.mkdirSync(directory, { recursive: true }); } fs.writeFileSync(path, generateContent(), { flag: "w+" }); console.log(`- ${ANSI.fg.cyan}${path}${ANSI.reset}`); }); console.log(`\n${ANSI.fg.yellow}Generating pages...${ANSI.reset}`); const html = fs.readFileSync(PATHS.indexHtml, "utf-8"); const template = generateTemplate(html); generate404Page(template); generateAppPages(template); console.log(`\n${ANSI.fg.green}✓ Staging complete${ANSI.reset}`); } catch (error) { console.error(error); console.log(`${ANSI.fg.red}⚠ Staging failed${ANSI.reset}`); process.exit(1); } } stage();