100 lines
3.1 KiB
JavaScript
100 lines
3.1 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Post-build: generate sitemap.xml from prerendered pages in dist/.
|
|
* Scans for index.html (root and under each path), excludes 410 paths.
|
|
* Run after vite build and critters, before copy-410-paths so 410 dirs don't exist yet.
|
|
*/
|
|
|
|
import fs from 'fs';
|
|
import path from 'path';
|
|
import { fileURLToPath } from 'url';
|
|
|
|
import { PATHS as PATHS_410 } from './410-paths.mjs';
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const DIST = path.join(__dirname, '..', 'dist');
|
|
|
|
/** Must match src/lib/seo.ts SEO_DEFAULTS.baseUrl */
|
|
const BASE_URL = 'https://mifi.ventures';
|
|
|
|
const EXCLUDE_FILES = new Set(['404.html', '410.html']);
|
|
const excludeSet = new Set(PATHS_410);
|
|
|
|
/**
|
|
* adapter-static emits path.html or path/index.html. Walk dist and collect
|
|
* every .html that represents a page (exclude 404/410 and 410-gone paths).
|
|
*/
|
|
function findPages(dir, basePath = '') {
|
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
const pages = [];
|
|
|
|
for (const e of entries) {
|
|
const rel = basePath ? `${basePath}/${e.name}` : e.name;
|
|
|
|
if (e.isDirectory()) {
|
|
const indexPath = path.join(dir, e.name, 'index.html');
|
|
if (fs.existsSync(indexPath)) {
|
|
if (!excludeSet.has(rel)) {
|
|
pages.push({ path: rel, indexPath });
|
|
}
|
|
} else {
|
|
pages.push(...findPages(path.join(dir, e.name), rel));
|
|
}
|
|
} else if (e.name.endsWith('.html') && !EXCLUDE_FILES.has(e.name)) {
|
|
const urlPath = e.name === 'index.html'
|
|
? basePath
|
|
: (basePath ? `${basePath}/${e.name.slice(0, -5)}` : e.name.slice(0, -5));
|
|
if (!excludeSet.has(urlPath)) {
|
|
pages.push({ path: urlPath, indexPath: path.join(dir, e.name) });
|
|
}
|
|
}
|
|
}
|
|
|
|
return pages;
|
|
}
|
|
|
|
function escapeXml(s) {
|
|
return s
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, ''');
|
|
}
|
|
|
|
function main() {
|
|
const indexHtml = path.join(DIST, 'index.html');
|
|
if (!fs.existsSync(indexHtml)) {
|
|
console.error('dist/index.html not found. Run build first.');
|
|
process.exit(1);
|
|
}
|
|
|
|
const pages = findPages(DIST).sort((a, b) => {
|
|
if (a.path === '') return -1;
|
|
if (b.path === '') return 1;
|
|
return a.path.localeCompare(b.path);
|
|
});
|
|
const urlElements = [];
|
|
|
|
for (const { path: pagePath, indexPath } of pages) {
|
|
const loc = pagePath ? `${BASE_URL}/${pagePath}` : BASE_URL;
|
|
const stat = fs.statSync(indexPath);
|
|
const lastmod = stat.mtime.toISOString().slice(0, 10);
|
|
|
|
urlElements.push(
|
|
` <url>\n <loc>${escapeXml(loc)}</loc>\n <lastmod>${lastmod}</lastmod>\n </url>`
|
|
);
|
|
}
|
|
|
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
${urlElements.join('\n')}
|
|
</urlset>
|
|
`;
|
|
|
|
fs.writeFileSync(path.join(DIST, 'sitemap.xml'), xml, 'utf8');
|
|
console.log('✓ sitemap.xml generated with', pages.length, 'URLs.');
|
|
}
|
|
|
|
main();
|