astro check restored: tsconfig excludes public/ (the 2 MB vendored CMS bundle was what exhausted the heap) and extends tsconfigs/strict; type annotations in template scripts, z from astro/zod; exact version pins; compressHTML true (lossless whitespace, no words glued to links) and explicit trailingSlash/build.format; README: upgrading section

This commit is contained in:
2026-09-06 15:12:56 +02:00
parent e8b47ac888
commit bd95bacfc5
10 changed files with 53 additions and 25 deletions
+2 -1
View File
@@ -29,4 +29,5 @@ presentation. Pages are `.astro` templates (plain HTML).
(astro.config.mjs) — the default minifier breaks `animation-timeline`.
- `--header-h` drives the sticky-header offsets (anchors, TOC rail, reading
progress); change the header height only through the token.
- Deterministic gate before pushing: `npm run check` (build + link check).
- Deterministic gate before pushing: `npm run check` (types + build + link
check, a few seconds).
+18 -1
View File
@@ -50,9 +50,12 @@ git clone https://astro-git.isl-dev.grid.cyfronet.pl/ctao/content.git ../ctao-co
./scripts/link-content.sh # copies news/pages/uploads into the dev tree
npm run dev # http://localhost:4321
npm run build # static output in dist/
npm run check # deterministic gate: build + internal-link check
npm run check # deterministic gate: astro check (types) + build + internal-link check
```
`tsconfig.json` excludes `public/` on purpose: the vendored CMS bundle there is
2 MB of minified JS and would make `astro check` run out of memory.
## Layout
| Path | What |
@@ -76,3 +79,17 @@ Saving commits to `main`; the machine polls and republishes automatically
Note for styling work: `public/admin/preview.css` mirrors the `.prose` rules
from `global.css` so the editor preview matches the site 1:1 — keep them in
sync (both files carry a KEEP IN SYNC comment).
## Upgrading
Versions are pinned exactly; an upgrade is a deliberate change, never a side
effect of `npm install`. Astro ships security fixes only for the current and
one previous major, so plan one upgrade per major rather than skipping several.
```
npx @astrojs/upgrade # moves astro and official integrations together
npm run check # types + build + links must pass
npm run preview # verify the real dist/ output, not the dev server
```
No experimental flags in `astro.config.mjs`: they can change in minor releases.
+8
View File
@@ -6,6 +6,14 @@ export default defineConfig({
// Public origin — used to build absolute URLs (canonical, og:*, RSS,
// sitemap). Change when the portal moves to its production domain.
site: 'https://astro.isl-dev.grid.cyfronet.pl',
// URL contract, pinned explicitly (these are the defaults): changing either
// later rewrites every public URL. nginx serves /path/ via try_files $uri/.
trailingSlash: 'ignore',
build: { format: 'directory' },
// Lossless whitespace handling instead of the v7 default 'jsx', which drops
// line breaks around inline elements and silently glues words to links when
// a template line wraps before <a>. Templates render as written.
compressHTML: true,
// Built-in prefetch on every internal link (no per-link attributes needed).
// Default 'hover' strategy: near-instant navigation without the bandwidth
// cost of 'viewport' on a 24-card grid; auto-falls back to 'tap' on
+3 -3
View File
@@ -8,11 +8,11 @@
"name": "ctao-portal",
"version": "0.1.0",
"dependencies": {
"astro": "^7.2.10"
"astro": "7.2.10"
},
"devDependencies": {
"@astrojs/check": "^0.9.10",
"typescript": "^6.0.3"
"@astrojs/check": "0.9.10",
"typescript": "6.0.3"
}
},
"node_modules/@astrojs/check": {
+4 -4
View File
@@ -7,13 +7,13 @@
"dev": "astro dev",
"build": "astro build",
"preview": "astro preview",
"check": "astro build && node scripts/check-links.mjs"
"check": "astro check && astro build && node scripts/check-links.mjs"
},
"dependencies": {
"astro": "^7.2.10"
"astro": "7.2.10"
},
"devDependencies": {
"@astrojs/check": "^0.9.10",
"typescript": "^6.0.3"
"@astrojs/check": "0.9.10",
"typescript": "6.0.3"
}
}
+2 -1
View File
@@ -1,4 +1,5 @@
import { defineCollection, z } from 'astro:content';
import { defineCollection } from 'astro:content';
import { z } from 'astro/zod';
import { glob } from 'astro/loaders';
// News/articles live as Markdown files in src/content/news/*.md.
+1 -1
View File
@@ -11,7 +11,7 @@ const canonical = new URL(Astro.url.pathname, site);
// LinkedIn) don't render SVG, so those articles fall back to the brand card.
const ogImage = new URL(image && !image.endsWith('.svg') ? image : '/brand/og-card.jpg', site);
const path = Astro.url.pathname;
const isActive = (href) => (href === '/' ? path === '/' : path.startsWith(href));
const isActive = (href: string) => (href === '/' ? path === '/' : path.startsWith(href));
// Navigation = services from the Science Portal spec (REQUIREMENTS.md §2/§5).
// Order: serial-position effect (NN/g) — News (universal entry) first, Support
// (help convention) last; Data/Proposals by task frequency; Dashboard is the
+2 -2
View File
@@ -16,7 +16,7 @@ export async function getStaticPaths() {
const chunks = [posts.slice(0, FIRST)];
for (let i = FIRST; i < posts.length; i += REST) chunks.push(posts.slice(i, i + REST));
const lastPage = chunks.length;
const hrefOf = (n) => (n === 1 ? '/news' : `/news/${n}`);
const hrefOf = (n: number) => (n === 1 ? '/news' : `/news/${n}`);
return chunks.map((data, i) => {
const n = i + 1;
return {
@@ -42,7 +42,7 @@ for (let n = 1; n <= page.lastPage; n++) {
if (n === 1 || n === page.lastPage || Math.abs(n - page.currentPage) <= 1) nums.push(n);
else if (nums.at(-1) !== 0) nums.push(0);
}
const hrefFor = (n) => (n === 1 ? '/news' : `/news/${n}`);
const hrefFor = (n: number) => (n === 1 ? '/news' : `/news/${n}`);
---
<Base
title={first ? 'CTAO Science Portal - News' : `CTAO Science Portal - News, page ${page.currentPage}`}
+11 -10
View File
@@ -96,10 +96,10 @@ const readMin = readMinOf(post);
// enters it is mapped back to its governing h2/h3, whose TOC link(s) get
// aria-current="true" (set in BOTH renderings — inline <details> and rail).
// Progressive enhancement: without JS the TOC is plain working anchors.
const links = [...document.querySelectorAll('.toc a[href^="#"]')];
const links = [...document.querySelectorAll<HTMLAnchorElement>('.toc a[href^="#"]')];
if (links.length) {
let current = [];
const setCurrent = (id) => {
let current: HTMLAnchorElement[] = [];
const setCurrent = (id: string | null) => {
const next = id ? links.filter((a) => decodeURIComponent(a.hash.slice(1)) === id) : [];
if (next[0] === current[0]) return;
for (const a of current) a.removeAttribute('aria-current');
@@ -108,35 +108,36 @@ const readMin = readMinOf(post);
};
const blocks = [...document.querySelectorAll('.prose > *')];
// Governing heading = the block itself or the nearest h2/h3[id] above it
const headingFor = (el) => {
const headingFor = (el: Element) => {
for (let i = blocks.indexOf(el); i >= 0; i--) {
if (blocks[i].matches('h2[id], h3[id]')) return blocks[i].id;
}
return null; // intro before the first heading — nothing highlighted
};
const onIntersect = (entries) => {
const onIntersect = (entries: IntersectionObserverEntry[]) => {
for (const e of entries) {
if (e.isIntersecting) { setCurrent(headingFor(e.target)); break; }
}
};
let observer;
let observer: IntersectionObserver | undefined;
const observe = () => {
observer?.disconnect();
// Narrow band below the sticky header (--header-h 56px + 12px slack;
// JS can't read the token cheaply, keep in sync) — Starlight's rootMargin trick
const top = 68, band = 64;
observer = new IntersectionObserver(onIntersect, {
const io = new IntersectionObserver(onIntersect, {
rootMargin: `-${top}px 0px ${top + band - document.documentElement.clientHeight}px`,
});
blocks.forEach((b) => observer.observe(b));
observer = io;
blocks.forEach((b) => io.observe(b));
};
observe();
let timer;
let timer: ReturnType<typeof setTimeout> | undefined;
addEventListener('resize', () => { clearTimeout(timer); timer = setTimeout(observe, 200); });
// Short final section: at page bottom, the last heading wins
addEventListener('scroll', () => {
if (innerHeight + scrollY >= document.documentElement.scrollHeight - 4) {
setCurrent(headingFor(blocks.at(-1)));
setCurrent(headingFor(blocks[blocks.length - 1]));
}
}, { passive: true });
}
+2 -2
View File
@@ -1,5 +1,5 @@
{
"extends": "astro/tsconfigs/base",
"extends": "astro/tsconfigs/strict",
"include": [".astro/types.d.ts", "**/*"],
"exclude": ["dist"]
"exclude": ["dist", "public"]
}