Give public pages crawler-ready HTML and production analytics.
Unit Tests / test (pull_request) Successful in 37s
Unit Tests / test (pull_request) Successful in 37s
Crawlers were reading one shared shell, so titles, descriptions, links, and markup were missing or the same on every URL. HeyCatch and Google Analytics load only on the production site and the iOS and Android apps. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
1 parent
8b58192026
commit
f9d39f6589
45 files changed
+1880
-174
No files matched your search
@@ -0,0 +1,153 @@
|
||||
/**
|
||||
* 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) => ` <url>
|
||||
<loc>${entry.loc}</loc>
|
||||
<lastmod>${entry.lastmod}</lastmod>
|
||||
</url>`,
|
||||
)
|
||||
.join('\n');
|
||||
return `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${urls}
|
||||
</urlset>
|
||||
`;
|
||||
}
|
||||
|
||||
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,
|
||||
};
|
||||
Reference in new issue
Block a user