From 1d8417e7b1b8bc05acf9707bd7ee52a66f7cf288 Mon Sep 17 00:00:00 2001 From: ElianCodes Date: Sat, 26 Sep 2026 22:14:18 +0200 Subject: [PATCH] chore: add CI, a drift check, a licence and contributor guidance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This repo had no .github/, no CI and no LICENSE — alone among the Coral repos. A broken cross-link or bad frontmatter shipped unnoticed. - ci.yml builds and checks that every internal link resolves. Renaming a page used to quietly take its inbound links with it. - env-drift.yml reads each module's .env.example straight from its repo and fails when a variable is missing from the matching docs page. This is the check that would have caught LIBRARIAN_REQUIRE_LOGIN. It runs weekly as well as on PRs, because upstream drifts without anything changing here. On its first run it found AURORA_STREAM_TOKEN_SECRET, added on aurora@main and undocumented. Deliberately one-directional: docs legitimately describe variables that code reads but .env.example omits, such as HOST and PORT. Variables declared upstream but read by nothing live in KNOWN_DEAD with a reason — documenting them would imply they work. - AGENTS.md records the rule that produced the content rewrite: verify every claim against the source repo, and where a README and the code disagree, the code wins. - LICENSE (MIT), matching the npm packages' declared licence. - styles.css: restores the code-block copy button, which was hidden on a site made almost entirely of install commands; drops the pinned Starlight hash class .header.astro-wh26sp3i, which silently stops matching on any upgrade; and removes the dead rules styling a theme switcher that is display:none. - Drops Tailwind. It was installed and configured but never imported — the stylesheet is hand-written CSS throughout. --- .github/workflows/ci.yml | 30 +++++ .github/workflows/env-drift.yml | 32 +++++ AGENTS.md | 97 ++++++++++++++ LICENSE | 21 +++ README.md | 21 ++- astro.config.mjs | 4 - package.json | 10 +- pnpm-lock.yaml | 227 +------------------------------- scripts/check-env-drift.mjs | 111 ++++++++++++++++ scripts/check-links.mjs | 76 +++++++++++ src/styles.css | 49 +------ 11 files changed, 395 insertions(+), 283 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 .github/workflows/env-drift.yml create mode 100644 AGENTS.md create mode 100644 LICENSE create mode 100644 scripts/check-env-drift.mjs create mode 100644 scripts/check-links.mjs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..9ebf20a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,30 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: pnpm/action-setup@v4 + with: + version: 10 + + - uses: actions/setup-node@v5 + with: + node-version: 24 + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - name: Build + run: pnpm build + + # A renamed page used to take six cross-links down with it, silently. + - name: Check internal links + run: pnpm check:links diff --git a/.github/workflows/env-drift.yml b/.github/workflows/env-drift.yml new file mode 100644 index 0000000..54ec519 --- /dev/null +++ b/.github/workflows/env-drift.yml @@ -0,0 +1,32 @@ +name: Environment drift + +# The module repos change daily and these docs did not, which is how Librarian +# shipped a sign-in requirement the docs never mentioned. This reads each +# module's .env.example straight from its repo and fails when a variable is +# missing here. + +on: + push: + branches: [main] + pull_request: + paths: + - 'src/content/docs/modules/**' + - 'scripts/check-env-drift.mjs' + - '.github/workflows/env-drift.yml' + # Upstream can drift without anything changing in this repo. + schedule: + - cron: '17 6 * * 1' + workflow_dispatch: + +jobs: + env-drift: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + + - uses: actions/setup-node@v5 + with: + node-version: 24 + + - name: Compare documented environment variables against each repo + run: node scripts/check-env-drift.mjs diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..61be005 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,97 @@ +# Working on these docs + +This is the documentation site for the Coral ecosystem, at +[docs.getcoral.dev](https://docs.getcoral.dev). Astro + Starlight, deployed to +Vercel. + +## The one rule + +**Verify every factual claim against the source repository before you write it.** + +These docs once described Encore as a working music-request app while its `src/` +was the unmodified template, listed six Librarian features that had no code +behind them at all, and omitted `LIBRARIAN_REQUIRE_LOGIN=true` — a default that +stops a first-run user dead. All of it read plausibly. None of it was true. + +Repos are at `github.com/Get-Coral/`, and usually checked out locally +alongside this one. Before documenting a feature, an environment variable or a +flag: + +```bash +grep -rn "FEATURE_OR_VAR" ..//src +``` + +If it is not in the code, it does not go in the docs. A README is evidence, not +proof — several of them are stale in ways the code is not. + +Where the two disagree, the code wins, and the README is worth an upstream +issue. + +## Division of labour with getcoral.dev + +- **getcoral.dev owns *why***: positioning, comparisons, what a module is for. +- **These docs own *how***: install, configure, operate, troubleshoot. + +A module page opens with one self-contained sentence saying what the thing is +and what it needs, then goes operational. Link the marketing page under +*Related* rather than restating its pitch. + +## Module status + +`src/lib/modules.ts` records a status per module — `shipping`, `early` or +`scaffold` — and it drives the JSON-LD, the OG cards and the homepage. Every +module page repeats it in an aside near the top. + +It exists so a reader can tell Aurora from Encore without cloning both. Keep it +honest: the repo changes first, this file follows. If a module gains a feature, +do not promote its status in anticipation. + +Do not document planned work as though it ships. If it matters, put it in the +status aside as an explicit "not built yet". + +## Page skeleton + +Module pages follow the same order, so a reader learns it once: + +1. One-sentence definition +2. Status aside (and any safety aside — auth defaults, data loss, cost) +3. Requirements +4. Running it (Docker first — that is how people actually deploy these) +5. Environment reference, as a table, marked *verified against `/.env.example`* +6. How it behaves — the parts that surprise people +7. From source +8. Related + +## Checks + +```bash +pnpm build +pnpm check:links # internal links resolve (needs a build first) +pnpm check:env # documented env vars match each repo's .env.example +``` + +`pnpm check:env` fetches `.env.example` from each module repo on GitHub and +fails when a variable is missing here. It is one-directional on purpose: docs +legitimately describe variables that code reads but `.env.example` omits, such +as `HOST` and `PORT`. + +Variables that are declared upstream but read by nothing live in `KNOWN_DEAD` in +`scripts/check-env-drift.mjs`, each with a reason. Documenting them would imply +they work. + +## SEO and structured data + +Handled centrally; you should not need to touch it for a content change. + +- `src/lib/site.ts` — URLs, names, `sameAs`. Nothing else should hardcode a domain. +- `src/lib/schema.ts` — the JSON-LD `@graph`. The `Organization` and `WebSite` + `@id`s deliberately point at `getcoral.dev` so both hosts resolve to **one** + entity rather than two with the same name. +- `src/components/Head.astro` — emits the graph, the OG tags and the fonts. +- `src/pages/[...slug]/og.png.ts` — one social card per page, rasterised at build. +- `src/pages/llms.txt.ts`, `llms-full.txt.ts`, `robots.txt.ts`, + `.well-known/api-catalog.ts` — machine-readable surfaces. + +Frontmatter `description` becomes the meta description, the OG description and +the `llms.txt` entry. Write it as a standalone sentence under ~155 characters — +not "Get started with X". diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..274cbf2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2026 Elian Van Cutsem + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 0d3a90b..f6d837c 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ pnpm install pnpm dev ``` -Accessible at `http://localhost:3000` +Accessible at `http://localhost:4321` ### Build for production @@ -101,11 +101,14 @@ vercel Documentation is organized into sections: -- **Getting Started** - Intro to Coral ecosystem -- **Modules** - Individual module guides (Aurora, Fathom, Librarian, KAPOW) -- **Libraries** - SDK and library documentation (Jellyfin API Client) +- **Getting Started** - Intro, the Docker Compose stack, the CLI, module contracts +- **Modules** - Aurora, Tide, Librarian, Fathom, Marquee, KAPOW!, Encore +- **Libraries** - Jellyfin API client, Coral UI, published npm packages - **Contributing** - How to contribute and build new modules +Before editing content, read [AGENTS.md](./AGENTS.md) — particularly the rule +about verifying claims against the source repositories. + ## Adding Content ### Create a new page @@ -145,9 +148,17 @@ sidebar: [ ![Alt text](../../assets/image.png) ``` +## Checks + +```bash +pnpm build +pnpm check:links # internal links resolve (run after a build) +pnpm check:env # documented env vars match each module repo's .env.example +``` + ## Styling -Uses Tailwind CSS v4 and Starlight's component library: +Uses Starlight's component library, themed through `src/styles.css`: ```markdown import { Card, CardGrid, Aside } from '@astrojs/starlight/components'; diff --git a/astro.config.mjs b/astro.config.mjs index 36fb09f..12540c6 100644 --- a/astro.config.mjs +++ b/astro.config.mjs @@ -1,14 +1,10 @@ // @ts-check import { defineConfig } from 'astro/config'; import starlight from '@astrojs/starlight'; -import tailwindcss from '@tailwindcss/vite'; // https://astro.build/config export default defineConfig({ site: 'https://docs.getcoral.dev', - vite: { - plugins: [tailwindcss()], - }, integrations: [ starlight({ title: 'Coral Docs', diff --git a/package.json b/package.json index 3c4820e..a9e4ef9 100644 --- a/package.json +++ b/package.json @@ -7,13 +7,13 @@ "start": "astro dev", "build": "astro build", "preview": "astro preview", - "astro": "astro" + "astro": "astro", + "check:links": "node scripts/check-links.mjs", + "check:env": "node scripts/check-env-drift.mjs" }, "dependencies": { "@astrojs/starlight": "^0.38.3", - "@tailwindcss/vite": "^4.0.0", "astro": "^6.0.1", - "sharp": "^0.34.2", - "tailwindcss": "^4.0.0" + "sharp": "^0.34.2" } -} \ No newline at end of file +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7302944..1b17227 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,18 +11,12 @@ importers: '@astrojs/starlight': specifier: ^0.38.3 version: 0.38.3(astro@6.1.5(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1)) - '@tailwindcss/vite': - specifier: ^4.0.0 - version: 4.2.2(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)) astro: specifier: ^6.0.1 version: 6.1.5(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0)(rollup@4.60.1) sharp: specifier: ^0.34.2 version: 0.34.5 - tailwindcss: - specifier: ^4.0.0 - version: 4.2.2 packages: @@ -416,22 +410,9 @@ packages: cpu: [x64] os: [win32] - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - '@jridgewell/sourcemap-codec@1.5.5': resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - '@mdx-js/mdx@3.1.1': resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} @@ -672,100 +653,6 @@ packages: '@shikijs/vscode-textmate@10.0.2': resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} - '@tailwindcss/node@4.2.2': - resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} - - '@tailwindcss/oxide-android-arm64@4.2.2': - resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.2.2': - resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.2.2': - resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.2.2': - resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} - engines: {node: '>= 20'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': - resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} - engines: {node: '>= 20'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': - resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-arm64-musl@4.2.2': - resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-linux-x64-gnu@4.2.2': - resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-x64-musl@4.2.2': - resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-wasm32-wasi@4.2.2': - resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': - resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.2.2': - resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} - engines: {node: '>= 20'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.2.2': - resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} - engines: {node: '>= 20'} - - '@tailwindcss/vite@4.2.2': - resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==} - peerDependencies: - vite: ^5.2.0 || ^6 || ^7 || ^8 - '@types/debug@4.1.13': resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} @@ -1003,10 +890,6 @@ packages: resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} engines: {node: '>=4'} - enhanced-resolve@5.20.1: - resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} - engines: {node: '>=10.13.0'} - entities@4.5.0: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} @@ -1103,9 +986,6 @@ packages: github-slugger@2.0.0: resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - h3@1.15.11: resolution: {integrity: sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==} @@ -1754,13 +1634,6 @@ packages: engines: {node: '>=16'} hasBin: true - tailwindcss@4.2.2: - resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} - - tapable@2.3.2: - resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} - engines: {node: '>=6'} - tiny-inflate@1.0.3: resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} @@ -2335,25 +2208,8 @@ snapshots: '@img/sharp-win32-x64@0.34.5': optional: true - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - '@jridgewell/sourcemap-codec@1.5.5': {} - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - '@mdx-js/mdx@3.1.1': dependencies: '@types/estree': 1.0.8 @@ -2563,74 +2419,6 @@ snapshots: '@shikijs/vscode-textmate@10.0.2': {} - '@tailwindcss/node@4.2.2': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.20.1 - jiti: 2.6.1 - lightningcss: 1.32.0 - magic-string: 0.30.21 - source-map-js: 1.2.1 - tailwindcss: 4.2.2 - - '@tailwindcss/oxide-android-arm64@4.2.2': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.2.2': - optional: true - - '@tailwindcss/oxide-darwin-x64@4.2.2': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.2.2': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.2.2': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.2.2': - optional: true - - '@tailwindcss/oxide@4.2.2': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.2 - '@tailwindcss/oxide-darwin-arm64': 4.2.2 - '@tailwindcss/oxide-darwin-x64': 4.2.2 - '@tailwindcss/oxide-freebsd-x64': 4.2.2 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 - '@tailwindcss/oxide-linux-x64-musl': 4.2.2 - '@tailwindcss/oxide-wasm32-wasi': 4.2.2 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 - - '@tailwindcss/vite@4.2.2(vite@7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0))': - dependencies: - '@tailwindcss/node': 4.2.2 - '@tailwindcss/oxide': 4.2.2 - tailwindcss: 4.2.2 - vite: 7.3.2(@types/node@24.12.2)(jiti@2.6.1)(lightningcss@1.32.0) - '@types/debug@4.1.13': dependencies: '@types/ms': 2.1.0 @@ -2916,11 +2704,6 @@ snapshots: dset@3.1.4: {} - enhanced-resolve@5.20.1: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.2 - entities@4.5.0: {} entities@6.0.1: {} @@ -3047,8 +2830,6 @@ snapshots: github-slugger@2.0.0: {} - graceful-fs@4.2.11: {} - h3@1.15.11: dependencies: cookie-es: 1.2.3 @@ -3289,7 +3070,8 @@ snapshots: dependencies: is-inside-container: 1.0.0 - jiti@2.6.1: {} + jiti@2.6.1: + optional: true js-yaml@4.1.1: dependencies: @@ -3345,6 +3127,7 @@ snapshots: lightningcss-linux-x64-musl: 1.32.0 lightningcss-win32-arm64-msvc: 1.32.0 lightningcss-win32-x64-msvc: 1.32.0 + optional: true longest-streak@3.1.0: {} @@ -4236,10 +4019,6 @@ snapshots: picocolors: 1.1.1 sax: 1.6.0 - tailwindcss@4.2.2: {} - - tapable@2.3.2: {} - tiny-inflate@1.0.3: {} tinyclip@0.1.12: {} diff --git a/scripts/check-env-drift.mjs b/scripts/check-env-drift.mjs new file mode 100644 index 0000000..7b2b244 --- /dev/null +++ b/scripts/check-env-drift.mjs @@ -0,0 +1,111 @@ +#!/usr/bin/env node +/** + * Fails when a module's `.env.example` names a variable its docs page does not. + * + * This exists because the docs drifted badly once: Librarian shipped + * `LIBRARIAN_REQUIRE_LOGIN=true` — a default that stops a first-run user dead — + * and the docs never mentioned it, along with four other variables. The module + * repos change daily; nobody is going to catch this by reading. + * + * Deliberately one-directional. Docs legitimately describe variables that are + * read by code but absent from `.env.example` (`HOST` and `PORT` in most + * modules), so flagging those would be noise. + * + * node scripts/check-env-drift.mjs + * node scripts/check-env-drift.mjs --ref my-branch + */ +import { readFile } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +import { dirname, join } from "node:path"; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), ".."); + +/** repo name on GitHub -> docs page slug. They differ for KAPOW. */ +const MODULES = [ + ["aurora", "aurora"], + ["tide", "tide"], + ["librarian", "librarian"], + ["fathom", "fathom"], + ["marquee", "marquee"], + ["encore", "encore"], + ["KAPOW", "kapow"], +]; + +/** + * Declared in a repo's `.env.example` but verified to be read by nothing. + * Documenting these would imply they work. Each needs a reason and, ideally, + * an upstream issue to delete it. + */ +const KNOWN_DEAD = { + aurora: { + PLEX_URL: "Leftover from a dropped Plex experiment; no reference in aurora/src or server.mjs.", + PLEX_TOKEN: "Leftover from a dropped Plex experiment; no reference in aurora/src or server.mjs.", + }, +}; + +const refArg = process.argv.indexOf("--ref"); +const REF = refArg === -1 ? "main" : process.argv[refArg + 1]; + +/** Picks up `FOO=` and commented options like `# FOO=`, which are still real. */ +const parseEnvNames = (text) => { + const names = new Set(); + for (const line of text.split("\n")) { + const match = /^\s*#?\s*([A-Z][A-Z0-9_]*)\s*=/.exec(line); + if (match) names.add(match[1]); + } + return names; +}; + +const failures = []; +const missingSources = []; + +for (const [repo, slug] of MODULES) { + const url = `https://raw.githubusercontent.com/Get-Coral/${repo}/${REF}/.env.example`; + const response = await fetch(url); + if (!response.ok) { + missingSources.push(`${repo}: ${url} -> ${response.status}`); + continue; + } + + const declared = parseEnvNames(await response.text()); + const docs = await readFile(join(ROOT, "src/content/docs/modules", `${slug}.md`), "utf8"); + + // A bare substring match would let `TIDE_MEMORY_LIMIT` pass for + // `TIDE_MEMORY_LIMIT_MB`, so require a non-word boundary on both sides. + const dead = KNOWN_DEAD[repo] ?? {}; + const undocumented = [...declared].filter( + (name) => + !(name in dead) && + !new RegExp(`(^|[^A-Z0-9_])${name}([^A-Z0-9_]|$)`).test(docs), + ); + + for (const name of Object.keys(dead)) { + if (!declared.has(name)) { + console.log(`note ${repo}: ${name} is gone upstream — drop it from KNOWN_DEAD`); + } + } + + if (undocumented.length > 0) { + failures.push({ repo, slug, undocumented }); + } + console.log( + `${undocumented.length === 0 ? "ok " : "FAIL"} ${repo.padEnd(10)} ${declared.size} variables declared`, + ); +} + +if (missingSources.length > 0) { + console.error(`\nCould not read .env.example for:\n ${missingSources.join("\n ")}`); + process.exit(2); +} + +if (failures.length > 0) { + console.error("\nEnvironment variables missing from the docs:\n"); + for (const { repo, slug, undocumented } of failures) { + console.error(` src/content/docs/modules/${slug}.md is missing, from ${repo}/.env.example:`); + for (const name of undocumented) console.error(` - ${name}`); + } + console.error("\nDocument them or, if a variable is genuinely gone, remove it upstream."); + process.exit(1); +} + +console.log("\nNo environment drift."); diff --git a/scripts/check-links.mjs b/scripts/check-links.mjs new file mode 100644 index 0000000..7a6e12d --- /dev/null +++ b/scripts/check-links.mjs @@ -0,0 +1,76 @@ +#!/usr/bin/env node +/** + * Verifies every internal link in the built site resolves to a real page. + * + * Run after `pnpm build`. Catches the ordinary failure mode for a docs site + * with hand-written cross-links: a page gets renamed and six other pages keep + * pointing at the old slug. + */ +import { readdir, readFile, stat } from "node:fs/promises"; +import { fileURLToPath } from "node:url"; +import { dirname, join, resolve } from "node:path"; + +const ROOT = join(dirname(fileURLToPath(import.meta.url)), ".."); +const DIST = join(ROOT, "dist"); + +const walk = async (dir) => { + const out = []; + for (const entry of await readdir(dir, { withFileTypes: true })) { + const full = join(dir, entry.name); + if (entry.isDirectory()) out.push(...(await walk(full))); + else out.push(full); + } + return out; +}; + +const exists = async (path) => { + try { + await stat(path); + return true; + } catch { + return false; + } +}; + +const files = await walk(DIST); +const pages = files.filter((f) => f.endsWith(".html")); + +const broken = []; +let checked = 0; + +for (const page of pages) { + const html = await readFile(page, "utf8"); + const source = page.slice(DIST.length) || "/"; + + for (const match of html.matchAll(/(?:href|src)="(\/[^"#?]*)/g)) { + const target = match[1]; + // Hashed build assets and the search index are generated, not authored. + if (target.startsWith("/_astro/") || target.startsWith("/pagefind/")) continue; + checked++; + + const candidates = target.endsWith("/") + ? [join(DIST, target, "index.html")] + : [join(DIST, target), join(DIST, `${target}.html`), join(DIST, target, "index.html")]; + + let ok = false; + for (const candidate of candidates) { + // Guard against a traversal escaping dist. + if (!resolve(candidate).startsWith(DIST)) continue; + if (await exists(candidate)) { + ok = true; + break; + } + } + if (!ok) broken.push({ source, target }); + } +} + +console.log(`Checked ${checked} internal links across ${pages.length} pages.`); + +if (broken.length > 0) { + console.error("\nBroken internal links:\n"); + for (const { source, target } of broken) console.error(` ${source} -> ${target}`); + process.exit(1); +} + +console.log("No broken internal links."); diff --git a/src/styles.css b/src/styles.css index e650b0c..342b49c 100644 --- a/src/styles.css +++ b/src/styles.css @@ -1,4 +1,5 @@ -@import url('https://fonts.googleapis.com/css2?family=DM+Sans:opsz,wght@9..40,400;9..40,500;9..40,600;9..40,700&family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&display=swap'); +/* Fonts are loaded from src/components/Head.astro with a preconnect, rather + than a render-blocking @import here. */ :root { --sl-font-sans: 'DM Sans', sans-serif; @@ -82,8 +83,7 @@ body { } /* Header + nav contrast */ -header.header, -.header.astro-wh26sp3i { +header.header { background: linear-gradient(180deg, var(--sl-color-bg-nav), rgba(14, 20, 39, 0.8)) !important; border-bottom: 0 !important; box-shadow: none !important; @@ -95,8 +95,7 @@ starlight-theme-select { display: none !important; } -header.header::after, -.header.astro-wh26sp3i::after { +header.header::after { display: none !important; } @@ -227,15 +226,6 @@ pre code { color: rgba(214, 222, 235, 0.95) !important; } -.expressive-code .copy button { - display: none !important; -} - -.expressive-code .copy, -.expressive-code .copy [aria-live] { - display: none !important; -} - /* Card styling: only actual cards, not card-grid wrappers */ article.card { background-color: rgba(45, 212, 191, 0.06); @@ -354,37 +344,6 @@ table tbody tr:hover { color: #041a1f !important; } -/* Theme switcher */ -starlight-theme-select label { - display: inline-flex !important; - align-items: center; - gap: 0.4rem; - padding: 0.4rem 0.55rem !important; - background: rgba(45, 212, 191, 0.08) !important; - border: 1px solid rgba(45, 212, 191, 0.3) !important; - border-radius: 0.5rem !important; - color: #d8e7f2 !important; -} - -starlight-theme-select select { - appearance: none; - background: transparent !important; - border: 0 !important; - padding: 0.12rem 0.2rem !important; - color: #d8e7f2 !important; - font-weight: 500; -} - -starlight-theme-select option { - color: #d8e7f2; - background: #121b2e; -} - -starlight-theme-select .label-icon, -starlight-theme-select .caret { - color: #5ee7d9 !important; -} - strong { color: #f0ede8; font-weight: 600;