From bd95bacfc5504a475396141f01266723c2291a08 Mon Sep 17 00:00:00 2001 From: Mieszko Makuch Date: Sun, 6 Sep 2026 15:12:56 +0200 Subject: [PATCH] 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 --- DESIGN.md | 3 ++- README.md | 19 ++++++++++++++++++- astro.config.mjs | 8 ++++++++ package-lock.json | 6 +++--- package.json | 8 ++++---- src/content.config.ts | 3 ++- src/layouts/Base.astro | 2 +- src/pages/news/[...page].astro | 4 ++-- src/pages/news/[slug].astro | 21 +++++++++++---------- tsconfig.json | 4 ++-- 10 files changed, 53 insertions(+), 25 deletions(-) diff --git a/DESIGN.md b/DESIGN.md index 0a4990c..a140625 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -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). diff --git a/README.md b/README.md index 58d3a94..0716403 100644 --- a/README.md +++ b/README.md @@ -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. diff --git a/astro.config.mjs b/astro.config.mjs index 479fa62..34ffb95 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -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 . 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 diff --git a/package-lock.json b/package-lock.json index df1e087..9b107e9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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": { diff --git a/package.json b/package.json index dfe0140..0d1ec3a 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/content.config.ts b/src/content.config.ts index b620408..d0c0922 100644 --- a/src/content.config.ts +++ b/src/content.config.ts @@ -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. diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index 5ff67bc..14b7317 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -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 diff --git a/src/pages/news/[...page].astro b/src/pages/news/[...page].astro index 5b26a48..3df5590 100644 --- a/src/pages/news/[...page].astro +++ b/src/pages/news/[...page].astro @@ -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}`); --- and rail). // Progressive enhancement: without JS the TOC is plain working anchors. - const links = [...document.querySelectorAll('.toc a[href^="#"]')]; + const links = [...document.querySelectorAll('.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 | 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 }); } diff --git a/tsconfig.json b/tsconfig.json index f11a46c..36d027d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,5 +1,5 @@ { - "extends": "astro/tsconfigs/base", + "extends": "astro/tsconfigs/strict", "include": [".astro/types.d.ts", "**/*"], - "exclude": ["dist"] + "exclude": ["dist", "public"] }