2026-03-24 05:01:11 +07:00
|
|
|
/**
|
|
|
|
|
* Auto-generates public/sitemap.xml from seoRoutes.ts
|
|
|
|
|
* Run: npx tsx scripts/generate-sitemap.ts
|
|
|
|
|
* Integrated into: npm run build (via generate:sitemap)
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
import { writeFileSync } from 'fs';
|
|
|
|
|
import { resolve, dirname } from 'path';
|
|
|
|
|
import { fileURLToPath } from 'url';
|
|
|
|
|
|
|
|
|
|
// Import the single source of truth
|
|
|
|
|
import { SEO_ROUTES } from '../src/seoRoutes';
|
|
|
|
|
|
|
|
|
|
const __dirname_resolved = dirname(fileURLToPath(import.meta.url));
|
|
|
|
|
|
|
|
|
|
const DOMAIN = 'https://velxio.dev';
|
|
|
|
|
const TODAY = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
|
|
|
|
|
|
refactor(routes): OSS is the editor; marketing moves to the overlay
The open-source build now ships exactly its product surface: /editor, the
examples gallery (/examples, /examples/:id) and the per-example editor
(/example/:id). Landing, about, pricing, docs, the 14 keyword-targeted
simulator landings and the v2/v2.5/v3 showcases move to the private
overlay, registered through the same registerProRoutes seam that already
carries login/admin/classroom.
Root behaves per build: an overlay that registers an index route claims
'/' (velxio.dev keeps its landing); otherwise '/' redirects to /editor.
The redirect waits for the overlay import to settle — same contract as
markProExamplesSettled — so a velxio.dev visitor is never bounced into
the editor because the landing was 300ms away from registering.
Prerender and sitemap follow the split: entry-server pulls the marketing
page map from '@pro/pages/marketing' behind a VITE_PRO_BUILD-gated dynamic
import (the proven main.tsx pattern, with an OSS stub for tsc), and
generate-sitemap lists only served routes in OSS builds (2 URLs) while
pro builds stay byte-identical — verified against a pre-migration
baseline: sitemap (37 URLs) and prerendered /, /about, /docs,
/arduino-simulator, /esp32-simulator, /v3 all identical modulo hashed
asset names. OSS prerender drops exactly the 32 marketing pages (348→316).
The OSS header slims down to match: Editor, Examples, GitHub, Discord —
the marketing links only render in pro builds, where their routes exist.
The editor Help menu links those pages absolutely (velxio.dev) in OSS,
exactly like the desktop app's Help menu.
2026-07-30 22:50:14 +07:00
|
|
|
// The OSS build serves only the editor surface; its sitemap must not list
|
|
|
|
|
// marketing URLs that 404 on a self-hosted instance. Pro builds (velxio.dev)
|
|
|
|
|
// list everything, exactly as before.
|
|
|
|
|
const OSS_SERVED = new Set(['/editor', '/examples']);
|
|
|
|
|
const isProBuild = !!process.env.VITE_PRO_BUILD;
|
|
|
|
|
const indexableRoutes = SEO_ROUTES.filter(
|
|
|
|
|
(r) => !r.noindex && (isProBuild || OSS_SERVED.has(r.path)),
|
|
|
|
|
);
|
2026-03-24 05:01:11 +07:00
|
|
|
|
|
|
|
|
const xml = `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
|
|
|
|
|
xmlns:xhtml="http://www.w3.org/1999/xhtml">
|
|
|
|
|
${indexableRoutes
|
|
|
|
|
.map(
|
|
|
|
|
(r) => `
|
|
|
|
|
<url>
|
|
|
|
|
<loc>${DOMAIN}${r.path}</loc>
|
|
|
|
|
<lastmod>${TODAY}</lastmod>
|
|
|
|
|
<changefreq>${r.changefreq ?? 'monthly'}</changefreq>
|
|
|
|
|
<priority>${r.priority ?? 0.5}</priority>
|
|
|
|
|
</url>`
|
|
|
|
|
)
|
|
|
|
|
.join('')}
|
|
|
|
|
|
|
|
|
|
</urlset>
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const outPath = resolve(__dirname_resolved, '../public/sitemap.xml');
|
|
|
|
|
writeFileSync(outPath, xml.trimStart(), 'utf-8');
|
|
|
|
|
console.log(`sitemap.xml generated → ${indexableRoutes.length} URLs (${TODAY})`);
|
|
|
|
|
|
|
|
|
|
// Also ping Google & Bing sitemap endpoints (non-blocking, best-effort)
|
|
|
|
|
const SITEMAP_URL = `${DOMAIN}/sitemap.xml`;
|
|
|
|
|
const PING_URLS = [
|
|
|
|
|
`https://www.google.com/ping?sitemap=${encodeURIComponent(SITEMAP_URL)}`,
|
|
|
|
|
`https://www.bing.com/ping?sitemap=${encodeURIComponent(SITEMAP_URL)}`,
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
if (process.argv.includes('--ping')) {
|
|
|
|
|
console.log('Pinging search engines...');
|
|
|
|
|
Promise.allSettled(
|
|
|
|
|
PING_URLS.map(async (url) => {
|
|
|
|
|
try {
|
|
|
|
|
const res = await fetch(url);
|
|
|
|
|
console.log(` ${res.ok ? 'OK' : 'FAIL'} ${url.split('?')[0]}`);
|
|
|
|
|
} catch (e: any) {
|
|
|
|
|
console.log(` FAIL ${url.split('?')[0]}: ${e.message}`);
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
);
|
|
|
|
|
}
|