diff --git a/apps/docs/.gitignore b/apps/docs/.gitignore new file mode 100644 index 00000000..97e663b1 --- /dev/null +++ b/apps/docs/.gitignore @@ -0,0 +1 @@ +.astro/ diff --git a/apps/docs/astro.config.ts b/apps/docs/astro.config.ts new file mode 100644 index 00000000..be04c7ff --- /dev/null +++ b/apps/docs/astro.config.ts @@ -0,0 +1,71 @@ +// cloudhal.dev (ADR-0037, ADR-0039): Starlight over the repo's docs/, served as static assets +// by the `cloudhal-dev` Worker. scripts/postbuild.ts adds the schemas, the Markdown copies, +// llms.txt and the redirects to dist/. +import starlight from "@astrojs/starlight"; +import { defineConfig } from "astro/config"; + +export default defineConfig({ + site: "https://cloudhal.dev", + trailingSlash: "never", + build: { format: "directory" }, + integrations: [ + starlight({ + title: "Cloudhal", + description: "Open-source, agent-native site platform.", + social: [ + { icon: "github", label: "GitHub", href: "https://github.com/cloudhal-dev/cloudhal" }, + ], + customCss: ["./src/styles/custom.css"], + head: [ + { + tag: "link", + attrs: { rel: "alternate", type: "text/markdown", href: "/llms.txt", title: "llms.txt" }, + }, + ], + lastUpdated: false, + sidebar: [ + { + label: "Start", + items: [ + { label: "Overview", link: "/docs" }, + { label: "Deploy to Cloudflare", link: "/docs/guides/deploy-button" }, + { label: "Playground", link: "https://try.cloudhal.com" }, + ], + }, + { label: "Guides", items: [{ autogenerate: { directory: "docs/guides" } }] }, + { + label: "Contributing", + collapsed: true, + items: [ + { label: "How to contribute", link: "/docs/contributing" }, + { label: "Code structure", link: "/docs/engineering/code-structure" }, + { label: "Threat model", link: "/docs/security/threat-model" }, + { + label: "Studio design", + collapsed: true, + items: [{ autogenerate: { directory: "docs/design" } }], + }, + ], + }, + { + label: "Decisions (ADRs)", + collapsed: true, + items: [{ autogenerate: { directory: "docs/adr" } }], + }, + { + label: "Proposals (RFCs)", + collapsed: true, + items: [{ autogenerate: { directory: "docs/rfcs" } }], + }, + { + label: "Reference", + items: [ + { label: "JSON Schemas v1", link: "/schemas/v1/index.json" }, + { label: "Cloudhal-Fetch", link: "/fetch" }, + { label: "llms.txt", link: "/llms.txt" }, + ], + }, + ], + }), + ], +}); diff --git a/apps/docs/package.json b/apps/docs/package.json index d6850898..38c868a2 100644 --- a/apps/docs/package.json +++ b/apps/docs/package.json @@ -2,13 +2,24 @@ "name": "@cloudhal/docs", "version": "0.0.1", "private": true, - "description": "cloudhal.dev: the developer site (docs, JSON Schemas, the fetcher page). ADR-0037.", + "description": "cloudhal.dev: the developer site (Starlight over the repo's docs/, JSON Schemas, docs for agents). ADR-0037, ADR-0039.", "type": "module", "scripts": { - "build": "node scripts/build.ts", - "deploy": "node scripts/build.ts && wrangler deploy" + "dev": "astro dev", + "build": "astro build && node scripts/postbuild.ts", + "preview": "wrangler dev", + "deploy": "pnpm build && wrangler deploy", + "typecheck": "tsc --noEmit -p tsconfig.json", + "test": "vitest run" + }, + "dependencies": { + "@astrojs/starlight": "catalog:", + "astro": "catalog:" }, "devDependencies": { + "@types/node": "catalog:", + "typescript": "catalog:", + "vitest": "catalog:", "wrangler": "catalog:" } } diff --git a/apps/docs/public/favicon.svg b/apps/docs/public/favicon.svg new file mode 100644 index 00000000..a000f8eb --- /dev/null +++ b/apps/docs/public/favicon.svg @@ -0,0 +1 @@ +Cloudhal diff --git a/apps/docs/scripts/build.ts b/apps/docs/scripts/build.ts deleted file mode 100644 index 4920c8da..00000000 --- a/apps/docs/scripts/build.ts +++ /dev/null @@ -1,89 +0,0 @@ -/** - * Builds cloudhal.dev into `dist/` (ADR-0037): - * - * - `static/` as is (home page, /fetch, 404, `_headers`, `_redirects`, robots.txt); - * - `/schemas/v1/.schema.json` from `packages/core-contracts/schemas`, the permanent `$id` - * URLs, plus `/schemas/v1/index.json` listing them; - * - `/llms.txt`, an index for agents (llmstxt.org); - * - one `/docs/guides/` redirect per guide, prepended to `_redirects` (static rules win over - * the `/docs/*` catch-all). - * - * Every schema's `$id` must already be its URL here; the build fails otherwise. - */ -import { cp, mkdir, readdir, readFile, rm, writeFile } from "node:fs/promises"; -import path from "node:path"; -import { fileURLToPath } from "node:url"; - -const APP = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); -const SCHEMAS = path.resolve(APP, "../../packages/core-contracts/schemas"); -const GUIDES = path.resolve(APP, "../../docs/guides"); -const DIST = path.join(APP, "dist"); -const ORIGIN = "https://cloudhal.dev"; - -interface SchemaEntry { - readonly name: string; - readonly url: string; - readonly title: string; -} - -async function copySchemas(): Promise { - const out = path.join(DIST, "schemas/v1"); - await mkdir(out, { recursive: true }); - const entries: SchemaEntry[] = []; - for (const file of (await readdir(SCHEMAS)).filter((f) => f.endsWith(".schema.json")).sort()) { - const text = await readFile(path.join(SCHEMAS, file), "utf8"); - const schema = JSON.parse(text) as { $id?: string; title?: string }; - const url = `${ORIGIN}/schemas/v1/${file}`; - if (schema.$id !== url) throw new Error(`${file}: $id is ${schema.$id}, expected ${url}`); - await writeFile(path.join(out, file), text); - entries.push({ name: file, url, title: schema.title ?? file }); - } - await writeFile( - path.join(out, "index.json"), - `${JSON.stringify({ version: 1, schemas: entries }, null, 2)}\n`, - ); - return entries; -} - -function llmsTxt(schemas: readonly SchemaEntry[]): string { - return [ - "# Cloudhal", - "", - "> Agent-native site platform: sites are typed graphs, agents change them through an MCP server behind a policy engine, and every change is a snapshot you can diff, preview and roll back. Runs on Cloudflare or self-hosted. Open source, TypeScript.", - "", - "## Docs", - "", - "- [README](https://raw.githubusercontent.com/cloudhal-dev/cloudhal/main/README.md): install, first site, connect an agent", - "- [Guides](https://github.com/cloudhal-dev/cloudhal/tree/main/docs/guides): deploy button, search, schedules, comments, multi-site, translations", - "- [Architecture decisions](https://github.com/cloudhal-dev/cloudhal/tree/main/docs/adr)", - "- [AGENTS.md](https://raw.githubusercontent.com/cloudhal-dev/cloudhal/main/AGENTS.md): conventions for coding agents working on Cloudhal", - "", - "## JSON Schemas (v1)", - "", - ...schemas.map((s) => `- [${s.title}](${s.url})`), - "", - "## Optional", - "", - "- [Fetcher](https://cloudhal.dev/fetch): what the Cloudhal-Fetch/1 user agent requests", - "- [Playground](https://try.cloudhal.com): a private Cloudhal site for one hour, no account", - "", - ].join("\n"); -} - -await rm(DIST, { recursive: true, force: true }); -await cp(path.join(APP, "static"), DIST, { recursive: true }); -const schemas = await copySchemas(); -await writeFile(path.join(DIST, "llms.txt"), llmsTxt(schemas)); -const guides = (await readdir(GUIDES)).filter((f) => f.endsWith(".md")).map((f) => f.slice(0, -3)); -const redirects = path.join(DIST, "_redirects"); -await writeFile( - redirects, - guides - .map( - (g) => - `/docs/guides/${g} https://github.com/cloudhal-dev/cloudhal/blob/main/docs/guides/${g}.md 302`, - ) - .join("\n") - .concat("\n", await readFile(redirects, "utf8")), -); -console.log(`built cloudhal.dev: ${schemas.length} schemas`); diff --git a/apps/docs/scripts/postbuild.ts b/apps/docs/scripts/postbuild.ts new file mode 100644 index 00000000..b9408e12 --- /dev/null +++ b/apps/docs/scripts/postbuild.ts @@ -0,0 +1,139 @@ +/** + * Runs after `astro build` and completes cloudhal.dev's `dist/` (ADR-0037, ADR-0039): + * + * - `static/` (`_headers`, robots.txt) and `_redirects` from `src/lib/redirects.ts`; + * - `/schemas/v1/.schema.json` at the permanent `$id` URLs, plus `index.json`; the build + * fails when a schema's `$id` is not its URL here; + * - every page as Markdown at `.md` and `/index.md`, links absolute; + * - `/llms.txt`, `/llms-full.txt` and one pair per section (llmstxt.org). + */ +import { cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { allPages, type Page } from "../src/lib/pages.ts"; +import { redirectLines } from "../src/lib/redirects.ts"; +import { ORIGIN } from "../src/lib/repo-docs.ts"; + +const APP = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const REPO = path.resolve(APP, "../.."); +const SCHEMAS = path.join(REPO, "packages/core-contracts/schemas"); +const DIST = path.join(APP, "dist"); + +/** Sections with their own `llms.txt`, in index order. */ +const SECTIONS = [ + { key: "guides", prefix: "docs/guides/", title: "Guides" }, + { key: "contributing", prefix: "docs/", title: "Contributing and internals" }, + { key: "adr", prefix: "docs/adr", title: "Architecture decisions" }, + { key: "rfcs", prefix: "docs/rfcs", title: "Proposals" }, +] as const; + +interface SchemaEntry { + readonly name: string; + readonly url: string; + readonly title: string; +} + +async function copySchemas(): Promise { + const out = path.join(DIST, "schemas/v1"); + await mkdir(out, { recursive: true }); + const entries: SchemaEntry[] = []; + for (const file of (await readdir(SCHEMAS)).filter((f) => f.endsWith(".schema.json")).sort()) { + const text = await readFile(path.join(SCHEMAS, file), "utf8"); + const schema = JSON.parse(text) as { $id?: string; title?: string }; + const url = `${ORIGIN}/schemas/v1/${file}`; + if (schema.$id !== url) throw new Error(`${file}: $id is ${schema.$id}, expected ${url}`); + await writeFile(path.join(out, file), text); + entries.push({ name: file, url, title: schema.title ?? file }); + } + await writeFile( + path.join(out, "index.json"), + `${JSON.stringify({ version: 1, schemas: entries }, null, 2)}\n`, + ); + return entries; +} + +const urlOf = (page: Page) => (page.id === "index" ? `${ORIGIN}/` : `${ORIGIN}/${page.id}`); + +function markdownOf(page: Page): string { + const front = [ + "---", + `title: ${JSON.stringify(page.title)}`, + `description: ${JSON.stringify(page.description)}`, + `url: ${urlOf(page)}`, + ...(page.file + ? [`source: https://github.com/cloudhal-dev/cloudhal/blob/main/${page.file}`] + : []), + "---", + "", + ]; + return `${front.join("\n")}# ${page.title}\n\n${page.body.trim()}\n`; +} + +async function writeMarkdown(pages: readonly Page[]): Promise { + for (const page of pages) { + const md = markdownOf(page); + const base = page.id === "index" ? "index" : page.id; + await mkdir(path.dirname(path.join(DIST, `${base}.md`)), { recursive: true }); + await writeFile(path.join(DIST, `${base}.md`), md); + if (page.id !== "index") { + await mkdir(path.join(DIST, page.id), { recursive: true }); + await writeFile(path.join(DIST, page.id, "index.md"), md); + } + } +} + +function sectionOf(page: Page): (typeof SECTIONS)[number] | undefined { + if (!page.id.startsWith("docs/")) return undefined; + return SECTIONS.find((s) => + s.key === "contributing" + ? !/^docs\/(guides|adr|rfcs)(\/|$)/.test(page.id) + : page.id === s.prefix || page.id.startsWith(s.prefix), + ); +} + +const linkLine = (page: Page) => `- [${page.title}](${urlOf(page)}.md): ${page.description}`; + +function llmsIndex(pages: readonly Page[], schemas: readonly SchemaEntry[]): string { + const lines = [ + "# Cloudhal", + "", + "> Agent-native site platform: sites are typed graphs, agents change them through an MCP server behind a policy engine, and every change is a snapshot you can diff, preview and roll back. Runs on Cloudflare or self-hosted. Open source, TypeScript.", + "", + "Every page below is Markdown. The full text is in /llms-full.txt; each section also has its own llms.txt and llms-full.txt.", + "", + "## Start", + "", + ...pages.filter((p) => !p.id.startsWith("docs/") || p.id === "docs").map(linkLine), + ]; + for (const section of SECTIONS) { + lines.push("", `## ${section.title}`, ""); + lines.push(...pages.filter((p) => p.id !== "docs" && sectionOf(p) === section).map(linkLine)); + } + lines.push("", "## JSON Schemas (v1)", "", ...schemas.map((s) => `- [${s.title}](${s.url})`), ""); + return lines.join("\n"); +} + +const fullText = (pages: readonly Page[]) => pages.map(markdownOf).join("\n\n"); + +async function writeLlms(pages: readonly Page[], schemas: readonly SchemaEntry[]): Promise { + await writeFile(path.join(DIST, "llms.txt"), llmsIndex(pages, schemas)); + await writeFile(path.join(DIST, "llms-full.txt"), fullText(pages)); + for (const section of SECTIONS) { + const own = pages.filter((p) => p.id !== "docs" && sectionOf(p) === section); + const dir = path.join(DIST, "docs", section.key); + await mkdir(dir, { recursive: true }); + await writeFile( + path.join(dir, "llms.txt"), + [`# Cloudhal: ${section.title}`, "", ...own.map(linkLine), ""].join("\n"), + ); + await writeFile(path.join(dir, "llms-full.txt"), fullText(own)); + } +} + +await cp(path.join(APP, "static"), DIST, { recursive: true }); +await writeFile(path.join(DIST, "_redirects"), `${redirectLines().join("\n")}\n`); +const schemas = await copySchemas(); +const pages = allPages(REPO, true); +await writeMarkdown(pages); +await writeLlms(pages, schemas); +console.log(`cloudhal.dev: ${pages.length} pages, ${schemas.length} schemas`); diff --git a/apps/docs/src/content.config.ts b/apps/docs/src/content.config.ts new file mode 100644 index 00000000..644771d0 --- /dev/null +++ b/apps/docs/src/content.config.ts @@ -0,0 +1,61 @@ +/** + * Starlight's `docs` collection, loaded from the repo's `docs/` (ADR-0039) instead of + * `src/content/docs`: titles come from each file's H1, descriptions from its first paragraph, + * and relative links are rewritten to site or GitHub URLs (`src/lib/repo-docs.ts`). + */ + +import { defineCollection } from "astro:content"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { docsSchema } from "@astrojs/starlight/schema"; +import type { Loader } from "astro/loaders"; +import { allPages } from "./lib/pages.ts"; + +const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../.."); + +const repoDocs: Loader = { + name: "cloudhal-repo-docs", + async load({ store, parseData, renderMarkdown, watcher }) { + const sync = async () => { + store.clear(); + const pages = allPages(REPO); + const isFolder = (id: string) => pages.some((p) => p.id.startsWith(`${id}/`)); + for (const page of pages) { + const data = await parseData({ + id: page.id, + data: { + title: page.title, + description: page.description, + ...(page.label || page.order !== undefined + ? { + sidebar: { + ...(page.label ? { label: page.label } : {}), + ...(page.order !== undefined ? { order: page.order } : {}), + }, + } + : {}), + ...page.data, + }, + }); + store.set({ + id: page.id, + data, + body: page.body, + rendered: await renderMarkdown(page.body), + // Starlight builds autogenerated sidebar groups from paths under its collection + // folder; the files do not exist there (the source is `page.file`). + filePath: `src/content/docs/${isFolder(page.id) ? `${page.id}/index` : page.id}.md`, + }); + } + }; + await sync(); + watcher?.add(path.join(REPO, "docs")); + watcher?.on("change", (changed) => { + if (changed.startsWith(path.join(REPO, "docs"))) void sync(); + }); + }, +}; + +export const collections = { + docs: defineCollection({ loader: repoDocs, schema: docsSchema() }), +}; diff --git a/apps/docs/src/lib/pages.ts b/apps/docs/src/lib/pages.ts new file mode 100644 index 00000000..2eaead26 --- /dev/null +++ b/apps/docs/src/lib/pages.ts @@ -0,0 +1,29 @@ +/** + * Every page cloudhal.dev publishes, in one list: the site's own pages, the repo's `docs/`, and + * generated folder indexes. The content loader renders them; the post-build step writes their + * Markdown copies and `llms.txt` from the same list. + */ +import { folderIndex, listDocs, readDoc } from "./repo-docs.ts"; +import { SITE_PAGES } from "./site-pages.ts"; + +export interface Page { + readonly id: string; + readonly title: string; + readonly description: string; + readonly body: string; + /** Repo file the page is built from, if any. */ + readonly file?: string; + readonly label?: string; + readonly order?: number; + readonly data?: Record; +} + +/** Folders without a README get a generated index. */ +const FOLDER_INDEXES = [{ dir: "docs/rfcs", title: "Proposals (RFCs)" }] as const; + +/** `absolute` writes site links as `https://cloudhal.dev/…` (for Markdown read off the site). */ +export function allPages(repoRoot: string, absolute = false): Page[] { + const docs = listDocs(repoRoot).map((file) => readDoc(repoRoot, file, absolute)); + const indexes = FOLDER_INDEXES.map(({ dir, title }) => folderIndex(repoRoot, dir, title)); + return [...SITE_PAGES, ...indexes, ...docs]; +} diff --git a/apps/docs/src/lib/redirects.ts b/apps/docs/src/lib/redirects.ts new file mode 100644 index 00000000..0648029f --- /dev/null +++ b/apps/docs/src/lib/redirects.ts @@ -0,0 +1,21 @@ +/** + * Permanent redirects for cloudhal.dev (ADR-0039 rule 3): a URL the site has served never 404s. + * When a page moves, add `old → new` here. The post-build step writes these into `_redirects` + * ahead of the static rules. + */ +export const REDIRECTS: readonly (readonly [from: string, to: string])[] = [ + // Linked by the playground Worker's `deployUrl` (packages/cloudflare README). + ["/docs/deploy", "/docs/guides/deploy-button"], + // ADR-0037: the Studio design system is DESIGN.md. + ["/docs/design", "/docs/design/design"], + // Folders without an index page. + ["/docs/guides", "/docs"], + ["/docs/engineering", "/docs/engineering/code-structure"], + ["/docs/security", "/docs/security/threat-model"], + ["/github", "https://github.com/cloudhal-dev/cloudhal"], +]; + +/** `_redirects` lines; site-internal targets are 301, external ones 302. */ +export function redirectLines(): string[] { + return REDIRECTS.map(([from, to]) => `${from} ${to} ${to.startsWith("/") ? 301 : 302}`); +} diff --git a/apps/docs/src/lib/repo-docs.ts b/apps/docs/src/lib/repo-docs.ts new file mode 100644 index 00000000..8e1c9de5 --- /dev/null +++ b/apps/docs/src/lib/repo-docs.ts @@ -0,0 +1,212 @@ +/** + * The repo's `docs/` folder as cloudhal.dev pages (ADR-0039). Pure functions over file paths and + * Markdown text, shared by the Starlight content loader and the post-build step (`.md` per page, + * `llms.txt`), so both publish the same pages under the same URLs. + * + * Paths are repo-relative with forward slashes (`docs/adr/0012-workers.md`). A page's id is its + * URL path without the leading slash: lower case, no `.md`, `README.md` standing for its folder. + */ +import { existsSync, readdirSync, readFileSync, statSync } from "node:fs"; +import path from "node:path"; + +export const ORIGIN = "https://cloudhal.dev"; +export const REPO_BLOB = "https://github.com/cloudhal-dev/cloudhal/blob/main"; +export const REPO_TREE = "https://github.com/cloudhal-dev/cloudhal/tree/main"; + +export interface RepoDoc { + /** `docs/adr/0012-workers` (also the URL path). */ + readonly id: string; + /** `docs/adr/0012-workers.md`, repo-relative. */ + readonly file: string; + readonly title: string; + readonly description: string; + /** Short label for the sidebar (ADRs and RFCs). */ + readonly label?: string; + /** Sort key for the sidebar (ADR and RFC number). */ + readonly order?: number; + /** The Markdown without its H1, links rewritten to site or GitHub URLs. */ + readonly body: string; +} + +/** `docs/contributing/README.md` → `docs/contributing`; `docs/design/DESIGN.md` → `docs/design/design`. */ +export function docId(file: string): string { + return file + .replace(/\.md$/, "") + .replace(/\/README$/i, "") + .toLowerCase(); +} + +/** Every Markdown file under `docs/`, repo-relative and sorted. */ +export function listDocs(repoRoot: string): string[] { + const out: string[] = []; + const walk = (rel: string) => { + for (const name of readdirSync(path.join(repoRoot, rel)).sort()) { + const child = `${rel}/${name}`; + if (statSync(path.join(repoRoot, child)).isDirectory()) walk(child); + else if (name.endsWith(".md")) out.push(child); + } + }; + walk("docs"); + return out; +} + +/** Splits off the first `# ` heading. Files without one use the file name as title. */ +export function splitTitle(markdown: string, file: string): { title: string; rest: string } { + const match = /^#[ \t]+(.+?)[ \t]*$/m.exec(markdown); + if (!match || markdown.slice(0, match.index).trim() !== "") { + return { title: path.basename(file, ".md"), rest: markdown }; + } + return { + title: match[1] ?? "", + rest: markdown.slice(match.index + match[0].length).replace(/^\s*\n/, ""), + }; +} + +const PROSE_SKIP = /^(?:[-*+>|#]|\d+\.|```|<|!\[)/; + +function firstParagraph(text: string): string { + for (const block of text.split(/\n\s*\n/)) { + const lines = block.trim().split("\n"); + if (!lines[0] || PROSE_SKIP.test(lines[0].trim())) continue; + return lines.join(" ").trim(); + } + return ""; +} + +/** Plain text of a Markdown snippet: links, emphasis and code marks removed. */ +function plain(text: string): string { + return text + .replace(/!\[[^\]]*]\([^)]*\)/g, "") + .replace(/\[([^\]]+)]\([^)]*\)/g, "$1") + .replace(/[*_`]/g, "") + .replace(/\s+/g, " ") + .trim(); +} + +/** At most `max` characters, cut at a sentence or word boundary. */ +function clip(text: string, max = 220): string { + if (text.length <= max) return text; + const cut = text.slice(0, max); + const sentence = cut.lastIndexOf(". "); + if (sentence > max / 2) return cut.slice(0, sentence + 1); + return `${cut.slice(0, cut.lastIndexOf(" "))}…`; +} + +/** The first paragraph of a section, else its first list item (summaries written as lists). */ +function firstText(section: string): string { + const paragraph = firstParagraph(section); + if (paragraph) return paragraph; + return /^[-*+][ \t]+(.+)$/m.exec(section)?.[1] ?? ""; +} + +/** The first text under `## Summary` or `## Context`, else the first paragraph. */ +export function describe(body: string): string { + for (const heading of ["Summary", "Context"]) { + const at = new RegExp(`^##[ \\t]+${heading}\\b.*$`, "m").exec(body); + if (at) { + const rest = body.slice(at.index + at[0].length); + const next = /^##[ \t]/m.exec(rest); + const text = firstText(next ? rest.slice(0, next.index) : rest); + if (text) return clip(plain(text)); + } + } + return clip(plain(firstParagraph(body))); +} + +/** `ADR-0039: cloudhal.dev: Starlight …` → label `0039 · cloudhal.dev`, order 39. */ +export function numbered(title: string): { label: string; order: number } | undefined { + const match = /^(?:ADR|RFC)-(\d{4}):?\s*(.*)$/.exec(title); + if (!match) return undefined; + const subject = (match[2] ?? "").split(/:\s|\s\(|,\s/)[0] ?? ""; + const short = subject.length > 44 ? `${subject.slice(0, 43).trimEnd()}…` : subject; + return { label: `${match[1]} · ${short}`, order: Number(match[1]) }; +} + +/** Where a link from `file` to `target` points on the site, or on GitHub for non-docs files. */ +export function resolveLink(file: string, target: string, repoRoot: string): string { + if (/^(?:[a-z][a-z0-9+.-]*:|#|\/)/i.test(target)) return target; + const hashAt = target.indexOf("#"); + const pathPart = hashAt === -1 ? target : target.slice(0, hashAt); + const hash = hashAt === -1 ? "" : target.slice(hashAt); + if (pathPart === "") return target; + const rel = path.posix.normalize(path.posix.join(path.posix.dirname(file), pathPart)); + if (rel.startsWith("../")) return target; + if (rel.startsWith("docs/") && rel.endsWith(".md")) return `/${docId(rel)}${hash.toLowerCase()}`; + const full = path.join(repoRoot, rel); + const isDir = existsSync(full) && statSync(full).isDirectory(); + return `${isDir ? REPO_TREE : REPO_BLOB}/${rel.replace(/\/$/, "")}${hash}`; +} + +/** + * Rewrites inline links and reference definitions outside fenced code. `absolute` makes site + * links absolute (`https://cloudhal.dev/…`), for Markdown read outside the site. + */ +export function rewriteLinks( + markdown: string, + file: string, + repoRoot: string, + absolute = false, +): string { + const fix = (target: string) => { + const url = resolveLink(file, target, repoRoot); + return absolute && url.startsWith("/") ? `${ORIGIN}${url}` : url; + }; + return markdown + .split(/(^```[\s\S]*?^```[ \t]*$)/m) + .map((part, i) => + i % 2 === 1 + ? part + : part + .replace(/(\]\()(<[^>]+>|[^)\s]+)/g, (_, open: string, target: string) => { + const bare = target.replace(/^<|>$/g, ""); + return `${open}${fix(bare)}`; + }) + .replace(/^(\[[^\]]+\]:[ \t]+)(\S+)/gm, (_, def: string, target: string) => { + return `${def}${fix(target)}`; + }), + ) + .join(""); +} + +/** The first word after `- Status:` (`Accepted`, `Draft`, …), if the doc has one. */ +export function status(markdown: string): string | undefined { + return /^[-*][ \t]+Status:[ \t]*\**([A-Za-z]+)/m.exec(markdown)?.[1]; +} + +/** A generated index for a folder without a README (`docs/rfcs`): one table row per doc. */ +export function folderIndex( + repoRoot: string, + dir: string, + title: string, +): { id: string; title: string; description: string; body: string } { + const rows = listDocs(repoRoot) + .filter((f) => path.posix.dirname(f) === dir) + .map((file) => { + const source = readFileSync(path.join(repoRoot, file), "utf8"); + const doc = readDoc(repoRoot, file); + return `| [${doc.title}](/${doc.id}) | ${status(source) ?? ""} |`; + }); + return { + id: dir, + title, + description: `Every ${title.toLowerCase()} in Cloudhal's repository, with its status.`, + body: ["| Document | Status |", "|---|---|", ...rows, ""].join("\n"), + }; +} + +/** Reads one doc. `absolute` as in `rewriteLinks`. */ +export function readDoc(repoRoot: string, file: string, absolute = false): RepoDoc { + const source = readFileSync(path.join(repoRoot, file), "utf8"); + const { title, rest } = splitTitle(source, file); + const body = rewriteLinks(rest, file, repoRoot, absolute); + const num = + file.startsWith("docs/adr/") || file.startsWith("docs/rfcs/") ? numbered(title) : undefined; + return { + id: docId(file), + file, + title: plain(title), + description: describe(rest), + ...(num ? { label: num.label, order: num.order } : {}), + body, + }; +} diff --git a/apps/docs/src/lib/site-pages.ts b/apps/docs/src/lib/site-pages.ts new file mode 100644 index 00000000..0b59df9f --- /dev/null +++ b/apps/docs/src/lib/site-pages.ts @@ -0,0 +1,125 @@ +/** + * cloudhal.dev's own pages, the ones not in the repo's `docs/`: the home page, the docs overview + * and `/fetch` (RFC-0004 §5.7). Written here, in Markdown, so the loader and the post-build step + * treat them like every other page. + */ + +export interface SitePage { + readonly id: string; + readonly title: string; + readonly description: string; + readonly body: string; + /** Extra Starlight frontmatter (template, hero). */ + readonly data?: Record; +} + +const home: SitePage = { + id: "index", + title: "Cloudhal for developers", + description: + "Cloudhal is an open-source, agent-native site platform. Install it, read the docs, use the JSON Schemas.", + data: { + template: "splash", + hero: { + tagline: + "An open-source site platform. Sites are typed graphs; agents change them through an MCP server behind a policy engine; every change is a snapshot you can diff, preview and roll back.", + actions: [ + { text: "Read the docs", link: "/docs", icon: "right-arrow" }, + { + text: "GitHub", + link: "https://github.com/cloudhal-dev/cloudhal", + icon: "github", + variant: "minimal", + }, + ], + }, + }, + body: `## Start + +\`\`\`sh +npm create cloudhal@latest my-site +cd my-site +npx cloudhal dev +\`\`\` + +Then connect your agent. Any MCP client works: + +\`\`\`sh +claude mcp add cloudhal -- npx cloudhal mcp --mode edits +\`\`\` + +Or deploy a blog to your Cloudflare account in one click with the [Deploy to Cloudflare button](https://deploy.workers.cloudflare.com/?url=https://github.com/cloudhal-dev/cloudhal-template), or try a private site for an hour at [try.cloudhal.com](https://try.cloudhal.com). + +## Reference + +- [Docs](/docs): guides, decisions and proposals. +- [JSON Schemas v1](/schemas/v1/index.json): the contracts every Cloudhal implementation shares. Their URLs are permanent. +- [llms.txt](/llms.txt): an index of this site for agents. Every page is also available as Markdown: add \`.md\` to its URL. +- [Cloudhal-Fetch](/fetch): what the fetcher requests from your server. + +Cloudhal is open source under Apache-2.0. Product site: [cloudhal.com](https://cloudhal.com). +`, +}; + +const docsIndex: SitePage = { + id: "docs", + title: "Documentation", + description: "Guides, architecture decisions and proposals for Cloudhal.", + body: `Cloudhal's docs live in the [repository](https://github.com/cloudhal-dev/cloudhal/tree/main/docs) and change in the same pull request as the code they describe. + +## Get going + +- [Install and run locally](https://github.com/cloudhal-dev/cloudhal#readme): \`npm create cloudhal@latest\`, \`cloudhal dev\`, connect an agent. +- [Deploy to Cloudflare with one click](/docs/guides/deploy-button). +- [Harden a Cloudflare deployment](/docs/guides/cloudflare-hardening). + +## Guides + +- [Search](/docs/guides/search), [schedules](/docs/guides/schedules), [comments](/docs/guides/comments) +- [Several sites in one deployment](/docs/guides/multi-site) +- [Analytics and privacy](/docs/guides/analytics-privacy) +- [Translations with Weblate](/docs/guides/translations-weblate) + +## How Cloudhal is built + +- [Architecture decisions (ADRs)](/docs/adr): what was decided and why, with sources. +- [Proposals (RFCs)](/docs/rfcs): designs before they are built. +- [Threat model](/docs/security/threat-model) and [code structure](/docs/engineering/code-structure). +- [Contributing](/docs/contributing) and the [Studio design system](/docs/design/design). + +## For agents + +Every page is available as Markdown: add \`.md\` to its URL. [/llms.txt](/llms.txt) lists all pages and [/llms-full.txt](/llms-full.txt) holds them in one file. +`, +}; + +const fetchPage: SitePage = { + id: "fetch", + title: "Cloudhal-Fetch", + description: + "What the Cloudhal-Fetch/1 user agent requests from your server, and how to reach the people running it.", + body: `You found \`Cloudhal-Fetch/1 (+https://cloudhal.dev/fetch)\` in your logs. It is the media fetcher of Cloudhal, an open-source site platform. + +## Who sends it + +Cloudhal is self-hosted: every site runs on its owner's own Cloudflare account or server. A request with this user agent comes from **one such site**, when its owner or an agent they approved imports an image or file by URL. The Cloudhal project does not crawl the web and does not run these requests itself. + +## What it requests + +- A single \`GET\` for the exact URL someone gave it. It does not follow links or crawl. +- No cookies, no \`Authorization\` header, no headers forwarded from the person's browser. +- HTTPS on port 443 only; at most 3 redirects, each checked again. +- Media types only (images, video, audio, documents). Web pages are refused. +- At most 25 MB, within 30 seconds. + +## Blocking it + +Because each request comes from a different site's infrastructure, block by user agent: refuse requests whose \`User-Agent\` starts with \`Cloudhal-Fetch/\`. The fetcher treats any status other than 200 as a failure and tells the person the import did not work. + +## Contact + +If a Cloudhal site is misusing the fetcher, write to [security@cloudhal.com](mailto:security@cloudhal.com) with the request time, URL and source address. The rules above are specified in [RFC-0004 §5.7](/docs/rfcs/0004-assets-and-media). +`, +}; + +export const SITE_PAGES: readonly SitePage[] = [home, docsIndex, fetchPage]; diff --git a/apps/docs/src/styles/custom.css b/apps/docs/src/styles/custom.css new file mode 100644 index 00000000..11fd33f4 --- /dev/null +++ b/apps/docs/src/styles/custom.css @@ -0,0 +1,11 @@ +/* Cloudhal accent on Starlight's palette (light and dark). */ +:root { + --sl-color-accent-low: #3b1a0e; + --sl-color-accent: #d0643e; + --sl-color-accent-high: #f6c3ad; +} +:root[data-theme="light"] { + --sl-color-accent-low: #f8dccf; + --sl-color-accent: #b4441f; + --sl-color-accent-high: #5a200c; +} diff --git a/apps/docs/static/404.html b/apps/docs/static/404.html deleted file mode 100644 index 53eb48be..00000000 --- a/apps/docs/static/404.html +++ /dev/null @@ -1,16 +0,0 @@ - - - - - - Not found - - - -
-
Cloudhal
-

