/** * Sitemap lastmod comes from the page source, not from the build clock. * Changelog uses the newest entry date. Terms use the "Last Updated" line. * Other public pages use the last git commit that touched that file. */ const { execFileSync } = require('child_process'); const fs = require('fs'); const path = require('path'); const ORIGIN = 'https://hesychia.ai'; const INDEXNOW_KEY = '8d1667dd71c6ef137171989c26eeb632'; const INDEXNOW_HOST = 'hesychia.ai'; const llmFe = path.resolve(__dirname, '..'); const repoRoot = path.resolve(llmFe, '..'); /** Public pages only. Same URLs the site already lists for crawlers. */ const SITEMAP_PAGES = [ { path: '/', file: 'llm-fe/src/llm-fe/pages/Landing/LandingPage.tsx', lastmod: 'git', }, { path: '/pricing/', file: 'llm-fe/src/llm-fe/pages/Pricing/PricingPage.tsx', lastmod: 'git', }, { path: '/privacy/', file: 'llm-fe/src/llm-fe/pages/Privacy/PrivacyPage.tsx', lastmod: 'git', }, { path: '/changelog/', file: 'llm-fe/src/llm-fe/pages/Changelog/changelogEntries.ts', lastmod: 'changelog', }, { path: '/terms_of_service/', file: 'llm-fe/src/llm-fe/components/legal/TermsOfServiceDocument.tsx', lastmod: 'terms', }, { path: '/account-deletion/', file: 'llm-fe/src/llm-fe/pages/AccountDeletion/AccountDeletionPage.tsx', lastmod: 'git', }, ]; const MONTHS = { January: '01', February: '02', March: '03', April: '04', May: '05', June: '06', July: '07', August: '08', September: '09', October: '10', November: '11', December: '12', }; function newestChangelogDate(source) { const dates = [...source.matchAll(/date:\s*'(\d{4}-\d{2}-\d{2})'/g)].map((match) => match[1]); if (dates.length === 0) { throw new Error('changelog entries have no dates'); } return dates.sort().at(-1); } function termsUpdated(source) { const match = source.match(/Last Updated:\s+([A-Z][a-z]+)\s+(\d{1,2}),\s+(\d{4})/); if (!match) { throw new Error('terms have no Last Updated date'); } const month = MONTHS[match[1]]; if (!month) { throw new Error(`unknown month in terms date: ${match[1]}`); } return `${match[3]}-${month}-${match[2].padStart(2, '0')}`; } function gitLastmod(file) { const out = execFileSync('git', ['log', '-1', '--format=%cs', '--', file], { cwd: repoRoot, encoding: 'utf8', }).trim(); if (!/^\d{4}-\d{2}-\d{2}$/.test(out)) { throw new Error(`no git commit date for ${file}`); } return out; } function lastmodFor(page) { const abs = path.join(repoRoot, page.file); const source = fs.readFileSync(abs, 'utf8'); if (page.lastmod === 'changelog') return newestChangelogDate(source); if (page.lastmod === 'terms') return termsUpdated(source); return gitLastmod(page.file); } function sitemapEntries() { return SITEMAP_PAGES.map((page) => ({ loc: `${ORIGIN}${page.path}`, lastmod: lastmodFor(page), })); } function renderSitemap(entries) { const urls = entries .map( (entry) => ` ${entry.loc} ${entry.lastmod} `, ) .join('\n'); return ` ${urls} `; } function writeSitemap() { const xml = renderSitemap(sitemapEntries()); fs.writeFileSync(path.join(llmFe, 'public', 'sitemap.xml'), xml); return xml; } function indexNowBody(key, urls) { return { host: INDEXNOW_HOST, key, keyLocation: `https://${INDEXNOW_HOST}/${key}.txt`, urlList: urls, }; } module.exports = { INDEXNOW_KEY, SITEMAP_PAGES, newestChangelogDate, termsUpdated, gitLastmod, sitemapEntries, renderSitemap, writeSitemap, indexNowBody, };