CTAO Science Portal: static Astro site with git-based CMS (Sveltia + Gitea)

Portal code only; editorial content lives in the ctao/content repo on the
demo machine's Gitea and is overlaid at build time (see README.md and
deploy/README.md). Live demo: https://astro.isl-dev.grid.cyfronet.pl
This commit is contained in:
2026-09-08 13:53:30 +02:00
parent 845c1ccf0d
commit 0772462742
50 changed files with 11564 additions and 44 deletions
+38
View File
@@ -0,0 +1,38 @@
// Deterministic internal-link check over the built site (zero dependencies).
// Run AFTER `astro build`: scans dist/**/*.html for root-relative href/src
// values and fails (exit 1) if any target has no file in dist/. External
// URLs, mailto:, data: and pure-#fragment links are out of scope.
import { readdirSync, readFileSync, existsSync, statSync } from 'node:fs';
import { join } from 'node:path';
const DIST = new URL('../dist', import.meta.url).pathname;
if (!existsSync(DIST)) { console.error('dist/ not found — run `npm run build` first'); process.exit(1); }
const htmlFiles = [];
(function walk(dir) {
for (const name of readdirSync(dir)) {
const p = join(dir, name);
if (statSync(p).isDirectory()) walk(p);
else if (name.endsWith('.html')) htmlFiles.push(p);
}
})(DIST);
const resolves = (path) => {
const clean = decodeURI(path.split(/[?#]/)[0]);
if (clean === '/') return true;
return ['', '.html', '/index.html'].some((suffix) =>
existsSync(join(DIST, clean.replace(/\/$/, '') + suffix)));
};
let broken = 0;
for (const file of htmlFiles) {
const html = readFileSync(file, 'utf8');
for (const [, , url] of html.matchAll(/\s(href|src)="(\/[^"]*)"/g)) {
if (!resolves(url)) {
console.error(`broken: ${url} (in ${file.slice(DIST.length + 1)})`);
broken++;
}
}
}
console.log(broken ? `${broken} broken internal link(s)` : `OK — ${htmlFiles.length} pages, all internal links resolve`);
process.exit(broken ? 1 : 0);