Not found

-

There is nothing at this address. Try the home page, the docs or the JSON Schemas.

-
- - diff --git a/apps/docs/static/_headers b/apps/docs/static/_headers index 0ef1b82b..5ad367ee 100644 --- a/apps/docs/static/_headers +++ b/apps/docs/static/_headers @@ -10,6 +10,14 @@ Access-Control-Allow-Origin: * Cache-Control: public, max-age=3600 -/llms.txt +# Markdown for agents (ADR-0039): llms.txt files and every page's .md copy. +/*llms.txt Content-Type: text/markdown; charset=utf-8 Access-Control-Allow-Origin: * + +/*llms-full.txt + Content-Type: text/markdown; charset=utf-8 + Access-Control-Allow-Origin: * + +/*.md + Access-Control-Allow-Origin: * diff --git a/apps/docs/static/_redirects b/apps/docs/static/_redirects deleted file mode 100644 index 588bac5b..00000000 --- a/apps/docs/static/_redirects +++ /dev/null @@ -1,5 +0,0 @@ -# Until the docs site lands, /docs points at the docs on GitHub (302: these links will change). -/docs/deploy https://github.com/cloudhal-dev/cloudhal/blob/main/docs/guides/deploy-button.md 302 -/docs https://github.com/cloudhal-dev/cloudhal#readme 302 -/docs/* https://github.com/cloudhal-dev/cloudhal/tree/main/docs 302 -/github https://github.com/cloudhal-dev/cloudhal 302 diff --git a/apps/docs/static/fetch.html b/apps/docs/static/fetch.html deleted file mode 100644 index e2f98c92..00000000 --- a/apps/docs/static/fetch.html +++ /dev/null @@ -1,41 +0,0 @@ - - - - - - Cloudhal-Fetch - - - - -
-
- Cloudhal - -
- -

Cloudhal-Fetch

-

You found Cloudhal-Fetch/1 (+https://cloudhal.dev/fetch) in your logs. It is the media fetcher of Cloudhal, an open-source site platform.

- -

Who sends it

-

Cloudhal is self-hosted: every site runs on its owner's own Cloudflare account or server. A request with this user agent comes from one such site, when its owner or an agent they approved imports an image or file by URL. The Cloudhal project does not crawl the web and does not run these requests itself.

- -

What it requests

-
    -
  • A single GET for the exact URL someone gave it. It does not follow links or crawl.
  • -
  • No cookies, no Authorization header, no headers forwarded from the person's browser.
  • -
  • HTTPS on port 443 only; at most 3 redirects, each checked again.
  • -
  • Media types only (images, video, audio, documents). Web pages are refused.
  • -
  • At most 25 MB, within 30 seconds.
  • -
- -

Blocking it

-

Because each request comes from a different site's infrastructure, block by user agent: refuse requests whose User-Agent starts with Cloudhal-Fetch/. The fetcher treats any status other than 200 as a failure and tells the person the import did not work.

- -

Contact

-

If a Cloudhal site is misusing the fetcher, write to security@cloudhal.com with the request time, URL and source address. The rules above are specified in RFC-0004 §5.7.

- - -
- - diff --git a/apps/docs/static/index.html b/apps/docs/static/index.html deleted file mode 100644 index 5b761d62..00000000 --- a/apps/docs/static/index.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - Cloudhal for developers - - - - - -
-
- Cloudhal - -
- -

Sites your agents can build, safely.

-

Cloudhal is an open-source site platform. Sites are typed graphs; agents change them through an MCP server behind a policy engine; every change is a snapshot you can diff, preview and roll back. It runs on Cloudflare or on your own machine.

- -

Start

-
npm create cloudhal@latest my-site
-cd my-site
-npx cloudhal dev
-

Then connect your agent: claude mcp add cloudhal -- npx cloudhal mcp --mode edits. Any MCP client works.

-

Or deploy a blog to your Cloudflare account in one click with the Deploy to Cloudflare button, or try a private site for an hour at try.cloudhal.com.

- -

Reference

-
    -
  • Docs: guides and architecture decisions (on GitHub until the docs site is ready).
  • -
  • JSON Schemas v1: the contracts every Cloudhal implementation shares. Their URLs are permanent.
  • -
  • llms.txt: an index of this site for agents.
  • -
  • Cloudhal-Fetch: what the fetcher requests from your server, and how to reach us.
  • -
- - -
- - diff --git a/apps/docs/static/site.css b/apps/docs/static/site.css deleted file mode 100644 index 89dd6dcc..00000000 --- a/apps/docs/static/site.css +++ /dev/null @@ -1,112 +0,0 @@ -:root { - --bg: #fbfbfa; - --fg: #1c1c1a; - --muted: #5f5f5a; - --line: #e4e4e0; - --accent: #b4441f; - --code: #f1f1ee; - color-scheme: light; -} -@media (prefers-color-scheme: dark) { - :root { - --bg: #141413; - --fg: #ececea; - --muted: #a3a39d; - --line: #2c2c2a; - --accent: #ee8a64; - --code: #1f1f1d; - color-scheme: dark; - } -} -* { - box-sizing: border-box; -} -body { - margin: 0; - background: var(--bg); - color: var(--fg); - font: - 16px / 1.6 ui-sans-serif, - system-ui, - -apple-system, - "Segoe UI", - sans-serif; -} -main { - max-width: 44rem; - margin: 0 auto; - padding: 4rem 1rem 5rem; -} -header { - display: flex; - justify-content: space-between; - align-items: baseline; - gap: 1rem; - flex-wrap: wrap; -} -.brand { - font-weight: 600; - font-size: 1.125rem; - color: var(--fg); - text-decoration: none; -} -nav { - display: flex; - gap: 1rem; - flex-wrap: wrap; - font-size: 0.9375rem; -} -h1 { - font-size: 2rem; - line-height: 1.2; - font-weight: 600; - margin: 3rem 0 0.75rem; -} -h2 { - font-size: 1.125rem; - font-weight: 600; - margin: 2.5rem 0 0.5rem; -} -p, -li { - color: var(--muted); -} -strong { - color: var(--fg); - font-weight: 600; -} -a { - color: var(--accent); -} -pre, -code { - font-family: ui-monospace, SFMono-Regular, Menlo, monospace; - font-size: 0.875em; -} -pre { - background: var(--code); - border: 1px solid var(--line); - border-radius: 8px; - padding: 0.75rem 1rem; - overflow-x: auto; - color: var(--fg); -} -code { - background: var(--code); - border-radius: 4px; - padding: 0.1em 0.3em; -} -pre code { - background: none; - padding: 0; -} -ul { - padding-inline-start: 1.25rem; -} -footer { - margin-top: 4rem; - padding-top: 1.5rem; - border-top: 1px solid var(--line); - font-size: 0.875rem; - color: var(--muted); -} diff --git a/apps/docs/test/repo-docs.test.ts b/apps/docs/test/repo-docs.test.ts new file mode 100644 index 00000000..bc12ab5a --- /dev/null +++ b/apps/docs/test/repo-docs.test.ts @@ -0,0 +1,158 @@ +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, expect, it } from "vitest"; +import { allPages } from "../src/lib/pages.ts"; +import { redirectLines } from "../src/lib/redirects.ts"; +import { + describe as describeDoc, + docId, + numbered, + resolveLink, + rewriteLinks, + splitTitle, + status, +} from "../src/lib/repo-docs.ts"; + +const REPO = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../../.."); + +describe("docId", () => { + it("drops .md, lower-cases, and lets README stand for its folder", () => { + expect(docId("docs/adr/0012-workers.md")).toBe("docs/adr/0012-workers"); + expect(docId("docs/contributing/README.md")).toBe("docs/contributing"); + expect(docId("docs/design/DESIGN.md")).toBe("docs/design/design"); + }); +}); + +describe("splitTitle", () => { + it("takes the leading H1 and removes it from the body", () => { + expect(splitTitle("# Search guide\n\nBody.\n", "docs/guides/search.md")).toEqual({ + title: "Search guide", + rest: "Body.\n", + }); + }); + it("falls back to the file name when the file does not start with an H1", () => { + expect(splitTitle("Intro\n\n# Later\n", "docs/x/notes.md").title).toBe("notes"); + }); +}); + +describe("describe", () => { + it("prefers the Summary section and strips Markdown", () => { + const body = + "- Status: Draft\n\n## Summary\nOne **bold** [link](x.md) here.\n\n## Motivation\nNo."; + expect(describeDoc(body)).toBe("One bold link here."); + }); + it("skips lists, tables and code to find prose", () => { + expect(describeDoc("- a\n- b\n\n| t |\n\n```\ncode\n```\n\nThe prose.")).toBe("The prose."); + }); +}); + +describe("numbered", () => { + it("gives ADRs and RFCs a short sidebar label and their number as order", () => { + expect(numbered("ADR-0039: cloudhal.dev: Starlight over the repo's docs")).toEqual({ + label: "0039 · cloudhal.dev", + order: 39, + }); + expect(numbered("Search guide")).toBeUndefined(); + }); +}); + +describe("status", () => { + it("reads the first word of the Status line", () => { + expect(status("# RFC\n- Status: Accepted (2026-10-21)\n")).toBe("Accepted"); + expect(status("no status")).toBeUndefined(); + }); +}); + +describe("resolveLink", () => { + const from = "docs/rfcs/0037-cloudhal-dev-docs-site.md"; + it("maps links between docs to site paths, keeping (lower-cased) anchors", () => { + expect(resolveLink(from, "../adr/0037-domain-strategy.md#Decision", REPO)).toBe( + "/docs/adr/0037-domain-strategy#decision", + ); + expect(resolveLink(from, "../contributing/README.md", REPO)).toBe("/docs/contributing"); + }); + it("sends other repo files to GitHub, folders as tree links", () => { + expect(resolveLink(from, "../../AGENTS.md", REPO)).toBe( + "https://github.com/cloudhal-dev/cloudhal/blob/main/AGENTS.md", + ); + expect(resolveLink(from, "../../packages/cli", REPO)).toBe( + "https://github.com/cloudhal-dev/cloudhal/tree/main/packages/cli", + ); + }); + it("leaves absolute URLs, anchors and site paths alone", () => { + for (const target of ["https://example.com/a.md", "#top", "/docs", "mailto:a@b.c"]) { + expect(resolveLink(from, target, REPO)).toBe(target); + } + }); +}); + +describe("rewriteLinks", () => { + const file = "docs/guides/search.md"; + it("rewrites inline links and reference definitions, not fenced code", () => { + const md = [ + "See [ADR](../adr/0011-mcp-server.md) and [ref].", + "", + "```md", + "[keep](../adr/0011-mcp-server.md)", + "```", + "", + "[ref]: ../rfcs/0029-search-providers-and-search-ui.md", + ].join("\n"); + expect(rewriteLinks(md, file, REPO)).toBe( + [ + "See [ADR](/docs/adr/0011-mcp-server) and [ref].", + "", + "```md", + "[keep](../adr/0011-mcp-server.md)", + "```", + "", + "[ref]: /docs/rfcs/0029-search-providers-and-search-ui", + ].join("\n"), + ); + }); + it("makes site links absolute for Markdown read off the site", () => { + expect(rewriteLinks("[x](schedules.md)", file, REPO, true)).toBe( + "[x](https://cloudhal.dev/docs/guides/schedules)", + ); + }); +}); + +describe("the published page set", () => { + const pages = allPages(REPO); + const ids = new Set(pages.map((p) => p.id)); + + it("has unique ids and a title and description on every page", () => { + expect(ids.size).toBe(pages.length); + for (const page of pages) { + expect(page.title, page.id).not.toBe(""); + expect(page.description, page.id).not.toBe(""); + } + }); + + it("only links to pages that exist (or redirect)", () => { + const redirected = new Set(redirectLines().map((l) => l.split(" ")[0]?.slice(1))); + const broken: string[] = []; + for (const page of pages) { + for (const [, target] of page.body.matchAll(/\]\((\/[^)#\s]*)/g)) { + const id = target?.slice(1) || "index"; + const asset = /^(schemas|llms|pagefind)/.test(id) || id.endsWith(".txt"); + if (!asset && !ids.has(id) && !redirected.has(id)) broken.push(`${page.id} → /${id}`); + } + } + expect(broken).toEqual([]); + }); + + it("keeps the URLs other code links to", () => { + expect(ids.has("docs/guides/deploy-button")).toBe(true); + expect(redirectLines()).toContain("/docs/deploy /docs/guides/deploy-button 301"); + expect(ids.has("fetch")).toBe(true); + }); +}); + +describe("describe with list summaries", () => { + it("uses the first item when the Summary is a list", () => { + expect( + describeDoc("## Summary\n- **What.** A generator.\n- **How.** Drafts.\n\n## Next\nNo."), + ).toBe("What. A generator."); + }); +}); diff --git a/apps/docs/tsconfig.json b/apps/docs/tsconfig.json new file mode 100644 index 00000000..6d54adea --- /dev/null +++ b/apps/docs/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "declaration": false + }, + "include": ["src/lib", "scripts", "test"] +} diff --git a/docs/rfcs/0037-cloudhal-dev-docs-site.md b/docs/rfcs/0037-cloudhal-dev-docs-site.md index e5609350..4d0f9b3f 100644 --- a/docs/rfcs/0037-cloudhal-dev-docs-site.md +++ b/docs/rfcs/0037-cloudhal-dev-docs-site.md @@ -120,3 +120,6 @@ Who links where: 1. ADRs and RFCs on the public site: render all of them, or only Accepted ones, with drafts staying on GitHub? Recommendation: all, with the status shown in the header. 2. Error codes: one registry in `packages/core-contracts` (shared by TS and Rust), or per package? Recommendation: one registry, since the API, CLI and Studio report the same codes. 3. Should the docs MCP also be offered as an MCP registry entry (`registry.cloudhal.dev`, ADR-0037), or wait until there is a registry? Recommendation: wait. + +## Implementation notes +- **Phase 1** (`apps/docs`): a custom content loader (`src/lib/repo-docs.ts`) reads `docs/` and the site's own pages (`src/lib/site-pages.ts`). The H1 becomes the title; the description is the first text under `## Summary` or `## Context`; relative links become site paths, or GitHub links for files outside `docs/`. Folders without a README get a generated index (`/docs/rfcs`). `scripts/postbuild.ts` writes the schemas, the `.md` copies, `llms.txt`/`llms-full.txt` (site and per section) and `_redirects` from `src/lib/redirects.ts` from the same page list. The `starlight-llms-txt` plugin is not used: one list feeds the HTML, the Markdown and the indexes, so they cannot disagree. diff --git a/packages/cli/test/render-chrome.test.ts b/packages/cli/test/render-chrome.test.ts index c5d1df21..aff951d3 100644 --- a/packages/cli/test/render-chrome.test.ts +++ b/packages/cli/test/render-chrome.test.ts @@ -39,7 +39,8 @@ describe.skipIf(!chrome)("render_page checks in Chrome", () => { const r = await renderPage( chromeRenderer(chrome as string, { idleMs: 10 }), pathToFileURL(file).href, - { widths: [1440, 390], fullPage: true }, + // Two widths, full page: the 20 s default is tight on a loaded CI runner (#35). + { widths: [1440, 390], fullPage: true, timeoutMs: 50_000 }, ); const check = (id: string) => r.checks.find((c) => c.id === id); expect(r.shots.map((s) => s.width)).toEqual([1440, 390]); @@ -95,7 +96,7 @@ header{position:sticky;top:0;height:80px;color:#fff;margin-bottom:-80px}header a const r = await renderPage( chromeRenderer(chrome as string, { idleMs: 10 }), pathToFileURL(file).href, - { widths, fullPage: false }, + { widths, fullPage: false, timeoutMs: 50_000 }, ); return (id: string) => r.checks.find((c) => c.id === id); }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4f223692..ab1d3107 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -167,6 +167,9 @@ catalogs: '@astrojs/cloudflare': specifier: 14.3.4 version: 14.3.4 + '@astrojs/starlight': + specifier: 0.42.6 + version: 0.42.6 '@better-auth/api-key': specifier: 1.7.7 version: 1.7.7 @@ -415,7 +418,23 @@ importers: version: 5.0.3(@types/node@26.6.5)(happy-dom@21.0.3)(vite@8.3.3(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) apps/docs: + dependencies: + '@astrojs/starlight': + specifier: 'catalog:' + version: 0.42.6(astro@7.3.6(@types/node@26.6.5)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(supports-color@10.2.2)(typescript@7.0.2) + astro: + specifier: 'catalog:' + version: 7.3.6(@types/node@26.6.5)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) devDependencies: + '@types/node': + specifier: 'catalog:' + version: 26.6.5 + typescript: + specifier: 'catalog:' + version: 7.0.2 + vitest: + specifier: 'catalog:' + version: 5.0.3(@types/node@26.6.5)(happy-dom@21.0.3)(vite@8.3.3(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) wrangler: specifier: 'catalog:' version: 4.148.0(@cloudflare/workers-types@5.20261007.1)(@types/node@26.6.5) @@ -1266,13 +1285,39 @@ packages: '@astrojs/internal-helpers@0.12.0': resolution: {integrity: sha512-pKPuwCDtO0L7zgmt1JGd9X4Aes1+0SX3IEBSqBNPHsScYdPMlfmed3Y7DTbXyF7E+8ylnb563o/0OzGQPOFehw==} + '@astrojs/internal-helpers@0.13.0': + resolution: {integrity: sha512-20jxQqnSmpskcGyad7N5l6Xf1gz0UCa6lNde44bEBdaXGpncEYfAZJ142r2ZgzBaG4dUyzGmhqZWv1INU2eiHQ==} + '@astrojs/markdown-satteri@0.4.3': resolution: {integrity: sha512-PFbEHPnwUlLvpXd7RXrrKM+j12akF4bG+DSuNuoN+3eiwjOwp4HaPi+QQdbkBV7QEYXmzyKca50OJsmGxhNIUA==} + '@astrojs/mdx@8.1.0': + resolution: {integrity: sha512-xzrlXrjpBYvC27MTkP+2NMiJG4Jjl2DcoNc/EF9RNxAp0dwhvXC3b22+7OO4kHJQKfISk2uy38HYp1UxX7JfSg==} + engines: {node: '>=22.22.0'} + peerDependencies: + '@astrojs/markdown-remark': ^7.3.0 + '@astrojs/markdown-satteri': ^0.4.0 + astro: ^7.2.10 + peerDependenciesMeta: + '@astrojs/markdown-remark': + optional: true + '@astrojs/prism@4.0.2': resolution: {integrity: sha512-KTivpmnz6lDsC6o9H4+DNm2SrE/GHzw8cNAvEJwAvUT+eoaEnn/4NtbDNfRRaxaJHdp15gf+tfHAWiXR4wB3BA==} engines: {node: '>=22.12.0'} + '@astrojs/sitemap@3.7.4': + resolution: {integrity: sha512-LbKNC24bdUWcQf/pThB6qLlSqHojxGjZDURIzFocY8rlWnAn2t74nnhnK6S5x0NHriHoAduLEpVjRykmeGiVvA==} + + '@astrojs/starlight@0.42.6': + resolution: {integrity: sha512-Cfs7GdgzgNRCauDxQVsFUVL1thrGHK/rQdbv/SURv/xhGBXx+j5ypOHW9ENPraQxbIzUC8KAnJV7L9mvEVRuyQ==} + peerDependencies: + '@astrojs/markdown-remark': ^7.3.0 + astro: ^7.2.10 + peerDependenciesMeta: + '@astrojs/markdown-remark': + optional: true + '@astrojs/telemetry@3.3.3': resolution: {integrity: sha512-C1TLn5sPJr0x4vk56piHWKbnqlEB8BKyte5Y45V02U+D7BGO5eMqZDH5aPjnkXQWJggvmsTXxH03QMZ9NgWLzQ==} engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} @@ -1730,6 +1775,10 @@ packages: resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==} engines: {node: '>=12'} + '@ctrl/tinycolor@4.2.1': + resolution: {integrity: sha512-Vh5uy9Y1JBMId6J1f68TMXnZrTCvNdo9Fu2k0d1YnJngR+sM5kWTKl441c/3TFs+cG0NWOjRWgQ/iaWhbYx+7A==} + engines: {node: '>=14'} + '@date-fns/tz@1.5.0': resolution: {integrity: sha512-lwYN/vDPeNRULcepoE/LO2Pgx+7/RV+S9ARfbc9lr2DtGkOD7pAiruHvbR1RX3Qyf6ja47EWJDMsNK5vK08DJg==} @@ -2063,6 +2112,18 @@ packages: cpu: [x64] os: [win32] + '@expressive-code/core@0.44.2': + resolution: {integrity: sha512-Yw5KonQCD1HoOO2P89y4wifqS9qrJcGVY0LkVMUsVZjhi++bhz4WwQ37inenKFNOEVo50P7cjoVsCdaVnUbq8w==} + + '@expressive-code/plugin-frames@0.44.2': + resolution: {integrity: sha512-gVLm6TQMNIRWIaM7o+mCtxkGNjLZY5/2faSTLoXeo9wv/Pkj/0QAtZyXPVPrRsPARqiipATNFmfvQFLJudxPHQ==} + + '@expressive-code/plugin-shiki@0.44.2': + resolution: {integrity: sha512-ebWIvCDzXvjyD8rH6T6DfytYJw1GoArLR4nusIAE4VxXrCQf3d+owytiv010KU/dUg+SRLZskDn0N+HaFXeuSg==} + + '@expressive-code/plugin-text-markers@0.44.2': + resolution: {integrity: sha512-SNI7EgUSiADqbqUzzMbj1ZeC/r2pa5eg3KyFOGAd3a7EsIA4qzjXQLp+ODWQtHVDKFCdGs4J3ovSJUqvKIK96g==} + '@floating-ui/core@1.8.0': resolution: {integrity: sha512-0CIZ5itps/8x7BG8dEIhs53BvCUH2PCoogtakwRTut+Arm58sJooJ0AuZhLw2HJYIR5cMLNPBSS728sPho2khQ==} @@ -2657,6 +2718,44 @@ packages: '@oxc-project/types@0.152.0': resolution: {integrity: sha512-oM/5rLBm2tPkg0iBgkH/FOeR3PCDpY19GTgAZjMFM8h9WI9VW7cLgzp6nwtarYKmovavIQZ+Fe/RKX/8C8O/Rw==} + '@pagefind/darwin-arm64@1.5.2': + resolution: {integrity: sha512-MXpI+7HsAdPkvJ0gk9xj9g541BCqBZOBbdwj9g6lB5LCj6kSV6nqDSjzcAJwvOsfu0fjwvC8hQU+ecfhp+MpiQ==} + cpu: [arm64] + os: [darwin] + + '@pagefind/darwin-x64@1.5.2': + resolution: {integrity: sha512-IojxFWMEJe0RQ7PQ3KXQsPIImNsbpPYpoZ+QUDrL8fAl/O27IX+LVLs74/UzEZy5uA2LD8Nz1AiwKr72vrkZQw==} + cpu: [x64] + os: [darwin] + + '@pagefind/default-ui@1.5.2': + resolution: {integrity: sha512-pm1LMnQg8N2B3n2TnjKlhaFihpz6zTiA4HiGQ6/slKO/+8K9CAU5kcjdSSPgpuk1PMuuN4hxLipUIifnrkl3Sg==} + + '@pagefind/freebsd-x64@1.5.2': + resolution: {integrity: sha512-7EVzo9+0w+2cbe671BtMj10UlNo83I+HrLVLfRxO731svHRJKUfJ/mo05gU14pe9PCfpKNQT8FS3Xc/oDN6pOA==} + cpu: [x64] + os: [freebsd] + + '@pagefind/linux-arm64@1.5.2': + resolution: {integrity: sha512-Ovt9+K35sqzn8H3ZMXGwls4TD/wMJuvRtShHIsmUQREmaxjrDEX7gHckRCrwYJ4XE1H1p6HkLz3wukrAnsfXQw==} + cpu: [arm64] + os: [linux] + + '@pagefind/linux-x64@1.5.2': + resolution: {integrity: sha512-V+tFqHKXhQKq/WqPBD67AFy7scn1/aZID00ws4fSDd+1daSi5UHR9VVlRrOUYKxn3VuFQYRD7lYXdZK1WED1YA==} + cpu: [x64] + os: [linux] + + '@pagefind/windows-arm64@1.5.2': + resolution: {integrity: sha512-hN9Nh90fNW61nNRCW9ZyQrAj/mD0eRvmJ8NlTUzkbuW8kIzGJUi3cxjFkEcMZ5h/8FsKWD/VcouZl4yo1F7B6g==} + cpu: [arm64] + os: [win32] + + '@pagefind/windows-x64@1.5.2': + resolution: {integrity: sha512-Fa2Iyw7kaDRzGMfNYNUXNW2zbL5FQVDgSOcbDHdzBrDEdpqOqg8TcZ68F22ol6NJ9IGzvUdmeyZypLW5dyhqsg==} + cpu: [x64] + os: [win32] + '@phosphor-icons/core@2.1.1': resolution: {integrity: sha512-v4ARvrip4qBCImOE5rmPUylOEK4iiED9ZyKjcvzuezqMaiRASCHKcRIuvvxL/twvLpkfnEODCOJp5dM4eZilxQ==} @@ -3199,6 +3298,9 @@ packages: '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + '@types/debug@4.1.13': + resolution: {integrity: sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==} + '@types/deep-eql@4.0.2': resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} @@ -3211,6 +3313,9 @@ packages: '@types/hast@3.0.5': resolution: {integrity: sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g==} + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + '@types/json-schema@7.0.15': resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} @@ -3220,9 +3325,15 @@ packages: '@types/mdast@4.0.4': resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + '@types/nlcst@2.0.3': resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==} + '@types/node@24.19.2': + resolution: {integrity: sha512-93d49wCWJkCyN87MxLpNN6KXPCwFw7bVUg79mGUV1OS40JGWwVCr8n61DXCSqz7CV6/VfPkb7xec7uP2M+QHWQ==} + '@types/node@26.6.5': resolution: {integrity: sha512-uMzsv9stDdofIZBx4UGh4Z9ZgXnGC9seRDscilBcTHAeOFk67MZg/k9bWVfO+bk4qZmG+l52hRnLwUiH2LnJTg==} @@ -3234,6 +3345,12 @@ packages: '@types/react@19.3.0': resolution: {integrity: sha512-N0rFCuH9YoxG9/m61l9MfpJKfmLOVU0em7ipIz6TRgSSkvReLB9vL85GB+yr8Bs5leqpvg96JSwF4ZS1s4viQg==} + '@types/sax@1.2.7': + resolution: {integrity: sha512-rO73L89PJxeYM3s3pPPjiPgVVcymqU490g0YO5n5By0k2Erzj6tay/4lr1CHAAU4JyOWd1rpQ8bCf6cZfHU96A==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + '@types/unist@3.0.3': resolution: {integrity: sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==} @@ -3426,6 +3543,9 @@ packages: resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} engines: {node: '>= 8'} + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -3440,6 +3560,11 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + astro-expressive-code@0.44.2: + resolution: {integrity: sha512-avop7nZcRwC7quvIxVguhwEZ9Tp6IoTzKUCLwP30Fb7+er+pWQgcysP01dcuzRIJo81kLGg30q/YAI2Yb1Sdzg==} + peerDependencies: + astro: ^4.0.0-beta || ^5.0.0-beta || ^3.3.0 || ^6.0.0-beta || ^7.0.0 + astro@7.3.6: resolution: {integrity: sha512-3q0MKxfE4cpr5IoQ9EWrN0gwxeT5tkCaSVe3SrGlLR5fnTIywHqjVqFokQkQiZexfwpNtB9dtBdUcXV9FuD+4w==} engines: {node: '>=22.12.0', npm: '>=9.6.5', pnpm: '>=7.1.0'} @@ -3457,6 +3582,12 @@ packages: bail@2.0.2: resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + bcp-47-match@2.0.3: + resolution: {integrity: sha512-JtTezzbAibu8G0R9op9zb3vcWZd9JF6M0xOYGPn0fNCd7wOpRB1mU2mH9T8gaBGbAAyIIVgB2G7xG0GP98zMAQ==} + + bcp-47@2.1.1: + resolution: {integrity: sha512-KLw+H/gd2p4zly1X7Yh/qziuyae5/w/QFnvTng9eZL5fvszL7Whl3MBoWF8yxL7ksUjBfOD+OxkytiqbBpG+Fw==} + better-auth@1.7.7: resolution: {integrity: sha512-Zhgxi/c5ylmfB/sX+nwnBbsFf/j6k0L8DvL6CJXZSrXgxc5pPMC/e7j812zNd4L4B4ELmdURCPw3X8nAOUdcjw==} peerDependencies: @@ -3555,6 +3686,12 @@ packages: character-entities-legacy@3.0.0: resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + chokidar@5.0.0: resolution: {integrity: sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==} engines: {node: '>= 20.19.0'} @@ -3609,6 +3746,9 @@ packages: css-select@6.0.0: resolution: {integrity: sha512-rZZVSLle8v0+EY8QAkDWrKhpgt6SA5OtHsgBnsj6ZaLb5dmDVOWUDtQitd9ydxxvEjhewNudS6eTVU7uOyzvXw==} + css-selector-parser@3.3.0: + resolution: {integrity: sha512-Y2asgMGFqJKF4fq4xHDSlFYIkeVfRsm69lQC1q9kbEsH5XtnINTMrweLkjYMeaUgiXBy/uvKeO/a1JHTNnmB2g==} + css-tree@2.2.1: resolution: {integrity: sha512-OA0mILzGc1kCOCSJerOeqDxDQ4HOh+G8NbOJFOTgOCzpw7fCBubk0fEyxp8AgOL/jvLgYA/uV0cMbe43ElF1JA==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} @@ -3624,6 +3764,11 @@ packages: css.escape@1.5.1: resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + csso@5.0.5: resolution: {integrity: sha512-0LrrStPOdJj+SPCCrGhzryycLjwcgUSHBtxNA8aIDxf0GLsRh1cKYhB00Gd1lDOS4yGH69+SNn13+TWbVHETFQ==} engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0, npm: '>=7.0.0'} @@ -3645,6 +3790,18 @@ packages: date-fns@4.4.0: resolution: {integrity: sha512-+1UMbeh68lH1SegH83CGWwpb6OHHbpSgr3+s5Eww5M4CAgswBpoWS0AjTOfEJ33HiYKz1hdj/KTFprzXHmq/6w==} + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + defu@6.1.7: resolution: {integrity: sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==} @@ -3669,6 +3826,10 @@ packages: resolution: {integrity: sha512-svtcdpS8CgJyqAjEQIXdb3OjhFVVYjzGAPO8WGCmRbrml64SPw/jJD4GoE98aR7r25A0XcgrK3F02yw9R/vhQw==} engines: {node: '>=0.3.1'} + direction@2.0.1: + resolution: {integrity: sha512-9S6m9Sukh1cZNknO1CWAr2QAWsbKLafQiyM5gZ7VgXHeuaoUwffKN4q6NC4A/Mf9iiPlOXQEKW/Mv/mh9/3YFA==} + hasBin: true + dom-accessibility-api@0.5.16: resolution: {integrity: sha512-X7BJ2yElsnOJ30pZF4uIIDfBEVgF4XEBxL9Bxhy6dnrm5hkzqmsWHGTiHqRiITNhMyFLyAiWndIJP7Z1NTteDg==} @@ -3748,6 +3909,9 @@ packages: resolution: {integrity: sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==} engines: {node: '>=12.0.0'} + expressive-code@0.44.2: + resolution: {integrity: sha512-MLNghkvhyFgVW4oCD5DxBuYnnTXIf0PygzGmfT6OZeOOYWh/D4sH1j931biElkA5RG/J9wY/iuSMGAPF5vzHOg==} + extend@3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} @@ -3830,12 +3994,48 @@ packages: resolution: {integrity: sha512-PesNgu5BnIEmAwzKGewB3K1ORFeznFZJ4JrRz89ea1wkHnxskdpzX9sN/+zb4KO6k8oSMhyJSj/cwXAFrYBBNA==} engines: {node: '>=20.0.0'} + hast-util-embedded@3.0.0: + resolution: {integrity: sha512-naH8sld4Pe2ep03qqULEtvYr7EjrLK2QHY8KJR6RJkTUjPGObe1vnx585uzem2hGra+s1q08DZZpfgDVYRbaXA==} + + hast-util-format@1.1.0: + resolution: {integrity: sha512-yY1UDz6bC9rDvCWHpx12aIBGRG7krurX0p0Fm6pT547LwDIZZiNr8a+IHDogorAdreULSEzP82Nlv5SZkHZcjA==} + + hast-util-has-property@3.0.0: + resolution: {integrity: sha512-MNilsvEKLFpV604hwfhVStK0usFY/QmM5zX16bo7EjnAEGofr5YyI37kzopBlZJkHD4t887i+q/C8/tr5Q94cA==} + + hast-util-is-body-ok-link@3.0.1: + resolution: {integrity: sha512-0qpnzOBLztXHbHQenVB8uNuxTnm/QBFUOmdOSsEn7GnBtyY07+ENTWVFBAnXd/zEgd9/SUG3lRY7hSIBWRgGpQ==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-minify-whitespace@1.0.1: + resolution: {integrity: sha512-L96fPOVpnclQE0xzdWb/D12VT5FabA7SnZOUMtL1DbXmYiHJMXZvFkIZfiMmTCNJHUeO2K9UYNXoVyfz+QHuOw==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-phrasing@3.0.1: + resolution: {integrity: sha512-6h60VfI3uBQUxHqTyMymMZnEbNl1XmEGtOxxKYL7stY2o601COo62AWAYBQR9lZbYXYSBoxag8UpPRXK+9fqSQ==} + + hast-util-select@6.0.4: + resolution: {integrity: sha512-RqGS1ZgI0MwxLaKLDxjprynNzINEkRHY2i8ln4DDjgv9ZhcYVIHN9rlpiYsqtFwrgpYU361SyWDQcGNIBVu3lw==} + hast-util-to-html@9.0.5: resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + hast-util-to-string@3.0.1: + resolution: {integrity: sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==} + + hast-util-to-text@4.0.2: + resolution: {integrity: sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==} + hast-util-whitespace@3.0.0: resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} + hastscript@9.0.1: + resolution: {integrity: sha512-g7df9rMFX/SPi34tyGCyUBREQoKkapwdY/T04Qn9TDWfHhAYt4/I0gMVirzK5wEzeUqIjEB+LXC/ypb7Aqno5w==} + hono@4.13.13: resolution: {integrity: sha512-CQ46U0ZkAGmbT/4UxdzzGJpacP2IeKgY4a5/tOI9AABbpOMfK739wfDXmv1usCk+3RkKj1hQy4/fjhiwa2xlrA==} engines: {node: '>=16.9.0'} @@ -3846,6 +4046,9 @@ packages: html-void-elements@3.0.0: resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + html-whitespace-sensitive-tag-names@3.0.1: + resolution: {integrity: sha512-q+310vW8zmymYHALr1da4HyXUQ0zgiIwIicEfotYPWGN0OJVEN/58IJ3A4GBYcEq3LGAZqKb+ugvP0GNB9CEAA==} + http-cache-semantics@4.3.0: resolution: {integrity: sha512-M5t5LlJpS1UHMjvwRQVdFHvPISGeLAxNcrWuJkeGh0KxsqCHZ1O3NXZU/8x7cD0BDcGW8kapxMKTvwlqrNkHkA==} @@ -3853,6 +4056,14 @@ packages: resolution: {integrity: sha512-Z+8unD6em/MJwyrkWQ2M3wygRGI1+MztbutiNwk1DCuEPXOZRWGjtcMPZYP1exY9iNZMhd7ZOSMQSo9WYtiQRw==} hasBin: true + i18next@26.5.3: + resolution: {integrity: sha512-6pRwOwJ6lBCBB66fqNZaSoUmvsALHMord7FL6GsDhubT1CrPabTMEJIqddm0nn2YEbKofdI/3VAtGrQPuztY1Q==} + peerDependencies: + typescript: ^5 || ^6 || ^7 + peerDependenciesMeta: + typescript: + optional: true + import-meta-resolve@4.2.0: resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} @@ -3867,11 +4078,23 @@ packages: iron-webcrypto@1.2.1: resolution: {integrity: sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==} + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + is-docker@4.0.0: resolution: {integrity: sha512-LHE+wROyG/Y/0ZnbktRCoTix2c1RhgWaZraMZ8o1Q7zCh0VSrICJQO5oqIIISrcSBtrXv0o233w1IYwsWCjTzA==} engines: {node: '>=20'} hasBin: true + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + is-plain-obj@4.1.0: resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} engines: {node: '>=12'} @@ -3919,6 +4142,10 @@ packages: resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} engines: {node: '>=6'} + klona@2.0.6: + resolution: {integrity: sha512-dhG34DXATL5hSxJbIexCft8FChFXtmskoZYnoPWjXQuebWYCNkVeV3KkGegCK9CP1oswI/vQibS2GY7Em/sJJA==} + engines: {node: '>= 8'} + kysely@0.29.6: resolution: {integrity: sha512-hHaB8C/rfzDDtr/t8YZwxAuPJTT0zHyaPoVzcXwDYhYNAgH/4sIfVhi/XLLIY+bL/FqaIJnjATDbi8ObSELmxg==} engines: {node: '>=22.0.0'} @@ -4080,6 +4307,9 @@ packages: lodash@4.18.1: resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + lru-cache@11.5.3: resolution: {integrity: sha512-U4N8FgzmWxc8k1VH8Kr6lQg18U7Fjvby6wXHVRX/ZZ7IwWbRMgrRbP0Wrb5q5NVinryp4SQampHKdvtecItxUg==} engines: {node: 20 || >=22} @@ -4107,30 +4337,99 @@ packages: engines: {node: '>= 20'} hasBin: true + mdast-util-directive@3.1.1: + resolution: {integrity: sha512-Gqpa3MHorXYEZ0VfbroyvBlVKUfz371V6mIERqOYMcb0W1D3LwmFq1ZW4O2TDrbn+4ZmGLwMWeFRJgYZ+bc5fQ==} + + mdast-util-from-markdown@2.1.0: + resolution: {integrity: sha512-KRLgmPaP2H1ul2bpoJkOS+9pAcDAAj9Nu1mqLnh8NLeJG5tlvj42TH80PIGBpROWlRXLk0lDrWNuZCFy/tsB4A==} + + mdast-util-phrasing@4.1.0: + resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + mdast-util-to-hast@13.2.1: resolution: {integrity: sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==} + mdast-util-to-markdown@2.2.0: + resolution: {integrity: sha512-2Jn/ADrHFFyyniBMi0bncTbJqU78K3/TEYvzvwrWWzD9MYPUa/Gm4r3nOeKFQg/PDM5BsbDYdc8ackW4NTY6NA==} + + mdast-util-to-string@4.0.0: + resolution: {integrity: sha512-0H44vDimn51F0YwvxSJSm0eCDOJTRlmN0R1yBh4HLj9wiV1Dn0QoXGbvFAWj2hSItVTlCmBF1hqKlIyUBVFLPg==} + mdn-data@2.0.28: resolution: {integrity: sha512-aylIc7Z9y4yzHYAJNuESG3hfhC+0Ibp/MAMiaOZgNv4pmEdFyfZhhhny4MNiAfWdBQ1RQ2mfDWmM1x8SvGyp8g==} mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + micromark-core-commonmark@2.0.4: + resolution: {integrity: sha512-wxEeE8v8XVvOrxn1TZj74qYhAtTQsSqufHVS3uNVyT1MupXxhyaHk8/9xDNPh9BmL77QNDgWTkZ3RsYHB2ry7w==} + + micromark-extension-directive@4.0.0: + resolution: {integrity: sha512-/C2nqVmXXmiseSSuCdItCMho7ybwwop6RrrRPk0KbOHW21JKoCldC+8rFOaundDoRBUWBnJJcxeA/Kvi34WQXg==} + + micromark-factory-destination@2.0.1: + resolution: {integrity: sha512-Xe6rDdJlkmbFRExpTOmRj9N3MaWmbAgdpSrBQvCFqhezUn4AHqJHbaEnfbVYYiexVSs//tqOdY/DxhjdCiJnIA==} + + micromark-factory-label@2.0.1: + resolution: {integrity: sha512-VFMekyQExqIW7xIChcXn4ok29YE3rnuyveW3wZQWWqF4Nv9Wk5rgJ99KzPvHjkmPXF93FXIbBp6YdW3t71/7Vg==} + + micromark-factory-space@2.1.0: + resolution: {integrity: sha512-fS8hnLIjnjvdQIj39Geug8wWsR0HrYZ43KShKxNfwT7t2LOHo/LbWZEzEaSOjwtjdCsjoE6syHhonEzW0zv0+Q==} + + micromark-factory-title@2.0.1: + resolution: {integrity: sha512-5bZ+3CjhAd9eChYTHsjy6TGxpOFSKgKKJPJxr293jTbfry2KDoWkhBb6TcPVB4NmzaPhMs1Frm9AZH7OD4Cjzw==} + + micromark-factory-whitespace@2.0.1: + resolution: {integrity: sha512-Ob0nuZ3PKt/n0hORHyvoD9uZhr+Za8sFoP+OnMcnWK5lngSzALgQYKMr9RJVOWLqQYuyn6ulqGWSXdwf6F80lQ==} + micromark-util-character@2.1.1: resolution: {integrity: sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==} + micromark-util-chunked@2.0.1: + resolution: {integrity: sha512-QUNFEOPELfmvv+4xiNg2sRYeS/P84pTW0TCgP5zc9FpXetHY0ab7SxKyAQCNCc1eK0459uoLI1y5oO5Vc1dbhA==} + + micromark-util-classify-character@2.0.1: + resolution: {integrity: sha512-K0kHzM6afW/MbeWYWLjoHQv1sgg2Q9EccHEDzSkxiP/EaagNzCm7T/WMKZ3rjMbvIpvBiZgwR3dKMygtA4mG1Q==} + + micromark-util-combine-extensions@2.0.1: + resolution: {integrity: sha512-OnAnH8Ujmy59JcyZw8JSbK9cGpdVY44NKgSM7E9Eh7DiLS2E9RNQf0dONaGDzEG9yjEl5hcqeIsj4hfRkLH/Bg==} + + micromark-util-decode-numeric-character-reference@2.0.2: + resolution: {integrity: sha512-ccUbYk6CwVdkmCQMyr64dXz42EfHGkPQlBj5p7YVGzq8I7CtjXZJrubAYezf7Rp+bjPseiROqe7G6foFd+lEuw==} + + micromark-util-decode-string@2.0.1: + resolution: {integrity: sha512-nDV/77Fj6eH1ynwscYTOsbK7rR//Uj0bZXBwJZRfaLEJ1iGBR6kIfNmlNqaqJf649EP0F3NWNdeJi03elllNUQ==} + + micromark-util-edit-map@1.0.0: + resolution: {integrity: sha512-Pa2ljlsEL6sVwFaYeyrOLSYbQt73JvGbPOYFq+9AElXiSnfI5Q4465RRZ1sHv7lDjDOnGRDDaqPXqClqWK/q1Q==} + micromark-util-encode@2.0.1: resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + micromark-util-html-tag-name@2.0.1: + resolution: {integrity: sha512-2cNEiYDhCWKI+Gs9T0Tiysk136SnR13hhO8yW6BGNyhOC4qYFnwF1nKfD3HFAIXA5c45RrIG1ub11GiXeYd1xA==} + + micromark-util-normalize-identifier@2.0.1: + resolution: {integrity: sha512-sxPqmo70LyARJs0w2UclACPUUEqltCkJ6PhKdMIDuJ3gSf/Q+/GIe3WKl0Ijb/GyH9lOpUkRAO2wp0GVkLvS9Q==} + + micromark-util-resolve-all@2.0.1: + resolution: {integrity: sha512-VdQyxFWFT2/FGJgwQnJYbe1jjQoNTS4RjglmSjTUlpUMa95Htx9NHeYW4rGDJzbjvCsl9eLjMQwGeElsqmzcHg==} + micromark-util-sanitize-uri@2.0.1: resolution: {integrity: sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==} + micromark-util-subtokenize@2.1.0: + resolution: {integrity: sha512-XQLu552iSctvnEcgXw6+Sx75GflAPNED1qx7eBJ+wydBb2KCbRZe+NwvIEEMM83uml1+2WSXpBAcp9IUCgCYWA==} + micromark-util-symbol@2.0.1: resolution: {integrity: sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==} micromark-util-types@2.0.3: resolution: {integrity: sha512-oxB2Ik03hI0gv+VNn9tnh1t1YEe9MDPptViAEgfdf3YQHsn0pzGTgCdlSCJXcwhqm8phaEuM7zeEu3QQzVBrPg==} + micromark@4.0.3: + resolution: {integrity: sha512-oGYfQzHSG5dOMovQcJ3fyTmZlWAWpi0XA0sJwJs+i6OT88o1+Jtw/8z0CmdowSLxGhhFK89rQ/oXph/wN02PNw==} + min-indent@1.0.1: resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} engines: {node: '>=4'} @@ -4171,6 +4470,9 @@ packages: resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} engines: {node: '>=10'} + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + nanoid@3.3.20: resolution: {integrity: sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} @@ -4242,6 +4544,13 @@ packages: package-manager-detector@1.9.0: resolution: {integrity: sha512-zAMfbta9nPA8AKkA4WdmNAT8R8s6Dk0/xwPsqNnRidMFcLo1YkLuPDTgs2f64XdzS8vbLNnoJ+uUIkgbz7oRHA==} + pagefind@1.5.2: + resolution: {integrity: sha512-XTUaK0hXMCu2jszWE584JGQT7y284TmMV9l/HX3rnG5uo3rHI/uHU56XTyyyPFjeWEBxECbAi0CaFDJOONtG0Q==} + hasBin: true + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + parse5@8.0.1: resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} @@ -4273,6 +4582,16 @@ packages: resolution: {integrity: sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==} engines: {node: '>=16.20.0'} + postcss-nested@6.2.0: + resolution: {integrity: sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.2.14 + + postcss-selector-parser@6.1.5: + resolution: {integrity: sha512-TEHFzb5KRDKaOgJ4TYE+0jyJtFbjLvkdVOu0fZ2b6MTWzoqZd4Le+61aEppWkvTlYePgOX4ATL1wsAIPMLTw6Q==} + engines: {node: '>=4'} + postcss@8.5.29: resolution: {integrity: sha512-49cGhUbXj8Qenv0iTMxA1cFBzxXoctpC9Ujd77t1WcbJIr6nF/eI7g/8MgxrYldFRuAXvja7xQRwavoW7kgrxQ==} engines: {node: ^10 || ^12 || >=14} @@ -4374,6 +4693,12 @@ packages: regex@6.1.0: resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + rehype-expressive-code@0.44.2: + resolution: {integrity: sha512-jvjQpsfWKBquDrEl5YQA3CJybRRIuDOYsui1BJTs5oKDUK8pPkJSSxY8FjxNlIFIdtgrTjC1zuahuY4Cc1j9SQ==} + + remark-directive@4.0.0: + resolution: {integrity: sha512-7sxn4RfF1o3izevPV1DheyGDD6X4c9hrGpfdUpm7uC++dqrnJxIZVkk7CoKqcLm0VUMAuOol7Mno3m6g8cfMuA==} + require-from-string@2.0.2: resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} engines: {node: '>=0.10.0'} @@ -4470,10 +4795,19 @@ packages: sisteransi@1.0.5: resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + sitemap@9.0.1: + resolution: {integrity: sha512-S6hzjGJSG3d6if0YoF5kTyeRJvia6FSTBroE5fQ0bu1QNxyJqhhinfUsXi9fH3MgtXODWvwo2BDyQSnhPQ88uQ==} + engines: {node: '>=20.19.5', npm: '>=10.8.2'} + hasBin: true + smol-toml@1.9.0: resolution: {integrity: sha512-hpd+HLON7HdZXqYchMM/+LaTTbdK0AU3NngIJ4KVyWbY9bfQqdL9cD+4yf6dUoU2Ap4VsU0JkQi6FxAI1B2mXQ==} engines: {node: '>= 18'} + smol-toml@1.9.1: + resolution: {integrity: sha512-j7WyPdf0CJoutMzMoPLF/bVXHtKZIT9Rb4eGeEbWw8X2t5oirjPgpL+OpaENZrILYaXqYwnVH+qgUwVbl+pUJQ==} + engines: {node: '>= 18'} + source-map-js@1.2.2: resolution: {integrity: sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==} engines: {node: '>=0.10.0'} @@ -4573,6 +4907,9 @@ packages: uncrypto@0.1.3: resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + undici-types@8.9.0: resolution: {integrity: sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg==} @@ -4597,6 +4934,9 @@ packages: unifont@0.7.5: resolution: {integrity: sha512-ULe/Cs+ZIsq+dcFofNkhqielCrUJnb5mr+Yc4EBM2VlL+6OZR6+cjtI2mT1bJvRBrVncqHAbLURxmPLcCXzWMg==} + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + unist-util-is@6.0.1: resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} @@ -4674,11 +5014,18 @@ packages: uploadthing: optional: true + url-extras@0.1.0: + resolution: {integrity: sha512-8tzwTeXFPuX/5PHuCDQE5Dd9Ts4rwoq2t9aIT+HS4iAVpmj5l4Ao7Q+BuuFjvWRqrLswBhQDk8O96ZicgCqQqw==} + engines: {node: '>=20'} + use-sync-external-store@1.7.0: resolution: {integrity: sha512-6L+EeigHMQhdaIPNIFUKwfWJSwWFQ8gJbJ2DLOs5sDIegTwR9fRxvnM3uciHKjIZhFz+KAv2emhWMRvDmMcY8A==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + verkit@0.5.0: resolution: {integrity: sha512-u4J1hr6Y+C/hit34GwL3eIcUf3wngT8TlYUjK7tk0jxydKLAq3sYvifjdTDA6U3KCKRcQCQDGf/srjxfZF3l7g==} engines: {node: '>=18.12.0'} @@ -4973,6 +5320,17 @@ snapshots: unified: 11.0.5 yaml: 2.9.1 + '@astrojs/internal-helpers@0.13.0': + dependencies: + '@types/hast': 3.0.5 + '@types/mdast': 4.0.4 + picomatch: 4.0.7 + retext-smartypants: 6.2.0 + shiki: 4.5.0 + smol-toml: 1.9.1 + unified: 11.0.5 + yaml: 2.9.1 + '@astrojs/markdown-satteri@0.4.3': dependencies: '@astrojs/internal-helpers': 0.12.0 @@ -4980,10 +5338,57 @@ snapshots: github-slugger: 2.0.0 satteri: 0.10.5 + '@astrojs/mdx@8.1.0(@astrojs/markdown-satteri@0.4.3)(astro@7.3.6(@types/node@26.6.5)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))': + dependencies: + '@astrojs/internal-helpers': 0.13.0 + '@astrojs/markdown-satteri': 0.4.3 + astro: 7.3.6(@types/node@26.6.5)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) + es-module-lexer: 3.0.3 + '@astrojs/prism@4.0.2': dependencies: prismjs: 1.30.0 + '@astrojs/sitemap@3.7.4': + dependencies: + sitemap: 9.0.1 + zod: 4.6.5 + + '@astrojs/starlight@0.42.6(astro@7.3.6(@types/node@26.6.5)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))(supports-color@10.2.2)(typescript@7.0.2)': + dependencies: + '@astrojs/markdown-satteri': 0.4.3 + '@astrojs/mdx': 8.1.0(@astrojs/markdown-satteri@0.4.3)(astro@7.3.6(@types/node@26.6.5)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + '@astrojs/sitemap': 3.7.4 + '@pagefind/default-ui': 1.5.2 + '@types/hast': 3.0.5 + '@types/js-yaml': 4.0.9 + '@types/mdast': 4.0.4 + astro: 7.3.6(@types/node@26.6.5)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) + astro-expressive-code: 0.44.2(astro@7.3.6(@types/node@26.6.5)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)) + bcp-47: 2.1.1 + hast-util-format: 1.1.0 + hast-util-select: 6.0.4 + hast-util-to-html: 9.0.5 + hast-util-to-string: 3.0.1 + hastscript: 9.0.1 + i18next: 26.5.3(typescript@7.0.2) + js-yaml: 4.3.2 + klona: 2.0.6 + magic-string: 1.4.3 + mdast-util-directive: 3.1.1(supports-color@10.2.2) + mdast-util-to-markdown: 2.2.0 + mdast-util-to-string: 4.0.0 + pagefind: 1.5.2 + remark-directive: 4.0.0(supports-color@10.2.2) + satteri: 0.10.5 + ultrahtml: 1.7.0 + unified: 11.0.5 + unist-util-visit: 5.1.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + - typescript + '@astrojs/telemetry@3.3.3': dependencies: ci-info: 4.4.0 @@ -5449,6 +5854,8 @@ snapshots: dependencies: '@jridgewell/trace-mapping': 0.3.9 + '@ctrl/tinycolor@4.2.1': {} + '@date-fns/tz@1.5.0': {} '@emnapi/core@1.11.1': @@ -5639,6 +6046,31 @@ snapshots: '@esbuild/win32-x64@0.28.2': optional: true + '@expressive-code/core@0.44.2': + dependencies: + '@ctrl/tinycolor': 4.2.1 + hast-util-select: 6.0.4 + hast-util-to-html: 9.0.5 + hast-util-to-text: 4.0.2 + hastscript: 9.0.1 + postcss: 8.5.29 + postcss-nested: 6.2.0(postcss@8.5.29) + unist-util-visit: 5.1.0 + unist-util-visit-parents: 6.0.2 + + '@expressive-code/plugin-frames@0.44.2': + dependencies: + '@expressive-code/core': 0.44.2 + + '@expressive-code/plugin-shiki@0.44.2': + dependencies: + '@expressive-code/core': 0.44.2 + shiki: 4.5.0 + + '@expressive-code/plugin-text-markers@0.44.2': + dependencies: + '@expressive-code/core': 0.44.2 + '@floating-ui/core@1.8.0': dependencies: '@floating-ui/utils': 0.2.12 @@ -6093,6 +6525,29 @@ snapshots: '@oxc-project/types@0.152.0': {} + '@pagefind/darwin-arm64@1.5.2': + optional: true + + '@pagefind/darwin-x64@1.5.2': + optional: true + + '@pagefind/default-ui@1.5.2': {} + + '@pagefind/freebsd-x64@1.5.2': + optional: true + + '@pagefind/linux-arm64@1.5.2': + optional: true + + '@pagefind/linux-x64@1.5.2': + optional: true + + '@pagefind/windows-arm64@1.5.2': + optional: true + + '@pagefind/windows-x64@1.5.2': + optional: true + '@phosphor-icons/core@2.1.1': {} '@phosphor-icons/react@2.1.10(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': @@ -6574,6 +7029,10 @@ snapshots: '@types/deep-eql': 4.0.2 assertion-error: 2.0.1 + '@types/debug@4.1.13': + dependencies: + '@types/ms': 2.1.0 + '@types/deep-eql@4.0.2': {} '@types/estree-jsx@1.0.5': @@ -6586,6 +7045,8 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/js-yaml@4.0.9': {} + '@types/json-schema@7.0.15': {} '@types/lodash@4.17.25': {} @@ -6594,10 +7055,16 @@ snapshots: dependencies: '@types/unist': 3.0.3 + '@types/ms@2.1.0': {} + '@types/nlcst@2.0.3': dependencies: '@types/unist': 3.0.3 + '@types/node@24.19.2': + dependencies: + undici-types: 7.24.6 + '@types/node@26.6.5': dependencies: undici-types: 8.9.0 @@ -6610,6 +7077,12 @@ snapshots: dependencies: csstype: 3.2.3 + '@types/sax@1.2.7': + dependencies: + '@types/node': 26.6.5 + + '@types/unist@2.0.11': {} + '@types/unist@3.0.3': {} '@types/use-sync-external-store@0.0.6': {} @@ -6722,6 +7195,8 @@ snapshots: normalize-path: 3.0.0 picomatch: 2.3.2 + arg@5.0.2: {} + argparse@2.0.1: {} aria-query@5.3.0: @@ -6732,6 +7207,12 @@ snapshots: assertion-error@2.0.1: {} + astro-expressive-code@0.44.2(astro@7.3.6(@types/node@26.6.5)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1)): + dependencies: + astro: 7.3.6(@types/node@26.6.5)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1) + rehype-expressive-code: 0.44.2 + url-extras: 0.1.0 + astro@7.3.6(@types/node@26.6.5)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1): dependencies: '@astrojs/compiler-rs': 0.5.1 @@ -6825,6 +7306,14 @@ snapshots: bail@2.0.2: {} + bcp-47-match@2.0.3: {} + + bcp-47@2.1.1: + dependencies: + is-alphabetical: 2.0.1 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + better-auth@1.7.7(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(vitest@5.0.3(@types/node@26.6.5)(happy-dom@21.0.3)(vite@8.3.3(@types/node@26.6.5)(esbuild@0.28.2)(jiti@2.7.0)(tsx@4.23.15)(yaml@2.9.1))): dependencies: '@better-auth/core': 1.7.7(@better-auth/utils@0.4.2)(@better-fetch/fetch@1.3.2)(better-call@1.4.0(zod@4.6.5))(jose@6.2.12)(kysely@0.29.6)(nanostores@1.5.5) @@ -6882,6 +7371,10 @@ snapshots: character-entities-legacy@3.0.0: {} + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + chokidar@5.0.0: dependencies: readdirp: 5.1.1 @@ -6926,6 +7419,8 @@ snapshots: domutils: 3.2.2 nth-check: 2.1.1 + css-selector-parser@3.3.0: {} + css-tree@2.2.1: dependencies: mdn-data: 2.0.28 @@ -6940,6 +7435,8 @@ snapshots: css.escape@1.5.1: {} + cssesc@3.0.0: {} + csso@5.0.5: dependencies: css-tree: 2.2.1 @@ -6958,6 +7455,16 @@ snapshots: date-fns@4.4.0: {} + debug@4.4.3(supports-color@10.2.2): + dependencies: + ms: 2.1.3 + optionalDependencies: + supports-color: 10.2.2 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + defu@6.1.7: {} dequal@2.0.3: {} @@ -6974,6 +7481,8 @@ snapshots: diff@9.0.0: {} + direction@2.0.1: {} + dom-accessibility-api@0.5.16: {} dom-accessibility-api@0.6.3: {} @@ -7092,6 +7601,13 @@ snapshots: expect-type@1.4.0: {} + expressive-code@0.44.2: + dependencies: + '@expressive-code/core': 0.44.2 + '@expressive-code/plugin-frames': 0.44.2 + '@expressive-code/plugin-shiki': 0.44.2 + '@expressive-code/plugin-text-markers': 0.44.2 + extend@3.0.2: {} fast-deep-equal@3.1.3: {} @@ -7171,6 +7687,71 @@ snapshots: - bufferutil - utf-8-validate + hast-util-embedded@3.0.0: + dependencies: + '@types/hast': 3.0.5 + hast-util-is-element: 3.0.0 + + hast-util-format@1.1.0: + dependencies: + '@types/hast': 3.0.5 + hast-util-embedded: 3.0.0 + hast-util-minify-whitespace: 1.0.1 + hast-util-phrasing: 3.0.1 + hast-util-whitespace: 3.0.0 + html-whitespace-sensitive-tag-names: 3.0.1 + unist-util-visit-parents: 6.0.2 + + hast-util-has-property@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-is-body-ok-link@3.0.1: + dependencies: + '@types/hast': 3.0.5 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-minify-whitespace@1.0.1: + dependencies: + '@types/hast': 3.0.5 + hast-util-embedded: 3.0.0 + hast-util-is-element: 3.0.0 + hast-util-whitespace: 3.0.0 + unist-util-is: 6.0.1 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.5 + + hast-util-phrasing@3.0.1: + dependencies: + '@types/hast': 3.0.5 + hast-util-embedded: 3.0.0 + hast-util-has-property: 3.0.0 + hast-util-is-body-ok-link: 3.0.1 + hast-util-is-element: 3.0.0 + + hast-util-select@6.0.4: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + bcp-47-match: 2.0.3 + comma-separated-tokens: 2.0.3 + css-selector-parser: 3.3.0 + devlop: 1.1.0 + direction: 2.0.1 + hast-util-has-property: 3.0.0 + hast-util-to-string: 3.0.1 + hast-util-whitespace: 3.0.0 + nth-check: 2.1.1 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + hast-util-to-html@9.0.5: dependencies: '@types/hast': 3.0.5 @@ -7185,20 +7766,45 @@ snapshots: stringify-entities: 4.0.4 zwitch: 2.0.4 + hast-util-to-string@3.0.1: + dependencies: + '@types/hast': 3.0.5 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.5 + '@types/unist': 3.0.3 + hast-util-is-element: 3.0.0 + unist-util-find-after: 5.0.0 + hast-util-whitespace@3.0.0: dependencies: '@types/hast': 3.0.5 + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.5 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.2.0 + space-separated-tokens: 2.0.2 + hono@4.13.13: {} html-escaper@3.0.3: {} html-void-elements@3.0.0: {} + html-whitespace-sensitive-tag-names@3.0.1: {} + http-cache-semantics@4.3.0: {} human-id@4.2.2: {} + i18next@26.5.3(typescript@7.0.2): + optionalDependencies: + typescript: 7.0.2 + import-meta-resolve@4.2.0: {} indent-string@4.0.0: {} @@ -7207,8 +7813,19 @@ snapshots: iron-webcrypto@1.2.1: {} + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-decimal@2.0.1: {} + is-docker@4.0.0: {} + is-hexadecimal@2.0.1: {} + is-plain-obj@4.1.0: {} isbot@5.2.2: {} @@ -7248,6 +7865,8 @@ snapshots: kleur@4.1.5: {} + klona@2.0.6: {} + kysely@0.29.6: {} launch-editor@2.14.2: @@ -7357,6 +7976,8 @@ snapshots: lodash@4.18.1: {} + longest-streak@3.1.0: {} + lru-cache@11.5.3: {} lz-string@1.5.0: {} @@ -7379,6 +8000,43 @@ snapshots: marked@18.1.0: {} + mdast-util-directive@3.1.1(supports-color@10.2.2): + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.1.0(supports-color@10.2.2) + mdast-util-to-markdown: 2.2.0 + micromark-util-character: 2.1.1 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-visit-parents: 6.0.2 + transitivePeerDependencies: + - supports-color + + mdast-util-from-markdown@2.1.0(supports-color@10.2.2): + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.3(supports-color@10.2.2) + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-decode-string: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@4.1.0: + dependencies: + '@types/mdast': 4.0.4 + unist-util-is: 6.0.1 + mdast-util-to-hast@13.2.1: dependencies: '@types/hast': 3.0.5 @@ -7391,27 +8049,177 @@ snapshots: unist-util-visit: 5.1.0 vfile: 6.0.3 + mdast-util-to-markdown@2.2.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + longest-streak: 3.1.0 + mdast-util-phrasing: 4.1.0 + mdast-util-to-string: 4.0.0 + micromark-util-character: 2.1.1 + micromark-util-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + micromark-util-html-tag-name: 2.0.1 + unist-util-visit: 5.1.0 + zwitch: 2.0.4 + + mdast-util-to-string@4.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdn-data@2.0.28: {} mdn-data@2.27.1: {} + micromark-core-commonmark@2.0.4: + dependencies: + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.1.0 + micromark-factory-title: 2.0.1 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-edit-map: 1.0.0 + micromark-util-html-tag-name: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-extension-directive@4.0.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.1.0 + micromark-factory-whitespace: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + parse-entities: 4.0.2 + + micromark-factory-destination@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-factory-label@2.0.1: + dependencies: + devlop: 1.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-factory-space@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.3 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + micromark-util-character@2.1.1: dependencies: micromark-util-symbol: 2.0.1 micromark-util-types: 2.0.3 + micromark-util-chunked@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-classify-character@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.3 + + micromark-util-decode-numeric-character-reference@2.0.2: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-decode-string@2.0.1: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-edit-map@1.0.0: + dependencies: + micromark-util-types: 2.0.3 + micromark-util-encode@2.0.1: {} + micromark-util-html-tag-name@2.0.1: {} + + micromark-util-normalize-identifier@2.0.1: + dependencies: + micromark-util-symbol: 2.0.1 + + micromark-util-resolve-all@2.0.1: + dependencies: + micromark-util-types: 2.0.3 + micromark-util-sanitize-uri@2.0.1: dependencies: micromark-util-character: 2.1.1 micromark-util-encode: 2.0.1 micromark-util-symbol: 2.0.1 + micromark-util-subtokenize@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + micromark-util-symbol@2.0.1: {} micromark-util-types@2.0.3: {} + micromark@4.0.3(supports-color@10.2.2): + dependencies: + '@types/debug': 4.1.13 + debug: 4.4.3(supports-color@10.2.2) + decode-named-character-reference: 1.3.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.4 + micromark-factory-space: 2.1.0 + micromark-util-character: 2.1.1 + micromark-util-chunked: 2.0.1 + micromark-util-combine-extensions: 2.0.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-edit-map: 1.0.0 + micromark-util-encode: 2.0.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-subtokenize: 2.1.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.3 + transitivePeerDependencies: + - supports-color + min-indent@1.0.1: {} miniflare@4.20260730.0: @@ -7457,6 +8265,8 @@ snapshots: mrmime@2.0.1: {} + ms@2.1.3: {} + nanoid@3.3.20: {} nanostores@1.5.5: {} @@ -7514,6 +8324,26 @@ snapshots: package-manager-detector@1.9.0: {} + pagefind@1.5.2: + optionalDependencies: + '@pagefind/darwin-arm64': 1.5.2 + '@pagefind/darwin-x64': 1.5.2 + '@pagefind/freebsd-x64': 1.5.2 + '@pagefind/linux-arm64': 1.5.2 + '@pagefind/linux-x64': 1.5.2 + '@pagefind/windows-arm64': 1.5.2 + '@pagefind/windows-x64': 1.5.2 + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + parse5@8.0.1: dependencies: entities: 8.1.0 @@ -7534,6 +8364,16 @@ snapshots: pkce-challenge@5.0.1: {} + postcss-nested@6.2.0(postcss@8.5.29): + dependencies: + postcss: 8.5.29 + postcss-selector-parser: 6.1.5 + + postcss-selector-parser@6.1.5: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + postcss@8.5.29: dependencies: nanoid: 3.3.20 @@ -7664,6 +8504,19 @@ snapshots: dependencies: regex-utilities: 2.3.0 + rehype-expressive-code@0.44.2: + dependencies: + expressive-code: 0.44.2 + + remark-directive@4.0.0(supports-color@10.2.2): + dependencies: + '@types/mdast': 4.0.4 + mdast-util-directive: 3.1.1(supports-color@10.2.2) + micromark-extension-directive: 4.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + require-from-string@2.0.2: {} reselect@5.3.0: {} @@ -7856,8 +8709,17 @@ snapshots: sisteransi@1.0.5: {} + sitemap@9.0.1: + dependencies: + '@types/node': 24.19.2 + '@types/sax': 1.2.7 + arg: 5.0.2 + sax: 1.6.1 + smol-toml@1.9.0: {} + smol-toml@1.9.1: {} + source-map-js@1.2.2: {} space-separated-tokens@2.0.2: {} @@ -7965,6 +8827,8 @@ snapshots: uncrypto@0.1.3: {} + undici-types@7.24.6: {} + undici-types@8.9.0: {} undici@7.28.0: {} @@ -7993,6 +8857,11 @@ snapshots: ohash: 2.0.12 undici: 8.11.2 + unist-util-find-after@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-is@6.0.1: dependencies: '@types/unist': 3.0.3 @@ -8027,10 +8896,14 @@ snapshots: ofetch: 1.5.1 ufo: 1.6.4 + url-extras@0.1.0: {} + use-sync-external-store@1.7.0(react@19.3.0): dependencies: react: 19.3.0 + util-deprecate@1.0.2: {} + verkit@0.5.0: {} vfile-message@4.0.3: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 4fabbdaf..5afbf70a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -25,6 +25,7 @@ catalog: "@cloudflare/workers-types": ^5.20261007.1 astro: ^7.3.6 "@astrojs/cloudflare": 14.3.4 + "@astrojs/starlight": 0.42.6 wrangler: 4.148.0 "@tailwindcss/vite": ^4.3.3 tailwindcss: ^4.3.3