diff --git a/.gitignore b/.gitignore index c15b86ccb..8322b349a 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,7 @@ monkeytype.sqlite3 **/CLAUDE.local.md **/CLAUDE.*.md **/.claude/settings.local.json + +# Allow Astro app helpers (lib/ is ignored globally) +!astro/apps/docs/src/lib/ +!astro/apps/docs/src/lib/** diff --git a/astro/.gitignore b/astro/.gitignore new file mode 100644 index 000000000..0d1951c6e --- /dev/null +++ b/astro/.gitignore @@ -0,0 +1,100 @@ +### +### Sphinx +### + +# macOS +.DS_Store + +.env +*.pyc +*.tar.gz +*.egg-info + +_build + +# ignore virtualenvs +/*env*/ + +# npm +node_modules/ + +# editors +.vim/ +.vscode/ + +# cache +.mypy_cache/ + +**/.claude/settings.local.json + +### +### Node.js +### + +# Dependencies +node_modules/ +.pnpm-store/ + +# Build outputs +dist/ +.vite-react-ssg-temp/ +.vite/ +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? + +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +# OS files +.DS_Store +Thumbs.db + +# Environment variables +.env +.env.local +.env.*.local + +# TypeScript +*.tsbuildinfo + +# Generated files +routeTree.gen.ts +.tanstack/ + +# Astro +.astro/ + +# Testing +coverage/ +.nyc_output/ + +# Temporary files +.tmp/ +temp/ + +# MyST build files +_build/ +.myst/ + +# Build analytics +build-analytics.json + +# Playwright +playwright-report/ +.playwright-mcp/ diff --git a/astro/AGENTS.md b/astro/AGENTS.md new file mode 100644 index 000000000..47b9a36a0 --- /dev/null +++ b/astro/AGENTS.md @@ -0,0 +1,73 @@ +# AGENTS.md + +## Project Context + +Astro-based documentation monorepo for libtmux. It replaces the existing Sphinx site with a static-first Astro site that renders API docs by scanning Python source with a TypeScript toolchain and a small Python AST helper. + +## Tech Stack + +- **Framework**: Astro v5 (islands) + optional React for hydration +- **Styling**: Tailwind CSS v4 +- **Language**: TypeScript (strict) +- **Testing**: Vitest +- **Quality**: Biome (lint/format) +- **Package Manager**: pnpm 10.x workspaces +- **Python tooling**: uv/uvx + Python AST for API scanning + +## Architecture Map + +``` +packages/ +├── schema/ # Zod contracts (schema firewall) +├── py-bridge/ # uv/uvx subprocess boundary +├── py-parse/ # Static AST parsing +├── py-introspect/ # Runtime introspection +├── api-model/ # High-level API model + snapshots +├── core/ # Orchestration + caching +├── intersphinx/ # Intersphinx inventory parser +├── astro-autodoc/ # Astro components + helpers for API rendering +└── astro-intersphinx/ # Astro helpers for intersphinx resolution + +apps/ +└── docs/ # Astro docs site for libtmux + +python/ +└── pyautodoc_sidecar/ # Python sidecar invoked via uv run +``` + +## Workflow Commands + +```bash +pnpm install +``` + +```bash +pnpm start +``` + +```bash +pnpm type-check +``` + +```bash +pnpm biome-all +``` + +```bash +pnpm test +``` + +```bash +pnpm update:all +``` + +```bash +pnpm ncu +``` + +## Hard Constraints + +1. **Monorepo layering**: core packages must not depend on astro packages. +2. **Autodoc data**: use Zod schemas for validated data flow between packages. +3. **Python scanning**: default to `uv run` with a sidecar project; allow an override for local Python executables and optional `uvx` tool mode. +4. **Astro hydration**: any React components must include an explicit client directive. diff --git a/astro/README.md b/astro/README.md new file mode 100644 index 000000000..bd7c2e549 --- /dev/null +++ b/astro/README.md @@ -0,0 +1,64 @@ +# libtmux docs (Astro) + +Astro-based documentation monorepo for libtmux. It replaces the Sphinx site with a static-first site and a TypeScript-driven API doc pipeline that scans Python source via a Python sidecar. + +## Packages + +- `packages/schema`: Zod schema firewall +- `packages/py-bridge`: Python subprocess bridge (uv run / uvx) +- `packages/py-parse`: Python AST parser +- `packages/py-introspect`: Runtime introspection wrapper +- `packages/api-model`: High-level API model built from scan results +- `packages/core`: Orchestration + caching +- `packages/intersphinx`: Sphinx intersphinx inventory parser +- `packages/astro-autodoc`: Astro components for API docs +- `packages/astro-intersphinx`: Astro helpers for intersphinx links +- `apps/docs`: The docs website + +## Setup + +Install dependencies: + +```bash +pnpm install +``` + +## Development + +Start the docs site: + +```bash +pnpm start +``` + +## Quality checks + +Run the type checker: + +```bash +pnpm type-check +``` + +Run the linter: + +```bash +pnpm biome-all +``` + +Run tests: + +```bash +pnpm test +``` + +## Updating dependencies + +Update all dependencies with npm-check-updates: + +```bash +pnpm update:all +``` + +## Python scanning + +The sidecar defaults to `uv run python` and can be overridden with `LIBTMUX_PYTHON_COMMAND`. diff --git a/astro/apps/docs/astro.config.ts b/astro/apps/docs/astro.config.ts new file mode 100644 index 000000000..617fd2459 --- /dev/null +++ b/astro/apps/docs/astro.config.ts @@ -0,0 +1,15 @@ +import mdx from '@astrojs/mdx' +import react from '@astrojs/react' +import tailwind from '@tailwindcss/vite' +import { defineConfig } from 'astro/config' + +export default defineConfig({ + site: 'https://libtmux.git-pull.com', + integrations: [react(), mdx()], + server: { + port: 4350, + }, + vite: { + plugins: [tailwind()], + }, +}) diff --git a/astro/apps/docs/package.json b/astro/apps/docs/package.json new file mode 100644 index 000000000..e4f289f92 --- /dev/null +++ b/astro/apps/docs/package.json @@ -0,0 +1,49 @@ +{ + "name": "@libtmux/docs-site", + "version": "0.0.1", + "type": "module", + "description": "Astro docs site for libtmux", + "scripts": { + "dev": "astro dev", + "start": "astro dev", + "build": "astro build", + "preview": "astro preview", + "astro": "astro", + "lint": "biome lint .", + "format": "pnpm biome check . --write; biome format . --write", + "type-check": "astro check && tsc --noEmit", + "test": "vitest run", + "test:watch": "vitest", + "test:ui": "vitest --ui", + "test:coverage": "vitest run --coverage", + "ncu": "ncu", + "update": "pnpm update", + "clean": "rm -rf dist/ node_modules/.cache/", + "biome": "biome check . --write && biome format . --write" + }, + "dependencies": { + "@astrojs/mdx": "^4.3.13", + "@astrojs/react": "^4.4.2", + "@fontsource/fraunces": "^5.2.9", + "@fontsource/ibm-plex-mono": "^5.2.7", + "@fontsource/space-grotesk": "^5.2.10", + "@libtmux/api-model": "workspace:*", + "@libtmux/astro-autodoc": "workspace:*", + "@libtmux/astro-intersphinx": "workspace:*", + "@libtmux/intersphinx": "workspace:*", + "@libtmux/py-parse": "workspace:*", + "@tailwindcss/typography": "^0.5.19", + "@tailwindcss/vite": "^4.1.18", + "astro": "^5.16.6", + "react": "^19.2.3", + "react-dom": "^19.2.3", + "tailwindcss": "^4.1.18" + }, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@astrojs/check": "^0.9.6", + "@types/node": "^25.0.3", + "typescript": "^5.9.3", + "vitest": "^4.0.16" + } +} diff --git a/astro/apps/docs/src/components/SiteFooter.astro b/astro/apps/docs/src/components/SiteFooter.astro new file mode 100644 index 000000000..d6c588cb4 --- /dev/null +++ b/astro/apps/docs/src/components/SiteFooter.astro @@ -0,0 +1,14 @@ + diff --git a/astro/apps/docs/src/components/SiteNav.astro b/astro/apps/docs/src/components/SiteNav.astro new file mode 100644 index 000000000..0954860e0 --- /dev/null +++ b/astro/apps/docs/src/components/SiteNav.astro @@ -0,0 +1,42 @@ +--- +const navLinks = [ + { label: 'Overview', href: '/' }, + { label: 'Guides', href: '/guides' }, + { label: 'API', href: '/api' }, + { label: 'Architecture', href: '/architecture' }, +] +--- + +
+
+ + libtmux + Docs + + + +
+
+ {navLinks.map((link) => ( + + {link.label} + + ))} +
+
diff --git a/astro/apps/docs/src/env.d.ts b/astro/apps/docs/src/env.d.ts new file mode 100644 index 000000000..283b70b83 --- /dev/null +++ b/astro/apps/docs/src/env.d.ts @@ -0,0 +1,4 @@ +declare module '*.astro' { + const Component: (props: Record) => unknown + export default Component +} diff --git a/astro/apps/docs/src/layouts/BaseLayout.astro b/astro/apps/docs/src/layouts/BaseLayout.astro new file mode 100644 index 000000000..134c6bff8 --- /dev/null +++ b/astro/apps/docs/src/layouts/BaseLayout.astro @@ -0,0 +1,31 @@ +--- +import '@fontsource/space-grotesk/400.css' +import '@fontsource/space-grotesk/600.css' +import '@fontsource/fraunces/400.css' +import '@fontsource/fraunces/600.css' +import '@fontsource/ibm-plex-mono/400.css' +import '../styles/global.css' +import SiteFooter from '../components/SiteFooter.astro' +import SiteNav from '../components/SiteNav.astro' + +const { title, description } = Astro.props +const pageTitle = title ? `${title} | libtmux docs` : 'libtmux docs' +const pageDescription = description ?? 'Typed Python ORM for tmux.' +--- + + + + + + + {pageTitle} + + + + +
+ +
+ + + diff --git a/astro/apps/docs/src/lib/api.ts b/astro/apps/docs/src/lib/api.ts new file mode 100644 index 000000000..bb8ac65ad --- /dev/null +++ b/astro/apps/docs/src/lib/api.ts @@ -0,0 +1,39 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import type { ApiPackage } from '@libtmux/api-model' +import { loadApiPackage } from '@libtmux/astro-autodoc' + +let cached: ApiPackage | null = null + +const resolveRepoRoot = (): string => { + const fromEnv = process.env.LIBTMUX_REPO_ROOT + if (fromEnv) { + return fromEnv + } + + const here = path.dirname(fileURLToPath(import.meta.url)) + return path.resolve(here, '../../../../../') +} + +export const getApiModel = async (): Promise => { + if (cached) { + return cached + } + + const repoRoot = resolveRepoRoot() + const sourceRoot = path.join(repoRoot, 'src') + const packageRoot = path.join(sourceRoot, 'libtmux') + + cached = await loadApiPackage({ + name: 'libtmux', + root: sourceRoot, + paths: [packageRoot], + introspect: true, + introspectPackage: 'libtmux', + annotationFormat: 'string', + mockImports: ['pytest'], + docstringRenderer: 'rst-lite', + }) + + return cached +} diff --git a/astro/apps/docs/src/pages/api/index.astro b/astro/apps/docs/src/pages/api/index.astro new file mode 100644 index 000000000..b6da1a13d --- /dev/null +++ b/astro/apps/docs/src/pages/api/index.astro @@ -0,0 +1,20 @@ +--- +import { AutodocPackage } from '@libtmux/astro-autodoc' +import BaseLayout from '../../layouts/BaseLayout.astro' +import { getApiModel } from '../../lib/api' + +const api = await getApiModel() +--- + + +
+
API
+

API reference

+

+ This reference is generated directly from the libtmux source tree using the new AST-powered + pipeline. Use the module index to jump to classes, methods, and helper functions. +

+
+ + +
diff --git a/astro/apps/docs/src/pages/architecture.astro b/astro/apps/docs/src/pages/architecture.astro new file mode 100644 index 000000000..677e92c21 --- /dev/null +++ b/astro/apps/docs/src/pages/architecture.astro @@ -0,0 +1,40 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro' + +const layers = [ + { name: 'Server', description: 'Top-level tmux server connection and command runner.' }, + { name: 'Session', description: 'Named tmux sessions with window lifecycle controls.' }, + { name: 'Window', description: 'Windows inside sessions that host panes.' }, + { name: 'Pane', description: 'Terminal panes that execute commands and capture output.' }, +] +--- + + +
+
Architecture
+

A layered object model.

+

+ libtmux mirrors tmux itself. Each object understands its place in the hierarchy and exposes + typed helpers that keep automation predictable. +

+
+ +
+ {layers.map((layer) => ( +
+

{layer.name}

+

{layer.description}

+
+ ))} +
+ +
+

Hierarchy map

+
+Server
+  Session
+    Window
+      Pane
+    
+
+
diff --git a/astro/apps/docs/src/pages/guides.astro b/astro/apps/docs/src/pages/guides.astro new file mode 100644 index 000000000..448283273 --- /dev/null +++ b/astro/apps/docs/src/pages/guides.astro @@ -0,0 +1,38 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro' + +const guides = [ + { + title: 'Session orchestration', + description: 'Create, rename, and connect sessions with confidence.', + }, + { + title: 'Window choreography', + description: 'Split panes, resize layouts, and move windows programmatically.', + }, + { + title: 'Testing with tmux', + description: 'Use pytest fixtures to spin up temporary tmux environments.', + }, +] +--- + + +
+
Guides
+

Workflow-first documentation.

+

+ These guides mirror the tmux lifecycle. Each guide follows a workflow, from session startup + through pane automation. Use them as blueprints for your own tooling. +

+
+ +
+ {guides.map((guide) => ( +
+

{guide.title}

+

{guide.description}

+
+ ))} +
+
diff --git a/astro/apps/docs/src/pages/index.astro b/astro/apps/docs/src/pages/index.astro new file mode 100644 index 000000000..66f8281c6 --- /dev/null +++ b/astro/apps/docs/src/pages/index.astro @@ -0,0 +1,69 @@ +--- +import BaseLayout from '../layouts/BaseLayout.astro' + +const features = [ + { + title: 'Typed tmux ORM', + description: 'Navigate servers, sessions, windows, and panes with a typed Python API.', + }, + { + title: 'Built for automation', + description: 'Drive tmux workflows for testing, pipelines, and local tooling.', + }, + { + title: 'Sphinx parity', + description: 'Autodoc-grade API reference generated directly from the source tree.', + }, +] + +const links = [ + { label: 'Get started', href: '/guides' }, + { label: 'API reference', href: '/api' }, + { label: 'Architecture', href: '/architecture' }, +] +--- + + +
+
libtmux docs
+

A calm, scriptable cockpit for tmux.

+

+ libtmux wraps tmux with a typed ORM so you can orchestrate terminals, sessions, and panes with + confidence. This Astro site keeps the docs fast, expressive, and API-first. +

+
+ {links.map((link) => ( + + {link.label} + + ))} +
+
+ +
+ {features.map((feature) => ( +
+

{feature.title}

+

{feature.description}

+
+ ))} +
+ +
+
+
+

Quick start

+

Install libtmux and open a new tmux session programmatically.

+
+
+ pip install libtmux +
+
+
+ python -c "import libtmux; libtmux.Server().new_session(session_name='docs')" +
+
+
diff --git a/astro/apps/docs/src/styles/global.css b/astro/apps/docs/src/styles/global.css new file mode 100644 index 000000000..ba90ce8cb --- /dev/null +++ b/astro/apps/docs/src/styles/global.css @@ -0,0 +1,69 @@ +@import "tailwindcss"; + +:root { + --color-paper: 0.97 0.02 95; + --color-ink: 0.22 0.04 260; + --color-accent: 0.74 0.18 35; + --color-accent-two: 0.72 0.12 210; + --color-moss: 0.76 0.09 140; +} + +html { + scroll-behavior: smooth; +} + +body { + background: + radial-gradient(circle at 10% 20%, oklch(var(--color-accent) / 0.12), transparent 60%), + radial-gradient(circle at 90% 10%, oklch(var(--color-accent-two) / 0.18), transparent 55%), + linear-gradient(120deg, oklch(var(--color-paper) / 1), oklch(var(--color-paper) / 0.96)); + color: oklch(var(--color-ink) / 1); + font-family: "Space Grotesk", ui-sans-serif, sans-serif; +} + +h1, +h2, +h3, +h4 { + font-family: "Fraunces", ui-serif, serif; + letter-spacing: -0.02em; +} + +code, +pre { + font-family: "IBM Plex Mono", ui-monospace, monospace; +} + +@layer components { + .surface { + border-radius: 24px; + border: 1px solid oklch(var(--color-ink) / 0.12); + background: color-mix(in oklab, oklch(var(--color-paper) / 0.9), transparent 20%); + box-shadow: 0 24px 80px -60px oklch(var(--color-ink) / 0.35); + } + + .surface-glow { + box-shadow: 0 28px 70px -40px oklch(var(--color-accent-two) / 0.5); + } + + .tag { + display: inline-flex; + align-items: center; + gap: 0.35rem; + border-radius: 999px; + border: 1px solid oklch(var(--color-ink) / 0.18); + padding: 0.2rem 0.7rem; + font-size: 0.75rem; + text-transform: uppercase; + letter-spacing: 0.08em; + } + + .nav-link { + border-bottom: 1px solid transparent; + transition: border-color 0.2s ease; + } + + .nav-link:hover { + border-color: oklch(var(--color-accent) / 1); + } +} diff --git a/astro/apps/docs/tailwind.config.ts b/astro/apps/docs/tailwind.config.ts new file mode 100644 index 000000000..3c9931f68 --- /dev/null +++ b/astro/apps/docs/tailwind.config.ts @@ -0,0 +1,28 @@ +import typography from '@tailwindcss/typography' +import type { Config } from 'tailwindcss' + +const config: Config = { + content: ['./src/**/*.{astro,ts,tsx,mdx}'], + theme: { + extend: { + colors: { + ink: 'oklch(var(--color-ink) / )', + paper: 'oklch(var(--color-paper) / )', + accent: 'oklch(var(--color-accent) / )', + accentTwo: 'oklch(var(--color-accent-two) / )', + moss: 'oklch(var(--color-moss) / )', + }, + fontFamily: { + sans: ['"Space Grotesk"', 'ui-sans-serif', 'sans-serif'], + serif: ['"Fraunces"', 'ui-serif', 'serif'], + mono: ['"IBM Plex Mono"', 'ui-monospace', 'monospace'], + }, + boxShadow: { + glow: '0 20px 60px -30px oklch(var(--color-accent) / 0.5)', + }, + }, + }, + plugins: [typography], +} + +export default config diff --git a/astro/apps/docs/tests/smoke.test.ts b/astro/apps/docs/tests/smoke.test.ts new file mode 100644 index 000000000..2bbbced32 --- /dev/null +++ b/astro/apps/docs/tests/smoke.test.ts @@ -0,0 +1,8 @@ +import { buildApiPackage } from '@libtmux/api-model' +import { describe, expect, it } from 'vitest' + +describe('docs site', () => { + it('references core api model helpers', () => { + expect(typeof buildApiPackage).toBe('function') + }) +}) diff --git a/astro/apps/docs/tsconfig.json b/astro/apps/docs/tsconfig.json new file mode 100644 index 000000000..043a53d36 --- /dev/null +++ b/astro/apps/docs/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist", + "types": ["astro/client"], + "rootDir": "../../", + "paths": {} + }, + "include": ["src", "tests"], + "references": [] +} diff --git a/astro/apps/docs/vitest.config.ts b/astro/apps/docs/vitest.config.ts new file mode 100644 index 000000000..9eef0e8a8 --- /dev/null +++ b/astro/apps/docs/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, +}) diff --git a/astro/biome.jsonc b/astro/biome.jsonc new file mode 100644 index 000000000..657cd94e1 --- /dev/null +++ b/astro/biome.jsonc @@ -0,0 +1,87 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.3.11/schema.json", + "css": { + "parser": { + "tailwindDirectives": true + } + }, + "files": { + "includes": [ + "**", + "!**/.venv", + "!**/.astro", + "!**/node_modules", + "!**/dist", + "!**/coverage", + "!**/.vitest", + "!**/notes", + "!**/playwright-report", + "!**/pnpm-lock.yaml", + "!**/.pnpm-store", + "!**/build-analytics.json", + "!**/CHANGELOG.md" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 120 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "complexity": { + "noExcessiveCognitiveComplexity": "off" + }, + "suspicious": { + "noExplicitAny": "off", + "noArrayIndexKey": "off" + }, + "style": { + "noNonNullAssertion": "off", + "noParameterAssign": "error", + "useAsConstAssertion": "error", + "useDefaultParameterLast": "error", + "useEnumInitializers": "error", + "useSelfClosingElements": "error", + "useSingleVarDeclarator": "error", + "noUnusedTemplateLiteral": "error", + "useNumberNamespace": "error", + "noInferrableTypes": "error", + "noUselessElse": "error" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "single", + "trailingCommas": "all", + "semicolons": "asNeeded" + } + }, + "overrides": [ + { + "includes": ["**/*.astro"], + "linter": { + "rules": { + "correctness": { + "noUnusedImports": "off", + "noUnusedVariables": "off" + } + } + } + }, + { + "includes": ["**/*.css"], + "linter": { + "rules": { + "suspicious": { + "noUnknownAtRules": "off" + } + } + } + } + ] +} diff --git a/astro/package.json b/astro/package.json new file mode 100644 index 000000000..b977d4728 --- /dev/null +++ b/astro/package.json @@ -0,0 +1,35 @@ +{ + "name": "@libtmux/docs-monorepo", + "version": "0.0.1", + "private": true, + "type": "module", + "scripts": { + "start": "pnpm --filter @libtmux/docs-site start", + "build": "pnpm --filter @libtmux/docs-site build", + "preview": "pnpm --filter @libtmux/docs-site preview", + "clean": "pnpm run --recursive clean", + "distclean": "pnpm run --recursive distclean", + "format": "pnpm run --recursive format", + "format:check": "biome format .", + "lint": "biome lint .", + "ncu": "pnpm run --recursive ncu", + "ncu-local": "ncu", + "biome-all": "pnpm run --recursive biome", + "biome-full": "biome lint . --write --max-diagnostics=100 && biome check . --write && biome format . --write", + "test": "pnpm run --recursive test", + "update": "pnpm run --recursive update", + "update:all": "ncu -u; pnpm --recursive run 'ncu' -u", + "deduplicate": "pnpm dedupe", + "type-check": "pnpm run --recursive type-check", + "type-check:watch": "pnpm run --recursive type-check:watch" + }, + "engines": { + "node": ">=24" + }, + "packageManager": "pnpm@10.27.0", + "devDependencies": { + "@biomejs/biome": "2.3.11", + "npm-check-updates": "^19.2.1", + "typescript": "^5.9.3" + } +} diff --git a/astro/packages/api-model/package.json b/astro/packages/api-model/package.json new file mode 100644 index 000000000..9d6d3fb0f --- /dev/null +++ b/astro/packages/api-model/package.json @@ -0,0 +1,34 @@ +{ + "name": "@libtmux/api-model", + "version": "0.0.1", + "type": "module", + "description": "API model builder for libtmux docs", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "src" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "lint": "biome lint .", + "format": "pnpm biome check . --write; biome format . --write", + "type-check": "tsc --noEmit", + "type-check:watch": "tsc --noEmit --watch", + "update": "pnpm update", + "ncu": "ncu", + "clean": "rm -rf dist/ node_modules/.cache/", + "biome": "biome check . --write && biome format . --write" + }, + "dependencies": { + "@libtmux/py-parse": "workspace:*", + "@libtmux/schema": "workspace:*", + "zod": "^4.3.5" + }, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@types/node": "^25.0.3", + "typescript": "^5.9.3", + "vitest": "^4.0.16" + } +} diff --git a/astro/packages/api-model/src/build.ts b/astro/packages/api-model/src/build.ts new file mode 100644 index 000000000..e21104f3f --- /dev/null +++ b/astro/packages/api-model/src/build.ts @@ -0,0 +1,338 @@ +import type { + PyClass, + PyFunction, + PyImport, + PyIntrospectClass, + PyIntrospectFunction, + PyIntrospectModule, + PyIntrospectParameter, + PyIntrospectVariable, + PyModule, + PyParameter, + PyVariable, +} from '@libtmux/schema' +import { + type ApiClass, + ApiClassSchema, + type ApiFunction, + ApiFunctionSchema, + type ApiMethod, + ApiMethodSchema, + type ApiModule, + ApiModuleSchema, + type ApiPackage, + ApiPackageSchema, + type ApiParameter, + ApiParameterSchema, + type ApiVariable, + ApiVariableSchema, +} from './schema.ts' + +export type BuildOptions = { + name: string + root: string + includePrivate?: boolean + generatedAt?: string + introspection?: PyIntrospectModule[] +} + +const summarizeDocstring = (docstring: string | null): string | null => { + if (!docstring) { + return null + } + + const [firstLine] = docstring.trim().split('\n') + return firstLine?.trim() || null +} + +const formatParameterSignature = (param: { + name: string + kind: string + annotation?: string | null + annotationText?: string | null + default?: string | null +}): string => { + let name = param.name + + if (param.kind === 'var-positional') { + name = `*${name}` + } + + if (param.kind === 'var-keyword') { + name = `**${name}` + } + + const annotation = param.annotationText ?? param.annotation + if (annotation) { + name = `${name}: ${annotation}` + } + + if (param.default) { + name = `${name} = ${param.default}` + } + + return name +} + +const buildParameter = (param: PyParameter, introspected?: PyIntrospectParameter): ApiParameter => + ApiParameterSchema.parse({ + name: param.name, + kind: introspected?.kind ?? param.kind, + annotation: introspected?.annotationText ?? param.annotation, + annotationValue: introspected?.annotationValue ?? null, + default: introspected?.default ?? param.default, + signature: formatParameterSignature({ + name: param.name, + kind: introspected?.kind ?? param.kind, + annotationText: introspected?.annotationText ?? null, + annotation: param.annotation, + default: introspected?.default ?? param.default, + }), + }) + +const buildSignature = ( + parameters: Array< + { kind: string } & { + name: string + annotation?: string | null + annotationText?: string | null + default?: string | null + } + >, +): string => { + const positionalOnly = parameters.filter((param) => param.kind === 'positional-only') + const positional = parameters.filter((param) => param.kind === 'positional-or-keyword') + const varPositional = parameters.find((param) => param.kind === 'var-positional') + const keywordOnly = parameters.filter((param) => param.kind === 'keyword-only') + const varKeyword = parameters.find((param) => param.kind === 'var-keyword') + + const parts: string[] = [] + + for (const param of positionalOnly) { + parts.push(formatParameterSignature(param)) + } + + if (positionalOnly.length > 0) { + parts.push('/') + } + + for (const param of positional) { + parts.push(formatParameterSignature(param)) + } + + if (varPositional) { + parts.push(formatParameterSignature(varPositional)) + } + + if (keywordOnly.length > 0 && !varPositional) { + parts.push('*') + } + + for (const param of keywordOnly) { + parts.push(formatParameterSignature(param)) + } + + if (varKeyword) { + parts.push(formatParameterSignature(varKeyword)) + } + + return `(${parts.join(', ')})` +} + +const stripSignatureReturn = (signature: string): string => { + const arrowIndex = signature.lastIndexOf(' -> ') + if (arrowIndex === -1) { + return signature + } + + return signature.slice(0, arrowIndex) +} + +const buildDocFields = ( + docstring: string | null, + introspected?: { + docstringRaw: string | null + docstringFormat: 'rst' | 'markdown' | 'plain' | 'unknown' + docstringHtml: string | null + summary: string | null + }, +) => { + const raw = introspected?.docstringRaw ?? docstring + return { + docstring: raw, + docstringFormat: introspected?.docstringFormat ?? 'unknown', + docstringHtml: introspected?.docstringHtml ?? null, + summary: introspected?.summary ?? summarizeDocstring(raw), + } +} + +const buildFunction = (fn: PyFunction, introspected?: PyIntrospectFunction): ApiFunction => { + const parameters = fn.parameters.map((param) => + buildParameter( + param, + introspected?.parameters.find((item) => item.name === param.name), + ), + ) + + return ApiFunctionSchema.parse({ + ...fn, + kind: 'function', + signature: introspected ? stripSignatureReturn(introspected.signature) : buildSignature(parameters), + parameters, + returns: introspected?.returns.annotationText ?? fn.returns, + returnsValue: introspected?.returns.annotationValue ?? null, + isAsync: introspected?.isAsync ?? fn.isAsync, + ...buildDocFields(fn.docstring, introspected), + }) +} + +const buildMethod = (fn: PyFunction, className: string, introspected?: PyIntrospectFunction): ApiMethod => { + const parameters = fn.parameters.map((param) => + buildParameter( + param, + introspected?.parameters.find((item) => item.name === param.name), + ), + ) + + return ApiMethodSchema.parse({ + ...fn, + kind: 'method', + signature: introspected ? stripSignatureReturn(introspected.signature) : buildSignature(parameters), + parameters, + returns: introspected?.returns.annotationText ?? fn.returns, + returnsValue: introspected?.returns.annotationValue ?? null, + isAsync: introspected?.isAsync ?? fn.isAsync, + ...buildDocFields(fn.docstring, introspected), + className, + }) +} + +const buildVariable = (variable: PyVariable, introspected?: PyIntrospectVariable): ApiVariable => + ApiVariableSchema.parse({ + ...variable, + annotation: introspected?.annotationText ?? variable.annotation, + annotationValue: introspected?.annotationValue ?? null, + value: introspected?.value ?? variable.value, + ...buildDocFields(variable.docstring, introspected), + }) + +const indexByQualname = (items: T[] | undefined): Map => { + if (!items) { + return new Map() + } + + return new Map(items.map((item) => [item.qualname, item])) +} + +const buildClass = (klass: PyClass, includePrivate: boolean, introspected?: PyIntrospectClass): ApiClass => { + const methodIndex = indexByQualname(introspected?.methods) + const attributeIndex = indexByQualname(introspected?.attributes) + + const methods = klass.methods + .filter((method) => includePrivate || !method.isPrivate) + .map((method) => buildMethod(method, klass.name, methodIndex.get(method.qualname))) + + const attributes = klass.attributes + .filter((attribute) => includePrivate || !attribute.isPrivate) + .map((attribute) => buildVariable(attribute, attributeIndex.get(attribute.qualname))) + + return ApiClassSchema.parse({ + ...klass, + kind: 'class', + bases: introspected?.bases ?? klass.bases, + ...buildDocFields(klass.docstring, introspected), + methods, + attributes, + }) +} + +const buildImportLine = (item: PyImport): string => { + if (item.module) { + return `from ${item.module} import ${item.names.join(', ')}` + } + + return `import ${item.names.join(', ')}` +} + +const buildModule = (module: PyModule, includePrivate: boolean, introspected?: PyIntrospectModule): ApiModule => { + const classIndex = indexByQualname(introspected?.classes) + const functionIndex = indexByQualname(introspected?.functions) + const variableIndex = indexByQualname(introspected?.variables) + + const classes = module.items + .filter((item): item is PyClass => item.kind === 'class') + .filter((item) => includePrivate || !item.isPrivate) + .map((item) => buildClass(item, includePrivate, classIndex.get(item.qualname))) + + const functions = module.items + .filter((item): item is PyFunction => item.kind === 'function') + .filter((item) => includePrivate || !item.isPrivate) + .map((item) => buildFunction(item, functionIndex.get(item.qualname))) + + const variables = module.items + .filter((item): item is PyVariable => item.kind === 'variable') + .filter((item) => includePrivate || !item.isPrivate) + .map((item) => buildVariable(item, variableIndex.get(item.qualname))) + + return ApiModuleSchema.parse({ + kind: 'module', + name: module.name, + qualname: module.qualname, + ...buildDocFields(module.docstring, introspected), + isPrivate: module.name.startsWith('_'), + location: module.location, + path: module.path, + exports: module.exports, + imports: module.imports.map(buildImportLine), + classes, + functions, + variables, + }) +} + +export const buildApiPackage = (modules: PyModule[], options: BuildOptions): ApiPackage => { + const includePrivate = options.includePrivate ?? false + const generatedAt = options.generatedAt ?? new Date().toISOString() + const introspectionIndex = indexByQualname(options.introspection) + + const apiModules = modules.map((module) => + buildModule(module, includePrivate, introspectionIndex.get(module.qualname)), + ) + + return ApiPackageSchema.parse({ + name: options.name, + root: options.root, + generatedAt, + modules: apiModules, + }) +} + +export const buildApiIndex = ( + api: ApiPackage, +): Map => { + const index = new Map() + + for (const module of api.modules) { + index.set(module.qualname, module) + + for (const klass of module.classes) { + index.set(klass.qualname, klass) + for (const method of klass.methods) { + index.set(method.qualname, method) + } + for (const attribute of klass.attributes) { + index.set(attribute.qualname, attribute) + } + } + + for (const fn of module.functions) { + index.set(fn.qualname, fn) + } + + for (const variable of module.variables) { + index.set(variable.qualname, variable) + } + } + + return index +} diff --git a/astro/packages/api-model/src/index.ts b/astro/packages/api-model/src/index.ts new file mode 100644 index 000000000..faedd6ce1 --- /dev/null +++ b/astro/packages/api-model/src/index.ts @@ -0,0 +1,2 @@ +export * from './build.ts' +export * from './schema.ts' diff --git a/astro/packages/api-model/src/schema.ts b/astro/packages/api-model/src/schema.ts new file mode 100644 index 000000000..6cb684492 --- /dev/null +++ b/astro/packages/api-model/src/schema.ts @@ -0,0 +1,85 @@ +import { z } from 'zod' + +export const ApiLocationSchema = z.object({ + lineno: z.number().int().nonnegative(), + colOffset: z.number().int().nonnegative(), + endLineno: z.number().int().nonnegative().nullable(), + endColOffset: z.number().int().nonnegative().nullable(), +}) + +export const ApiParameterSchema = z.object({ + name: z.string(), + kind: z.enum(['positional-only', 'positional-or-keyword', 'var-positional', 'keyword-only', 'var-keyword']), + annotation: z.string().nullable(), + annotationValue: z.string().nullable().optional(), + default: z.string().nullable(), + signature: z.string(), +}) + +export const ApiBaseSchema = z.object({ + name: z.string(), + qualname: z.string(), + docstring: z.string().nullable(), + docstringFormat: z.enum(['rst', 'markdown', 'plain', 'unknown']), + docstringHtml: z.string().nullable(), + summary: z.string().nullable(), + isPrivate: z.boolean(), + location: ApiLocationSchema, +}) + +export const ApiFunctionSchema = ApiBaseSchema.extend({ + kind: z.literal('function'), + signature: z.string(), + parameters: z.array(ApiParameterSchema), + returns: z.string().nullable(), + returnsValue: z.string().nullable().optional(), + decorators: z.array(z.string()), + isAsync: z.boolean(), +}) + +export const ApiMethodSchema = ApiFunctionSchema.extend({ + kind: z.literal('method'), + className: z.string(), +}) + +export const ApiVariableSchema = ApiBaseSchema.extend({ + kind: z.literal('variable'), + annotation: z.string().nullable(), + annotationValue: z.string().nullable().optional(), + value: z.string().nullable(), +}) + +export const ApiClassSchema = ApiBaseSchema.extend({ + kind: z.literal('class'), + bases: z.array(z.string()), + decorators: z.array(z.string()), + methods: z.array(ApiMethodSchema), + attributes: z.array(ApiVariableSchema), +}) + +export const ApiModuleSchema = ApiBaseSchema.extend({ + kind: z.literal('module'), + path: z.string(), + exports: z.array(z.string()), + imports: z.array(z.string()), + classes: z.array(ApiClassSchema), + functions: z.array(ApiFunctionSchema), + variables: z.array(ApiVariableSchema), +}) + +export const ApiPackageSchema = z.object({ + name: z.string(), + root: z.string(), + generatedAt: z.string(), + modules: z.array(ApiModuleSchema), +}) + +export type ApiLocation = z.infer +export type ApiParameter = z.infer +export type ApiBase = z.infer +export type ApiFunction = z.infer +export type ApiMethod = z.infer +export type ApiVariable = z.infer +export type ApiClass = z.infer +export type ApiModule = z.infer +export type ApiPackage = z.infer diff --git a/astro/packages/api-model/tests/__snapshots__/build.test.ts.snap b/astro/packages/api-model/tests/__snapshots__/build.test.ts.snap new file mode 100644 index 000000000..7f9a95ba8 --- /dev/null +++ b/astro/packages/api-model/tests/__snapshots__/build.test.ts.snap @@ -0,0 +1,352 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`buildApiPackage > builds a package model suitable for rendering 1`] = ` +{ + "generatedAt": "2025-01-01T00:00:00.000Z", + "modules": [ + { + "classes": [ + { + "attributes": [ + { + "annotation": "str", + "annotationValue": null, + "docstring": null, + "docstringFormat": "unknown", + "docstringHtml": null, + "isPrivate": false, + "kind": "variable", + "location": { + "colOffset": 4, + "endColOffset": 13, + "endLineno": 35, + "lineno": 35, + }, + "name": "name", + "qualname": "sample_module.Widget.name", + "summary": null, + "value": null, + }, + { + "annotation": "int", + "annotationValue": null, + "docstring": null, + "docstringFormat": "unknown", + "docstringHtml": null, + "isPrivate": false, + "kind": "variable", + "location": { + "colOffset": 4, + "endColOffset": 17, + "endLineno": 36, + "lineno": 36, + }, + "name": "size", + "qualname": "sample_module.Widget.size", + "summary": null, + "value": "1", + }, + ], + "bases": [], + "decorators": [ + "dataclasses.dataclass", + ], + "docstring": "Widget model.", + "docstringFormat": "unknown", + "docstringHtml": null, + "isPrivate": false, + "kind": "class", + "location": { + "colOffset": 0, + "endColOffset": 41, + "endLineno": 46, + "lineno": 32, + }, + "methods": [ + { + "className": "Widget", + "decorators": [], + "docstring": "Return a label. + +Returns +------- +str + Label string.", + "docstringFormat": "unknown", + "docstringHtml": null, + "isAsync": false, + "isPrivate": false, + "kind": "method", + "location": { + "colOffset": 4, + "endColOffset": 41, + "endLineno": 46, + "lineno": 38, + }, + "name": "label", + "parameters": [ + { + "annotation": null, + "annotationValue": null, + "default": null, + "kind": "positional-or-keyword", + "name": "self", + "signature": "self", + }, + ], + "qualname": "sample_module.Widget.label", + "returns": "str", + "returnsValue": null, + "signature": "(self)", + "summary": "Return a label.", + }, + ], + "name": "Widget", + "qualname": "sample_module.Widget", + "summary": "Widget model.", + }, + { + "attributes": [ + { + "annotation": "int", + "annotationValue": null, + "docstring": null, + "docstringFormat": "unknown", + "docstringHtml": null, + "isPrivate": false, + "kind": "variable", + "location": { + "colOffset": 4, + "endColOffset": 18, + "endLineno": 52, + "lineno": 52, + }, + "name": "count", + "qualname": "sample_module.Container.count", + "summary": null, + "value": "0", + }, + ], + "bases": [], + "decorators": [], + "docstring": "Container type.", + "docstringFormat": "unknown", + "docstringHtml": null, + "isPrivate": false, + "kind": "class", + "location": { + "colOffset": 0, + "endColOffset": 19, + "endLineno": 77, + "lineno": 49, + }, + "methods": [ + { + "className": "Container", + "decorators": [ + "property", + ], + "docstring": "Return count of items. + +Returns +------- +int + Item count.", + "docstringFormat": "unknown", + "docstringHtml": null, + "isAsync": false, + "isPrivate": false, + "kind": "method", + "location": { + "colOffset": 4, + "endColOffset": 30, + "endLineno": 73, + "lineno": 65, + }, + "name": "item_count", + "parameters": [ + { + "annotation": null, + "annotationValue": null, + "default": null, + "kind": "positional-or-keyword", + "name": "self", + "signature": "self", + }, + ], + "qualname": "sample_module.Container.item_count", + "returns": "int", + "returnsValue": null, + "signature": "(self)", + "summary": "Return count of items.", + }, + { + "className": "Container", + "decorators": [], + "docstring": "Refresh the container.", + "docstringFormat": "unknown", + "docstringHtml": null, + "isAsync": true, + "isPrivate": false, + "kind": "method", + "location": { + "colOffset": 4, + "endColOffset": 19, + "endLineno": 77, + "lineno": 75, + }, + "name": "refresh", + "parameters": [ + { + "annotation": null, + "annotationValue": null, + "default": null, + "kind": "positional-or-keyword", + "name": "self", + "signature": "self", + }, + ], + "qualname": "sample_module.Container.refresh", + "returns": "None", + "returnsValue": null, + "signature": "(self)", + "summary": "Refresh the container.", + }, + ], + "name": "Container", + "qualname": "sample_module.Container", + "summary": "Container type.", + }, + ], + "docstring": "Sample module for AST scanning.", + "docstringFormat": "unknown", + "docstringHtml": null, + "exports": [], + "functions": [ + { + "decorators": [], + "docstring": "Return a friendly greeting. + +Parameters +---------- +name : str + Name to greet. + +Returns +------- +str + Greeting string.", + "docstringFormat": "unknown", + "docstringHtml": null, + "isAsync": false, + "isPrivate": false, + "kind": "function", + "location": { + "colOffset": 0, + "endColOffset": 26, + "endLineno": 24, + "lineno": 11, + }, + "name": "greet", + "parameters": [ + { + "annotation": "str", + "annotationValue": null, + "default": null, + "kind": "positional-or-keyword", + "name": "name", + "signature": "name: str", + }, + ], + "qualname": "sample_module.greet", + "returns": "str", + "returnsValue": null, + "signature": "(name: str)", + "summary": "Return a friendly greeting.", + }, + { + "decorators": [], + "docstring": "Return provided value. + +Parameters +---------- +value : typing.Any + Input value. + +Returns +------- +typing.Any + Input value.", + "docstringFormat": "unknown", + "docstringHtml": null, + "isAsync": false, + "isPrivate": false, + "kind": "function", + "location": { + "colOffset": 0, + "endColOffset": 16, + "endLineno": 93, + "lineno": 80, + }, + "name": "uses_typing", + "parameters": [ + { + "annotation": "t.Any", + "annotationValue": null, + "default": null, + "kind": "positional-or-keyword", + "name": "value", + "signature": "value: t.Any", + }, + ], + "qualname": "sample_module.uses_typing", + "returns": "t.Any", + "returnsValue": null, + "signature": "(value: t.Any)", + "summary": "Return provided value.", + }, + ], + "imports": [ + "from __future__ import annotations", + "from dataclasses import dataclasses", + "from typing import typing as t", + ], + "isPrivate": false, + "kind": "module", + "location": { + "colOffset": 0, + "endColOffset": null, + "endLineno": null, + "lineno": 1, + }, + "name": "sample_module", + "path": "/home/d/work/python/libtmux/astro/packages/py-parse/tests/fixtures/sample_module.py", + "qualname": "sample_module", + "summary": "Sample module for AST scanning.", + "variables": [ + { + "annotation": "str", + "annotationValue": null, + "docstring": null, + "docstringFormat": "unknown", + "docstringHtml": null, + "isPrivate": false, + "kind": "variable", + "location": { + "colOffset": 0, + "endColOffset": 23, + "endLineno": 8, + "lineno": 8, + }, + "name": "CONSTANT", + "qualname": "sample_module.CONSTANT", + "summary": null, + "value": "'value'", + }, + ], + }, + ], + "name": "libtmux", + "root": "/home/d/work/python/libtmux/astro/packages/py-parse/tests/fixtures", +} +`; diff --git a/astro/packages/api-model/tests/build.test.ts b/astro/packages/api-model/tests/build.test.ts new file mode 100644 index 000000000..005402807 --- /dev/null +++ b/astro/packages/api-model/tests/build.test.ts @@ -0,0 +1,93 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { scanPythonPaths } from '@libtmux/py-parse' +import type { PyIntrospectModule } from '@libtmux/schema' +import { describe, expect, it } from 'vitest' +import { buildApiPackage } from '../src/build' + +const fixturesRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../../py-parse/tests/fixtures') + +const samplePath = path.join(fixturesRoot, 'sample_module.py') + +describe('buildApiPackage', () => { + it('builds a package model suitable for rendering', async () => { + const modules = await scanPythonPaths({ + root: fixturesRoot, + paths: [samplePath], + pythonCommand: ['python3'], + }) + + const api = buildApiPackage(modules, { + name: 'libtmux', + root: fixturesRoot, + includePrivate: false, + generatedAt: '2025-01-01T00:00:00.000Z', + }) + + expect(api).toMatchSnapshot() + }) + + it('prefers introspected docstrings and signatures when provided', async () => { + const modules = await scanPythonPaths({ + root: fixturesRoot, + paths: [samplePath], + pythonCommand: ['python3'], + }) + + const introspection = [ + { + kind: 'module', + name: 'sample_module', + qualname: 'sample_module', + isPrivate: false, + classes: [], + functions: [ + { + kind: 'function', + name: 'greet', + qualname: 'sample_module.greet', + module: 'sample_module', + signature: '(name: str) -> str', + parameters: [ + { + name: 'name', + kind: 'positional-or-keyword', + default: null, + annotationText: 'str', + annotationValue: null, + }, + ], + returns: { + annotationText: 'str', + annotationValue: null, + }, + isAsync: false, + isPrivate: false, + docstringRaw: 'Say hello.', + docstringFormat: 'rst', + docstringHtml: '

Say hello.

', + summary: 'Say hello.', + }, + ], + variables: [], + docstringRaw: null, + docstringFormat: 'unknown', + docstringHtml: null, + summary: null, + }, + ] satisfies PyIntrospectModule[] + + const api = buildApiPackage(modules, { + name: 'libtmux', + root: fixturesRoot, + includePrivate: false, + generatedAt: '2025-01-01T00:00:00.000Z', + introspection, + }) + + const greet = api.modules[0]?.functions.find((fn) => fn.name === 'greet') + expect(greet?.signature).toBe('(name: str)') + expect(greet?.returns).toBe('str') + expect(greet?.docstringHtml).toBe('

Say hello.

') + }) +}) diff --git a/astro/packages/api-model/tsconfig.json b/astro/packages/api-model/tsconfig.json new file mode 100644 index 000000000..52234f529 --- /dev/null +++ b/astro/packages/api-model/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist", + "rootDir": "../../", + "paths": {} + }, + "include": ["src", "tests"], + "references": [] +} diff --git a/astro/packages/api-model/vitest.config.ts b/astro/packages/api-model/vitest.config.ts new file mode 100644 index 000000000..9eef0e8a8 --- /dev/null +++ b/astro/packages/api-model/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, +}) diff --git a/astro/packages/astro-autodoc/package.json b/astro/packages/astro-autodoc/package.json new file mode 100644 index 000000000..9c898168d --- /dev/null +++ b/astro/packages/astro-autodoc/package.json @@ -0,0 +1,39 @@ +{ + "name": "@libtmux/astro-autodoc", + "version": "0.0.1", + "type": "module", + "description": "Astro components for libtmux API docs", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "src" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "lint": "biome lint .", + "format": "pnpm biome check . --write; biome format . --write", + "type-check": "tsc --noEmit", + "type-check:watch": "tsc --noEmit --watch", + "update": "pnpm update", + "ncu": "ncu", + "clean": "rm -rf dist/ node_modules/.cache/", + "biome": "biome check . --write && biome format . --write" + }, + "dependencies": { + "@libtmux/api-model": "workspace:*", + "@libtmux/py-bridge": "workspace:*", + "@libtmux/py-introspect": "workspace:*", + "@libtmux/py-parse": "workspace:*", + "@libtmux/rst-lite": "workspace:*" + }, + "peerDependencies": { + "astro": "^5.0.0" + }, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@types/node": "^25.0.3", + "typescript": "^5.9.3", + "vitest": "^4.0.16" + } +} diff --git a/astro/packages/astro-autodoc/src/components/AutodocClass.astro b/astro/packages/astro-autodoc/src/components/AutodocClass.astro new file mode 100644 index 000000000..5aca64cbd --- /dev/null +++ b/astro/packages/astro-autodoc/src/components/AutodocClass.astro @@ -0,0 +1,48 @@ +--- +import '../styles.css' +import { slugify } from '../utils' +import AutodocFunction from './AutodocFunction.astro' +import AutodocVariable from './AutodocVariable.astro' +import Docstring from './Docstring.astro' + +const { klass } = Astro.props +const anchor = slugify(klass.qualname) +const bases = klass.bases.length > 0 ? `(${klass.bases.join(', ')})` : '' +--- + +
+
+
class {klass.name}{bases}
+ {klass.decorators.length > 0 ? ( +
+ {klass.decorators.map((decorator) => ( + @{decorator} + ))} +
+ ) : null} +
+ + + + {klass.attributes.length > 0 ? ( +
+

Attributes

+
+ {klass.attributes.map((attribute) => ( + + ))} +
+
+ ) : null} + + {klass.methods.length > 0 ? ( +
+

Methods

+
+ {klass.methods.map((method) => ( + + ))} +
+
+ ) : null} +
diff --git a/astro/packages/astro-autodoc/src/components/AutodocFunction.astro b/astro/packages/astro-autodoc/src/components/AutodocFunction.astro new file mode 100644 index 000000000..a4e336703 --- /dev/null +++ b/astro/packages/astro-autodoc/src/components/AutodocFunction.astro @@ -0,0 +1,16 @@ +--- +import '../styles.css' +import { formatSignature, slugify } from '../utils' +import Docstring from './Docstring.astro' + +const { fn, label } = Astro.props +const anchor = slugify(fn.qualname) +const title = label ?? fn.name +const signature = formatSignature(fn.signature, fn.returns) +--- + +
+
{title}
+
{signature}
+ +
diff --git a/astro/packages/astro-autodoc/src/components/AutodocModule.astro b/astro/packages/astro-autodoc/src/components/AutodocModule.astro new file mode 100644 index 000000000..4099a2327 --- /dev/null +++ b/astro/packages/astro-autodoc/src/components/AutodocModule.astro @@ -0,0 +1,55 @@ +--- +import '../styles.css' +import { slugify } from '../utils' +import AutodocClass from './AutodocClass.astro' +import AutodocFunction from './AutodocFunction.astro' +import AutodocVariable from './AutodocVariable.astro' +import Docstring from './Docstring.astro' + +const { module } = Astro.props +const anchor = slugify(module.qualname) +--- + +
+
+
module {module.qualname}
+ + {module.imports.length > 0 ? ( +
+
Imports
+
    + {module.imports.map((item) => ( +
  • {item}
  • + ))} +
+
+ ) : null} +
+ + {module.classes.length > 0 ? ( +
+

Classes

+ {module.classes.map((klass) => ( + + ))} +
+ ) : null} + + {module.functions.length > 0 ? ( +
+

Functions

+ {module.functions.map((fn) => ( + + ))} +
+ ) : null} + + {module.variables.length > 0 ? ( +
+

Variables

+ {module.variables.map((variable) => ( + + ))} +
+ ) : null} +
diff --git a/astro/packages/astro-autodoc/src/components/AutodocPackage.astro b/astro/packages/astro-autodoc/src/components/AutodocPackage.astro new file mode 100644 index 000000000..a3ea0a8a1 --- /dev/null +++ b/astro/packages/astro-autodoc/src/components/AutodocPackage.astro @@ -0,0 +1,34 @@ +--- +import '../styles.css' +import { slugify } from '../utils' +import AutodocModule from './AutodocModule.astro' + +const { pkg } = Astro.props +--- + +
+
+
{pkg.name} API Reference
+

+ Generated at {pkg.generatedAt} from {pkg.root}. +

+
+ Modules + +
+
+ +
+ {pkg.modules.map((module) => ( + + ))} +
+
diff --git a/astro/packages/astro-autodoc/src/components/AutodocVariable.astro b/astro/packages/astro-autodoc/src/components/AutodocVariable.astro new file mode 100644 index 000000000..87effedce --- /dev/null +++ b/astro/packages/astro-autodoc/src/components/AutodocVariable.astro @@ -0,0 +1,18 @@ +--- +import '../styles.css' +import { slugify } from '../utils' +import Docstring from './Docstring.astro' + +const { variable } = Astro.props +const anchor = slugify(variable.qualname) +const signature = variable.annotation ? `${variable.name}: ${variable.annotation}` : variable.name +const value = variable.value ? `= ${variable.value}` : '' +--- + +
+
{variable.name}
+
+ {signature} {value} +
+ +
diff --git a/astro/packages/astro-autodoc/src/components/Docstring.astro b/astro/packages/astro-autodoc/src/components/Docstring.astro new file mode 100644 index 000000000..3b3dd67b1 --- /dev/null +++ b/astro/packages/astro-autodoc/src/components/Docstring.astro @@ -0,0 +1,17 @@ +--- +import '../styles.css' +import { splitDocstring } from '../utils' + +const { value, html } = Astro.props +const blocks = splitDocstring(value ?? null) +--- + +{html ? ( +
+) : blocks.length > 0 ? ( +
+ {blocks.map((block) => ( +

{block}

+ ))} +
+) : null} diff --git a/astro/packages/astro-autodoc/src/docstrings.ts b/astro/packages/astro-autodoc/src/docstrings.ts new file mode 100644 index 000000000..7b332b1bf --- /dev/null +++ b/astro/packages/astro-autodoc/src/docstrings.ts @@ -0,0 +1,79 @@ +import type { ApiClass, ApiFunction, ApiMethod, ApiModule, ApiPackage, ApiVariable } from '@libtmux/api-model' +import { parseRst, type RoleResolver, renderHtml } from '@libtmux/rst-lite' + +export type DocstringRenderMode = 'introspect' | 'rst-lite' | 'none' + +export type DocstringRenderOptions = { + mode?: DocstringRenderMode + roleResolver?: RoleResolver +} + +const shouldRender = (docstring: string | null, format: string): boolean => { + if (!docstring) { + return false + } + if (format === 'markdown') { + return false + } + return true +} + +const renderDocstring = (docstring: string | null, format: string, roleResolver?: RoleResolver): string | null => { + if (!shouldRender(docstring, format)) { + return null + } + const doc = parseRst(docstring ?? '') + return renderHtml(doc, { roleResolver }) +} + +const applyToFunction = (fn: ApiFunction, roleResolver?: RoleResolver): ApiFunction => { + return { + ...fn, + docstringHtml: renderDocstring(fn.docstring, fn.docstringFormat, roleResolver), + } +} + +const applyToMethod = (method: ApiMethod, roleResolver?: RoleResolver): ApiMethod => { + return { + ...method, + docstringHtml: renderDocstring(method.docstring, method.docstringFormat, roleResolver), + } +} + +const applyToVariable = (variable: ApiVariable, roleResolver?: RoleResolver): ApiVariable => { + return { + ...variable, + docstringHtml: renderDocstring(variable.docstring, variable.docstringFormat, roleResolver), + } +} + +const applyToClass = (klass: ApiClass, roleResolver?: RoleResolver): ApiClass => { + return { + ...klass, + docstringHtml: renderDocstring(klass.docstring, klass.docstringFormat, roleResolver), + methods: klass.methods.map((method) => applyToMethod(method, roleResolver)), + attributes: klass.attributes.map((attribute) => applyToVariable(attribute, roleResolver)), + } +} + +const applyToModule = (module: ApiModule, roleResolver?: RoleResolver): ApiModule => { + return { + ...module, + docstringHtml: renderDocstring(module.docstring, module.docstringFormat, roleResolver), + classes: module.classes.map((klass) => applyToClass(klass, roleResolver)), + functions: module.functions.map((fn) => applyToFunction(fn, roleResolver)), + variables: module.variables.map((variable) => applyToVariable(variable, roleResolver)), + } +} + +export const renderDocstrings = (api: ApiPackage, options: DocstringRenderOptions = {}): ApiPackage => { + const mode = options.mode ?? 'introspect' + if (mode !== 'rst-lite') { + return api + } + + return { + ...api, + modules: api.modules.map((module) => applyToModule(module, options.roleResolver)), + } +} diff --git a/astro/packages/astro-autodoc/src/env.d.ts b/astro/packages/astro-autodoc/src/env.d.ts new file mode 100644 index 000000000..283b70b83 --- /dev/null +++ b/astro/packages/astro-autodoc/src/env.d.ts @@ -0,0 +1,4 @@ +declare module '*.astro' { + const Component: (props: Record) => unknown + export default Component +} diff --git a/astro/packages/astro-autodoc/src/index.ts b/astro/packages/astro-autodoc/src/index.ts new file mode 100644 index 000000000..a63136d70 --- /dev/null +++ b/astro/packages/astro-autodoc/src/index.ts @@ -0,0 +1,21 @@ +import AutodocClass from './components/AutodocClass.astro' +import AutodocFunction from './components/AutodocFunction.astro' +import AutodocModule from './components/AutodocModule.astro' +import AutodocPackage from './components/AutodocPackage.astro' +import AutodocVariable from './components/AutodocVariable.astro' +import Docstring from './components/Docstring.astro' + +export { AutodocPackage, AutodocModule, AutodocClass, AutodocFunction, AutodocVariable, Docstring } + +export const autodocComponents = { + AutodocPackage, + AutodocModule, + AutodocClass, + AutodocFunction, + AutodocVariable, + Docstring, +} + +export * from './docstrings.ts' +export * from './load.ts' +export * from './utils.ts' diff --git a/astro/packages/astro-autodoc/src/load.ts b/astro/packages/astro-autodoc/src/load.ts new file mode 100644 index 000000000..cdf2fd788 --- /dev/null +++ b/astro/packages/astro-autodoc/src/load.ts @@ -0,0 +1,58 @@ +import type { ApiPackage } from '@libtmux/api-model' +import { buildApiPackage } from '@libtmux/api-model' +import type { PythonCommand } from '@libtmux/py-bridge' +import { introspectPackage } from '@libtmux/py-introspect' +import { scanPythonPaths } from '@libtmux/py-parse' +import type { RoleResolver } from '@libtmux/rst-lite' +import { type DocstringRenderMode, renderDocstrings } from './docstrings.ts' + +export type LoadApiOptions = { + name: string + root: string + paths: string[] + includePrivate?: boolean + generatedAt?: string + introspect?: boolean + introspectPackage?: string + annotationFormat?: 'string' | 'value' + mockImports?: string[] + autodocMock?: boolean + docstringRenderer?: DocstringRenderMode + docstringRoleResolver?: RoleResolver + pythonCommand?: PythonCommand +} + +export const loadApiPackage = async (options: LoadApiOptions): Promise => { + const modules = await scanPythonPaths({ + root: options.root, + paths: options.paths, + includePrivate: options.includePrivate, + pythonCommand: options.pythonCommand, + }) + + const introspection = options.introspect + ? ( + await introspectPackage(options.introspectPackage ?? options.name, { + root: options.root, + includePrivate: options.includePrivate, + annotationFormat: options.annotationFormat, + mockImports: options.mockImports, + autodocMock: options.autodocMock, + pythonCommand: options.pythonCommand, + }) + ).modules + : undefined + + const api = buildApiPackage(modules, { + name: options.name, + root: options.root, + includePrivate: options.includePrivate, + generatedAt: options.generatedAt, + introspection, + }) + + return renderDocstrings(api, { + mode: options.docstringRenderer, + roleResolver: options.docstringRoleResolver, + }) +} diff --git a/astro/packages/astro-autodoc/src/styles.css b/astro/packages/astro-autodoc/src/styles.css new file mode 100644 index 000000000..dbe10e9c5 --- /dev/null +++ b/astro/packages/astro-autodoc/src/styles.css @@ -0,0 +1,61 @@ +.autodoc { + --autodoc-border: color-mix(in oklab, currentColor 14%, transparent); + --autodoc-muted: color-mix(in oklab, currentColor 55%, transparent); + --autodoc-highlight: color-mix(in oklab, currentColor 90%, transparent); + font-family: inherit; +} + +.autodoc h2, +.autodoc h3, +.autodoc h4 { + letter-spacing: -0.01em; +} + +.autodoc-card { + border: 1px solid var(--autodoc-border); + border-radius: 16px; + padding: 1.25rem; + background: color-mix(in oklab, currentColor 2%, transparent); +} + +.autodoc-grid { + display: grid; + gap: 1rem; +} + +.autodoc-title { + font-size: 1.2rem; + font-weight: 600; +} + +.autodoc-signature { + font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; + font-size: 0.95rem; + color: var(--autodoc-highlight); +} + +.autodoc-doc { + color: var(--autodoc-muted); + line-height: 1.6; +} + +.autodoc-divider { + border-top: 1px dashed var(--autodoc-border); + margin: 1.25rem 0; +} + +.autodoc-anchor { + text-decoration: none; + color: inherit; +} + +.autodoc-pill { + display: inline-flex; + align-items: center; + gap: 0.35rem; + padding: 0.15rem 0.6rem; + border-radius: 999px; + font-size: 0.75rem; + border: 1px solid var(--autodoc-border); + color: var(--autodoc-muted); +} diff --git a/astro/packages/astro-autodoc/src/utils.ts b/astro/packages/astro-autodoc/src/utils.ts new file mode 100644 index 000000000..b6fc430e4 --- /dev/null +++ b/astro/packages/astro-autodoc/src/utils.ts @@ -0,0 +1,29 @@ +export const slugify = (value: string): string => + value + .toLowerCase() + .replace(/[^a-z0-9]+/g, '-') + .replace(/(^-|-$)+/g, '') + +export const formatSignature = (signature: string, returns: string | null): string => { + if (!returns) { + return signature + } + + if (signature.includes('->')) { + return signature + } + + return `${signature} -> ${returns}` +} + +export const splitDocstring = (docstring: string | null): string[] => { + if (!docstring) { + return [] + } + + return docstring + .trim() + .split(/\n\s*\n/) + .map((block) => block.trim()) + .filter(Boolean) +} diff --git a/astro/packages/astro-autodoc/tests/docstrings.test.ts b/astro/packages/astro-autodoc/tests/docstrings.test.ts new file mode 100644 index 000000000..3e8106cbc --- /dev/null +++ b/astro/packages/astro-autodoc/tests/docstrings.test.ts @@ -0,0 +1,42 @@ +import type { ApiPackage } from '@libtmux/api-model' +import { describe, expect, it } from 'vitest' +import { renderDocstrings } from '../src/docstrings.ts' + +const location = { + lineno: 1, + colOffset: 0, + endLineno: null, + endColOffset: null, +} + +const sampleApi: ApiPackage = { + name: 'demo', + root: '/demo', + generatedAt: '2026-01-05T00:00:00.000Z', + modules: [ + { + kind: 'module', + name: 'demo', + qualname: 'demo', + docstring: 'Hello *world*', + docstringFormat: 'rst', + docstringHtml: null, + summary: null, + isPrivate: false, + location, + path: '/demo/__init__.py', + exports: [], + imports: [], + classes: [], + functions: [], + variables: [], + }, + ], +} + +describe('renderDocstrings', () => { + it('renders rst-lite HTML for module docstrings', () => { + const rendered = renderDocstrings(sampleApi, { mode: 'rst-lite' }) + expect(rendered.modules[0]?.docstringHtml).toMatchInlineSnapshot('"

Hello world

"') + }) +}) diff --git a/astro/packages/astro-autodoc/tests/utils.test.ts b/astro/packages/astro-autodoc/tests/utils.test.ts new file mode 100644 index 000000000..ef139ea2c --- /dev/null +++ b/astro/packages/astro-autodoc/tests/utils.test.ts @@ -0,0 +1,17 @@ +import { describe, expect, it } from 'vitest' +import { formatSignature, slugify, splitDocstring } from '../src/utils' + +describe('autodoc utils', () => { + it('slugifies qualnames', () => { + expect(slugify('libtmux.Server.new_window')).toBe('libtmux-server-new-window') + }) + + it('formats signatures with return types', () => { + expect(formatSignature('(name: str)', 'Window')).toBe('(name: str) -> Window') + }) + + it('splits docstrings into blocks', () => { + const blocks = splitDocstring('Line one.\n\nLine two.') + expect(blocks).toEqual(['Line one.', 'Line two.']) + }) +}) diff --git a/astro/packages/astro-autodoc/tsconfig.json b/astro/packages/astro-autodoc/tsconfig.json new file mode 100644 index 000000000..52234f529 --- /dev/null +++ b/astro/packages/astro-autodoc/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist", + "rootDir": "../../", + "paths": {} + }, + "include": ["src", "tests"], + "references": [] +} diff --git a/astro/packages/astro-autodoc/vitest.config.ts b/astro/packages/astro-autodoc/vitest.config.ts new file mode 100644 index 000000000..9eef0e8a8 --- /dev/null +++ b/astro/packages/astro-autodoc/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, +}) diff --git a/astro/packages/astro-intersphinx/package.json b/astro/packages/astro-intersphinx/package.json new file mode 100644 index 000000000..2eaad39d4 --- /dev/null +++ b/astro/packages/astro-intersphinx/package.json @@ -0,0 +1,35 @@ +{ + "name": "@libtmux/astro-intersphinx", + "version": "0.0.1", + "type": "module", + "description": "Astro helpers for intersphinx resolution", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "src" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "lint": "biome lint .", + "format": "pnpm biome check . --write; biome format . --write", + "type-check": "tsc --noEmit", + "type-check:watch": "tsc --noEmit --watch", + "update": "pnpm update", + "ncu": "ncu", + "clean": "rm -rf dist/ node_modules/.cache/", + "biome": "biome check . --write && biome format . --write" + }, + "dependencies": { + "@libtmux/intersphinx": "workspace:*" + }, + "peerDependencies": { + "astro": "^5.0.0" + }, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@types/node": "^25.0.3", + "typescript": "^5.9.3", + "vitest": "^4.0.16" + } +} diff --git a/astro/packages/astro-intersphinx/src/components/IntersphinxLink.astro b/astro/packages/astro-intersphinx/src/components/IntersphinxLink.astro new file mode 100644 index 000000000..acdddf91f --- /dev/null +++ b/astro/packages/astro-intersphinx/src/components/IntersphinxLink.astro @@ -0,0 +1,15 @@ +--- +import { resolveIntersphinx } from '../resolve' + +const { inventory, ref, label, class: className } = Astro.props +const item = resolveIntersphinx(inventory, ref) +const text = label ?? item?.displayName ?? ref.name +--- + +{item ? ( + + {text} + +) : ( + {text} +)} diff --git a/astro/packages/astro-intersphinx/src/env.d.ts b/astro/packages/astro-intersphinx/src/env.d.ts new file mode 100644 index 000000000..283b70b83 --- /dev/null +++ b/astro/packages/astro-intersphinx/src/env.d.ts @@ -0,0 +1,4 @@ +declare module '*.astro' { + const Component: (props: Record) => unknown + export default Component +} diff --git a/astro/packages/astro-intersphinx/src/index.ts b/astro/packages/astro-intersphinx/src/index.ts new file mode 100644 index 000000000..b6b734366 --- /dev/null +++ b/astro/packages/astro-intersphinx/src/index.ts @@ -0,0 +1,2 @@ +export { default as IntersphinxLink } from './components/IntersphinxLink.astro' +export * from './resolve.ts' diff --git a/astro/packages/astro-intersphinx/src/resolve.ts b/astro/packages/astro-intersphinx/src/resolve.ts new file mode 100644 index 000000000..f6afa7963 --- /dev/null +++ b/astro/packages/astro-intersphinx/src/resolve.ts @@ -0,0 +1,31 @@ +import { readFile } from 'node:fs/promises' +import { type IntersphinxInventory, type IntersphinxItem, parseInventory } from '@libtmux/intersphinx' + +export type IntersphinxRef = { + name: string + domain?: string + role?: string +} + +export const buildIntersphinxIndex = (inventory: IntersphinxInventory): Map => { + const index = new Map() + for (const item of inventory.items) { + index.set(`${item.domain}:${item.role}:${item.name}`, item) + } + return index +} + +export const resolveIntersphinx = ( + inventory: IntersphinxInventory, + ref: IntersphinxRef, +): IntersphinxItem | undefined => { + const domain = ref.domain ?? 'py' + const role = ref.role ?? 'module' + const index = buildIntersphinxIndex(inventory) + return index.get(`${domain}:${role}:${ref.name}`) +} + +export const loadInventoryFromFile = async (filePath: string, baseUrl: string): Promise => { + const buffer = await readFile(filePath) + return parseInventory(buffer, baseUrl) +} diff --git a/astro/packages/astro-intersphinx/tests/resolve.test.ts b/astro/packages/astro-intersphinx/tests/resolve.test.ts new file mode 100644 index 000000000..c0df9f6be --- /dev/null +++ b/astro/packages/astro-intersphinx/tests/resolve.test.ts @@ -0,0 +1,28 @@ +import type { IntersphinxInventory } from '@libtmux/intersphinx' +import { describe, expect, it } from 'vitest' +import { resolveIntersphinx } from '../src/resolve' + +describe('resolveIntersphinx', () => { + it('resolves inventory entries by ref', () => { + const inventory: IntersphinxInventory = { + project: 'libtmux', + version: '0.36.0', + baseUrl: 'https://libtmux.git-pull.com/', + items: [ + { + name: 'libtmux', + domain: 'py', + role: 'module', + priority: 1, + uri: 'api.html#module-libtmux', + displayName: 'libtmux', + url: 'https://libtmux.git-pull.com/api.html#module-libtmux', + baseUrl: 'https://libtmux.git-pull.com/', + }, + ], + } + + const item = resolveIntersphinx(inventory, { name: 'libtmux', domain: 'py', role: 'module' }) + expect(item?.url).toContain('api.html') + }) +}) diff --git a/astro/packages/astro-intersphinx/tsconfig.json b/astro/packages/astro-intersphinx/tsconfig.json new file mode 100644 index 000000000..52234f529 --- /dev/null +++ b/astro/packages/astro-intersphinx/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist", + "rootDir": "../../", + "paths": {} + }, + "include": ["src", "tests"], + "references": [] +} diff --git a/astro/packages/astro-intersphinx/vitest.config.ts b/astro/packages/astro-intersphinx/vitest.config.ts new file mode 100644 index 000000000..9eef0e8a8 --- /dev/null +++ b/astro/packages/astro-intersphinx/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, +}) diff --git a/astro/packages/core/package.json b/astro/packages/core/package.json new file mode 100644 index 000000000..26ff8ff54 --- /dev/null +++ b/astro/packages/core/package.json @@ -0,0 +1,35 @@ +{ + "name": "@libtmux/core", + "version": "0.0.1", + "type": "module", + "description": "Autodoc orchestration layer for libtmux docs", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "src" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "lint": "biome lint .", + "format": "pnpm biome check . --write; biome format . --write", + "type-check": "tsc --noEmit", + "type-check:watch": "tsc --noEmit --watch", + "update": "pnpm update", + "ncu": "ncu", + "clean": "rm -rf dist/ node_modules/.cache/", + "biome": "biome check . --write && biome format . --write" + }, + "dependencies": { + "@libtmux/api-model": "workspace:*", + "@libtmux/py-introspect": "workspace:*", + "@libtmux/py-parse": "workspace:*", + "@libtmux/schema": "workspace:*" + }, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@types/node": "^25.0.3", + "typescript": "^5.9.3", + "vitest": "^4.0.16" + } +} diff --git a/astro/packages/core/src/index.ts b/astro/packages/core/src/index.ts new file mode 100644 index 000000000..be815437b --- /dev/null +++ b/astro/packages/core/src/index.ts @@ -0,0 +1,3 @@ +export type CoreOptions = { + cacheDir?: string +} diff --git a/astro/packages/core/tests/smoke.test.ts b/astro/packages/core/tests/smoke.test.ts new file mode 100644 index 000000000..8e0fb876f --- /dev/null +++ b/astro/packages/core/tests/smoke.test.ts @@ -0,0 +1,7 @@ +import { describe, expect, it } from 'vitest' + +describe('core package', () => { + it('loads without side effects', () => { + expect(true).toBe(true) + }) +}) diff --git a/astro/packages/core/tsconfig.json b/astro/packages/core/tsconfig.json new file mode 100644 index 000000000..52234f529 --- /dev/null +++ b/astro/packages/core/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist", + "rootDir": "../../", + "paths": {} + }, + "include": ["src", "tests"], + "references": [] +} diff --git a/astro/packages/core/vitest.config.ts b/astro/packages/core/vitest.config.ts new file mode 100644 index 000000000..9eef0e8a8 --- /dev/null +++ b/astro/packages/core/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, +}) diff --git a/astro/packages/intersphinx/package.json b/astro/packages/intersphinx/package.json new file mode 100644 index 000000000..1a4addc42 --- /dev/null +++ b/astro/packages/intersphinx/package.json @@ -0,0 +1,32 @@ +{ + "name": "@libtmux/intersphinx", + "version": "0.0.1", + "type": "module", + "description": "Sphinx intersphinx inventory parser", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "src" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "lint": "biome lint .", + "format": "pnpm biome check . --write; biome format . --write", + "type-check": "tsc --noEmit", + "type-check:watch": "tsc --noEmit --watch", + "update": "pnpm update", + "ncu": "ncu", + "clean": "rm -rf dist/ node_modules/.cache/", + "biome": "biome check . --write && biome format . --write" + }, + "dependencies": { + "zod": "^4.3.5" + }, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@types/node": "^25.0.3", + "typescript": "^5.9.3", + "vitest": "^4.0.16" + } +} diff --git a/astro/packages/intersphinx/src/index.ts b/astro/packages/intersphinx/src/index.ts new file mode 100644 index 000000000..4e33ac170 --- /dev/null +++ b/astro/packages/intersphinx/src/index.ts @@ -0,0 +1,2 @@ +export * from './parse.ts' +export * from './schema.ts' diff --git a/astro/packages/intersphinx/src/parse.ts b/astro/packages/intersphinx/src/parse.ts new file mode 100644 index 000000000..07a19b216 --- /dev/null +++ b/astro/packages/intersphinx/src/parse.ts @@ -0,0 +1,83 @@ +import { inflateSync } from 'node:zlib' +import { + type IntersphinxInventory, + IntersphinxInventorySchema, + type IntersphinxItem, + IntersphinxItemSchema, +} from './schema.ts' + +const HEADER_LINES = 4 + +const readHeader = (buffer: Buffer): { header: string[]; offset: number } => { + const header: string[] = [] + let offset = 0 + + for (let index = 0; index < HEADER_LINES; index += 1) { + const end = buffer.indexOf('\n', offset) + if (end === -1) { + throw new Error('Invalid intersphinx inventory header') + } + const line = buffer.slice(offset, end).toString('utf-8').replace(/\r$/, '') + header.push(line) + offset = end + 1 + } + + return { header, offset } +} + +const parseHeaderValue = (header: string[], prefix: string): string => { + const line = header.find((value) => value.startsWith(prefix)) + if (!line) { + return '' + } + return line.slice(prefix.length).trim() +} + +const parseItems = (content: string, baseUrl: string): IntersphinxItem[] => { + const items: IntersphinxItem[] = [] + + for (const line of content.split(/\r?\n/)) { + if (!line.trim()) { + continue + } + + const [name, domainRole, priorityRaw, uri, ...displayParts] = line.split(' ') + const [domain, role] = domainRole.split(':') + const displayName = displayParts.join(' ') || name + const resolvedUri = uri.replace('$', name) + const url = new URL(resolvedUri, baseUrl).toString() + + items.push( + IntersphinxItemSchema.parse({ + name, + domain, + role, + priority: Number(priorityRaw), + uri: resolvedUri, + displayName: displayName === '-' ? name : displayName, + url, + baseUrl, + }), + ) + } + + return items +} + +export const parseInventory = (input: Buffer | string, baseUrl: string): IntersphinxInventory => { + const buffer = typeof input === 'string' ? Buffer.from(input, 'utf-8') : input + const { header, offset } = readHeader(buffer) + const compressed = buffer.slice(offset) + const decompressed = inflateSync(compressed).toString('utf-8') + + const project = parseHeaderValue(header, '# Project:') + const version = parseHeaderValue(header, '# Version:') + const items = parseItems(decompressed, baseUrl) + + return IntersphinxInventorySchema.parse({ + project, + version, + baseUrl, + items, + }) +} diff --git a/astro/packages/intersphinx/src/schema.ts b/astro/packages/intersphinx/src/schema.ts new file mode 100644 index 000000000..c387717c5 --- /dev/null +++ b/astro/packages/intersphinx/src/schema.ts @@ -0,0 +1,22 @@ +import { z } from 'zod' + +export const IntersphinxItemSchema = z.object({ + name: z.string(), + domain: z.string(), + role: z.string(), + priority: z.number().int(), + uri: z.string(), + displayName: z.string(), + url: z.string(), + baseUrl: z.string(), +}) + +export const IntersphinxInventorySchema = z.object({ + project: z.string(), + version: z.string(), + baseUrl: z.string(), + items: z.array(IntersphinxItemSchema), +}) + +export type IntersphinxItem = z.infer +export type IntersphinxInventory = z.infer diff --git a/astro/packages/intersphinx/tests/__snapshots__/parse.test.ts.snap b/astro/packages/intersphinx/tests/__snapshots__/parse.test.ts.snap new file mode 100644 index 000000000..1ac545622 --- /dev/null +++ b/astro/packages/intersphinx/tests/__snapshots__/parse.test.ts.snap @@ -0,0 +1,31 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`parseInventory > parses a minimal intersphinx inventory 1`] = ` +{ + "baseUrl": "https://libtmux.git-pull.com/", + "items": [ + { + "baseUrl": "https://libtmux.git-pull.com/", + "displayName": "libtmux", + "domain": "py", + "name": "libtmux", + "priority": 1, + "role": "module", + "uri": "api.html#module-libtmux", + "url": "https://libtmux.git-pull.com/api.html#module-libtmux", + }, + { + "baseUrl": "https://libtmux.git-pull.com/", + "displayName": "libtmux.Server", + "domain": "py", + "name": "libtmux.Server", + "priority": 1, + "role": "class", + "uri": "api.html#libtmux.Server", + "url": "https://libtmux.git-pull.com/api.html#libtmux.Server", + }, + ], + "project": "libtmux", + "version": "0.36.0", +} +`; diff --git a/astro/packages/intersphinx/tests/parse.test.ts b/astro/packages/intersphinx/tests/parse.test.ts new file mode 100644 index 000000000..53c36f1a4 --- /dev/null +++ b/astro/packages/intersphinx/tests/parse.test.ts @@ -0,0 +1,32 @@ +import { deflateSync } from 'node:zlib' +import { describe, expect, it } from 'vitest' +import { parseInventory } from '../src/parse' + +const buildInventory = (body: string): Buffer => { + const header = [ + '# Sphinx inventory version 2', + '# Project: libtmux', + '# Version: 0.36.0', + '# The remainder of this file is compressed using zlib.', + ].join('\n') + + const compressed = deflateSync(Buffer.from(body, 'utf-8')) + return Buffer.concat([Buffer.from(`${header}\n`, 'utf-8'), compressed]) +} + +describe('parseInventory', () => { + it('parses a minimal intersphinx inventory', () => { + const body = [ + 'libtmux py:module 1 api.html#module-libtmux libtmux', + 'libtmux.Server py:class 1 api.html#libtmux.Server -', + ].join('\n') + + const buffer = buildInventory(body) + const inventory = parseInventory(buffer, 'https://libtmux.git-pull.com/') + + expect(inventory.project).toBe('libtmux') + expect(inventory.items).toHaveLength(2) + expect(inventory.items[1].url).toContain('https://libtmux.git-pull.com/api.html') + expect(inventory).toMatchSnapshot() + }) +}) diff --git a/astro/packages/intersphinx/tsconfig.json b/astro/packages/intersphinx/tsconfig.json new file mode 100644 index 000000000..52234f529 --- /dev/null +++ b/astro/packages/intersphinx/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist", + "rootDir": "../../", + "paths": {} + }, + "include": ["src", "tests"], + "references": [] +} diff --git a/astro/packages/intersphinx/vitest.config.ts b/astro/packages/intersphinx/vitest.config.ts new file mode 100644 index 000000000..9eef0e8a8 --- /dev/null +++ b/astro/packages/intersphinx/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, +}) diff --git a/astro/packages/py-bridge/package.json b/astro/packages/py-bridge/package.json new file mode 100644 index 000000000..9ea9faeaa --- /dev/null +++ b/astro/packages/py-bridge/package.json @@ -0,0 +1,32 @@ +{ + "name": "@libtmux/py-bridge", + "version": "0.0.1", + "type": "module", + "description": "Python subprocess bridge for the libtmux autodoc pipeline", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "src" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "lint": "biome lint .", + "format": "pnpm biome check . --write; biome format . --write", + "type-check": "tsc --noEmit", + "type-check:watch": "tsc --noEmit --watch", + "update": "pnpm update", + "ncu": "ncu", + "clean": "rm -rf dist/ node_modules/.cache/", + "biome": "biome check . --write && biome format . --write" + }, + "dependencies": { + "execa": "^9.6.1" + }, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@types/node": "^25.0.3", + "typescript": "^5.9.3", + "vitest": "^4.0.16" + } +} diff --git a/astro/packages/py-bridge/src/index.ts b/astro/packages/py-bridge/src/index.ts new file mode 100644 index 000000000..cc0913454 --- /dev/null +++ b/astro/packages/py-bridge/src/index.ts @@ -0,0 +1,77 @@ +import path from 'node:path' +import { execa } from 'execa' + +export type PythonCommand = [string, ...string[]] + +export type PythonRunOptions = { + pythonCommand?: PythonCommand + env?: NodeJS.ProcessEnv + cwd?: string + timeoutMs?: number +} + +const DEFAULT_COMMAND_ENV = 'LIBTMUX_PYTHON_COMMAND' +const DEFAULT_COMMAND: PythonCommand = ['uv', 'run', 'python'] +const FALLBACK_COMMAND: PythonCommand = process.platform === 'win32' ? ['python'] : ['python3'] + +export const parsePythonCommand = (value: string | undefined): PythonCommand | undefined => { + if (!value) { + return undefined + } + + const parts = value.split(' ').filter(Boolean) + if (parts.length === 0) { + return undefined + } + + return parts as PythonCommand +} + +const canRun = async (command: PythonCommand): Promise => { + const result = await execa(command[0], [...command.slice(1), '--version'], { + reject: false, + stdin: 'ignore', + }) + + return result.exitCode === 0 +} + +export const resolvePythonCommand = async (override?: PythonCommand): Promise => { + if (override) { + return override + } + + const fromEnv = parsePythonCommand(process.env[DEFAULT_COMMAND_ENV]) + if (fromEnv) { + return fromEnv + } + + if (await canRun(DEFAULT_COMMAND)) { + return DEFAULT_COMMAND + } + + return FALLBACK_COMMAND +} + +export const mergePythonPath = (env: NodeJS.ProcessEnv | undefined, extraPath: string): NodeJS.ProcessEnv => { + const current = env?.PYTHONPATH ?? process.env.PYTHONPATH ?? '' + const pythonPath = [extraPath, current].filter(Boolean).join(path.delimiter) + + return { + ...process.env, + ...env, + PYTHONPATH: pythonPath, + } +} + +export const runPythonJson = async (args: string[], options: PythonRunOptions = {}): Promise => { + const command = await resolvePythonCommand(options.pythonCommand) + const result = await execa(command[0], [...command.slice(1), ...args], { + cwd: options.cwd, + env: options.env, + reject: true, + timeout: options.timeoutMs, + }) + + return JSON.parse(result.stdout) as unknown +} diff --git a/astro/packages/py-bridge/tests/bridge.test.ts b/astro/packages/py-bridge/tests/bridge.test.ts new file mode 100644 index 000000000..1da86cd1d --- /dev/null +++ b/astro/packages/py-bridge/tests/bridge.test.ts @@ -0,0 +1,18 @@ +import path from 'node:path' +import { describe, expect, it } from 'vitest' +import { mergePythonPath, parsePythonCommand } from '../src/index.ts' + +describe('parsePythonCommand', () => { + it('parses commands from strings', () => { + expect(parsePythonCommand('uv run python')).toEqual(['uv', 'run', 'python']) + expect(parsePythonCommand('')).toBeUndefined() + expect(parsePythonCommand(undefined)).toBeUndefined() + }) +}) + +describe('mergePythonPath', () => { + it('prepends paths to PYTHONPATH', () => { + const env = mergePythonPath({ PYTHONPATH: `/tmp${path.delimiter}./local` }, '/sidecar') + expect(env.PYTHONPATH?.startsWith('/sidecar')).toBe(true) + }) +}) diff --git a/astro/packages/py-bridge/tsconfig.json b/astro/packages/py-bridge/tsconfig.json new file mode 100644 index 000000000..52234f529 --- /dev/null +++ b/astro/packages/py-bridge/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist", + "rootDir": "../../", + "paths": {} + }, + "include": ["src", "tests"], + "references": [] +} diff --git a/astro/packages/py-bridge/vitest.config.ts b/astro/packages/py-bridge/vitest.config.ts new file mode 100644 index 000000000..9eef0e8a8 --- /dev/null +++ b/astro/packages/py-bridge/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, +}) diff --git a/astro/packages/py-introspect/package.json b/astro/packages/py-introspect/package.json new file mode 100644 index 000000000..45a6d78dc --- /dev/null +++ b/astro/packages/py-introspect/package.json @@ -0,0 +1,33 @@ +{ + "name": "@libtmux/py-introspect", + "version": "0.0.1", + "type": "module", + "description": "Python runtime introspection wrapper for the libtmux autodoc pipeline", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "src" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "lint": "biome lint .", + "format": "pnpm biome check . --write; biome format . --write", + "type-check": "tsc --noEmit", + "type-check:watch": "tsc --noEmit --watch", + "update": "pnpm update", + "ncu": "ncu", + "clean": "rm -rf dist/ node_modules/.cache/", + "biome": "biome check . --write && biome format . --write" + }, + "dependencies": { + "@libtmux/py-bridge": "workspace:*", + "@libtmux/schema": "workspace:*" + }, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@types/node": "^25.0.3", + "typescript": "^5.9.3", + "vitest": "^4.0.16" + } +} diff --git a/astro/packages/py-introspect/src/index.ts b/astro/packages/py-introspect/src/index.ts new file mode 100644 index 000000000..89480a707 --- /dev/null +++ b/astro/packages/py-introspect/src/index.ts @@ -0,0 +1,104 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { mergePythonPath, type PythonCommand, runPythonJson } from '@libtmux/py-bridge' +import { type PyIntrospectPayload, PyIntrospectPayloadSchema } from '@libtmux/schema' + +export type IntrospectOptions = { + root?: string + includePrivate?: boolean + annotationFormat?: 'string' | 'value' + mockImports?: string[] + autodocMock?: boolean + pythonCommand?: PythonCommand + env?: NodeJS.ProcessEnv + cwd?: string + timeoutMs?: number +} + +export const resolveSidecarRoot = (): string => { + const here = path.dirname(fileURLToPath(import.meta.url)) + return path.resolve(here, '../../../python/pyautodoc_sidecar') +} + +const buildEnv = (env: NodeJS.ProcessEnv | undefined, sidecarRoot: string): NodeJS.ProcessEnv => { + const pythonPath = path.join(sidecarRoot, 'src') + return mergePythonPath(env, pythonPath) +} + +export const introspectModule = async ( + moduleName: string, + options: IntrospectOptions = {}, +): Promise => { + const sidecarRoot = resolveSidecarRoot() + const args = ['-m', 'pyautodoc_sidecar', 'introspect-module', '--module', moduleName] + + if (options.root) { + args.push('--root', options.root) + } + + if (options.includePrivate) { + args.push('--include-private') + } + + if (options.annotationFormat) { + args.push('--annotation-format', options.annotationFormat) + } + + if (options.mockImports) { + for (const name of options.mockImports) { + args.push('--mock-import', name) + } + } + + if (options.autodocMock) { + args.push('--autodoc-mock') + } + + const payload = await runPythonJson(args, { + pythonCommand: options.pythonCommand, + cwd: options.cwd ?? sidecarRoot, + env: buildEnv(options.env, sidecarRoot), + timeoutMs: options.timeoutMs, + }) + + return PyIntrospectPayloadSchema.parse(payload) +} + +export const introspectPackage = async ( + packageName: string, + options: IntrospectOptions = {}, +): Promise => { + const sidecarRoot = resolveSidecarRoot() + const args = ['-m', 'pyautodoc_sidecar', 'introspect-package', '--package', packageName] + + if (options.root) { + args.push('--root', options.root) + } + + if (options.includePrivate) { + args.push('--include-private') + } + + if (options.annotationFormat) { + args.push('--annotation-format', options.annotationFormat) + } + + if (options.mockImports) { + for (const name of options.mockImports) { + args.push('--mock-import', name) + } + } + + if (options.autodocMock) { + args.push('--autodoc-mock') + } + + const payload = await runPythonJson(args, { + pythonCommand: options.pythonCommand, + cwd: options.cwd ?? sidecarRoot, + env: buildEnv(options.env, sidecarRoot), + timeoutMs: options.timeoutMs, + }) + + return PyIntrospectPayloadSchema.parse(payload) +} diff --git a/astro/packages/py-introspect/tests/sidecar.test.ts b/astro/packages/py-introspect/tests/sidecar.test.ts new file mode 100644 index 000000000..9a3a69254 --- /dev/null +++ b/astro/packages/py-introspect/tests/sidecar.test.ts @@ -0,0 +1,11 @@ +import fs from 'node:fs' +import path from 'node:path' +import { describe, expect, it } from 'vitest' +import { resolveSidecarRoot } from '../src/index.ts' + +describe('resolveSidecarRoot', () => { + it('points at the sidecar project', () => { + const root = resolveSidecarRoot() + expect(fs.existsSync(path.join(root, 'pyproject.toml'))).toBe(true) + }) +}) diff --git a/astro/packages/py-introspect/tsconfig.json b/astro/packages/py-introspect/tsconfig.json new file mode 100644 index 000000000..52234f529 --- /dev/null +++ b/astro/packages/py-introspect/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist", + "rootDir": "../../", + "paths": {} + }, + "include": ["src", "tests"], + "references": [] +} diff --git a/astro/packages/py-introspect/vitest.config.ts b/astro/packages/py-introspect/vitest.config.ts new file mode 100644 index 000000000..9eef0e8a8 --- /dev/null +++ b/astro/packages/py-introspect/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, +}) diff --git a/astro/packages/py-parse/package.json b/astro/packages/py-parse/package.json new file mode 100644 index 000000000..3a99890fa --- /dev/null +++ b/astro/packages/py-parse/package.json @@ -0,0 +1,33 @@ +{ + "name": "@libtmux/py-parse", + "version": "0.0.1", + "type": "module", + "description": "Python AST scanner for libtmux docs", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "src" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "lint": "biome lint .", + "format": "pnpm biome check . --write; biome format . --write", + "type-check": "tsc --noEmit", + "type-check:watch": "tsc --noEmit --watch", + "update": "pnpm update", + "ncu": "ncu", + "clean": "rm -rf dist/ node_modules/.cache/", + "biome": "biome check . --write && biome format . --write" + }, + "dependencies": { + "@libtmux/py-bridge": "workspace:*", + "@libtmux/schema": "workspace:*" + }, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@types/node": "^25.0.3", + "typescript": "^5.9.3", + "vitest": "^4.0.16" + } +} diff --git a/astro/packages/py-parse/src/index.ts b/astro/packages/py-parse/src/index.ts new file mode 100644 index 000000000..24934ca32 --- /dev/null +++ b/astro/packages/py-parse/src/index.ts @@ -0,0 +1,95 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { mergePythonPath, type PythonCommand, runPythonJson } from '@libtmux/py-bridge' +import { + type PyImport, + type PyModule, + PyModuleSchema, + type PyNode, + PyNodeSchema, + PyParsePayloadSchema, +} from '@libtmux/schema' + +export type ScanOptions = { + root: string + paths: string[] + includePrivate?: boolean + pythonCommand?: PythonCommand + env?: NodeJS.ProcessEnv + cwd?: string + timeoutMs?: number +} + +const resolveSidecarRoot = (): string => { + const here = path.dirname(fileURLToPath(import.meta.url)) + return path.resolve(here, '../../../python/pyautodoc_sidecar') +} + +const buildEnv = (env: NodeJS.ProcessEnv | undefined, sidecarRoot: string): NodeJS.ProcessEnv => { + const pythonPath = path.join(sidecarRoot, 'src') + return mergePythonPath(env, pythonPath) +} + +export const scanPythonPaths = async (options: ScanOptions): Promise => { + const sidecarRoot = resolveSidecarRoot() + const args = ['-m', 'pyautodoc_sidecar', 'parse-files', '--root', options.root, '--paths', ...options.paths] + + if (options.includePrivate) { + args.push('--include-private') + } + + const payload = await runPythonJson(args, { + pythonCommand: options.pythonCommand, + cwd: options.cwd ?? sidecarRoot, + env: buildEnv(options.env, sidecarRoot), + timeoutMs: options.timeoutMs, + }) + + return PyParsePayloadSchema.parse(payload).modules +} + +export const scanPythonModule = async ( + root: string, + modulePath: string, + options: Omit = {}, +): Promise => { + const modules = await scanPythonPaths({ + root, + paths: [modulePath], + ...options, + }) + + if (modules.length === 0) { + throw new Error(`No modules found for ${modulePath}`) + } + + return PyModuleSchema.parse(modules[0]) +} + +export const collectImports = (modules: PyModule[]): PyImport[] => modules.flatMap((module) => module.imports) + +export type PyQualifiedNode = Exclude + +export function* walkPyNodes(modules: PyModule[]): Generator { + for (const module of modules) { + for (const item of module.items) { + if (item.kind === 'import') { + continue + } + yield item + if (item.kind === 'class') { + for (const method of item.methods) { + yield method + } + for (const attribute of item.attributes) { + yield attribute + } + } + } + } +} + +export const parsePyNode = (input: unknown): PyNode => PyNodeSchema.parse(input) + +export type { PyImport, PyModule, PyNode } +export { PyModuleSchema, PyNodeSchema, PyParsePayloadSchema } diff --git a/astro/packages/py-parse/tests/__snapshots__/scan.test.ts.snap b/astro/packages/py-parse/tests/__snapshots__/scan.test.ts.snap new file mode 100644 index 000000000..4e5f5e845 --- /dev/null +++ b/astro/packages/py-parse/tests/__snapshots__/scan.test.ts.snap @@ -0,0 +1,352 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`scanPythonPaths > parses python modules into zod-backed structures 1`] = ` +{ + "docstring": "Sample module for AST scanning.", + "exports": [], + "imports": [ + { + "kind": "import", + "level": 0, + "location": { + "colOffset": 0, + "endColOffset": 34, + "endLineno": 3, + "lineno": 3, + }, + "module": "__future__", + "names": [ + "annotations", + ], + }, + { + "kind": "import", + "level": null, + "location": { + "colOffset": 0, + "endColOffset": 18, + "endLineno": 5, + "lineno": 5, + }, + "module": "dataclasses", + "names": [ + "dataclasses", + ], + }, + { + "kind": "import", + "level": null, + "location": { + "colOffset": 0, + "endColOffset": 18, + "endLineno": 6, + "lineno": 6, + }, + "module": "typing", + "names": [ + "typing as t", + ], + }, + ], + "isPackage": false, + "items": [ + { + "annotation": "str", + "docstring": null, + "isPrivate": false, + "kind": "variable", + "location": { + "colOffset": 0, + "endColOffset": 23, + "endLineno": 8, + "lineno": 8, + }, + "name": "CONSTANT", + "qualname": "sample_module.CONSTANT", + "value": "'value'", + }, + { + "decorators": [], + "docstring": "Return a friendly greeting. + +Parameters +---------- +name : str + Name to greet. + +Returns +------- +str + Greeting string.", + "isAsync": false, + "isPrivate": false, + "kind": "function", + "location": { + "colOffset": 0, + "endColOffset": 26, + "endLineno": 24, + "lineno": 11, + }, + "name": "greet", + "parameters": [ + { + "annotation": "str", + "default": null, + "kind": "positional-or-keyword", + "name": "name", + }, + ], + "qualname": "sample_module.greet", + "returns": "str", + }, + { + "attributes": [ + { + "annotation": "str", + "docstring": null, + "isPrivate": false, + "kind": "variable", + "location": { + "colOffset": 4, + "endColOffset": 13, + "endLineno": 35, + "lineno": 35, + }, + "name": "name", + "qualname": "sample_module.Widget.name", + "value": null, + }, + { + "annotation": "int", + "docstring": null, + "isPrivate": false, + "kind": "variable", + "location": { + "colOffset": 4, + "endColOffset": 17, + "endLineno": 36, + "lineno": 36, + }, + "name": "size", + "qualname": "sample_module.Widget.size", + "value": "1", + }, + ], + "bases": [], + "decorators": [ + "dataclasses.dataclass", + ], + "docstring": "Widget model.", + "isPrivate": false, + "kind": "class", + "location": { + "colOffset": 0, + "endColOffset": 41, + "endLineno": 46, + "lineno": 32, + }, + "methods": [ + { + "decorators": [], + "docstring": "Return a label. + +Returns +------- +str + Label string.", + "isAsync": false, + "isPrivate": false, + "kind": "function", + "location": { + "colOffset": 4, + "endColOffset": 41, + "endLineno": 46, + "lineno": 38, + }, + "name": "label", + "parameters": [ + { + "annotation": null, + "default": null, + "kind": "positional-or-keyword", + "name": "self", + }, + ], + "qualname": "sample_module.Widget.label", + "returns": "str", + }, + ], + "name": "Widget", + "qualname": "sample_module.Widget", + }, + { + "attributes": [ + { + "annotation": "int", + "docstring": null, + "isPrivate": false, + "kind": "variable", + "location": { + "colOffset": 4, + "endColOffset": 18, + "endLineno": 52, + "lineno": 52, + }, + "name": "count", + "qualname": "sample_module.Container.count", + "value": "0", + }, + ], + "bases": [], + "decorators": [], + "docstring": "Container type.", + "isPrivate": false, + "kind": "class", + "location": { + "colOffset": 0, + "endColOffset": 19, + "endLineno": 77, + "lineno": 49, + }, + "methods": [ + { + "decorators": [], + "docstring": "Create a container. + +Parameters +---------- +items : list[str] + Items to store.", + "isAsync": false, + "isPrivate": true, + "kind": "function", + "location": { + "colOffset": 4, + "endColOffset": 26, + "endLineno": 62, + "lineno": 54, + }, + "name": "__init__", + "parameters": [ + { + "annotation": null, + "default": null, + "kind": "positional-or-keyword", + "name": "self", + }, + { + "annotation": "list[str]", + "default": null, + "kind": "positional-or-keyword", + "name": "items", + }, + ], + "qualname": "sample_module.Container.__init__", + "returns": "None", + }, + { + "decorators": [ + "property", + ], + "docstring": "Return count of items. + +Returns +------- +int + Item count.", + "isAsync": false, + "isPrivate": false, + "kind": "function", + "location": { + "colOffset": 4, + "endColOffset": 30, + "endLineno": 73, + "lineno": 65, + }, + "name": "item_count", + "parameters": [ + { + "annotation": null, + "default": null, + "kind": "positional-or-keyword", + "name": "self", + }, + ], + "qualname": "sample_module.Container.item_count", + "returns": "int", + }, + { + "decorators": [], + "docstring": "Refresh the container.", + "isAsync": true, + "isPrivate": false, + "kind": "function", + "location": { + "colOffset": 4, + "endColOffset": 19, + "endLineno": 77, + "lineno": 75, + }, + "name": "refresh", + "parameters": [ + { + "annotation": null, + "default": null, + "kind": "positional-or-keyword", + "name": "self", + }, + ], + "qualname": "sample_module.Container.refresh", + "returns": "None", + }, + ], + "name": "Container", + "qualname": "sample_module.Container", + }, + { + "decorators": [], + "docstring": "Return provided value. + +Parameters +---------- +value : typing.Any + Input value. + +Returns +------- +typing.Any + Input value.", + "isAsync": false, + "isPrivate": false, + "kind": "function", + "location": { + "colOffset": 0, + "endColOffset": 16, + "endLineno": 93, + "lineno": 80, + }, + "name": "uses_typing", + "parameters": [ + { + "annotation": "t.Any", + "default": null, + "kind": "positional-or-keyword", + "name": "value", + }, + ], + "qualname": "sample_module.uses_typing", + "returns": "t.Any", + }, + ], + "kind": "module", + "location": { + "colOffset": 0, + "endColOffset": null, + "endLineno": null, + "lineno": 1, + }, + "name": "sample_module", + "path": "/home/d/work/python/libtmux/astro/packages/py-parse/tests/fixtures/sample_module.py", + "qualname": "sample_module", +} +`; diff --git a/astro/packages/py-parse/tests/fixtures/sample_module.py b/astro/packages/py-parse/tests/fixtures/sample_module.py new file mode 100644 index 000000000..761563181 --- /dev/null +++ b/astro/packages/py-parse/tests/fixtures/sample_module.py @@ -0,0 +1,93 @@ +"""Sample module for AST scanning.""" + +from __future__ import annotations + +import dataclasses +import typing as t + +CONSTANT: str = "value" + + +def greet(name: str) -> str: + """Return a friendly greeting. + + Parameters + ---------- + name : str + Name to greet. + + Returns + ------- + str + Greeting string. + """ + return f"hello {name}" + + +def _private() -> None: + """Private helper.""" + + +@dataclasses.dataclass +class Widget: + """Widget model.""" + + name: str + size: int = 1 + + def label(self) -> str: + """Return a label. + + Returns + ------- + str + Label string. + """ + return f"{self.name}:{self.size}" + + +class Container: + """Container type.""" + + count: int = 0 + + def __init__(self, items: list[str]) -> None: + """Create a container. + + Parameters + ---------- + items : list[str] + Items to store. + """ + self.items = items + + @property + def item_count(self) -> int: + """Return count of items. + + Returns + ------- + int + Item count. + """ + return len(self.items) + + async def refresh(self) -> None: + """Refresh the container.""" + return None + + +def uses_typing(value: t.Any) -> t.Any: + """Return provided value. + + Parameters + ---------- + value : typing.Any + Input value. + + Returns + ------- + typing.Any + Input value. + """ + return value diff --git a/astro/packages/py-parse/tests/scan.test.ts b/astro/packages/py-parse/tests/scan.test.ts new file mode 100644 index 000000000..6cffd07d3 --- /dev/null +++ b/astro/packages/py-parse/tests/scan.test.ts @@ -0,0 +1,35 @@ +import path from 'node:path' +import { fileURLToPath } from 'node:url' +import { describe, expect, it } from 'vitest' +import { scanPythonPaths, walkPyNodes } from '../src/index' + +const fixturesRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), 'fixtures') + +const samplePath = path.join(fixturesRoot, 'sample_module.py') + +describe('scanPythonPaths', () => { + it('parses python modules into zod-backed structures', async () => { + const modules = await scanPythonPaths({ + root: fixturesRoot, + paths: [samplePath], + pythonCommand: ['python3'], + }) + + expect(modules).toHaveLength(1) + expect(modules[0].name).toBe('sample_module') + expect(modules[0].imports.map((item) => item.module)).toContain('typing') + expect(modules[0]).toMatchSnapshot() + }) + + it('walks nested nodes', async () => { + const modules = await scanPythonPaths({ + root: fixturesRoot, + paths: [samplePath], + pythonCommand: ['python3'], + }) + + const names = Array.from(walkPyNodes(modules)).map((node) => node.qualname) + expect(names).toContain('sample_module.Widget.label') + expect(names).toContain('sample_module.greet') + }) +}) diff --git a/astro/packages/py-parse/tsconfig.json b/astro/packages/py-parse/tsconfig.json new file mode 100644 index 000000000..52234f529 --- /dev/null +++ b/astro/packages/py-parse/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist", + "rootDir": "../../", + "paths": {} + }, + "include": ["src", "tests"], + "references": [] +} diff --git a/astro/packages/py-parse/vitest.config.ts b/astro/packages/py-parse/vitest.config.ts new file mode 100644 index 000000000..9eef0e8a8 --- /dev/null +++ b/astro/packages/py-parse/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, +}) diff --git a/astro/packages/rst-lite/package.json b/astro/packages/rst-lite/package.json new file mode 100644 index 000000000..d16020c29 --- /dev/null +++ b/astro/packages/rst-lite/package.json @@ -0,0 +1,30 @@ +{ + "name": "@libtmux/rst-lite", + "version": "0.0.1", + "type": "module", + "description": "RST-lite parser/renderer for libtmux docstrings", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "src" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "lint": "biome lint .", + "format": "pnpm biome check . --write; biome format . --write", + "type-check": "tsc --noEmit", + "type-check:watch": "tsc --noEmit --watch", + "update": "pnpm update", + "ncu": "ncu", + "clean": "rm -rf dist/ node_modules/.cache/", + "biome": "biome check . --write && biome format . --write" + }, + "dependencies": {}, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@types/node": "^25.0.3", + "typescript": "^5.9.3", + "vitest": "^4.0.16" + } +} diff --git a/astro/packages/rst-lite/src/ast.ts b/astro/packages/rst-lite/src/ast.ts new file mode 100644 index 000000000..85988c218 --- /dev/null +++ b/astro/packages/rst-lite/src/ast.ts @@ -0,0 +1,70 @@ +export type InlineNode = + | { type: 'text'; value: string } + | { type: 'emphasis'; value: InlineNode[] } + | { type: 'strong'; value: InlineNode[] } + | { type: 'literal'; value: string } + | { type: 'role'; role: string; value: string } + +export type ParagraphNode = { + type: 'paragraph' + content: InlineNode[] +} + +export type HeadingNode = { + type: 'heading' + level: number + content: InlineNode[] +} + +export type CodeBlockNode = { + type: 'code' + text: string +} + +export type ListItemNode = { + type: 'list_item' + children: BlockNode[] +} + +export type ListNode = { + type: 'list' + ordered: boolean + items: ListItemNode[] +} + +export type FieldItemNode = { + name: string + typeText?: string + body: BlockNode[] +} + +export type FieldListNode = { + type: 'field_list' + items: FieldItemNode[] +} + +export type AdmonitionNode = { + type: 'admonition' + name: string + title?: string + body: BlockNode[] +} + +export type BlockQuoteNode = { + type: 'blockquote' + children: BlockNode[] +} + +export type BlockNode = + | ParagraphNode + | HeadingNode + | CodeBlockNode + | ListNode + | FieldListNode + | AdmonitionNode + | BlockQuoteNode + +export type DocumentNode = { + type: 'document' + children: BlockNode[] +} diff --git a/astro/packages/rst-lite/src/index.ts b/astro/packages/rst-lite/src/index.ts new file mode 100644 index 000000000..2252eb2be --- /dev/null +++ b/astro/packages/rst-lite/src/index.ts @@ -0,0 +1,16 @@ +export type { + AdmonitionNode, + BlockNode, + BlockQuoteNode, + CodeBlockNode, + DocumentNode, + FieldItemNode, + FieldListNode, + HeadingNode, + InlineNode, + ListItemNode, + ListNode, + ParagraphNode, +} from './ast.ts' +export { parseRst } from './parser.ts' +export { type RenderOptions, type RoleResolution, type RoleResolver, renderHtml } from './renderer.ts' diff --git a/astro/packages/rst-lite/src/parser.ts b/astro/packages/rst-lite/src/parser.ts new file mode 100644 index 000000000..9d07199ab --- /dev/null +++ b/astro/packages/rst-lite/src/parser.ts @@ -0,0 +1,526 @@ +import type { + AdmonitionNode, + BlockNode, + BlockQuoteNode, + CodeBlockNode, + DocumentNode, + FieldItemNode, + FieldListNode, + HeadingNode, + InlineNode, + ListItemNode, + ListNode, + ParagraphNode, +} from './ast.ts' + +const HEADING_LEVELS = new Map([ + ['=', 1], + ['-', 2], + ['~', 3], + ['^', 4], + ['"', 5], + ['`', 6], +]) + +type Line = { + raw: string + indent: number + text: string +} + +const toLine = (raw: string): Line => { + const match = raw.match(/^[ \t]*/) + const prefix = match ? match[0] : '' + let indent = 0 + for (const ch of prefix) { + indent += ch === '\t' ? 4 : 1 + } + return { raw, indent, text: raw.slice(prefix.length) } +} + +const isBlank = (line: Line): boolean => line.text.trim().length === 0 + +const isHeadingUnderline = (line: Line): { level: number } | null => { + const trimmed = line.text.trim() + if (trimmed.length < 3) { + return null + } + const char = trimmed[0] + if (!HEADING_LEVELS.has(char)) { + return null + } + if (!trimmed.split('').every((item) => item === char)) { + return null + } + return { level: HEADING_LEVELS.get(char) ?? 2 } +} + +const detectHeading = (lines: Line[], index: number, baseIndent: number): HeadingNode | null => { + const current = lines[index] + const next = lines[index + 1] + if (!current || !next) { + return null + } + if (current.indent !== baseIndent || next.indent !== baseIndent) { + return null + } + if (isBlank(current) || isBlank(next)) { + return null + } + const underline = isHeadingUnderline(next) + if (!underline) { + return null + } + return { + type: 'heading', + level: underline.level, + content: parseInline(current.text.trim()), + } +} + +type ListMarker = { + ordered: boolean + marker: string + content: string +} + +const parseListMarker = (text: string): ListMarker | null => { + const bulletMatch = text.match(/^([-+*])\s+(.*)$/) + if (bulletMatch) { + return { ordered: false, marker: bulletMatch[1], content: bulletMatch[2] } + } + const orderedMatch = text.match(/^(\d+)([.)])\s+(.*)$/) + if (orderedMatch) { + return { + ordered: true, + marker: `${orderedMatch[1]}${orderedMatch[2]}`, + content: orderedMatch[3], + } + } + return null +} + +const parseDirective = ( + lines: Line[], + index: number, + baseIndent: number, +): { node: AdmonitionNode; nextIndex: number } | null => { + const line = lines[index] + if (line.indent !== baseIndent) { + return null + } + const match = line.text.match(/^\.\.\s+(\w+)(::\s*(.*))?$/) + if (!match) { + return null + } + const name = match[1] + const arg = match[3]?.trim() + let nextIndex = index + 1 + while (nextIndex < lines.length && isBlank(lines[nextIndex])) { + nextIndex += 1 + } + const bodyLines: Line[] = [] + let bodyIndent = 0 + if (nextIndex < lines.length && lines[nextIndex].indent > baseIndent) { + bodyIndent = lines[nextIndex].indent + for (let i = nextIndex; i < lines.length; i += 1) { + const candidate = lines[i] + if (candidate.indent < bodyIndent && !isBlank(candidate)) { + break + } + if (candidate.indent < baseIndent) { + break + } + bodyLines.push(candidate) + nextIndex = i + 1 + } + } + const { nodes } = parseBlocks(bodyLines, 0, bodyIndent || baseIndent + 2) + return { + node: { + type: 'admonition', + name, + title: formatAdmonitionTitle(name, arg), + body: nodes, + }, + nextIndex, + } +} + +const formatAdmonitionTitle = (name: string, arg: string | undefined): string => { + const label = name.replace(/[-_]+/g, ' ').replace(/\b\w/g, (char) => char.toUpperCase()) + if (!arg) { + return label + } + return `${label}: ${arg}` +} + +const parseFieldList = ( + lines: Line[], + startIndex: number, + baseIndent: number, +): { node: FieldListNode; nextIndex: number } | null => { + const fieldMatch = matchField(lines[startIndex], baseIndent) + if (!fieldMatch) { + return null + } + const items: FieldItemNode[] = [] + let index = startIndex + while (index < lines.length) { + const current = lines[index] + const match = matchField(current, baseIndent) + if (!match) { + break + } + const { name, typeText } = match + index += 1 + const bodyLines: Line[] = [] + let bodyIndent = 0 + while (index < lines.length) { + const next = lines[index] + if (next.indent < baseIndent) { + break + } + if (next.indent === baseIndent && matchField(next, baseIndent)) { + break + } + if (next.indent > baseIndent && bodyIndent === 0 && !isBlank(next)) { + bodyIndent = next.indent + } + bodyLines.push(next) + index += 1 + } + const { nodes } = parseBlocks(bodyLines, 0, bodyIndent || baseIndent + 2) + items.push({ name, typeText, body: nodes }) + } + return { + node: { + type: 'field_list', + items, + }, + nextIndex: index, + } +} + +const matchField = (line: Line, baseIndent: number): { name: string; typeText?: string } | null => { + if (line.indent !== baseIndent) { + return null + } + const match = line.text.match(/^([A-Za-z_][\w.-]*)(\s*:\s*)(.+)$/) + if (!match) { + return null + } + return { name: match[1], typeText: match[3].trim() } +} + +const parseList = ( + lines: Line[], + startIndex: number, + baseIndent: number, +): { node: ListNode; nextIndex: number } | null => { + const first = lines[startIndex] + if (!first || first.indent !== baseIndent) { + return null + } + const marker = parseListMarker(first.text) + if (!marker) { + return null + } + const items: ListItemNode[] = [] + const ordered = marker.ordered + let index = startIndex + while (index < lines.length) { + const line = lines[index] + if (line.indent !== baseIndent) { + break + } + const markerMatch = parseListMarker(line.text) + if (!markerMatch || markerMatch.ordered !== ordered) { + break + } + const contentIndent = baseIndent + markerMatch.marker.length + 1 + const itemLines: Line[] = [] + itemLines.push({ raw: markerMatch.content, indent: contentIndent, text: markerMatch.content }) + index += 1 + while (index < lines.length) { + const next = lines[index] + if (next.indent < baseIndent) { + break + } + if (next.indent === baseIndent && parseListMarker(next.text)) { + break + } + if (next.indent < contentIndent && !isBlank(next)) { + itemLines.push({ raw: next.raw, indent: contentIndent, text: next.text.trim() }) + } else { + itemLines.push(next) + } + index += 1 + } + const { nodes } = parseBlocks(itemLines, 0, contentIndent) + items.push({ type: 'list_item', children: nodes }) + } + return { + node: { + type: 'list', + ordered, + items, + }, + nextIndex: index, + } +} + +const parseBlockQuote = ( + lines: Line[], + startIndex: number, + baseIndent: number, +): { node: BlockQuoteNode; nextIndex: number } => { + const quoteIndent = lines[startIndex].indent + const subset: Line[] = [] + let index = startIndex + while (index < lines.length) { + const line = lines[index] + if (line.indent < quoteIndent && !isBlank(line)) { + break + } + if (line.indent < baseIndent) { + break + } + subset.push(line) + index += 1 + } + const { nodes } = parseBlocks(subset, 0, quoteIndent) + return { + node: { type: 'blockquote', children: nodes }, + nextIndex: index, + } +} + +const parseParagraph = ( + lines: Line[], + startIndex: number, + baseIndent: number, +): { node: ParagraphNode; nextIndex: number; literalBlock: boolean } => { + const textLines: string[] = [] + let index = startIndex + while (index < lines.length) { + const line = lines[index] + if (line.indent < baseIndent) { + break + } + if (isBlank(line)) { + break + } + if (line.indent === baseIndent) { + if (detectHeading(lines, index, baseIndent)) { + break + } + if (parseListMarker(line.text)) { + break + } + if (matchField(line, baseIndent)) { + break + } + if (line.text.startsWith('.. ')) { + break + } + } + textLines.push(line.text.trim()) + index += 1 + } + let literalBlock = false + if (textLines.length > 0) { + const lastIndex = textLines.length - 1 + if (textLines[lastIndex].endsWith('::')) { + literalBlock = true + textLines[lastIndex] = textLines[lastIndex].replace(/::$/, ':') + } + } + const text = textLines.join('\n') + return { + node: { type: 'paragraph', content: parseInline(text) }, + nextIndex: index, + literalBlock, + } +} + +const parseLiteralBlock = ( + lines: Line[], + startIndex: number, + baseIndent: number, +): { node: CodeBlockNode | null; nextIndex: number } => { + let index = startIndex + while (index < lines.length && isBlank(lines[index])) { + index += 1 + } + if (index >= lines.length || lines[index].indent <= baseIndent) { + return { node: null, nextIndex: startIndex } + } + const blockLines: Line[] = [] + while (index < lines.length) { + const line = lines[index] + if (line.indent <= baseIndent && !isBlank(line)) { + break + } + blockLines.push(line) + index += 1 + } + const contentLines = blockLines.filter((line) => !isBlank(line)) + const minIndent = contentLines.reduce( + (min, line) => Math.min(min, line.indent), + contentLines.length > 0 ? contentLines[0].indent : baseIndent + 2, + ) + const text = blockLines + .map((line) => { + if (isBlank(line)) { + return '' + } + return line.raw.slice(minIndent) + }) + .join('\n') + return { node: { type: 'code', text }, nextIndex: index } +} + +const parseBlocks = ( + lines: Line[], + startIndex: number, + baseIndent: number, +): { nodes: BlockNode[]; nextIndex: number } => { + const nodes: BlockNode[] = [] + let index = startIndex + while (index < lines.length) { + const line = lines[index] + if (!line) { + break + } + if (line.indent < baseIndent) { + break + } + if (isBlank(line)) { + index += 1 + continue + } + + const heading = detectHeading(lines, index, baseIndent) + if (heading) { + nodes.push(heading) + index += 2 + continue + } + + const directive = parseDirective(lines, index, baseIndent) + if (directive) { + nodes.push(directive.node) + index = directive.nextIndex + continue + } + + const list = parseList(lines, index, baseIndent) + if (list) { + nodes.push(list.node) + index = list.nextIndex + continue + } + + const fieldList = parseFieldList(lines, index, baseIndent) + if (fieldList) { + nodes.push(fieldList.node) + index = fieldList.nextIndex + continue + } + + if (line.indent > baseIndent) { + const quote = parseBlockQuote(lines, index, baseIndent) + nodes.push(quote.node) + index = quote.nextIndex + continue + } + + const paragraph = parseParagraph(lines, index, baseIndent) + nodes.push(paragraph.node) + index = paragraph.nextIndex + if (paragraph.literalBlock) { + const literal = parseLiteralBlock(lines, index, baseIndent) + if (literal.node) { + nodes.push(literal.node) + index = literal.nextIndex + } + } + } + return { nodes, nextIndex: index } +} + +export const parseRst = (input: string): DocumentNode => { + const normalized = input.replace(/\r\n/g, '\n') + const lines = normalized.split('\n').map((line) => toLine(line)) + const { nodes } = parseBlocks(lines, 0, 0) + return { type: 'document', children: nodes } +} + +const parseInline = (text: string): InlineNode[] => { + const normalized = text.replace(/\n+/g, ' ') + const nodes: InlineNode[] = [] + let index = 0 + while (index < normalized.length) { + const rest = normalized.slice(index) + const roleMatch = rest.match(/^:([\w:]+):`([^`]+)`/) + if (roleMatch) { + nodes.push({ type: 'role', role: roleMatch[1], value: roleMatch[2] }) + index += roleMatch[0].length + continue + } + if (rest.startsWith('``')) { + const end = rest.indexOf('``', 2) + if (end !== -1) { + const value = rest.slice(2, end) + nodes.push({ type: 'literal', value }) + index += end + 2 + continue + } + } + if (rest.startsWith('**')) { + const end = rest.indexOf('**', 2) + if (end !== -1) { + const content = rest.slice(2, end) + nodes.push({ type: 'strong', value: parseInline(content) }) + index += end + 2 + continue + } + } + if (rest.startsWith('*')) { + const end = rest.indexOf('*', 1) + if (end !== -1) { + const content = rest.slice(1, end) + nodes.push({ type: 'emphasis', value: parseInline(content) }) + index += end + 1 + continue + } + } + const nextSpecial = rest.search(/[:*`]/) + if (nextSpecial === -1) { + nodes.push({ type: 'text', value: rest }) + break + } + if (nextSpecial > 0) { + nodes.push({ type: 'text', value: rest.slice(0, nextSpecial) }) + index += nextSpecial + } else { + nodes.push({ type: 'text', value: rest[0] }) + index += 1 + } + } + return mergeTextNodes(nodes) +} + +const mergeTextNodes = (nodes: InlineNode[]): InlineNode[] => { + const merged: InlineNode[] = [] + for (const node of nodes) { + const last = merged.at(-1) + if (node.type === 'text' && last?.type === 'text') { + last.value += node.value + } else { + merged.push(node) + } + } + return merged +} diff --git a/astro/packages/rst-lite/src/renderer.ts b/astro/packages/rst-lite/src/renderer.ts new file mode 100644 index 000000000..c60de86db --- /dev/null +++ b/astro/packages/rst-lite/src/renderer.ts @@ -0,0 +1,117 @@ +import type { + AdmonitionNode, + BlockNode, + DocumentNode, + FieldListNode, + InlineNode, + ListItemNode, + ListNode, +} from './ast.ts' + +export type RoleResolution = { + href: string + text?: string +} + +export type RoleResolver = (role: string, value: string) => RoleResolution | null + +export type RenderOptions = { + roleResolver?: RoleResolver +} + +export const renderHtml = (doc: DocumentNode, options: RenderOptions = {}): string => { + return doc.children.map((node) => renderBlock(node, options)).join('') +} + +const renderBlock = (node: BlockNode, options: RenderOptions): string => { + switch (node.type) { + case 'paragraph': + return `

${renderInline(node.content, options)}

` + case 'heading': + return `${renderInline(node.content, options)}` + case 'code': + return `
${escapeHtml(node.text)}
` + case 'list': + return renderList(node, options) + case 'field_list': + return renderFieldList(node, options) + case 'admonition': + return renderAdmonition(node, options) + case 'blockquote': + return `
${node.children.map((child) => renderBlock(child, options)).join('')}
` + default: + return '' + } +} + +const renderList = (node: ListNode, options: RenderOptions): string => { + const tag = node.ordered ? 'ol' : 'ul' + const items = node.items.map((item) => renderListItem(item, options)).join('') + return `<${tag}>${items}` +} + +const renderListItem = (node: ListItemNode, options: RenderOptions): string => { + const body = node.children.map((child) => renderBlock(child, options)).join('') + return `
  • ${body}
  • ` +} + +const renderFieldList = (node: FieldListNode, options: RenderOptions): string => { + const items = node.items + .map((item) => { + const title = item.typeText ? `${item.name} : ${item.typeText}` : item.name + const body = item.body.map((child) => renderBlock(child, options)).join('') + return `
    ${escapeHtml(title)}
    ${body}
    ` + }) + .join('') + return `
    ${items}
    ` +} + +const renderAdmonition = (node: AdmonitionNode, options: RenderOptions): string => { + const title = node.title ?? node.name + const body = node.body.map((child) => renderBlock(child, options)).join('') + return `` +} + +const renderInline = (nodes: InlineNode[], options: RenderOptions): string => { + return nodes + .map((node) => { + switch (node.type) { + case 'text': + return escapeHtml(node.value) + case 'literal': + return `${escapeHtml(node.value)}` + case 'emphasis': + return `${renderInline(node.value, options)}` + case 'strong': + return `${renderInline(node.value, options)}` + case 'role': + return renderRole(node.role, node.value, options) + default: + return '' + } + }) + .join('') +} + +const renderRole = (role: string, value: string, options: RenderOptions): string => { + const resolver = options.roleResolver + if (resolver) { + const resolved = resolver(role, value) + if (resolved) { + const label = resolved.text ?? value + return `${escapeHtml(label)}` + } + } + return `${escapeHtml(value)}` +} + +const escapeHtml = (value: string): string => { + return value + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, ''') +} diff --git a/astro/packages/rst-lite/tests/__snapshots__/parse-render.test.ts.snap b/astro/packages/rst-lite/tests/__snapshots__/parse-render.test.ts.snap new file mode 100644 index 000000000..11cb09b74 --- /dev/null +++ b/astro/packages/rst-lite/tests/__snapshots__/parse-render.test.ts.snap @@ -0,0 +1,351 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`rst-lite parser > parses admonition > admonition-ast 1`] = ` +{ + "children": [ + { + "body": [ + { + "items": [ + { + "body": [], + "name": "Use", + "typeText": "meth:\`Foo.old\` instead.", + }, + ], + "type": "field_list", + }, + ], + "name": "deprecated", + "title": "Deprecated: 0.17", + "type": "admonition", + }, + ], + "type": "document", +} +`; + +exports[`rst-lite parser > parses heading-field-list > heading-field-list-ast 1`] = ` +{ + "children": [ + { + "content": [ + { + "type": "text", + "value": "Parameters", + }, + ], + "level": 2, + "type": "heading", + }, + { + "items": [ + { + "body": [ + { + "content": [ + { + "type": "text", + "value": "Foo line.", + }, + ], + "type": "paragraph", + }, + ], + "name": "foo", + "typeText": "int", + }, + { + "body": [ + { + "content": [ + { + "type": "text", + "value": "Bar line.", + }, + ], + "type": "paragraph", + }, + ], + "name": "bar", + "typeText": "str", + }, + ], + "type": "field_list", + }, + ], + "type": "document", +} +`; + +exports[`rst-lite parser > parses literal-block > literal-block-ast 1`] = ` +{ + "children": [ + { + "items": [ + { + "body": [], + "name": "Example", + "typeText": ":", + }, + ], + "type": "field_list", + }, + ], + "type": "document", +} +`; + +exports[`rst-lite parser > parses mixed-blocks > mixed-blocks-ast 1`] = ` +{ + "children": [ + { + "content": [ + { + "type": "text", + "value": "Notes", + }, + ], + "level": 2, + "type": "heading", + }, + { + "content": [ + { + "type": "text", + "value": "This is a paragraph.", + }, + ], + "type": "paragraph", + }, + { + "body": [ + { + "content": [ + { + "type": "text", + "value": "A note with ", + }, + { + "role": "class", + "type": "role", + "value": "Thing", + }, + { + "type": "text", + "value": ".", + }, + ], + "type": "paragraph", + }, + ], + "name": "note", + "title": "Note", + "type": "admonition", + }, + { + "items": [ + { + "children": [ + { + "content": [ + { + "type": "text", + "value": "Ordered", + }, + ], + "type": "paragraph", + }, + ], + "type": "list_item", + }, + { + "children": [ + { + "content": [ + { + "type": "text", + "value": "List", + }, + ], + "type": "paragraph", + }, + ], + "type": "list_item", + }, + ], + "ordered": true, + "type": "list", + }, + ], + "type": "document", +} +`; + +exports[`rst-lite parser > parses nested-lists > nested-lists-ast 1`] = ` +{ + "children": [ + { + "items": [ + { + "children": [ + { + "content": [ + { + "type": "text", + "value": "Item one", + }, + ], + "type": "paragraph", + }, + { + "items": [ + { + "children": [ + { + "content": [ + { + "type": "text", + "value": "Sub a", + }, + ], + "type": "paragraph", + }, + { + "items": [ + { + "children": [ + { + "content": [ + { + "type": "text", + "value": "Deep item", + }, + ], + "type": "paragraph", + }, + ], + "type": "list_item", + }, + ], + "ordered": false, + "type": "list", + }, + ], + "type": "list_item", + }, + { + "children": [ + { + "content": [ + { + "type": "text", + "value": "Sub b", + }, + ], + "type": "paragraph", + }, + ], + "type": "list_item", + }, + ], + "ordered": false, + "type": "list", + }, + ], + "type": "list_item", + }, + { + "children": [ + { + "content": [ + { + "type": "text", + "value": "Item two", + }, + ], + "type": "paragraph", + }, + ], + "type": "list_item", + }, + ], + "ordered": false, + "type": "list", + }, + ], + "type": "document", +} +`; + +exports[`rst-lite parser > parses paragraph-inline > paragraph-inline-ast 1`] = ` +{ + "children": [ + { + "content": [ + { + "type": "text", + "value": "Hello ", + }, + { + "type": "emphasis", + "value": [ + { + "type": "text", + "value": "world", + }, + ], + }, + { + "type": "text", + "value": " ", + }, + { + "type": "strong", + "value": [ + { + "type": "text", + "value": "bold", + }, + ], + }, + { + "type": "text", + "value": " ", + }, + { + "type": "literal", + "value": "code", + }, + { + "type": "text", + "value": " and ", + }, + { + "role": "class", + "type": "role", + "value": "Foo", + }, + { + "type": "text", + "value": ".", + }, + ], + "type": "paragraph", + }, + ], + "type": "document", +} +`; + +exports[`rst-lite parser > renders admonition > admonition-html 1`] = `""`; + +exports[`rst-lite parser > renders heading-field-list > heading-field-list-html 1`] = `"

    Parameters

    foo : int

    Foo line.

    bar : str

    Bar line.

    "`; + +exports[`rst-lite parser > renders literal-block > literal-block-html 1`] = `"
    Example : :
    "`; + +exports[`rst-lite parser > renders mixed-blocks > mixed-blocks-html 1`] = `"

    Notes

    This is a paragraph.

    1. Ordered

    2. List

    "`; + +exports[`rst-lite parser > renders nested-lists > nested-lists-html 1`] = `"
    • Item one

      • Sub a

        • Deep item

      • Sub b

    • Item two

    "`; + +exports[`rst-lite parser > renders paragraph-inline > paragraph-inline-html 1`] = `"

    Hello world bold code and Foo.

    "`; diff --git a/astro/packages/rst-lite/tests/parse-render.test.ts b/astro/packages/rst-lite/tests/parse-render.test.ts new file mode 100644 index 000000000..0a68e39bc --- /dev/null +++ b/astro/packages/rst-lite/tests/parse-render.test.ts @@ -0,0 +1,55 @@ +import { describe, expect, it } from 'vitest' +import { parseRst, type RoleResolver, renderHtml } from '../src/index.ts' + +const resolver: RoleResolver = (role, value) => { + if (role === 'class') { + return { href: `https://example.test/${value}`, text: value } + } + if (role === 'meth') { + return { href: `https://example.test/${value}#meth`, text: value } + } + return null +} + +describe('rst-lite parser', () => { + const cases = [ + { + name: 'paragraph-inline', + input: 'Hello *world* **bold** ``code`` and :class:`Foo`.', + }, + { + name: 'heading-field-list', + input: 'Parameters\n----------\nfoo : int\n Foo line.\nbar : str\n Bar line.\n', + }, + { + name: 'nested-lists', + input: '- Item one\n - Sub a\n - Deep item\n - Sub b\n- Item two\n', + }, + { + name: 'literal-block', + input: `Example::\n\n print('hi')\n print('there')\n`, + }, + { + name: 'admonition', + input: '.. deprecated:: 0.17\n\n Use :meth:`Foo.old` instead.\n', + }, + { + name: 'mixed-blocks', + input: + 'Notes\n-----\nThis is a paragraph.\n\n.. note::\n\n A note with :class:`Thing`.\n\n1. Ordered\n2. List\n', + }, + ] + + for (const fixture of cases) { + it(`parses ${fixture.name}`, () => { + const doc = parseRst(fixture.input) + expect(doc).toMatchSnapshot(`${fixture.name}-ast`) + }) + + it(`renders ${fixture.name}`, () => { + const doc = parseRst(fixture.input) + const html = renderHtml(doc, { roleResolver: resolver }) + expect(html).toMatchSnapshot(`${fixture.name}-html`) + }) + } +}) diff --git a/astro/packages/rst-lite/tsconfig.json b/astro/packages/rst-lite/tsconfig.json new file mode 100644 index 000000000..52234f529 --- /dev/null +++ b/astro/packages/rst-lite/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist", + "rootDir": "../../", + "paths": {} + }, + "include": ["src", "tests"], + "references": [] +} diff --git a/astro/packages/rst-lite/vitest.config.ts b/astro/packages/rst-lite/vitest.config.ts new file mode 100644 index 000000000..9eef0e8a8 --- /dev/null +++ b/astro/packages/rst-lite/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, +}) diff --git a/astro/packages/schema/package.json b/astro/packages/schema/package.json new file mode 100644 index 000000000..cd435a064 --- /dev/null +++ b/astro/packages/schema/package.json @@ -0,0 +1,32 @@ +{ + "name": "@libtmux/schema", + "version": "0.0.1", + "type": "module", + "description": "Zod schema firewall for the libtmux autodoc pipeline", + "main": "src/index.ts", + "types": "src/index.ts", + "files": [ + "src" + ], + "scripts": { + "test": "vitest run", + "test:watch": "vitest", + "lint": "biome lint .", + "format": "pnpm biome check . --write; biome format . --write", + "type-check": "tsc --noEmit", + "type-check:watch": "tsc --noEmit --watch", + "update": "pnpm update", + "ncu": "ncu", + "clean": "rm -rf dist/ node_modules/.cache/", + "biome": "biome check . --write && biome format . --write" + }, + "dependencies": { + "zod": "^4.3.5" + }, + "devDependencies": { + "@biomejs/biome": "2.3.11", + "@types/node": "^25.0.3", + "typescript": "^5.9.3", + "vitest": "^4.0.16" + } +} diff --git a/astro/packages/schema/src/common.ts b/astro/packages/schema/src/common.ts new file mode 100644 index 000000000..43668c363 --- /dev/null +++ b/astro/packages/schema/src/common.ts @@ -0,0 +1,13 @@ +import { z } from 'zod' + +export const ProtocolVersionSchema = z.literal(1) + +export const PyLocationSchema = z.object({ + lineno: z.number().int().nonnegative(), + colOffset: z.number().int().nonnegative(), + endLineno: z.number().int().nonnegative().nullable(), + endColOffset: z.number().int().nonnegative().nullable(), +}) + +export type ProtocolVersion = z.infer +export type PyLocation = z.infer diff --git a/astro/packages/schema/src/index.ts b/astro/packages/schema/src/index.ts new file mode 100644 index 000000000..0985bf18a --- /dev/null +++ b/astro/packages/schema/src/index.ts @@ -0,0 +1,3 @@ +export * from './common.ts' +export * from './py-introspect.ts' +export * from './py-parse.ts' diff --git a/astro/packages/schema/src/py-introspect.ts b/astro/packages/schema/src/py-introspect.ts new file mode 100644 index 000000000..f0ce0a302 --- /dev/null +++ b/astro/packages/schema/src/py-introspect.ts @@ -0,0 +1,88 @@ +import { z } from 'zod' +import { ProtocolVersionSchema } from './common.ts' +import { PyParameterKindSchema } from './py-parse.ts' + +const DocstringFormatSchema = z.enum(['rst', 'markdown', 'plain', 'unknown']) + +const DocstringFieldsSchema = z.object({ + docstringRaw: z.string().nullable(), + docstringFormat: DocstringFormatSchema, + docstringHtml: z.string().nullable(), + summary: z.string().nullable(), +}) + +const AnnotationFieldsSchema = z.object({ + annotationText: z.string().nullable(), + annotationValue: z.string().nullable(), +}) + +export const PyIntrospectParameterSchema = z + .object({ + name: z.string(), + kind: PyParameterKindSchema, + default: z.string().nullable(), + }) + .merge(AnnotationFieldsSchema) + +export const PyIntrospectFunctionSchema = z + .object({ + kind: z.enum(['function', 'method']), + name: z.string(), + qualname: z.string(), + module: z.string(), + signature: z.string(), + parameters: z.array(PyIntrospectParameterSchema), + returns: AnnotationFieldsSchema, + isAsync: z.boolean(), + isPrivate: z.boolean(), + }) + .merge(DocstringFieldsSchema) + +export const PyIntrospectVariableSchema = z + .object({ + kind: z.literal('variable'), + name: z.string(), + qualname: z.string(), + module: z.string(), + value: z.string().nullable(), + isPrivate: z.boolean(), + }) + .merge(AnnotationFieldsSchema) + .merge(DocstringFieldsSchema) + +export const PyIntrospectClassSchema = z + .object({ + kind: z.literal('class'), + name: z.string(), + qualname: z.string(), + module: z.string(), + bases: z.array(z.string()), + methods: z.array(PyIntrospectFunctionSchema), + attributes: z.array(PyIntrospectVariableSchema), + isPrivate: z.boolean(), + }) + .merge(DocstringFieldsSchema) + +export const PyIntrospectModuleSchema = z + .object({ + kind: z.literal('module'), + name: z.string(), + qualname: z.string(), + classes: z.array(PyIntrospectClassSchema), + functions: z.array(PyIntrospectFunctionSchema), + variables: z.array(PyIntrospectVariableSchema), + isPrivate: z.boolean(), + }) + .merge(DocstringFieldsSchema) + +export const PyIntrospectPayloadSchema = z.object({ + protocolVersion: ProtocolVersionSchema, + modules: z.array(PyIntrospectModuleSchema), +}) + +export type PyIntrospectParameter = z.infer +export type PyIntrospectFunction = z.infer +export type PyIntrospectVariable = z.infer +export type PyIntrospectClass = z.infer +export type PyIntrospectModule = z.infer +export type PyIntrospectPayload = z.infer diff --git a/astro/packages/schema/src/py-parse.ts b/astro/packages/schema/src/py-parse.ts new file mode 100644 index 000000000..ed9e10a31 --- /dev/null +++ b/astro/packages/schema/src/py-parse.ts @@ -0,0 +1,97 @@ +import { z } from 'zod' +import { ProtocolVersionSchema, PyLocationSchema } from './common.ts' + +export const PyParameterKindSchema = z.enum([ + 'positional-only', + 'positional-or-keyword', + 'var-positional', + 'keyword-only', + 'var-keyword', +]) + +export const PyParameterSchema = z.object({ + name: z.string(), + kind: PyParameterKindSchema, + annotation: z.string().nullable(), + default: z.string().nullable(), +}) + +export const PyVariableSchema = z.object({ + kind: z.literal('variable'), + name: z.string(), + qualname: z.string(), + annotation: z.string().nullable(), + value: z.string().nullable(), + docstring: z.string().nullable(), + isPrivate: z.boolean(), + location: PyLocationSchema, +}) + +export const PyFunctionSchema = z.object({ + kind: z.literal('function'), + name: z.string(), + qualname: z.string(), + docstring: z.string().nullable(), + decorators: z.array(z.string()), + parameters: z.array(PyParameterSchema), + returns: z.string().nullable(), + isAsync: z.boolean(), + isPrivate: z.boolean(), + location: PyLocationSchema, +}) + +export const PyClassSchema = z.object({ + kind: z.literal('class'), + name: z.string(), + qualname: z.string(), + docstring: z.string().nullable(), + bases: z.array(z.string()), + decorators: z.array(z.string()), + methods: z.array(PyFunctionSchema), + attributes: z.array(PyVariableSchema), + isPrivate: z.boolean(), + location: PyLocationSchema, +}) + +export const PyImportSchema = z.object({ + kind: z.literal('import'), + module: z.string().nullable(), + names: z.array(z.string()), + level: z.number().int().nonnegative().nullable(), + location: PyLocationSchema, +}) + +export const PyNodeSchema = z.discriminatedUnion('kind', [ + PyClassSchema, + PyFunctionSchema, + PyVariableSchema, + PyImportSchema, +]) + +export const PyModuleSchema = z.object({ + kind: z.literal('module'), + name: z.string(), + qualname: z.string(), + path: z.string(), + docstring: z.string().nullable(), + items: z.array(PyNodeSchema), + imports: z.array(PyImportSchema), + exports: z.array(z.string()), + isPackage: z.boolean(), + location: PyLocationSchema, +}) + +export const PyParsePayloadSchema = z.object({ + protocolVersion: ProtocolVersionSchema, + modules: z.array(PyModuleSchema), +}) + +export type PyParameterKind = z.infer +export type PyParameter = z.infer +export type PyVariable = z.infer +export type PyFunction = z.infer +export type PyClass = z.infer +export type PyImport = z.infer +export type PyNode = z.infer +export type PyModule = z.infer +export type PyParsePayload = z.infer diff --git a/astro/packages/schema/tests/py-introspect.test.ts b/astro/packages/schema/tests/py-introspect.test.ts new file mode 100644 index 000000000..e6f6ddd1a --- /dev/null +++ b/astro/packages/schema/tests/py-introspect.test.ts @@ -0,0 +1,36 @@ +import { describe, expect, it } from 'vitest' +import { PyIntrospectPayloadSchema } from '../src/index.ts' + +describe('PyIntrospectPayloadSchema', () => { + it('accepts an empty introspection payload', () => { + const payload = { + protocolVersion: 1, + modules: [], + } + + expect(() => PyIntrospectPayloadSchema.parse(payload)).not.toThrow() + }) + + it('accepts a minimal module payload', () => { + const payload = { + protocolVersion: 1, + modules: [ + { + kind: 'module', + name: 'demo', + qualname: 'demo', + isPrivate: false, + classes: [], + functions: [], + variables: [], + docstringRaw: null, + docstringFormat: 'unknown', + docstringHtml: null, + summary: null, + }, + ], + } + + expect(() => PyIntrospectPayloadSchema.parse(payload)).not.toThrow() + }) +}) diff --git a/astro/packages/schema/tests/py-parse.test.ts b/astro/packages/schema/tests/py-parse.test.ts new file mode 100644 index 000000000..812943c68 --- /dev/null +++ b/astro/packages/schema/tests/py-parse.test.ts @@ -0,0 +1,31 @@ +import { describe, expect, it } from 'vitest' +import { PyParsePayloadSchema } from '../src/index.ts' + +describe('PyParsePayloadSchema', () => { + it('accepts a minimal parse payload', () => { + const payload = { + protocolVersion: 1, + modules: [ + { + kind: 'module', + name: 'demo', + qualname: 'demo', + path: '/tmp/demo.py', + docstring: null, + items: [], + imports: [], + exports: [], + isPackage: false, + location: { + lineno: 1, + colOffset: 0, + endLineno: null, + endColOffset: null, + }, + }, + ], + } + + expect(() => PyParsePayloadSchema.parse(payload)).not.toThrow() + }) +}) diff --git a/astro/packages/schema/tsconfig.json b/astro/packages/schema/tsconfig.json new file mode 100644 index 000000000..52234f529 --- /dev/null +++ b/astro/packages/schema/tsconfig.json @@ -0,0 +1,11 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "composite": true, + "outDir": "dist", + "rootDir": "../../", + "paths": {} + }, + "include": ["src", "tests"], + "references": [] +} diff --git a/astro/packages/schema/vitest.config.ts b/astro/packages/schema/vitest.config.ts new file mode 100644 index 000000000..9eef0e8a8 --- /dev/null +++ b/astro/packages/schema/vitest.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + environment: 'node', + include: ['tests/**/*.test.ts'], + }, +}) diff --git a/astro/pnpm-lock.yaml b/astro/pnpm-lock.yaml new file mode 100644 index 000000000..0993ac6f8 --- /dev/null +++ b/astro/pnpm-lock.yaml @@ -0,0 +1,6296 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + devDependencies: + '@biomejs/biome': + specifier: 2.3.11 + version: 2.3.11 + npm-check-updates: + specifier: ^19.2.1 + version: 19.2.1 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + + apps/docs: + dependencies: + '@astrojs/mdx': + specifier: ^4.3.13 + version: 4.3.13(astro@5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2)) + '@astrojs/react': + specifier: ^4.4.2 + version: 4.4.2(@types/node@25.0.3)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(yaml@2.8.2) + '@fontsource/fraunces': + specifier: ^5.2.9 + version: 5.2.9 + '@fontsource/ibm-plex-mono': + specifier: ^5.2.7 + version: 5.2.7 + '@fontsource/space-grotesk': + specifier: ^5.2.10 + version: 5.2.10 + '@libtmux/api-model': + specifier: workspace:* + version: link:../../packages/api-model + '@libtmux/astro-autodoc': + specifier: workspace:* + version: link:../../packages/astro-autodoc + '@libtmux/astro-intersphinx': + specifier: workspace:* + version: link:../../packages/astro-intersphinx + '@libtmux/intersphinx': + specifier: workspace:* + version: link:../../packages/intersphinx + '@libtmux/py-parse': + specifier: workspace:* + version: link:../../packages/py-parse + '@tailwindcss/typography': + specifier: ^0.5.19 + version: 0.5.19(tailwindcss@4.1.18) + '@tailwindcss/vite': + specifier: ^4.1.18 + version: 4.1.18(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) + astro: + specifier: ^5.16.6 + version: 5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2) + react: + specifier: ^19.2.3 + version: 19.2.3 + react-dom: + specifier: ^19.2.3 + version: 19.2.3(react@19.2.3) + tailwindcss: + specifier: ^4.1.18 + version: 4.1.18 + devDependencies: + '@astrojs/check': + specifier: ^0.9.6 + version: 0.9.6(prettier@3.7.4)(typescript@5.9.3) + '@biomejs/biome': + specifier: 2.3.11 + version: 2.3.11 + '@types/node': + specifier: ^25.0.3 + version: 25.0.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.16 + version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + + packages/api-model: + dependencies: + '@libtmux/py-parse': + specifier: workspace:* + version: link:../py-parse + '@libtmux/schema': + specifier: workspace:* + version: link:../schema + zod: + specifier: ^4.3.5 + version: 4.3.5 + devDependencies: + '@biomejs/biome': + specifier: 2.3.11 + version: 2.3.11 + '@types/node': + specifier: ^25.0.3 + version: 25.0.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.16 + version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + + packages/astro-autodoc: + dependencies: + '@libtmux/api-model': + specifier: workspace:* + version: link:../api-model + '@libtmux/py-bridge': + specifier: workspace:* + version: link:../py-bridge + '@libtmux/py-introspect': + specifier: workspace:* + version: link:../py-introspect + '@libtmux/py-parse': + specifier: workspace:* + version: link:../py-parse + '@libtmux/rst-lite': + specifier: workspace:* + version: link:../rst-lite + astro: + specifier: ^5.0.0 + version: 5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2) + devDependencies: + '@biomejs/biome': + specifier: 2.3.11 + version: 2.3.11 + '@types/node': + specifier: ^25.0.3 + version: 25.0.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.16 + version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + + packages/astro-intersphinx: + dependencies: + '@libtmux/intersphinx': + specifier: workspace:* + version: link:../intersphinx + astro: + specifier: ^5.0.0 + version: 5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2) + devDependencies: + '@biomejs/biome': + specifier: 2.3.11 + version: 2.3.11 + '@types/node': + specifier: ^25.0.3 + version: 25.0.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.16 + version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + + packages/core: + dependencies: + '@libtmux/api-model': + specifier: workspace:* + version: link:../api-model + '@libtmux/py-introspect': + specifier: workspace:* + version: link:../py-introspect + '@libtmux/py-parse': + specifier: workspace:* + version: link:../py-parse + '@libtmux/schema': + specifier: workspace:* + version: link:../schema + devDependencies: + '@biomejs/biome': + specifier: 2.3.11 + version: 2.3.11 + '@types/node': + specifier: ^25.0.3 + version: 25.0.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.16 + version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + + packages/intersphinx: + dependencies: + zod: + specifier: ^4.3.5 + version: 4.3.5 + devDependencies: + '@biomejs/biome': + specifier: 2.3.11 + version: 2.3.11 + '@types/node': + specifier: ^25.0.3 + version: 25.0.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.16 + version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + + packages/py-bridge: + dependencies: + execa: + specifier: ^9.6.1 + version: 9.6.1 + devDependencies: + '@biomejs/biome': + specifier: 2.3.11 + version: 2.3.11 + '@types/node': + specifier: ^25.0.3 + version: 25.0.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.16 + version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + + packages/py-introspect: + dependencies: + '@libtmux/py-bridge': + specifier: workspace:* + version: link:../py-bridge + '@libtmux/schema': + specifier: workspace:* + version: link:../schema + devDependencies: + '@biomejs/biome': + specifier: 2.3.11 + version: 2.3.11 + '@types/node': + specifier: ^25.0.3 + version: 25.0.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.16 + version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + + packages/py-parse: + dependencies: + '@libtmux/py-bridge': + specifier: workspace:* + version: link:../py-bridge + '@libtmux/schema': + specifier: workspace:* + version: link:../schema + devDependencies: + '@biomejs/biome': + specifier: 2.3.11 + version: 2.3.11 + '@types/node': + specifier: ^25.0.3 + version: 25.0.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.16 + version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + + packages/rst-lite: + devDependencies: + '@biomejs/biome': + specifier: 2.3.11 + version: 2.3.11 + '@types/node': + specifier: ^25.0.3 + version: 25.0.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.16 + version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + + packages/schema: + dependencies: + zod: + specifier: ^4.3.5 + version: 4.3.5 + devDependencies: + '@biomejs/biome': + specifier: 2.3.11 + version: 2.3.11 + '@types/node': + specifier: ^25.0.3 + version: 25.0.3 + typescript: + specifier: ^5.9.3 + version: 5.9.3 + vitest: + specifier: ^4.0.16 + version: 4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + +packages: + + '@astrojs/check@0.9.6': + resolution: {integrity: sha512-jlaEu5SxvSgmfGIFfNgcn5/f+29H61NJzEMfAZ82Xopr4XBchXB1GVlcJsE+elUlsYSbXlptZLX+JMG3b/wZEA==} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + + '@astrojs/compiler@2.13.0': + resolution: {integrity: sha512-mqVORhUJViA28fwHYaWmsXSzLO9osbdZ5ImUfxBarqsYdMlPbqAqGJCxsNzvppp1BEzc1mJNjOVvQqeDN8Vspw==} + + '@astrojs/internal-helpers@0.7.5': + resolution: {integrity: sha512-vreGnYSSKhAjFJCWAwe/CNhONvoc5lokxtRoZims+0wa3KbHBdPHSSthJsKxPd8d/aic6lWKpRTYGY/hsgK6EA==} + + '@astrojs/language-server@2.16.2': + resolution: {integrity: sha512-J3hVx/mFi3FwEzKf8ExYXQNERogD6RXswtbU+TyrxoXRBiQoBO5ooo7/lRWJ+rlUKUd7+rziMPI9jYB7TRlh0w==} + hasBin: true + peerDependencies: + prettier: ^3.0.0 + prettier-plugin-astro: '>=0.11.0' + peerDependenciesMeta: + prettier: + optional: true + prettier-plugin-astro: + optional: true + + '@astrojs/markdown-remark@6.3.10': + resolution: {integrity: sha512-kk4HeYR6AcnzC4QV8iSlOfh+N8TZ3MEStxPyenyCtemqn8IpEATBFMTJcfrNW32dgpt6MY3oCkMM/Tv3/I4G3A==} + + '@astrojs/mdx@4.3.13': + resolution: {integrity: sha512-IHDHVKz0JfKBy3//52JSiyWv089b7GVSChIXLrlUOoTLWowG3wr2/8hkaEgEyd/vysvNQvGk+QhysXpJW5ve6Q==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + peerDependencies: + astro: ^5.0.0 + + '@astrojs/prism@3.3.0': + resolution: {integrity: sha512-q8VwfU/fDZNoDOf+r7jUnMC2//H2l0TuQ6FkGJL8vD8nw/q5KiL3DS1KKBI3QhI9UQhpJ5dc7AtqfbXWuOgLCQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/react@4.4.2': + resolution: {integrity: sha512-1tl95bpGfuaDMDn8O3x/5Dxii1HPvzjvpL2YTuqOOrQehs60I2DKiDgh1jrKc7G8lv+LQT5H15V6QONQ+9waeQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + peerDependencies: + '@types/react': ^17.0.50 || ^18.0.21 || ^19.0.0 + '@types/react-dom': ^17.0.17 || ^18.0.6 || ^19.0.0 + react: ^17.0.2 || ^18.0.0 || ^19.0.0 + react-dom: ^17.0.2 || ^18.0.0 || ^19.0.0 + + '@astrojs/telemetry@3.3.0': + resolution: {integrity: sha512-UFBgfeldP06qu6khs/yY+q1cDAaArM2/7AEIqQ9Cuvf7B1hNLq0xDrZkct+QoIGyjq56y8IaE2I3CTvG99mlhQ==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0} + + '@astrojs/yaml2ts@0.2.2': + resolution: {integrity: sha512-GOfvSr5Nqy2z5XiwqTouBBpy5FyI6DEe+/g/Mk5am9SjILN1S5fOEvYK0GuWHg98yS/dobP4m8qyqw/URW35fQ==} + + '@babel/code-frame@7.27.1': + resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.28.5': + resolution: {integrity: sha512-6uFXyCayocRbqhZOB+6XcuZbkMNimwfVGFji8CTZnCzOHVGvDqzvitu1re2AU5LROliz7eQPhB8CpAMvnx9EjA==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.28.5': + resolution: {integrity: sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.28.5': + resolution: {integrity: sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.27.2': + resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + engines: {node: '>=6.9.0'} + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.27.1': + resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.3': + resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-plugin-utils@7.27.1': + resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.4': + resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.28.5': + resolution: {integrity: sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-transform-react-jsx-self@7.27.1': + resolution: {integrity: sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-react-jsx-source@7.27.1': + resolution: {integrity: sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/template@7.27.2': + resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.28.5': + resolution: {integrity: sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.28.5': + resolution: {integrity: sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==} + engines: {node: '>=6.9.0'} + + '@biomejs/biome@2.3.11': + resolution: {integrity: sha512-/zt+6qazBWguPG6+eWmiELqO+9jRsMZ/DBU3lfuU2ngtIQYzymocHhKiZRyrbra4aCOoyTg/BmY+6WH5mv9xmQ==} + engines: {node: '>=14.21.3'} + hasBin: true + + '@biomejs/cli-darwin-arm64@2.3.11': + resolution: {integrity: sha512-/uXXkBcPKVQY7rc9Ys2CrlirBJYbpESEDme7RKiBD6MmqR2w3j0+ZZXRIL2xiaNPsIMMNhP1YnA+jRRxoOAFrA==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [darwin] + + '@biomejs/cli-darwin-x64@2.3.11': + resolution: {integrity: sha512-fh7nnvbweDPm2xEmFjfmq7zSUiox88plgdHF9OIW4i99WnXrAC3o2P3ag9judoUMv8FCSUnlwJCM1B64nO5Fbg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [darwin] + + '@biomejs/cli-linux-arm64-musl@2.3.11': + resolution: {integrity: sha512-XPSQ+XIPZMLaZ6zveQdwNjbX+QdROEd1zPgMwD47zvHV+tCGB88VH+aynyGxAHdzL+Tm/+DtKST5SECs4iwCLg==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-arm64@2.3.11': + resolution: {integrity: sha512-l4xkGa9E7Uc0/05qU2lMYfN1H+fzzkHgaJoy98wO+b/7Gl78srbCRRgwYSW+BTLixTBrM6Ede5NSBwt7rd/i6g==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [linux] + + '@biomejs/cli-linux-x64-musl@2.3.11': + resolution: {integrity: sha512-vU7a8wLs5C9yJ4CB8a44r12aXYb8yYgBn+WeyzbMjaCMklzCv1oXr8x+VEyWodgJt9bDmhiaW/I0RHbn7rsNmw==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-linux-x64@2.3.11': + resolution: {integrity: sha512-/1s9V/H3cSe0r0Mv/Z8JryF5x9ywRxywomqZVLHAoa/uN0eY7F8gEngWKNS5vbbN/BsfpCG5yeBT5ENh50Frxg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [linux] + + '@biomejs/cli-win32-arm64@2.3.11': + resolution: {integrity: sha512-PZQ6ElCOnkYapSsysiTy0+fYX+agXPlWugh6+eQ6uPKI3vKAqNp6TnMhoM3oY2NltSB89hz59o8xIfOdyhi9Iw==} + engines: {node: '>=14.21.3'} + cpu: [arm64] + os: [win32] + + '@biomejs/cli-win32-x64@2.3.11': + resolution: {integrity: sha512-43VrG813EW+b5+YbDbz31uUsheX+qFKCpXeY9kfdAx+ww3naKxeVkTD9zLIWxUPfJquANMHrmW3wbe/037G0Qg==} + engines: {node: '>=14.21.3'} + cpu: [x64] + os: [win32] + + '@capsizecss/unpack@3.0.1': + resolution: {integrity: sha512-8XqW8xGn++Eqqbz3e9wKuK7mxryeRjs4LOHLxbh2lwKeSbuNR4NFifDZT4KzvjU6HMOPbiNTsWpniK5EJfTWkg==} + engines: {node: '>=18'} + + '@emmetio/abbreviation@2.3.3': + resolution: {integrity: sha512-mgv58UrU3rh4YgbE/TzgLQwJ3pFsHHhCLqY20aJq+9comytTXUDNGG/SMtSeMJdkpxgXSXunBGLD8Boka3JyVA==} + + '@emmetio/css-abbreviation@2.1.8': + resolution: {integrity: sha512-s9yjhJ6saOO/uk1V74eifykk2CBYi01STTK3WlXWGOepyKa23ymJ053+DNQjpFcy1ingpaO7AxCcwLvHFY9tuw==} + + '@emmetio/css-parser@0.4.1': + resolution: {integrity: sha512-2bC6m0MV/voF4CTZiAbG5MWKbq5EBmDPKu9Sb7s7nVcEzNQlrZP6mFFFlIaISM8X6514H9shWMme1fCm8cWAfQ==} + + '@emmetio/html-matcher@1.3.0': + resolution: {integrity: sha512-NTbsvppE5eVyBMuyGfVu2CRrLvo7J4YHb6t9sBFLyY03WYhXET37qA4zOYUjBWFCRHO7pS1B9khERtY0f5JXPQ==} + + '@emmetio/scanner@1.0.4': + resolution: {integrity: sha512-IqRuJtQff7YHHBk4G8YZ45uB9BaAGcwQeVzgj/zj8/UdOhtQpEIupUhSk8dys6spFIWVZVeK20CzGEnqR5SbqA==} + + '@emmetio/stream-reader-utils@0.1.0': + resolution: {integrity: sha512-ZsZ2I9Vzso3Ho/pjZFsmmZ++FWeEd/txqybHTm4OgaZzdS8V9V/YYWQwg5TC38Z7uLWUV1vavpLLbjJtKubR1A==} + + '@emmetio/stream-reader@2.2.0': + resolution: {integrity: sha512-fXVXEyFA5Yv3M3n8sUGT7+fvecGrZP4k6FnWWMSZVQf69kAq0LLpaBQLGcPR30m3zMmKYhECP4k/ZkzvhEW5kw==} + + '@emnapi/runtime@1.8.1': + resolution: {integrity: sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==} + + '@esbuild/aix-ppc64@0.25.12': + resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/aix-ppc64@0.27.2': + resolution: {integrity: sha512-GZMB+a0mOMZs4MpDbj8RJp4cw+w1WV5NYD6xzgvzUJ5Ek2jerwfO2eADyI6ExDSUED+1X8aMbegahsJi+8mgpw==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.25.12': + resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.27.2': + resolution: {integrity: sha512-pvz8ZZ7ot/RBphf8fv60ljmaoydPU12VuXHImtAs0XhLLw+EXBi2BLe3OYSBslR4rryHvweW5gmkKFwTiFy6KA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.25.12': + resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.27.2': + resolution: {integrity: sha512-DVNI8jlPa7Ujbr1yjU2PfUSRtAUZPG9I1RwW4F4xFB1Imiu2on0ADiI/c3td+KmDtVKNbi+nffGDQMfcIMkwIA==} + engines: {node: '>=18'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.25.12': + resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.27.2': + resolution: {integrity: sha512-z8Ank4Byh4TJJOh4wpz8g2vDy75zFL0TlZlkUkEwYXuPSgX8yzep596n6mT7905kA9uHZsf/o2OJZubl2l3M7A==} + engines: {node: '>=18'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.25.12': + resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.27.2': + resolution: {integrity: sha512-davCD2Zc80nzDVRwXTcQP/28fiJbcOwvdolL0sOiOsbwBa72kegmVU0Wrh1MYrbuCL98Omp5dVhQFWRKR2ZAlg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.25.12': + resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.27.2': + resolution: {integrity: sha512-ZxtijOmlQCBWGwbVmwOF/UCzuGIbUkqB1faQRf5akQmxRJ1ujusWsb3CVfk/9iZKr2L5SMU5wPBi1UWbvL+VQA==} + engines: {node: '>=18'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.25.12': + resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.27.2': + resolution: {integrity: sha512-lS/9CN+rgqQ9czogxlMcBMGd+l8Q3Nj1MFQwBZJyoEKI50XGxwuzznYdwcav6lpOGv5BqaZXqvBSiB/kJ5op+g==} + engines: {node: '>=18'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.25.12': + resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.27.2': + resolution: {integrity: sha512-tAfqtNYb4YgPnJlEFu4c212HYjQWSO/w/h/lQaBK7RbwGIkBOuNKQI9tqWzx7Wtp7bTPaGC6MJvWI608P3wXYA==} + engines: {node: '>=18'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.25.12': + resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.27.2': + resolution: {integrity: sha512-hYxN8pr66NsCCiRFkHUAsxylNOcAQaxSSkHMMjcpx0si13t1LHFphxJZUiGwojB1a/Hd5OiPIqDdXONia6bhTw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.25.12': + resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.27.2': + resolution: {integrity: sha512-vWfq4GaIMP9AIe4yj1ZUW18RDhx6EPQKjwe7n8BbIecFtCQG4CfHGaHuh7fdfq+y3LIA2vGS/o9ZBGVxIDi9hw==} + engines: {node: '>=18'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.25.12': + resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.27.2': + resolution: {integrity: sha512-MJt5BRRSScPDwG2hLelYhAAKh9imjHK5+NE/tvnRLbIqUWa+0E9N4WNMjmp/kXXPHZGqPLxggwVhz7QP8CTR8w==} + engines: {node: '>=18'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.25.12': + resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.27.2': + resolution: {integrity: sha512-lugyF1atnAT463aO6KPshVCJK5NgRnU4yb3FUumyVz+cGvZbontBgzeGFO1nF+dPueHD367a2ZXe1NtUkAjOtg==} + engines: {node: '>=18'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.25.12': + resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.27.2': + resolution: {integrity: sha512-nlP2I6ArEBewvJ2gjrrkESEZkB5mIoaTswuqNFRv/WYd+ATtUpe9Y09RnJvgvdag7he0OWgEZWhviS1OTOKixw==} + engines: {node: '>=18'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.25.12': + resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.27.2': + resolution: {integrity: sha512-C92gnpey7tUQONqg1n6dKVbx3vphKtTHJaNG2Ok9lGwbZil6DrfyecMsp9CrmXGQJmZ7iiVXvvZH6Ml5hL6XdQ==} + engines: {node: '>=18'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.25.12': + resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.27.2': + resolution: {integrity: sha512-B5BOmojNtUyN8AXlK0QJyvjEZkWwy/FKvakkTDCziX95AowLZKR6aCDhG7LeF7uMCXEJqwa8Bejz5LTPYm8AvA==} + engines: {node: '>=18'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.25.12': + resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.27.2': + resolution: {integrity: sha512-p4bm9+wsPwup5Z8f4EpfN63qNagQ47Ua2znaqGH6bqLlmJ4bx97Y9JdqxgGZ6Y8xVTixUnEkoKSHcpRlDnNr5w==} + engines: {node: '>=18'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.25.12': + resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.27.2': + resolution: {integrity: sha512-uwp2Tip5aPmH+NRUwTcfLb+W32WXjpFejTIOWZFw/v7/KnpCDKG66u4DLcurQpiYTiYwQ9B7KOeMJvLCu/OvbA==} + engines: {node: '>=18'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-arm64@0.25.12': + resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-arm64@0.27.2': + resolution: {integrity: sha512-Kj6DiBlwXrPsCRDeRvGAUb/LNrBASrfqAIok+xB0LxK8CHqxZ037viF13ugfsIpePH93mX7xfJp97cyDuTZ3cw==} + engines: {node: '>=18'} + cpu: [arm64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.25.12': + resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.27.2': + resolution: {integrity: sha512-HwGDZ0VLVBY3Y+Nw0JexZy9o/nUAWq9MlV7cahpaXKW6TOzfVno3y3/M8Ga8u8Yr7GldLOov27xiCnqRZf0tCA==} + engines: {node: '>=18'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-arm64@0.25.12': + resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-arm64@0.27.2': + resolution: {integrity: sha512-DNIHH2BPQ5551A7oSHD0CKbwIA/Ox7+78/AWkbS5QoRzaqlev2uFayfSxq68EkonB+IKjiuxBFoV8ESJy8bOHA==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.25.12': + resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.27.2': + resolution: {integrity: sha512-/it7w9Nb7+0KFIzjalNJVR5bOzA9Vay+yIPLVHfIQYG/j+j9VTH84aNB8ExGKPU4AzfaEvN9/V4HV+F+vo8OEg==} + engines: {node: '>=18'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openharmony-arm64@0.25.12': + resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/openharmony-arm64@0.27.2': + resolution: {integrity: sha512-LRBbCmiU51IXfeXk59csuX/aSaToeG7w48nMwA6049Y4J4+VbWALAuXcs+qcD04rHDuSCSRKdmY63sruDS5qag==} + engines: {node: '>=18'} + cpu: [arm64] + os: [openharmony] + + '@esbuild/sunos-x64@0.25.12': + resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.27.2': + resolution: {integrity: sha512-kMtx1yqJHTmqaqHPAzKCAkDaKsffmXkPHThSfRwZGyuqyIeBvf08KSsYXl+abf5HDAPMJIPnbBfXvP2ZC2TfHg==} + engines: {node: '>=18'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.25.12': + resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.27.2': + resolution: {integrity: sha512-Yaf78O/B3Kkh+nKABUF++bvJv5Ijoy9AN1ww904rOXZFLWVc5OLOfL56W+C8F9xn5JQZa3UX6m+IktJnIb1Jjg==} + engines: {node: '>=18'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.25.12': + resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.27.2': + resolution: {integrity: sha512-Iuws0kxo4yusk7sw70Xa2E2imZU5HoixzxfGCdxwBdhiDgt9vX9VUCBhqcwY7/uh//78A1hMkkROMJq9l27oLQ==} + engines: {node: '>=18'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.25.12': + resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.27.2': + resolution: {integrity: sha512-sRdU18mcKf7F+YgheI/zGf5alZatMUTKj/jNS6l744f9u3WFu4v7twcUI9vu4mknF4Y9aDlblIie0IM+5xxaqQ==} + engines: {node: '>=18'} + cpu: [x64] + os: [win32] + + '@fontsource/fraunces@5.2.9': + resolution: {integrity: sha512-XDzuddBtoC7BZgZdBn6b7hsFZY2+V1hgN7yca5fBTKuHjb/lOd45a0Ji8dTUgFhPoL7RdGupo+bC2BFSt6UH8Q==} + + '@fontsource/ibm-plex-mono@5.2.7': + resolution: {integrity: sha512-MKAb8qV+CaiMQn2B0dIi1OV3565NYzp3WN5b4oT6LTkk+F0jR6j0ZN+5BKJiIhffDC3rtBULsYZE65+0018z9w==} + + '@fontsource/space-grotesk@5.2.10': + resolution: {integrity: sha512-XNXEbT74OIITPqw2H6HXwPDp85fy43uxfBwFR5PU+9sLnjuLj12KlhVM9nZVN6q6dlKjkuN8JisW/OBxwxgUew==} + + '@img/colour@1.0.0': + resolution: {integrity: sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==} + engines: {node: '>=18'} + + '@img/sharp-darwin-arm64@0.34.5': + resolution: {integrity: sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [darwin] + + '@img/sharp-darwin-x64@0.34.5': + resolution: {integrity: sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-darwin-arm64@1.2.4': + resolution: {integrity: sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==} + cpu: [arm64] + os: [darwin] + + '@img/sharp-libvips-darwin-x64@1.2.4': + resolution: {integrity: sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==} + cpu: [x64] + os: [darwin] + + '@img/sharp-libvips-linux-arm64@1.2.4': + resolution: {integrity: sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linux-arm@1.2.4': + resolution: {integrity: sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==} + cpu: [arm] + os: [linux] + + '@img/sharp-libvips-linux-ppc64@1.2.4': + resolution: {integrity: sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==} + cpu: [ppc64] + os: [linux] + + '@img/sharp-libvips-linux-riscv64@1.2.4': + resolution: {integrity: sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==} + cpu: [riscv64] + os: [linux] + + '@img/sharp-libvips-linux-s390x@1.2.4': + resolution: {integrity: sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==} + cpu: [s390x] + os: [linux] + + '@img/sharp-libvips-linux-x64@1.2.4': + resolution: {integrity: sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==} + cpu: [x64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + resolution: {integrity: sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==} + cpu: [arm64] + os: [linux] + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + resolution: {integrity: sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==} + cpu: [x64] + os: [linux] + + '@img/sharp-linux-arm64@0.34.5': + resolution: {integrity: sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linux-arm@0.34.5': + resolution: {integrity: sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm] + os: [linux] + + '@img/sharp-linux-ppc64@0.34.5': + resolution: {integrity: sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ppc64] + os: [linux] + + '@img/sharp-linux-riscv64@0.34.5': + resolution: {integrity: sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [riscv64] + os: [linux] + + '@img/sharp-linux-s390x@0.34.5': + resolution: {integrity: sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [s390x] + os: [linux] + + '@img/sharp-linux-x64@0.34.5': + resolution: {integrity: sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-linuxmusl-arm64@0.34.5': + resolution: {integrity: sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [linux] + + '@img/sharp-linuxmusl-x64@0.34.5': + resolution: {integrity: sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [linux] + + '@img/sharp-wasm32@0.34.5': + resolution: {integrity: sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [wasm32] + + '@img/sharp-win32-arm64@0.34.5': + resolution: {integrity: sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [arm64] + os: [win32] + + '@img/sharp-win32-ia32@0.34.5': + resolution: {integrity: sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [ia32] + os: [win32] + + '@img/sharp-win32-x64@0.34.5': + resolution: {integrity: sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + cpu: [x64] + os: [win32] + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@mdx-js/mdx@3.1.1': + resolution: {integrity: sha512-f6ZO2ifpwAQIpzGWaBQT2TXxPv6z3RBzQKpVftEWN78Vl/YweF1uwussDx8ECAXVtr3Rs89fKyG9YlzUs9DyGQ==} + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@oslojs/encoding@1.1.0': + resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==} + + '@rolldown/pluginutils@1.0.0-beta.27': + resolution: {integrity: sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==} + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.54.0': + resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.54.0': + resolution: {integrity: sha512-Skx39Uv+u7H224Af+bDgNinitlmHyQX1K/atIA32JP3JQw6hVODX5tkbi2zof/E69M1qH2UoN3Xdxgs90mmNYw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.54.0': + resolution: {integrity: sha512-k43D4qta/+6Fq+nCDhhv9yP2HdeKeP56QrUUTW7E6PhZP1US6NDqpJj4MY0jBHlJivVJD5P8NxrjuobZBJTCRw==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.54.0': + resolution: {integrity: sha512-cOo7biqwkpawslEfox5Vs8/qj83M/aZCSSNIWpVzfU2CYHa2G3P1UN5WF01RdTHSgCkri7XOlTdtk17BezlV3A==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.54.0': + resolution: {integrity: sha512-miSvuFkmvFbgJ1BevMa4CPCFt5MPGw094knM64W9I0giUIMMmRYcGW/JWZDriaw/k1kOBtsWh1z6nIFV1vPNtA==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.54.0': + resolution: {integrity: sha512-KGXIs55+b/ZfZsq9aR026tmr/+7tq6VG6MsnrvF4H8VhwflTIuYh+LFUlIsRdQSgrgmtM3fVATzEAj4hBQlaqQ==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + resolution: {integrity: sha512-EHMUcDwhtdRGlXZsGSIuXSYwD5kOT9NVnx9sqzYiwAc91wfYOE1g1djOEDseZJKKqtHAHGwnGPQu3kytmfaXLQ==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + resolution: {integrity: sha512-+pBrqEjaakN2ySv5RVrj/qLytYhPKEUwk+e3SFU5jTLHIcAtqh2rLrd/OkbNuHJpsBgxsD8ccJt5ga/SeG0JmA==} + cpu: [arm] + os: [linux] + + '@rollup/rollup-linux-arm64-gnu@4.54.0': + resolution: {integrity: sha512-NSqc7rE9wuUaRBsBp5ckQ5CVz5aIRKCwsoa6WMF7G01sX3/qHUw/z4pv+D+ahL1EIKy6Enpcnz1RY8pf7bjwng==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-arm64-musl@4.54.0': + resolution: {integrity: sha512-gr5vDbg3Bakga5kbdpqx81m2n9IX8M6gIMlQQIXiLTNeQW6CucvuInJ91EuCJ/JYvc+rcLLsDFcfAD1K7fMofg==} + cpu: [arm64] + os: [linux] + + '@rollup/rollup-linux-loong64-gnu@4.54.0': + resolution: {integrity: sha512-gsrtB1NA3ZYj2vq0Rzkylo9ylCtW/PhpLEivlgWe0bpgtX5+9j9EZa0wtZiCjgu6zmSeZWyI/e2YRX1URozpIw==} + cpu: [loong64] + os: [linux] + + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + resolution: {integrity: sha512-y3qNOfTBStmFNq+t4s7Tmc9hW2ENtPg8FeUD/VShI7rKxNW7O4fFeaYbMsd3tpFlIg1Q8IapFgy7Q9i2BqeBvA==} + cpu: [ppc64] + os: [linux] + + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + resolution: {integrity: sha512-89sepv7h2lIVPsFma8iwmccN7Yjjtgz0Rj/Ou6fEqg3HDhpCa+Et+YSufy27i6b0Wav69Qv4WBNl3Rs6pwhebQ==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-riscv64-musl@4.54.0': + resolution: {integrity: sha512-ZcU77ieh0M2Q8Ur7D5X7KvK+UxbXeDHwiOt/CPSBTI1fBmeDMivW0dPkdqkT4rOgDjrDDBUed9x4EgraIKoR2A==} + cpu: [riscv64] + os: [linux] + + '@rollup/rollup-linux-s390x-gnu@4.54.0': + resolution: {integrity: sha512-2AdWy5RdDF5+4YfG/YesGDDtbyJlC9LHmL6rZw6FurBJ5n4vFGupsOBGfwMRjBYH7qRQowT8D/U4LoSvVwOhSQ==} + cpu: [s390x] + os: [linux] + + '@rollup/rollup-linux-x64-gnu@4.54.0': + resolution: {integrity: sha512-WGt5J8Ij/rvyqpFexxk3ffKqqbLf9AqrTBbWDk7ApGUzaIs6V+s2s84kAxklFwmMF/vBNGrVdYgbblCOFFezMQ==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-linux-x64-musl@4.54.0': + resolution: {integrity: sha512-JzQmb38ATzHjxlPHuTH6tE7ojnMKM2kYNzt44LO/jJi8BpceEC8QuXYA908n8r3CNuG/B3BV8VR3Hi1rYtmPiw==} + cpu: [x64] + os: [linux] + + '@rollup/rollup-openharmony-arm64@4.54.0': + resolution: {integrity: sha512-huT3fd0iC7jigGh7n3q/+lfPcXxBi+om/Rs3yiFxjvSxbSB6aohDFXbWvlspaqjeOh+hx7DDHS+5Es5qRkWkZg==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.54.0': + resolution: {integrity: sha512-c2V0W1bsKIKfbLMBu/WGBz6Yci8nJ/ZJdheE0EwB73N3MvHYKiKGs3mVilX4Gs70eGeDaMqEob25Tw2Gb9Nqyw==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.54.0': + resolution: {integrity: sha512-woEHgqQqDCkAzrDhvDipnSirm5vxUXtSKDYTVpZG3nUdW/VVB5VdCYA2iReSj/u3yCZzXID4kuKG7OynPnB3WQ==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.54.0': + resolution: {integrity: sha512-dzAc53LOuFvHwbCEOS0rPbXp6SIhAf2txMP5p6mGyOXXw5mWY8NGGbPMPrs4P1WItkfApDathBj/NzMLUZ9rtQ==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.54.0': + resolution: {integrity: sha512-hYT5d3YNdSh3mbCU1gwQyPgQd3T2ne0A3KG8KSBdav5TiBg6eInVmV+TeR5uHufiIgSFg0XsOWGW5/RhNcSvPg==} + cpu: [x64] + os: [win32] + + '@sec-ant/readable-stream@0.4.1': + resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} + + '@shikijs/core@3.20.0': + resolution: {integrity: sha512-f2ED7HYV4JEk827mtMDwe/yQ25pRiXZmtHjWF8uzZKuKiEsJR7Ce1nuQ+HhV9FzDcbIo4ObBCD9GPTzNuy9S1g==} + + '@shikijs/engine-javascript@3.20.0': + resolution: {integrity: sha512-OFx8fHAZuk7I42Z9YAdZ95To6jDePQ9Rnfbw9uSRTSbBhYBp1kEOKv/3jOimcj3VRUKusDYM6DswLauwfhboLg==} + + '@shikijs/engine-oniguruma@3.20.0': + resolution: {integrity: sha512-Yx3gy7xLzM0ZOjqoxciHjA7dAt5tyzJE3L4uQoM83agahy+PlW244XJSrmJRSBvGYELDhYXPacD4R/cauV5bzQ==} + + '@shikijs/langs@3.20.0': + resolution: {integrity: sha512-le+bssCxcSHrygCWuOrYJHvjus6zhQ2K7q/0mgjiffRbkhM4o1EWu2m+29l0yEsHDbWaWPNnDUTRVVBvBBeKaA==} + + '@shikijs/themes@3.20.0': + resolution: {integrity: sha512-U1NSU7Sl26Q7ErRvJUouArxfM2euWqq1xaSrbqMu2iqa+tSp0D1Yah8216sDYbdDHw4C8b75UpE65eWorm2erQ==} + + '@shikijs/types@3.20.0': + resolution: {integrity: sha512-lhYAATn10nkZcBQ0BlzSbJA3wcmL5MXUUF8d2Zzon6saZDlToKaiRX60n2+ZaHJCmXEcZRWNzn+k9vplr8Jhsw==} + + '@shikijs/vscode-textmate@10.0.2': + resolution: {integrity: sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==} + + '@sindresorhus/merge-streams@4.0.0': + resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + engines: {node: '>=18'} + + '@standard-schema/spec@1.1.0': + resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} + + '@swc/helpers@0.5.18': + resolution: {integrity: sha512-TXTnIcNJQEKwThMMqBXsZ4VGAza6bvN4pa41Rkqoio6QBKMvo+5lexeTMScGCIxtzgQJzElcvIltani+adC5PQ==} + + '@tailwindcss/node@4.1.18': + resolution: {integrity: sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==} + + '@tailwindcss/oxide-android-arm64@4.1.18': + resolution: {integrity: sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + resolution: {integrity: sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.1.18': + resolution: {integrity: sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==} + engines: {node: '>= 10'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + resolution: {integrity: sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==} + engines: {node: '>= 10'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + resolution: {integrity: sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==} + engines: {node: '>= 10'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + resolution: {integrity: sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + resolution: {integrity: sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + resolution: {integrity: sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + resolution: {integrity: sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==} + engines: {node: '>= 10'} + cpu: [x64] + os: [linux] + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + resolution: {integrity: sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + resolution: {integrity: sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==} + engines: {node: '>= 10'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + resolution: {integrity: sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==} + engines: {node: '>= 10'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.1.18': + resolution: {integrity: sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==} + engines: {node: '>= 10'} + + '@tailwindcss/typography@0.5.19': + resolution: {integrity: sha512-w31dd8HOx3k9vPtcQh5QHP9GwKcgbMp87j58qi6xgiBnFFtKEAgCWnDw4qUT8aHwkCp8bKvb/KGKWWHedP0AAg==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders || >=4.0.0-alpha.20 || >=4.0.0-beta.1' + + '@tailwindcss/vite@4.1.18': + resolution: {integrity: sha512-jVA+/UpKL1vRLg6Hkao5jldawNmRo7mQYrZtNHMIVpLfLhDml5nMRUo/8MwoX2vNXvnaXNNMedrMfMugAVX1nA==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 + + '@types/babel__core@7.20.5': + resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} + + '@types/babel__generator@7.27.0': + resolution: {integrity: sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==} + + '@types/babel__template@7.4.4': + resolution: {integrity: sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==} + + '@types/babel__traverse@7.28.0': + resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + + '@types/chai@5.2.3': + resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/deep-eql@4.0.2': + resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/fontkit@2.0.8': + resolution: {integrity: sha512-wN+8bYxIpJf+5oZdrdtaX04qUuWHcKxcDEgRS9Qm9ZClSHjzEn13SxUC+5eRM+4yXIeTYk8mTzLAWGF64847ew==} + + '@types/hast@3.0.4': + resolution: {integrity: sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==} + + '@types/mdast@4.0.4': + resolution: {integrity: sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==} + + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + + '@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@25.0.3': + resolution: {integrity: sha512-W609buLVRVmeW693xKfzHeIV6nJGGz98uCPfeXI1ELMLXVeKYZ9m15fAMSaUPBHYLGFsVRcMmSCksQOrZV9BYA==} + + '@types/react-dom@19.2.3': + resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==} + peerDependencies: + '@types/react': ^19.2.0 + + '@types/react@19.2.7': + resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==} + + '@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==} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vitejs/plugin-react@4.7.0': + resolution: {integrity: sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 + + '@vitest/expect@4.0.16': + resolution: {integrity: sha512-eshqULT2It7McaJkQGLkPjPjNph+uevROGuIMJdG3V+0BSR2w9u6J9Lwu+E8cK5TETlfou8GRijhafIMhXsimA==} + + '@vitest/mocker@4.0.16': + resolution: {integrity: sha512-yb6k4AZxJTB+q9ycAvsoxGn+j/po0UaPgajllBgt1PzoMAAmJGYFdDk0uCcRcxb3BrME34I6u8gHZTQlkqSZpg==} + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + + '@vitest/pretty-format@4.0.16': + resolution: {integrity: sha512-eNCYNsSty9xJKi/UdVD8Ou16alu7AYiS2fCPRs0b1OdhJiV89buAXQLpTbe+X8V9L6qrs9CqyvU7OaAopJYPsA==} + + '@vitest/runner@4.0.16': + resolution: {integrity: sha512-VWEDm5Wv9xEo80ctjORcTQRJ539EGPB3Pb9ApvVRAY1U/WkHXmmYISqU5E79uCwcW7xYUV38gwZD+RV755fu3Q==} + + '@vitest/snapshot@4.0.16': + resolution: {integrity: sha512-sf6NcrYhYBsSYefxnry+DR8n3UV4xWZwWxYbCJUt2YdvtqzSPR7VfGrY0zsv090DAbjFZsi7ZaMi1KnSRyK1XA==} + + '@vitest/spy@4.0.16': + resolution: {integrity: sha512-4jIOWjKP0ZUaEmJm00E0cOBLU+5WE0BpeNr3XN6TEF05ltro6NJqHWxXD0kA8/Zc8Nh23AT8WQxwNG+WeROupw==} + + '@vitest/utils@4.0.16': + resolution: {integrity: sha512-h8z9yYhV3e1LEfaQ3zdypIrnAg/9hguReGZoS7Gl0aBG5xgA410zBqECqmaF/+RkTggRsfnzc1XaAHA6bmUufA==} + + '@volar/kit@2.4.27': + resolution: {integrity: sha512-ilZoQDMLzqmSsImJRWx4YiZ4FcvvPrPnFVmL6hSsIWB6Bn3qc7k88J9yP32dagrs5Y8EXIlvvD/mAFaiuEOACQ==} + peerDependencies: + typescript: '*' + + '@volar/language-core@2.4.27': + resolution: {integrity: sha512-DjmjBWZ4tJKxfNC1F6HyYERNHPYS7L7OPFyCrestykNdUZMFYzI9WTyvwPcaNaHlrEUwESHYsfEw3isInncZxQ==} + + '@volar/language-server@2.4.27': + resolution: {integrity: sha512-SymGNkErcHg8GjiG65iQN8sLkhqu1pwKhFySmxeBuYq5xFYagKBW36eiNITXQTdvT0tutI1GXcXdq/FdE/IyjA==} + + '@volar/language-service@2.4.27': + resolution: {integrity: sha512-SxKZ8yLhpWa7Y5e/RDxtNfm7j7xsXp/uf2urijXEffRNpPSmVdfzQrFFy5d7l8PNpZy+bHg+yakmqBPjQN+MOw==} + + '@volar/source-map@2.4.27': + resolution: {integrity: sha512-ynlcBReMgOZj2i6po+qVswtDUeeBRCTgDurjMGShbm8WYZgJ0PA4RmtebBJ0BCYol1qPv3GQF6jK7C9qoVc7lg==} + + '@volar/typescript@2.4.27': + resolution: {integrity: sha512-eWaYCcl/uAPInSK2Lze6IqVWaBu/itVqR5InXcHXFyles4zO++Mglt3oxdgj75BDcv1Knr9Y93nowS8U3wqhxg==} + + '@vscode/emmet-helper@2.11.0': + resolution: {integrity: sha512-QLxjQR3imPZPQltfbWRnHU6JecWTF1QSWhx3GAKQpslx7y3Dp6sIIXhKjiUJ/BR9FX8PVthjr9PD6pNwOJfAzw==} + + '@vscode/l10n@0.0.18': + resolution: {integrity: sha512-KYSIHVmslkaCDyw013pphY+d7x1qV8IZupYfeIfzNA+nsaWHbn5uPuQRvdRFsa9zFzGeudPuoGoZ1Op4jrJXIQ==} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.15.0: + resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + engines: {node: '>=0.4.0'} + hasBin: true + + ajv-draft-04@1.0.0: + resolution: {integrity: sha512-mv00Te6nmYbRp5DCwclxtt7yV/joXJPGS7nM+97GdxvuttCOfgI3K4U25zboyeX0O+myI8ERluxQe5wljMmVIw==} + peerDependencies: + ajv: ^8.5.0 + peerDependenciesMeta: + ajv: + optional: true + + ajv@8.17.1: + resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + + ansi-align@3.0.1: + resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-query@5.3.2: + resolution: {integrity: sha512-COROpnaoap1E2F000S62r6A60uHZnmlvomhfyT2DlTcrY1OrBKn2UhH7qn5wTC9zMvD0AY7csdPSNwKP+7WiQw==} + engines: {node: '>= 0.4'} + + array-iterate@2.0.1: + resolution: {integrity: sha512-I1jXZMjAgCMmxT4qxXfPXa6SthSoE8h6gkSI9BGGNv8mP8G/v0blc+qFnZu6K42vTOiuME596QaLO0TP3Lk0xg==} + + assertion-error@2.0.1: + resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} + engines: {node: '>=12'} + + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + astro@5.16.6: + resolution: {integrity: sha512-6mF/YrvwwRxLTu+aMEa5pwzKUNl5ZetWbTyZCs9Um0F12HUmxUiF5UHiZPy4rifzU3gtpM3xP2DfdmkNX9eZRg==} + engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'} + hasBin: true + + axobject-query@4.1.0: + resolution: {integrity: sha512-qIj0G9wZbMGNLjLmg1PT6v2mE9AH2zlnADJD/2tC6E00hgmhUOfEB6greHPAfLRSufHqROIUTkw6E+M3lH0PTQ==} + engines: {node: '>= 0.4'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + base-64@1.0.0: + resolution: {integrity: sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.9.11: + resolution: {integrity: sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==} + hasBin: true + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + boxen@8.0.1: + resolution: {integrity: sha512-F3PH5k5juxom4xktynS7MoFY+NUWH5LC4CnH11YB8NPew+HLpmBLCybSAEyb2F+4pRXhuhWqFesoQd6DAyc2hw==} + engines: {node: '>=18'} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + brotli@1.3.3: + resolution: {integrity: sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + camelcase@8.0.0: + resolution: {integrity: sha512-8WB3Jcas3swSvjIeA2yvCJ+Miyz5l1ZmB6HFb9R1317dt9LCQoswg/BGrmAmkWVEszSrrg4RwmO46qIm2OEnSA==} + engines: {node: '>=16'} + + caniuse-lite@1.0.30001762: + resolution: {integrity: sha512-PxZwGNvH7Ak8WX5iXzoK1KPZttBXNPuaOvI2ZYU7NrlM+d9Ov+TUvlLOBNGzVXAntMSMMlJPd+jY6ovrVjSmUw==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chai@6.2.2: + resolution: {integrity: sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==} + engines: {node: '>=18'} + + chalk@5.6.2: + resolution: {integrity: sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + 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@4.0.3: + resolution: {integrity: sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==} + engines: {node: '>= 14.16.0'} + + ci-info@4.3.1: + resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} + engines: {node: '>=8'} + + cli-boxes@3.0.0: + resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} + engines: {node: '>=10'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + clone@2.1.2: + resolution: {integrity: sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==} + engines: {node: '>=0.8'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + collapse-white-space@2.1.0: + resolution: {integrity: sha512-loKTxY1zCOuG4j9f6EPnuyyYkf58RnhhWTvRoZEokgB+WbdXehfjFviyOVYkqzEWz1Q5kRiZdBYS5SwxbQYwzw==} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@11.1.0: + resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} + engines: {node: '>=16'} + + common-ancestor-path@1.0.1: + resolution: {integrity: sha512-L3sHRo1pXXEqX8VU28kfgUY+YGsk09hPqZiZmLacNib6XNTCM8ubYeT7ryXQw8asB1sKgcU5lkB7ONug08aB8w==} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-es@1.2.2: + resolution: {integrity: sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==} + + cookie@1.1.1: + resolution: {integrity: sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ==} + engines: {node: '>=18'} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + crossws@0.3.5: + resolution: {integrity: sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==} + + css-select@5.2.2: + resolution: {integrity: sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==} + + 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'} + + css-tree@3.1.0: + resolution: {integrity: sha512-0eW44TGN5SQXU1mWSkKwFstI/22X2bG1nYzZTYMAWjylYURhse752YgbE4Cx46AC+bAvI+/dYTPRk1LqSUnu6w==} + engines: {node: ^10 || ^12.20.0 || ^14.13.0 || >=15.0.0} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + 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'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + 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.2.0: + resolution: {integrity: sha512-c6fcElNV6ShtZXmsgNgFFV5tVX2PaV4g+MOAkb8eXHvn6sryJBrZa9r0zV6+dtTyoCKxtDy5tyQ5ZwQuidtd+Q==} + + defu@6.1.4: + resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destr@2.0.5: + resolution: {integrity: sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + deterministic-object-hash@2.0.2: + resolution: {integrity: sha512-KxektNH63SrbfUyDiwXqRb1rLwKt33AmMv+5Nhsw1kqZ13SJBRTgZHtGbE+hH3a1mVW1cz+4pqSWVPAtLVXTzQ==} + engines: {node: '>=18'} + + devalue@5.6.1: + resolution: {integrity: sha512-jDwizj+IlEZBunHcOuuFVBnIMPAEHvTsJj0BcIp94xYguLRVBcXO853px/MyIJvbVzWdsGvrRweIUWJw8hBP7A==} + + devlop@1.1.0: + resolution: {integrity: sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==} + + dfa@1.2.0: + resolution: {integrity: sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==} + + diff@5.2.0: + resolution: {integrity: sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A==} + engines: {node: '>=0.3.1'} + + dlv@1.1.3: + resolution: {integrity: sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==} + + dom-serializer@2.0.0: + resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@5.0.3: + resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} + engines: {node: '>= 4'} + + domutils@3.2.2: + resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==} + + dset@3.1.4: + resolution: {integrity: sha512-2QF/g9/zTaPDc3BjNcVTGoBbXBgYfMTTceLaYcFJ/W9kggFUkhxD/hMEeuLKbugyef9SqAx8cpgwlIP/jinUTA==} + engines: {node: '>=4'} + + electron-to-chromium@1.5.267: + resolution: {integrity: sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==} + + emmet@2.4.11: + resolution: {integrity: sha512-23QPJB3moh/U9sT4rQzGgeyyGIrcM+GH5uVYg2C6wZIxAIJq7Ng3QLT79tl8FUwDXhyq9SusfknOrofAKqvgyQ==} + + emoji-regex@10.6.0: + resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + enhanced-resolve@5.18.4: + resolution: {integrity: sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==} + engines: {node: '>=10.13.0'} + + entities@4.5.0: + resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} + engines: {node: '>=0.12'} + + entities@6.0.1: + resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==} + engines: {node: '>=0.12'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + esast-util-from-estree@2.0.0: + resolution: {integrity: sha512-4CyanoAudUSBAn5K13H4JhsMH6L9ZP7XbLVe/dKybkxMO7eDyLsT8UHl9TRNrU2Gr9nz+FovfSIjuXWJ81uVwQ==} + + esast-util-from-js@2.0.1: + resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==} + + esbuild@0.25.12: + resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==} + engines: {node: '>=18'} + hasBin: true + + esbuild@0.27.2: + resolution: {integrity: sha512-HyNQImnsOC7X9PMNaCIeAm4ISCQXs5a5YasTXVliKv4uuBo1dKrG0A+uQS8M5eXjVMnLg3WgXaKvprHlFJQffw==} + engines: {node: '>=18'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-string-regexp@5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + + estree-util-attach-comments@3.0.0: + resolution: {integrity: sha512-cKUwm/HUcTDsYh/9FgnuFqpfquUbwIqwKM26BVCGDPVgvaCl/nDCCjUfiLlx6lsEZ3Z4RFxNbOQ60pkaEwFxGw==} + + estree-util-build-jsx@3.0.1: + resolution: {integrity: sha512-8U5eiL6BTrPxp/CHbs2yMgP8ftMhR5ww1eIKoWRMlqvltHF8fZn5LRDvTKuxD3DUn+shRbLGqXemcP51oFCsGQ==} + + estree-util-is-identifier-name@3.0.0: + resolution: {integrity: sha512-hFtqIDZTIUZ9BXLb8y4pYGyk6+wekIivNVTcmvk8NoOh+VeRn5y6cEHzbURrWbfp1fIqdVipilzj+lfaadNZmg==} + + estree-util-scope@1.0.0: + resolution: {integrity: sha512-2CAASclonf+JFWBNJPndcOpA8EMJwa0Q8LUFJEKqXLW6+qBvbFZuF5gItbQOs/umBUkjviCSDCbBwU2cXbmrhQ==} + + estree-util-to-js@2.0.0: + resolution: {integrity: sha512-WDF+xj5rRWmD5tj6bIqRi6CkLIXbbNQUcxQHzGysQzvHmdYG2G7p/Tf0J0gpxGgkeMZNTIjT/AoSvC9Xehcgdg==} + + estree-util-visit@2.0.0: + resolution: {integrity: sha512-m5KgiH85xAhhW8Wta0vShLcUvOsh3LLPI2YVwcbio1l7E09NTLL1EyMZFM1OyWowoH0skScNbhOPl4kcBgzTww==} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + execa@9.6.1: + resolution: {integrity: sha512-9Be3ZoN4LmYR90tUoVu2te2BsbzHfhJyfEiAVfz7N5/zv+jduIfLrV2xdQXOHbaD6KgpGdO9PRPM1Y4Q9QkPkA==} + engines: {node: ^18.19.0 || >=20.5.0} + + expect-type@1.3.0: + resolution: {integrity: sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==} + engines: {node: '>=12.0.0'} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-uri@3.1.0: + resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + figures@6.1.0: + resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} + engines: {node: '>=18'} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + flattie@1.1.1: + resolution: {integrity: sha512-9UbaD6XdAL97+k/n+N7JwX46K/M6Zc6KcFYskrYL8wbBV/Uyk0CTAMY0VT+qiK5PM7AIc9aTWYtq65U7T+aCNQ==} + engines: {node: '>=8'} + + fontace@0.3.1: + resolution: {integrity: sha512-9f5g4feWT1jWT8+SbL85aLIRLIXUaDygaM2xPXRmzPYxrOMNok79Lr3FGJoKVNKibE0WCunNiEVG2mwuE+2qEg==} + + fontkit@2.0.4: + resolution: {integrity: sha512-syetQadaUEDNdxdugga9CpEYVaQIxOwk7GlwZWWZ19//qW4zE5bknOKeMBDYAASwnpaSHKJITRLMF9m1fp3s6g==} + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-east-asian-width@1.4.0: + resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + engines: {node: '>=18'} + + get-stream@9.0.1: + resolution: {integrity: sha512-kVCxPF3vQM/N0B1PmoqVUqgHP+EeVjmZSQn+1oCRPxd2P21P2F19lIgbR3HBosbB1PUhOAoctJnfEn2GbN2eZA==} + engines: {node: '>=18'} + + github-slugger@2.0.0: + resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + h3@1.15.4: + resolution: {integrity: sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==} + + hast-util-from-html@2.0.3: + resolution: {integrity: sha512-CUSRHXyKjzHov8yKsQjGOElXy/3EKpyX56ELnkHH34vDVw1N1XSQ1ZcAvTyAPtGqLTuKP/uxM+aLkSPqF/EtMw==} + + hast-util-from-parse5@8.0.3: + resolution: {integrity: sha512-3kxEVkEKt0zvcZ3hCRYI8rqrgwtlIOFMWkbclACvjlDw8Li9S2hk/d51OI0nr/gIpdMHNepwgOKqZ/sy0Clpyg==} + + hast-util-is-element@3.0.0: + resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + + hast-util-parse-selector@4.0.0: + resolution: {integrity: sha512-wkQCkSYoOGCRKERFWcxMVMOcYE2K1AaNLU8DXS9arxnLOUEWbOXKXiJUNzEpqZ3JOKpnha3jkFrumEjVliDe7A==} + + hast-util-raw@9.1.0: + resolution: {integrity: sha512-Y8/SBAHkZGoNkpzqqfCldijcuUKh7/su31kEBp67cFY09Wy0mTRgtsLYsiIxMJxlu0f6AA5SUTbDR8K0rxnbUw==} + + hast-util-to-estree@3.1.3: + resolution: {integrity: sha512-48+B/rJWAp0jamNbAAf9M7Uf//UVqAoMmgXhBdxTDJLGKY+LRnZ99qcG+Qjl5HfMpYNzS5v4EAwVEF34LeAj7w==} + + hast-util-to-html@9.0.5: + resolution: {integrity: sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==} + + hast-util-to-jsx-runtime@2.3.6: + resolution: {integrity: sha512-zl6s8LwNyo1P9uw+XJGvZtdFF1GdAkOg8ujOw+4Pyb76874fLps4ueHXDhXWdk6YHQ6OgUtinliG7RsYvCbbBg==} + + hast-util-to-parse5@8.0.1: + resolution: {integrity: sha512-MlWT6Pjt4CG9lFCjiz4BH7l9wmrMkfkJYCxFwKQic8+RTZgWPuWxwAfjJElsXkex7DJjfSJsQIt931ilUgmwdA==} + + 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==} + + html-escaper@3.0.3: + resolution: {integrity: sha512-RuMffC89BOWQoY0WKGpIhn5gX3iI54O6nRA0yC124NYVtzjmFWBIiFd8M0x+ZdX0P9R4lADg1mgP8C7PxGOWuQ==} + + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} + + http-cache-semantics@4.2.0: + resolution: {integrity: sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==} + + human-signals@8.0.1: + resolution: {integrity: sha512-eKCa6bwnJhvxj14kZk5NCPc6Hb6BdsU9DZcOnmQKSnO1VKrfV0zCvtttPZUsBvjmNDn8rpcJfpwSYnHBjc95MQ==} + engines: {node: '>=18.18.0'} + + import-meta-resolve@4.2.0: + resolution: {integrity: sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg==} + + inline-style-parser@0.2.7: + resolution: {integrity: sha512-Nb2ctOyNR8DqQoR0OwRG95uNWIC0C1lCgf5Naz5H6Ji72KZ8OcFZLz2P5sNgwlyoJ8Yif11oMuYs5pBQa86csA==} + + 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@3.0.0: + resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-inside-container@1.0.0: + resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} + engines: {node: '>=14.16'} + hasBin: true + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-stream@4.0.1: + resolution: {integrity: sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==} + engines: {node: '>=18'} + + is-unicode-supported@2.1.0: + resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==} + engines: {node: '>=18'} + + is-wsl@3.1.0: + resolution: {integrity: sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw==} + engines: {node: '>=16'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.1.0: + resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} + engines: {node: '>=6'} + hasBin: true + + json-schema-traverse@1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonc-parser@2.3.1: + resolution: {integrity: sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==} + + jsonc-parser@3.3.1: + resolution: {integrity: sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==} + + kleur@3.0.3: + resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==} + engines: {node: '>=6'} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + lightningcss-android-arm64@1.30.2: + resolution: {integrity: sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.30.2: + resolution: {integrity: sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.30.2: + resolution: {integrity: sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.30.2: + resolution: {integrity: sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.30.2: + resolution: {integrity: sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.30.2: + resolution: {integrity: sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-arm64-musl@1.30.2: + resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + + lightningcss-linux-x64-gnu@1.30.2: + resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-linux-x64-musl@1.30.2: + resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + + lightningcss-win32-arm64-msvc@1.30.2: + resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.30.2: + resolution: {integrity: sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.30.2: + resolution: {integrity: sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==} + engines: {node: '>= 12.0.0'} + + lodash@4.17.21: + resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + magicast@0.5.1: + resolution: {integrity: sha512-xrHS24IxaLrvuo613F719wvOIv9xPHFWQHuvGUBmPnCA/3MQxKI3b+r7n1jAoDHmsbC5bRhTZYR77invLAxVnw==} + + markdown-extensions@2.0.0: + resolution: {integrity: sha512-o5vL7aDWatOTX8LzaS1WMoaoxIiLRQJuIKKe2wAw6IeULDHaqbiqiggmx+pKvZDb1Sj+pE46Sn1T7lCqfFtg1Q==} + engines: {node: '>=16'} + + markdown-table@3.0.4: + resolution: {integrity: sha512-wiYz4+JrLyb/DqW2hkFJxP7Vd7JuTDm77fvbM8VfEQdmSMqcImWeeRbHwZjBjIFki/VaMK2BhFi7oUUZeM5bqw==} + + mdast-util-definitions@6.0.0: + resolution: {integrity: sha512-scTllyX6pnYNZH/AIp/0ePz6s4cZtARxImwoPJ7kS42n+MnVsI4XbnG6d4ibehRIldYMWM2LD7ImQblVhUejVQ==} + + mdast-util-find-and-replace@3.0.2: + resolution: {integrity: sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==} + + mdast-util-from-markdown@2.0.2: + resolution: {integrity: sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==} + + mdast-util-gfm-autolink-literal@2.0.1: + resolution: {integrity: sha512-5HVP2MKaP6L+G6YaxPNjuL0BPrq9orG3TsrZ9YXbA3vDw/ACI4MEsnoDpn6ZNm7GnZgtAcONJyPhOP8tNJQavQ==} + + mdast-util-gfm-footnote@2.1.0: + resolution: {integrity: sha512-sqpDWlsHn7Ac9GNZQMeUzPQSMzR6Wv0WKRNvQRg0KqHh02fpTz69Qc1QSseNX29bhz1ROIyNyxExfawVKTm1GQ==} + + mdast-util-gfm-strikethrough@2.0.0: + resolution: {integrity: sha512-mKKb915TF+OC5ptj5bJ7WFRPdYtuHv0yTRxK2tJvi+BDqbkiG7h7u/9SI89nRAYcmap2xHQL9D+QG/6wSrTtXg==} + + mdast-util-gfm-table@2.0.0: + resolution: {integrity: sha512-78UEvebzz/rJIxLvE7ZtDd/vIQ0RHv+3Mh5DR96p7cS7HsBhYIICDBCu8csTNWNO6tBWfqXPWekRuj2FNOGOZg==} + + mdast-util-gfm-task-list-item@2.0.0: + resolution: {integrity: sha512-IrtvNvjxC1o06taBAVJznEnkiHxLFTzgonUdy8hzFVeDun0uTjxxrRGVaNFqkU1wJR3RBPEfsxmU6jDWPofrTQ==} + + mdast-util-gfm@3.1.0: + resolution: {integrity: sha512-0ulfdQOM3ysHhCJ1p06l0b0VKlhU0wuQs3thxZQagjcjPrlFRqY215uZGHHJan9GEAXd9MbfPjFJz+qMkVR6zQ==} + + mdast-util-mdx-expression@2.0.1: + resolution: {integrity: sha512-J6f+9hUp+ldTZqKRSg7Vw5V6MqjATc+3E4gf3CFNcuZNWD8XdyI6zQ8GqH7f8169MM6P7hMBRDVGnn7oHB9kXQ==} + + mdast-util-mdx-jsx@3.2.0: + resolution: {integrity: sha512-lj/z8v0r6ZtsN/cGNNtemmmfoLAFZnjMbNyLzBafjzikOM+glrjNHPlf6lQDOTccj9n5b0PPihEBbhneMyGs1Q==} + + mdast-util-mdx@3.0.0: + resolution: {integrity: sha512-JfbYLAW7XnYTTbUsmpu0kdBUVe+yKVJZBItEjwyYJiDJuZ9w4eeaqks4HQO+R7objWgS2ymV60GYpI14Ug554w==} + + mdast-util-mdxjs-esm@2.0.1: + resolution: {integrity: sha512-EcmOpxsZ96CvlP03NghtH1EsLtr0n9Tm4lPUJUBccV9RwUOneqSycg19n5HGzCf+10LozMRSObtVr3ee1WoHtg==} + + 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.1.2: + resolution: {integrity: sha512-xj68wMTvGXVOKonmog6LwyJKrYXZPvlwabaryTjLh9LuvovB/KAH+kvi8Gjj+7rJjsFi23nkUxRQv1KqSroMqA==} + + 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.12.2: + resolution: {integrity: sha512-IEn+pegP1aManZuckezWCO+XZQDplx1366JoVhTpMpBB1sPey/SbveZQUosKiKiGYjg1wH4pMlNgXbCiYgihQA==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + micromark-core-commonmark@2.0.3: + resolution: {integrity: sha512-RDBrHEMSxVFLg6xvnXmb1Ayr2WzLAWjeSATAoxwKYJV94TeNavgoIdA0a9ytzDSVzBy2YKFK+emCPOEibLeCrg==} + + micromark-extension-gfm-autolink-literal@2.1.0: + resolution: {integrity: sha512-oOg7knzhicgQ3t4QCjCWgTmfNhvQbDDnJeVu9v81r7NltNCVmhPy1fJRX27pISafdjL+SVc4d3l48Gb6pbRypw==} + + micromark-extension-gfm-footnote@2.1.0: + resolution: {integrity: sha512-/yPhxI1ntnDNsiHtzLKYnE3vf9JZ6cAisqVDauhp4CEHxlb4uoOTxOCJ+9s51bIB8U1N1FJ1RXOKTIlD5B/gqw==} + + micromark-extension-gfm-strikethrough@2.1.0: + resolution: {integrity: sha512-ADVjpOOkjz1hhkZLlBiYA9cR2Anf8F4HqZUO6e5eDcPQd0Txw5fxLzzxnEkSkfnD0wziSGiv7sYhk/ktvbf1uw==} + + micromark-extension-gfm-table@2.1.1: + resolution: {integrity: sha512-t2OU/dXXioARrC6yWfJ4hqB7rct14e8f7m0cbI5hUmDyyIlwv5vEtooptH8INkbLzOatzKuVbQmAYcbWoyz6Dg==} + + micromark-extension-gfm-tagfilter@2.0.0: + resolution: {integrity: sha512-xHlTOmuCSotIA8TW1mDIM6X2O1SiX5P9IuDtqGonFhEK0qgRI4yeC6vMxEV2dgyr2TiD+2PQ10o+cOhdVAcwfg==} + + micromark-extension-gfm-task-list-item@2.1.0: + resolution: {integrity: sha512-qIBZhqxqI6fjLDYFTBIa4eivDMnP+OZqsNwmQ3xNLE4Cxwc+zfQEfbs6tzAo2Hjq+bh6q5F+Z8/cksrLFYWQQw==} + + micromark-extension-gfm@3.0.0: + resolution: {integrity: sha512-vsKArQsicm7t0z2GugkCKtZehqUm31oeGBV/KVSorWSy8ZlNAv7ytjFhvaryUiCUJYqs+NoE6AFhpQvBTM6Q4w==} + + micromark-extension-mdx-expression@3.0.1: + resolution: {integrity: sha512-dD/ADLJ1AeMvSAKBwO22zG22N4ybhe7kFIZ3LsDI0GlsNr2A3KYxb0LdC1u5rj4Nw+CHKY0RVdnHX8vj8ejm4Q==} + + micromark-extension-mdx-jsx@3.0.2: + resolution: {integrity: sha512-e5+q1DjMh62LZAJOnDraSSbDMvGJ8x3cbjygy2qFEi7HCeUT4BDKCvMozPozcD6WmOt6sVvYDNBKhFSz3kjOVQ==} + + micromark-extension-mdx-md@2.0.0: + resolution: {integrity: sha512-EpAiszsB3blw4Rpba7xTOUptcFeBFi+6PY8VnJ2hhimH+vCQDirWgsMpz7w1XcZE7LVrSAUGb9VJpG9ghlYvYQ==} + + micromark-extension-mdxjs-esm@3.0.0: + resolution: {integrity: sha512-DJFl4ZqkErRpq/dAPyeWp15tGrcrrJho1hKK5uBS70BCtfrIFg81sqcTVu3Ta+KD1Tk5vAtBNElWxtAa+m8K9A==} + + micromark-extension-mdxjs@3.0.0: + resolution: {integrity: sha512-A873fJfhnJ2siZyUrJ31l34Uqwy4xIFmvPY1oj+Ean5PHcPBYzEsvqvWGaWcfEIr11O5Dlw3p2y0tZWpKHDejQ==} + + 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-mdx-expression@2.0.3: + resolution: {integrity: sha512-kQnEtA3vzucU2BkrIa8/VaSAsP+EJ3CKOvhMuJgOEGg9KDC6OAY6nSnNDVRiVNRqj7Y4SlSzcStaH/5jge8JdQ==} + + micromark-factory-space@2.0.1: + resolution: {integrity: sha512-zRkxjtBxxLd2Sc0d+fbnEunsTj46SWXgXciZmHq0kDYGnck/ZSGj9/wULTV95uoeYiK5hRXP2mJ98Uo4cq/LQg==} + + 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-encode@2.0.1: + resolution: {integrity: sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==} + + micromark-util-events-to-acorn@2.0.3: + resolution: {integrity: sha512-jmsiEIiZ1n7X1Rr5k8wVExBQCg5jy4UXVADItHmNk1zkwEVhBuIUKRu3fqv+hs4nxLISi2DQGlqIOGiFxgbfHg==} + + 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.2: + resolution: {integrity: sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==} + + micromark@4.0.2: + resolution: {integrity: sha512-zpe98Q6kvavpCr1NPVSCMebCKfD7CA2NqZ+rykeNhONIJBpc1tFKt9hucLGwha3jNTNI8lHpctWJWoimVF4PfA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mrmime@2.0.1: + resolution: {integrity: sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ==} + engines: {node: '>=10'} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + muggle-string@0.4.1: + resolution: {integrity: sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + neotraverse@0.6.18: + resolution: {integrity: sha512-Z4SmBUweYa09+o6pG+eASabEpP6QkQ70yHj351pQoEXIs8uHbaU2DWVmzBANKgflPa47A50PtB2+NgRpQvr7vA==} + engines: {node: '>= 10'} + + nlcst-to-string@4.0.0: + resolution: {integrity: sha512-YKLBCcUYKAg0FNlOBT6aI91qFmSiFKiluk655WzPF+DDMA02qIyy8uiRqI8QXtcFpEvll12LpL5MXqEmAZ+dcA==} + + node-fetch-native@1.6.7: + resolution: {integrity: sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==} + + node-mock-http@1.0.4: + resolution: {integrity: sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + npm-check-updates@19.2.1: + resolution: {integrity: sha512-jWIMuE6K+DSDXfjXpM3SlJ+Gg07ZV57dlFKPr2EZTpimFk9y+gt138+kmP3Uocdqk1PZlOKpTpFvAx7Z1QCeog==} + engines: {node: '>=20.0.0', npm: '>=8.12.1'} + hasBin: true + + npm-run-path@6.0.0: + resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} + engines: {node: '>=18'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + obug@2.1.1: + resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} + + ofetch@1.5.1: + resolution: {integrity: sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==} + + ohash@2.0.11: + resolution: {integrity: sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==} + + oniguruma-parser@0.12.1: + resolution: {integrity: sha512-8Unqkvk1RYc6yq2WBYRj4hdnsAxVze8i7iPfQr8e4uSP3tRv0rpZcbGUDvxfQQcdwHt/e9PrMvGCsa8OqG9X3w==} + + oniguruma-to-es@4.3.4: + resolution: {integrity: sha512-3VhUGN3w2eYxnTzHn+ikMI+fp/96KoRSVK9/kMTcFqj1NRDh2IhQCKvYxDnWePKRXY/AqH+Fuiyb7VHSzBjHfA==} + + p-limit@6.2.0: + resolution: {integrity: sha512-kuUqqHNUqoIWp/c467RI4X6mmyuojY5jGutNU0wVTmEOOfcuwLqyMVoAi9MKi2Ak+5i9+nhmrK4ufZE8069kHA==} + engines: {node: '>=18'} + + p-queue@8.1.1: + resolution: {integrity: sha512-aNZ+VfjobsWryoiPnEApGGmf5WmNsCo9xu8dfaYamG5qaLP7ClhLN6NgsFe6SwJ2UbLEBK5dv9x8Mn5+RVhMWQ==} + engines: {node: '>=18'} + + p-timeout@6.1.4: + resolution: {integrity: sha512-MyIV3ZA/PmyBN/ud8vV9XzwTrNtR4jFrObymZYnZqMmW0zA8Z17vnT0rBgFE/TlohB+YCHqXMgZzb3Csp49vqg==} + engines: {node: '>=14.16'} + + package-manager-detector@1.6.0: + resolution: {integrity: sha512-61A5ThoTiDG/C8s8UMZwSorAGwMJ0ERVGj2OjoW5pAalsNOg15+iQiPzrLJ4jhZ1HJzmC2PIHT2oEiH3R5fzNA==} + + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse-latin@7.0.0: + resolution: {integrity: sha512-mhHgobPPua5kZ98EF4HWiH167JWBfl4pvAIXXdbaVohtK7a6YBOy56kvhCqduqyo/f3yrHFWmqmiMg/BkBkYYQ==} + + parse-ms@4.0.0: + resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} + engines: {node: '>=18'} + + parse5@7.3.0: + resolution: {integrity: sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==} + + path-browserify@1.0.1: + resolution: {integrity: sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-key@4.0.0: + resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} + engines: {node: '>=12'} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + piccolore@0.1.3: + resolution: {integrity: sha512-o8bTeDWjE086iwKrROaDf31K0qC/BENdm15/uH9usSC/uZjJOKb2YGiVHfLY4GhwsERiPI1jmwI2XrA7ACOxVw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + postcss-selector-parser@6.0.10: + resolution: {integrity: sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w==} + engines: {node: '>=4'} + + postcss@8.5.6: + resolution: {integrity: sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==} + engines: {node: ^10 || ^12 || >=14} + + prettier@3.7.4: + resolution: {integrity: sha512-v6UNi1+3hSlVvv8fSaoUbggEM5VErKmmpGA7Pl3HF8V6uKY7rvClBOJlH6yNwQtfTueNkGVpOv/mtWL9L4bgRA==} + engines: {node: '>=14'} + hasBin: true + + pretty-ms@9.3.0: + resolution: {integrity: sha512-gjVS5hOP+M3wMm5nmNOucbIrqudzs9v/57bWRHQWLYklXqoXKrVfYW2W9+glfGsqtPgpiz5WwyEEB+ksXIx3gQ==} + engines: {node: '>=18'} + + prismjs@1.30.0: + resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==} + engines: {node: '>=6'} + + prompts@2.4.2: + resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} + engines: {node: '>= 6'} + + property-information@7.1.0: + resolution: {integrity: sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + radix3@1.1.2: + resolution: {integrity: sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==} + + react-dom@19.2.3: + resolution: {integrity: sha512-yELu4WmLPw5Mr/lmeEpox5rw3RETacE++JgHqQzd2dg+YbJuat3jH4ingc+WPZhxaoFzdv9y33G+F7Nl5O0GBg==} + peerDependencies: + react: ^19.2.3 + + react-refresh@0.17.0: + resolution: {integrity: sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==} + engines: {node: '>=0.10.0'} + + react@19.2.3: + resolution: {integrity: sha512-Ku/hhYbVjOQnXDZFv2+RibmLFGwFdeeKHFcOTlrt7xplBnya5OGn/hIRDsqDiSUcfORsDC7MPxwork8jBwsIWA==} + engines: {node: '>=0.10.0'} + + readdirp@4.1.2: + resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==} + engines: {node: '>= 14.18.0'} + + recma-build-jsx@1.0.0: + resolution: {integrity: sha512-8GtdyqaBcDfva+GUKDr3nev3VpKAhup1+RvkMvUxURHpW7QyIvk9F5wz7Vzo06CEMSilw6uArgRqhpiUcWp8ew==} + + recma-jsx@1.0.1: + resolution: {integrity: sha512-huSIy7VU2Z5OLv6oFLosQGGDqPqdO1iq6bWNAdhzMxSJP7RAso4fCZ1cKu8j9YHCZf3TPrq4dw3okhrylgcd7w==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + recma-parse@1.0.0: + resolution: {integrity: sha512-OYLsIGBB5Y5wjnSnQW6t3Xg7q3fQ7FWbw/vcXtORTnyaSFscOtABg+7Pnz6YZ6c27fG1/aN8CjfwoUEUIdwqWQ==} + + recma-stringify@1.0.0: + resolution: {integrity: sha512-cjwII1MdIIVloKvC9ErQ+OgAtwHBmcZ0Bg4ciz78FtbT8In39aAYbaA7zvxQ61xVMSPE8WxhLwLbhif4Js2C+g==} + + regex-recursion@6.0.2: + resolution: {integrity: sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==} + + regex-utilities@2.3.0: + resolution: {integrity: sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==} + + regex@6.1.0: + resolution: {integrity: sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==} + + rehype-parse@9.0.1: + resolution: {integrity: sha512-ksCzCD0Fgfh7trPDxr2rSylbwq9iYDkSn8TCDmEJ49ljEUBxDVCzCHv7QNzZOfODanX4+bWQ4WZqLCRWYLfhag==} + + rehype-raw@7.0.0: + resolution: {integrity: sha512-/aE8hCfKlQeA8LmyeyQvQF3eBiLRGNlfBJEvWH7ivp9sBqs7TNqBL5X3v157rM4IFETqDnIOO+z5M/biZbo9Ww==} + + rehype-recma@1.0.0: + resolution: {integrity: sha512-lqA4rGUf1JmacCNWWZx0Wv1dHqMwxzsDWYMTowuplHF3xH0N/MmrZ/G3BDZnzAkRmxDadujCjaKM2hqYdCBOGw==} + + rehype-stringify@10.0.1: + resolution: {integrity: sha512-k9ecfXHmIPuFVI61B9DeLPN0qFHfawM6RsuX48hoqlaKSF61RskNjSm1lI8PhBEM0MRdLxVVm4WmTqJQccH9mA==} + + rehype@13.0.2: + resolution: {integrity: sha512-j31mdaRFrwFRUIlxGeuPXXKWQxet52RBQRvCmzl5eCefn/KGbomK5GMHNMsOJf55fgo3qw5tST5neDuarDYR2A==} + + remark-gfm@4.0.1: + resolution: {integrity: sha512-1quofZ2RQ9EWdeN34S79+KExV1764+wCUGop5CPL1WGdD0ocPpu91lzPGbwWMECpEpd42kJGQwzRfyov9j4yNg==} + + remark-mdx@3.1.1: + resolution: {integrity: sha512-Pjj2IYlUY3+D8x00UJsIOg5BEvfMyeI+2uLPn9VO9Wg4MEtN/VTIq2NEJQfde9PnX15KgtHyl9S0BcTnWrIuWg==} + + remark-parse@11.0.0: + resolution: {integrity: sha512-FCxlKLNGknS5ba/1lmpYijMUzX2esxW5xQqjWxw2eHFfS2MSdaHVINFmhjo+qN1WhZhNimq0dZATN9pH0IDrpA==} + + remark-rehype@11.1.2: + resolution: {integrity: sha512-Dh7l57ianaEoIpzbp0PC9UKAdCSVklD8E5Rpw7ETfbTl3FqcOOgq5q2LVDhgGCkaBv7p24JXikPdvhhmHvKMsw==} + + remark-smartypants@3.0.2: + resolution: {integrity: sha512-ILTWeOriIluwEvPjv67v7Blgrcx+LZOkAUVtKI3putuhlZm84FnqDORNXPPm+HY3NdZOMhyDwZ1E+eZB/Df5dA==} + engines: {node: '>=16.0.0'} + + remark-stringify@11.0.0: + resolution: {integrity: sha512-1OSmLd3awB/t8qdoEOMazZkNsfVTeY4fTsgzcQFdXNq8ToTN4ZGwrMnlda4K6smTFKD+GRV6O48i6Z4iKgPPpw==} + + request-light@0.5.8: + resolution: {integrity: sha512-3Zjgh+8b5fhRJBQZoy+zbVKpAQGLyka0MPgW3zruTF4dFFJ8Fqcfu9YsAvi/rvdcaTeWG3MkbZv4WKxAn/84Lg==} + + request-light@0.7.0: + resolution: {integrity: sha512-lMbBMrDoxgsyO+yB3sDcrDuX85yYt7sS8BfQd11jtbW/z5ZWgLZRcEGLsLoYw7I0WSUGQBs8CC8ScIxkTX1+6Q==} + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + require-from-string@2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + + restructure@3.0.2: + resolution: {integrity: sha512-gSfoiOEA0VPE6Tukkrr7I0RBdE0s7H1eFCDBk05l1KIQT1UIKNc5JZy6jdyW6eYH3aR3g5b3PuL77rq0hvwtAw==} + + retext-latin@4.0.0: + resolution: {integrity: sha512-hv9woG7Fy0M9IlRQloq/N6atV82NxLGveq+3H2WOi79dtIYWN8OaxogDm77f8YnVXJL2VD3bbqowu5E3EMhBYA==} + + retext-smartypants@6.2.0: + resolution: {integrity: sha512-kk0jOU7+zGv//kfjXEBjdIryL1Acl4i9XNkHxtM7Tm5lFiCog576fjNC9hjoR7LTKQ0DsPWy09JummSsH1uqfQ==} + + retext-stringify@4.0.0: + resolution: {integrity: sha512-rtfN/0o8kL1e+78+uxPTqu1Klt0yPzKuQ2BfWwwfgIUSayyzxpM1PJzkKt4V8803uB9qSy32MvI7Xep9khTpiA==} + + retext@9.0.0: + resolution: {integrity: sha512-sbMDcpHCNjvlheSgMfEcVrZko3cDzdbe1x/e7G66dFp0Ff7Mldvi2uv6JkJQzdRcvLYE8CA8Oe8siQx8ZOgTcA==} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rollup@4.54.0: + resolution: {integrity: sha512-3nk8Y3a9Ea8szgKhinMlGMhGMw89mqule3KWczxhIzqudyHdCIOHw8WJlj/r329fACjKLEh13ZSk7oE22kyeIw==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + sax@1.4.3: + resolution: {integrity: sha512-yqYn1JhPczigF94DMS+shiDMjDowYO6y9+wB/4WgO0Y19jWYk0lQ4tuG5KI7kj4FTp1wxPj5IFfcrz/s1c3jjQ==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.3: + resolution: {integrity: sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==} + engines: {node: '>=10'} + hasBin: true + + sharp@0.34.5: + resolution: {integrity: sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==} + engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + shiki@3.20.0: + resolution: {integrity: sha512-kgCOlsnyWb+p0WU+01RjkCH+eBVsjL1jOwUYWv0YDWkM2/A46+LDKVs5yZCUXjJG6bj4ndFoAg5iLIIue6dulg==} + + siginfo@2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + sisteransi@1.0.5: + resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==} + + smol-toml@1.6.0: + resolution: {integrity: sha512-4zemZi0HvTnYwLfrpk/CF9LOd9Lt87kAt50GnqhMpyF9U3poDAP2+iukq2bZsO/ufegbYehBkqINbsWxj4l4cw==} + engines: {node: '>= 18'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + stackback@0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + + std-env@3.10.0: + resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@7.2.0: + resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} + engines: {node: '>=18'} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.1.2: + resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + engines: {node: '>=12'} + + strip-final-newline@4.0.0: + resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} + engines: {node: '>=18'} + + style-to-js@1.1.21: + resolution: {integrity: sha512-RjQetxJrrUJLQPHbLku6U/ocGtzyjbJMP9lCNK7Ag0CNh690nSH8woqWH9u16nMjYBAok+i7JO1NP2pOy8IsPQ==} + + style-to-object@1.0.14: + resolution: {integrity: sha512-LIN7rULI0jBscWQYaSswptyderlarFkjQ+t79nzty8tcIAceVomEVlLzH5VP4Cmsv6MtKhs7qaAiwlcp+Mgaxw==} + + svgo@4.0.0: + resolution: {integrity: sha512-VvrHQ+9uniE+Mvx3+C9IEe/lWasXCU0nXMY2kZeLrHNICuRiC8uMPyM14UEaMOFA5mhyQqEkB02VoQ16n3DLaw==} + engines: {node: '>=16'} + hasBin: true + + tailwindcss@4.1.18: + resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==} + + tapable@2.3.0: + resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==} + engines: {node: '>=6'} + + tiny-inflate@1.0.3: + resolution: {integrity: sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==} + + tinybench@2.9.0: + resolution: {integrity: sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==} + + tinyexec@1.0.2: + resolution: {integrity: sha512-W/KYk+NFhkmsYpuHq5JykngiOCnxeVL8v8dFnqxSD8qEEdRfXk1SDM6JzNqcERbcGYj9tMrDQBYV9cjgnunFIg==} + engines: {node: '>=18'} + + tinyglobby@0.2.15: + resolution: {integrity: sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==} + engines: {node: '>=12.0.0'} + + tinyrainbow@3.0.3: + resolution: {integrity: sha512-PSkbLUoxOFRzJYjjxHJt9xro7D+iilgMX/C9lawzVuYiIdcihh9DXmVibBe8lmcFrRi/VzlPjBxbN7rH24q8/Q==} + engines: {node: '>=14.0.0'} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + type-fest@4.41.0: + resolution: {integrity: sha512-TeTSQ6H5YHvpqVwBRcnLDCBnDOHWYu7IvGbHT6N8AOymcr9PJGjc1GTtiWZTYg0NCgYwvnYWEkVChQAr9bjfwA==} + engines: {node: '>=16'} + + typesafe-path@0.2.2: + resolution: {integrity: sha512-OJabfkAg1WLZSqJAJ0Z6Sdt3utnbzr/jh+NAHoyWHJe8CMSy79Gm085094M9nvTPy22KzTVn5Zq5mbapCI/hPA==} + + typescript-auto-import-cache@0.3.6: + resolution: {integrity: sha512-RpuHXrknHdVdK7wv/8ug3Fr0WNsNi5l5aB8MYYuXhq2UH5lnEB1htJ1smhtD5VeCsGr2p8mUDtd83LCQDFVgjQ==} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.1: + resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + + ultrahtml@1.6.0: + resolution: {integrity: sha512-R9fBn90VTJrqqLDwyMph+HGne8eqY1iPfYhPzZrvKpIfwkWZbcYlfpsb8B9dTvBfpy1/hqAD7Wi8EKfP9e8zdw==} + + uncrypto@0.1.3: + resolution: {integrity: sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==} + + undici-types@7.16.0: + resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + + unicode-properties@1.4.1: + resolution: {integrity: sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==} + + unicode-trie@2.0.0: + resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} + + unicorn-magic@0.3.0: + resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} + engines: {node: '>=18'} + + unified@11.0.5: + resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==} + + unifont@0.6.0: + resolution: {integrity: sha512-5Fx50fFQMQL5aeHyWnZX9122sSLckcDvcfFiBf3QYeHa7a1MKJooUy52b67moi2MJYkrfo/TWY+CoLdr/w0tTA==} + + unist-util-find-after@5.0.0: + resolution: {integrity: sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==} + + unist-util-is@6.0.1: + resolution: {integrity: sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==} + + unist-util-modify-children@4.0.0: + resolution: {integrity: sha512-+tdN5fGNddvsQdIzUF3Xx82CU9sMM+fA0dLgR9vOmT0oPT2jH+P1nd5lSqfCfXAw+93NhcXNY2qqvTUtE4cQkw==} + + unist-util-position-from-estree@2.0.0: + resolution: {integrity: sha512-KaFVRjoqLyF6YXCbVLNad/eS4+OfPQQn2yOd7zF/h5T/CSL2v8NpN6a5TPvtbXthAGw5nG+PuTtq+DdIZr+cRQ==} + + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + + unist-util-remove-position@5.0.0: + resolution: {integrity: sha512-Hp5Kh3wLxv0PHj9m2yZhhLt58KzPtEYKQQ4yxfYFEO7EvHwzyDYnduhHnY1mDxoqr7VUwVuHXk9RXKIiYS1N8Q==} + + unist-util-stringify-position@4.0.0: + resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} + + unist-util-visit-children@3.0.0: + resolution: {integrity: sha512-RgmdTfSBOg04sdPcpTSD1jzoNBjt9a80/ZCzp5cI9n1qPzLZWF9YdvWGN2zmTumP1HWhXKdUWexjy/Wy/lJ7tA==} + + unist-util-visit-parents@6.0.2: + resolution: {integrity: sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==} + + unist-util-visit@5.0.0: + resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==} + + unstorage@1.17.3: + resolution: {integrity: sha512-i+JYyy0DoKmQ3FximTHbGadmIYb8JEpq7lxUjnjeB702bCPum0vzo6oy5Mfu0lpqISw7hCyMW2yj4nWC8bqJ3Q==} + peerDependencies: + '@azure/app-configuration': ^1.8.0 + '@azure/cosmos': ^4.2.0 + '@azure/data-tables': ^13.3.0 + '@azure/identity': ^4.6.0 + '@azure/keyvault-secrets': ^4.9.0 + '@azure/storage-blob': ^12.26.0 + '@capacitor/preferences': ^6.0.3 || ^7.0.0 + '@deno/kv': '>=0.9.0' + '@netlify/blobs': ^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0 + '@planetscale/database': ^1.19.0 + '@upstash/redis': ^1.34.3 + '@vercel/blob': '>=0.27.1' + '@vercel/functions': ^2.2.12 || ^3.0.0 + '@vercel/kv': ^1.0.1 + aws4fetch: ^1.0.20 + db0: '>=0.2.1' + idb-keyval: ^6.2.1 + ioredis: ^5.4.2 + uploadthing: ^7.4.4 + peerDependenciesMeta: + '@azure/app-configuration': + optional: true + '@azure/cosmos': + optional: true + '@azure/data-tables': + optional: true + '@azure/identity': + optional: true + '@azure/keyvault-secrets': + optional: true + '@azure/storage-blob': + optional: true + '@capacitor/preferences': + optional: true + '@deno/kv': + optional: true + '@netlify/blobs': + optional: true + '@planetscale/database': + optional: true + '@upstash/redis': + optional: true + '@vercel/blob': + optional: true + '@vercel/functions': + optional: true + '@vercel/kv': + optional: true + aws4fetch: + optional: true + db0: + optional: true + idb-keyval: + optional: true + ioredis: + optional: true + uploadthing: + optional: true + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + vfile-location@5.0.3: + resolution: {integrity: sha512-5yXvWDEgqeiYiBe1lbxYF7UMAIm/IcopxMHrMQDq3nvKcjPKIhZklUKL+AE7J7uApI4kwe2snsK+eI6UTj9EHg==} + + vfile-message@4.0.3: + resolution: {integrity: sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==} + + vfile@6.0.3: + resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==} + + vite@6.4.1: + resolution: {integrity: sha512-+Oxm7q9hDoLMyJOYfUYBuHQo+dkAloi33apOPP56pzj+vsdJDzr+j1NISE5pyaAuKL4A3UD34qd0lx5+kfKp2g==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + peerDependencies: + '@types/node': ^18.0.0 || ^20.0.0 || >=22.0.0 + jiti: '>=1.21.0' + less: '*' + lightningcss: ^1.21.0 + sass: '*' + sass-embedded: '*' + stylus: '*' + sugarss: '*' + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vite@7.3.0: + resolution: {integrity: sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==} + engines: {node: ^20.19.0 || >=22.12.0} + hasBin: true + peerDependencies: + '@types/node': ^20.19.0 || >=22.12.0 + jiti: '>=1.21.0' + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: '>=0.54.8' + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + peerDependenciesMeta: + '@types/node': + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + + vitefu@1.1.1: + resolution: {integrity: sha512-B/Fegf3i8zh0yFbpzZ21amWzHmuNlLlmJT6n7bu5e+pCHUKQIfXSYokrqOBGEMMe9UG2sostKQF9mml/vYaWJQ==} + peerDependencies: + vite: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0-beta.0 + peerDependenciesMeta: + vite: + optional: true + + vitest@4.0.16: + resolution: {integrity: sha512-E4t7DJ9pESL6E3I8nFjPa4xGUd3PmiWDLsDztS2qXSJWfHtbQnwAWylaBvSNY48I3vr8PTqIZlyK8TE3V3CA4Q==} + engines: {node: ^20.0.0 || ^22.0.0 || >=24.0.0} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@opentelemetry/api': ^1.9.0 + '@types/node': ^20.0.0 || ^22.0.0 || >=24.0.0 + '@vitest/browser-playwright': 4.0.16 + '@vitest/browser-preview': 4.0.16 + '@vitest/browser-webdriverio': 4.0.16 + '@vitest/ui': 4.0.16 + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@opentelemetry/api': + optional: true + '@types/node': + optional: true + '@vitest/browser-playwright': + optional: true + '@vitest/browser-preview': + optional: true + '@vitest/browser-webdriverio': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + + volar-service-css@0.0.67: + resolution: {integrity: sha512-zV7C6enn9T9tuvQ6iSUyYEs34iPXR69Pf9YYWpbFYPWzVs22w96BtE8p04XYXbmjU6unt5oFt+iLL77bMB5fhA==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-emmet@0.0.67: + resolution: {integrity: sha512-UDBL5x7KptmuJZNCCXMlCndMhFult/tj+9jXq3FH1ZGS1E4M/1U5hC06pg1c6e4kn+vnR6bqmvX0vIhL4f98+A==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-html@0.0.67: + resolution: {integrity: sha512-ljREMF79JbcjNvObiv69HK2HCl5UT7WTD10zi6CRFUHMbPfiF2UZ42HGLsEGSzaHGZz6H4IFjSS/qfENRLUviQ==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-prettier@0.0.67: + resolution: {integrity: sha512-B4KnPJPNWFTkEDa6Fn08i5PpO6T1CecmLLTFZoXz2eI4Fxwba/3nDaaVSsEP7e/vEe+U5YqV9fBzayJT71G5xg==} + peerDependencies: + '@volar/language-service': ~2.4.0 + prettier: ^2.2 || ^3.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + prettier: + optional: true + + volar-service-typescript-twoslash-queries@0.0.67: + resolution: {integrity: sha512-LD2R7WivDYp1SPgZrxx/0222xVTitDjm36oKo5+bfYG5kEgnw+BOPVHdwmvpJKg/RfssfxDI1ouwD4XkEDEfbA==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-typescript@0.0.67: + resolution: {integrity: sha512-rfQBy36Rm1PU9vLWHk8BYJ4r2j/CI024vocJcH4Nb6K2RTc2Irmw6UOVY5DdGiPRV5r+e10wLMK5njj/EcL8sA==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + volar-service-yaml@0.0.67: + resolution: {integrity: sha512-jkdP/RF6wPIXEE3Ktnd81oJPn7aAvnVSiaqQHThC2Hrvo6xd9pEcqtbBUI+YfqVgvcMtXAkbtNO61K2GPhAiuA==} + peerDependencies: + '@volar/language-service': ~2.4.0 + peerDependenciesMeta: + '@volar/language-service': + optional: true + + vscode-css-languageservice@6.3.9: + resolution: {integrity: sha512-1tLWfp+TDM5ZuVWht3jmaY5y7O6aZmpeXLoHl5bv1QtRsRKt4xYGRMmdJa5Pqx/FTkgRbsna9R+Gn2xE+evVuA==} + + vscode-html-languageservice@5.6.1: + resolution: {integrity: sha512-5Mrqy5CLfFZUgkyhNZLA1Ye5g12Cb/v6VM7SxUzZUaRKWMDz4md+y26PrfRTSU0/eQAl3XpO9m2og+GGtDMuaA==} + + vscode-json-languageservice@4.1.8: + resolution: {integrity: sha512-0vSpg6Xd9hfV+eZAaYN63xVVMOTmJ4GgHxXnkLCh+9RsQBkWKIghzLhW2B9ebfG+LQQg8uLtsQ2aUKjTgE+QOg==} + engines: {npm: '>=7.0.0'} + + vscode-jsonrpc@8.2.0: + resolution: {integrity: sha512-C+r0eKJUIfiDIfwJhria30+TYWPtuHJXHtI7J0YlOmKAo7ogxP20T0zxB7HZQIFhIyvoBPwWskjxrvAtfjyZfA==} + engines: {node: '>=14.0.0'} + + vscode-languageserver-protocol@3.17.5: + resolution: {integrity: sha512-mb1bvRJN8SVznADSGWM9u/b07H7Ecg0I3OgXDuLdn307rl/J3A9YD6/eYOssqhecL27hK1IPZAsaqh00i/Jljg==} + + vscode-languageserver-textdocument@1.0.12: + resolution: {integrity: sha512-cxWNPesCnQCcMPeenjKKsOCKQZ/L6Tv19DTRIGuLWe32lyzWhihGVJ/rcckZXJxfdKCFvRLS3fpBIsV/ZGX4zA==} + + vscode-languageserver-types@3.17.5: + resolution: {integrity: sha512-Ld1VelNuX9pdF39h2Hgaeb5hEZM2Z3jUrrMgWQAu82jMtZp7p3vJT3BzToKtZI7NgQssZje5o0zryOrhQvzQAg==} + + vscode-languageserver@9.0.1: + resolution: {integrity: sha512-woByF3PDpkHFUreUa7Hos7+pUWdeWMXRd26+ZX2A8cFx6v/JPTtd4/uN0/jB6XQHYaOlHbio03NTHCqrgG5n7g==} + hasBin: true + + vscode-nls@5.2.0: + resolution: {integrity: sha512-RAaHx7B14ZU04EU31pT+rKz2/zSl7xMsfIZuo8pd+KZO6PXtQmpevpq3vxvWNcrGbdmhM/rr5Uw5Mz+NBfhVng==} + + vscode-uri@3.1.0: + resolution: {integrity: sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==} + + web-namespaces@2.0.1: + resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==} + + which-pm-runs@1.1.0: + resolution: {integrity: sha512-n1brCuqClxfFfq/Rb0ICg9giSZqCS+pLtccdag6C2HyufBrh3fBOiy9nb6ggRMvWOVH5GrdJskj5iGTZNxd7SA==} + engines: {node: '>=4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + why-is-node-running@2.3.0: + resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} + engines: {node: '>=8'} + hasBin: true + + widest-line@5.0.0: + resolution: {integrity: sha512-c9bZp7b5YtRj2wOe6dlj32MK+Bx/M/d+9VB2SHM1OtsUHR0aV0tdP6DWh/iMt0kWi1t5g1Iudu6hQRNd1A4PVA==} + engines: {node: '>=18'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@9.0.2: + resolution: {integrity: sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==} + engines: {node: '>=18'} + + xxhash-wasm@1.1.0: + resolution: {integrity: sha512-147y/6YNh+tlp6nd/2pWq38i9h6mz/EuQ6njIrmW8D1BS5nCqs0P6DG+m6zTGnNz5I+uhZ0SHxBs9BsPrwcKDA==} + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yaml-language-server@1.19.2: + resolution: {integrity: sha512-9F3myNmJzUN/679jycdMxqtydPSDRAarSj3wPiF7pchEPnO9Dg07Oc+gIYLqXR4L+g+FSEVXXv2+mr54StLFOg==} + hasBin: true + + yaml@2.7.1: + resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==} + engines: {node: '>= 14'} + hasBin: true + + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + engines: {node: '>= 14.6'} + hasBin: true + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yocto-queue@1.2.2: + resolution: {integrity: sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==} + engines: {node: '>=12.20'} + + yocto-spinner@0.2.3: + resolution: {integrity: sha512-sqBChb33loEnkoXte1bLg45bEBsOP9N1kzQh5JZNKj/0rik4zAPTNSAVPj3uQAdc6slYJ0Ksc403G2XgxsJQFQ==} + engines: {node: '>=18.19'} + + yoctocolors@2.1.2: + resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==} + engines: {node: '>=18'} + + zod-to-json-schema@3.25.1: + resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==} + peerDependencies: + zod: ^3.25 || ^4 + + zod-to-ts@1.2.0: + resolution: {integrity: sha512-x30XE43V+InwGpvTySRNz9kB7qFU8DlyEy7BsSTCHPH1R0QasMmHWZDCzYm6bVXtj/9NNJAZF3jW8rzFvH5OFA==} + peerDependencies: + typescript: ^4.9.4 || ^5.0.2 + zod: ^3 + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + + zod@4.3.5: + resolution: {integrity: sha512-k7Nwx6vuWx1IJ9Bjuf4Zt1PEllcwe7cls3VNzm4CQ1/hgtFUK2bRNG3rvnpPUhFjmqJKAKtjV576KnUkHocg/g==} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@astrojs/check@0.9.6(prettier@3.7.4)(typescript@5.9.3)': + dependencies: + '@astrojs/language-server': 2.16.2(prettier@3.7.4)(typescript@5.9.3) + chokidar: 4.0.3 + kleur: 4.1.5 + typescript: 5.9.3 + yargs: 17.7.2 + transitivePeerDependencies: + - prettier + - prettier-plugin-astro + + '@astrojs/compiler@2.13.0': {} + + '@astrojs/internal-helpers@0.7.5': {} + + '@astrojs/language-server@2.16.2(prettier@3.7.4)(typescript@5.9.3)': + dependencies: + '@astrojs/compiler': 2.13.0 + '@astrojs/yaml2ts': 0.2.2 + '@jridgewell/sourcemap-codec': 1.5.5 + '@volar/kit': 2.4.27(typescript@5.9.3) + '@volar/language-core': 2.4.27 + '@volar/language-server': 2.4.27 + '@volar/language-service': 2.4.27 + fast-glob: 3.3.3 + muggle-string: 0.4.1 + volar-service-css: 0.0.67(@volar/language-service@2.4.27) + volar-service-emmet: 0.0.67(@volar/language-service@2.4.27) + volar-service-html: 0.0.67(@volar/language-service@2.4.27) + volar-service-prettier: 0.0.67(@volar/language-service@2.4.27)(prettier@3.7.4) + volar-service-typescript: 0.0.67(@volar/language-service@2.4.27) + volar-service-typescript-twoslash-queries: 0.0.67(@volar/language-service@2.4.27) + volar-service-yaml: 0.0.67(@volar/language-service@2.4.27) + vscode-html-languageservice: 5.6.1 + vscode-uri: 3.1.0 + optionalDependencies: + prettier: 3.7.4 + transitivePeerDependencies: + - typescript + + '@astrojs/markdown-remark@6.3.10': + dependencies: + '@astrojs/internal-helpers': 0.7.5 + '@astrojs/prism': 3.3.0 + github-slugger: 2.0.0 + hast-util-from-html: 2.0.3 + hast-util-to-text: 4.0.2 + import-meta-resolve: 4.2.0 + js-yaml: 4.1.1 + mdast-util-definitions: 6.0.0 + rehype-raw: 7.0.0 + rehype-stringify: 10.0.1 + remark-gfm: 4.0.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + remark-smartypants: 3.0.2 + shiki: 3.20.0 + smol-toml: 1.6.0 + unified: 11.0.5 + unist-util-remove-position: 5.0.0 + unist-util-visit: 5.0.0 + unist-util-visit-parents: 6.0.2 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/mdx@4.3.13(astro@5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2))': + dependencies: + '@astrojs/markdown-remark': 6.3.10 + '@mdx-js/mdx': 3.1.1 + acorn: 8.15.0 + astro: 5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2) + es-module-lexer: 1.7.0 + estree-util-visit: 2.0.0 + hast-util-to-html: 9.0.5 + piccolore: 0.1.3 + rehype-raw: 7.0.0 + remark-gfm: 4.0.1 + remark-smartypants: 3.0.2 + source-map: 0.7.6 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@astrojs/prism@3.3.0': + dependencies: + prismjs: 1.30.0 + + '@astrojs/react@4.4.2(@types/node@25.0.3)(@types/react-dom@19.2.3(@types/react@19.2.7))(@types/react@19.2.7)(jiti@2.6.1)(lightningcss@1.30.2)(react-dom@19.2.3(react@19.2.3))(react@19.2.3)(yaml@2.8.2)': + dependencies: + '@types/react': 19.2.7 + '@types/react-dom': 19.2.3(@types/react@19.2.7) + '@vitejs/plugin-react': 4.7.0(vite@6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) + react: 19.2.3 + react-dom: 19.2.3(react@19.2.3) + ultrahtml: 1.6.0 + vite: 6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + transitivePeerDependencies: + - '@types/node' + - jiti + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - yaml + + '@astrojs/telemetry@3.3.0': + dependencies: + ci-info: 4.3.1 + debug: 4.4.3 + dlv: 1.1.3 + dset: 3.1.4 + is-docker: 3.0.0 + is-wsl: 3.1.0 + which-pm-runs: 1.1.0 + transitivePeerDependencies: + - supports-color + + '@astrojs/yaml2ts@0.2.2': + dependencies: + yaml: 2.8.2 + + '@babel/code-frame@7.27.1': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.28.5': {} + + '@babel/core@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-compilation-targets': 7.27.2 + '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.5) + '@babel/helpers': 7.28.4 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.28.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.1.0 + + '@babel/helper-compilation-targets@7.27.2': + dependencies: + '@babel/compat-data': 7.28.5 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-module-imports@7.27.1': + dependencies: + '@babel/traverse': 7.28.5 + '@babel/types': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-module-imports': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.28.5 + transitivePeerDependencies: + - supports-color + + '@babel/helper-plugin-utils@7.27.1': {} + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.4': + dependencies: + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + + '@babel/parser@7.28.5': + dependencies: + '@babel/types': 7.28.5 + + '@babel/plugin-transform-react-jsx-self@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/plugin-transform-react-jsx-source@7.27.1(@babel/core@7.28.5)': + dependencies: + '@babel/core': 7.28.5 + '@babel/helper-plugin-utils': 7.27.1 + + '@babel/template@7.27.2': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@babel/traverse@7.28.5': + dependencies: + '@babel/code-frame': 7.27.1 + '@babel/generator': 7.28.5 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.28.5 + '@babel/template': 7.27.2 + '@babel/types': 7.28.5 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.28.5': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@biomejs/biome@2.3.11': + optionalDependencies: + '@biomejs/cli-darwin-arm64': 2.3.11 + '@biomejs/cli-darwin-x64': 2.3.11 + '@biomejs/cli-linux-arm64': 2.3.11 + '@biomejs/cli-linux-arm64-musl': 2.3.11 + '@biomejs/cli-linux-x64': 2.3.11 + '@biomejs/cli-linux-x64-musl': 2.3.11 + '@biomejs/cli-win32-arm64': 2.3.11 + '@biomejs/cli-win32-x64': 2.3.11 + + '@biomejs/cli-darwin-arm64@2.3.11': + optional: true + + '@biomejs/cli-darwin-x64@2.3.11': + optional: true + + '@biomejs/cli-linux-arm64-musl@2.3.11': + optional: true + + '@biomejs/cli-linux-arm64@2.3.11': + optional: true + + '@biomejs/cli-linux-x64-musl@2.3.11': + optional: true + + '@biomejs/cli-linux-x64@2.3.11': + optional: true + + '@biomejs/cli-win32-arm64@2.3.11': + optional: true + + '@biomejs/cli-win32-x64@2.3.11': + optional: true + + '@capsizecss/unpack@3.0.1': + dependencies: + fontkit: 2.0.4 + + '@emmetio/abbreviation@2.3.3': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-abbreviation@2.1.8': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/css-parser@0.4.1': + dependencies: + '@emmetio/stream-reader': 2.2.0 + '@emmetio/stream-reader-utils': 0.1.0 + + '@emmetio/html-matcher@1.3.0': + dependencies: + '@emmetio/scanner': 1.0.4 + + '@emmetio/scanner@1.0.4': {} + + '@emmetio/stream-reader-utils@0.1.0': {} + + '@emmetio/stream-reader@2.2.0': {} + + '@emnapi/runtime@1.8.1': + dependencies: + tslib: 2.8.1 + optional: true + + '@esbuild/aix-ppc64@0.25.12': + optional: true + + '@esbuild/aix-ppc64@0.27.2': + optional: true + + '@esbuild/android-arm64@0.25.12': + optional: true + + '@esbuild/android-arm64@0.27.2': + optional: true + + '@esbuild/android-arm@0.25.12': + optional: true + + '@esbuild/android-arm@0.27.2': + optional: true + + '@esbuild/android-x64@0.25.12': + optional: true + + '@esbuild/android-x64@0.27.2': + optional: true + + '@esbuild/darwin-arm64@0.25.12': + optional: true + + '@esbuild/darwin-arm64@0.27.2': + optional: true + + '@esbuild/darwin-x64@0.25.12': + optional: true + + '@esbuild/darwin-x64@0.27.2': + optional: true + + '@esbuild/freebsd-arm64@0.25.12': + optional: true + + '@esbuild/freebsd-arm64@0.27.2': + optional: true + + '@esbuild/freebsd-x64@0.25.12': + optional: true + + '@esbuild/freebsd-x64@0.27.2': + optional: true + + '@esbuild/linux-arm64@0.25.12': + optional: true + + '@esbuild/linux-arm64@0.27.2': + optional: true + + '@esbuild/linux-arm@0.25.12': + optional: true + + '@esbuild/linux-arm@0.27.2': + optional: true + + '@esbuild/linux-ia32@0.25.12': + optional: true + + '@esbuild/linux-ia32@0.27.2': + optional: true + + '@esbuild/linux-loong64@0.25.12': + optional: true + + '@esbuild/linux-loong64@0.27.2': + optional: true + + '@esbuild/linux-mips64el@0.25.12': + optional: true + + '@esbuild/linux-mips64el@0.27.2': + optional: true + + '@esbuild/linux-ppc64@0.25.12': + optional: true + + '@esbuild/linux-ppc64@0.27.2': + optional: true + + '@esbuild/linux-riscv64@0.25.12': + optional: true + + '@esbuild/linux-riscv64@0.27.2': + optional: true + + '@esbuild/linux-s390x@0.25.12': + optional: true + + '@esbuild/linux-s390x@0.27.2': + optional: true + + '@esbuild/linux-x64@0.25.12': + optional: true + + '@esbuild/linux-x64@0.27.2': + optional: true + + '@esbuild/netbsd-arm64@0.25.12': + optional: true + + '@esbuild/netbsd-arm64@0.27.2': + optional: true + + '@esbuild/netbsd-x64@0.25.12': + optional: true + + '@esbuild/netbsd-x64@0.27.2': + optional: true + + '@esbuild/openbsd-arm64@0.25.12': + optional: true + + '@esbuild/openbsd-arm64@0.27.2': + optional: true + + '@esbuild/openbsd-x64@0.25.12': + optional: true + + '@esbuild/openbsd-x64@0.27.2': + optional: true + + '@esbuild/openharmony-arm64@0.25.12': + optional: true + + '@esbuild/openharmony-arm64@0.27.2': + optional: true + + '@esbuild/sunos-x64@0.25.12': + optional: true + + '@esbuild/sunos-x64@0.27.2': + optional: true + + '@esbuild/win32-arm64@0.25.12': + optional: true + + '@esbuild/win32-arm64@0.27.2': + optional: true + + '@esbuild/win32-ia32@0.25.12': + optional: true + + '@esbuild/win32-ia32@0.27.2': + optional: true + + '@esbuild/win32-x64@0.25.12': + optional: true + + '@esbuild/win32-x64@0.27.2': + optional: true + + '@fontsource/fraunces@5.2.9': {} + + '@fontsource/ibm-plex-mono@5.2.7': {} + + '@fontsource/space-grotesk@5.2.10': {} + + '@img/colour@1.0.0': + optional: true + + '@img/sharp-darwin-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-arm64': 1.2.4 + optional: true + + '@img/sharp-darwin-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-darwin-x64': 1.2.4 + optional: true + + '@img/sharp-libvips-darwin-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-darwin-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-arm@1.2.4': + optional: true + + '@img/sharp-libvips-linux-ppc64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-riscv64@1.2.4': + optional: true + + '@img/sharp-libvips-linux-s390x@1.2.4': + optional: true + + '@img/sharp-libvips-linux-x64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-arm64@1.2.4': + optional: true + + '@img/sharp-libvips-linuxmusl-x64@1.2.4': + optional: true + + '@img/sharp-linux-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm64': 1.2.4 + optional: true + + '@img/sharp-linux-arm@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-arm': 1.2.4 + optional: true + + '@img/sharp-linux-ppc64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-ppc64': 1.2.4 + optional: true + + '@img/sharp-linux-riscv64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-riscv64': 1.2.4 + optional: true + + '@img/sharp-linux-s390x@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-s390x': 1.2.4 + optional: true + + '@img/sharp-linux-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linux-x64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-arm64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + optional: true + + '@img/sharp-linuxmusl-x64@0.34.5': + optionalDependencies: + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + optional: true + + '@img/sharp-wasm32@0.34.5': + dependencies: + '@emnapi/runtime': 1.8.1 + optional: true + + '@img/sharp-win32-arm64@0.34.5': + optional: true + + '@img/sharp-win32-ia32@0.34.5': + optional: true + + '@img/sharp-win32-x64@0.34.5': + optional: true + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@mdx-js/mdx@3.1.1': + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdx': 2.0.13 + acorn: 8.15.0 + collapse-white-space: 2.1.0 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-util-scope: 1.0.0 + estree-walker: 3.0.3 + hast-util-to-jsx-runtime: 2.3.6 + markdown-extensions: 2.0.0 + recma-build-jsx: 1.0.0 + recma-jsx: 1.0.1(acorn@8.15.0) + recma-stringify: 1.0.0 + rehype-recma: 1.0.0 + remark-mdx: 3.1.1 + remark-parse: 11.0.0 + remark-rehype: 11.1.2 + source-map: 0.7.6 + unified: 11.0.5 + unist-util-position-from-estree: 2.0.0 + unist-util-stringify-position: 4.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + transitivePeerDependencies: + - supports-color + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@oslojs/encoding@1.1.0': {} + + '@rolldown/pluginutils@1.0.0-beta.27': {} + + '@rollup/pluginutils@5.3.0(rollup@4.54.0)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.54.0 + + '@rollup/rollup-android-arm-eabi@4.54.0': + optional: true + + '@rollup/rollup-android-arm64@4.54.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.54.0': + optional: true + + '@rollup/rollup-darwin-x64@4.54.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.54.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.54.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.54.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.54.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.54.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.54.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.54.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.54.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.54.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.54.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.54.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.54.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.54.0': + optional: true + + '@sec-ant/readable-stream@0.4.1': {} + + '@shikijs/core@3.20.0': + dependencies: + '@shikijs/types': 3.20.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + + '@shikijs/engine-javascript@3.20.0': + dependencies: + '@shikijs/types': 3.20.0 + '@shikijs/vscode-textmate': 10.0.2 + oniguruma-to-es: 4.3.4 + + '@shikijs/engine-oniguruma@3.20.0': + dependencies: + '@shikijs/types': 3.20.0 + '@shikijs/vscode-textmate': 10.0.2 + + '@shikijs/langs@3.20.0': + dependencies: + '@shikijs/types': 3.20.0 + + '@shikijs/themes@3.20.0': + dependencies: + '@shikijs/types': 3.20.0 + + '@shikijs/types@3.20.0': + dependencies: + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@10.0.2': {} + + '@sindresorhus/merge-streams@4.0.0': {} + + '@standard-schema/spec@1.1.0': {} + + '@swc/helpers@0.5.18': + dependencies: + tslib: 2.8.1 + + '@tailwindcss/node@4.1.18': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.18.4 + jiti: 2.6.1 + lightningcss: 1.30.2 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.1.18 + + '@tailwindcss/oxide-android-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.1.18': + optional: true + + '@tailwindcss/oxide-darwin-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.1.18': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.1.18': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.1.18': + optional: true + + '@tailwindcss/oxide@4.1.18': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-arm64': 4.1.18 + '@tailwindcss/oxide-darwin-x64': 4.1.18 + '@tailwindcss/oxide-freebsd-x64': 4.1.18 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.1.18 + '@tailwindcss/oxide-linux-arm64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-arm64-musl': 4.1.18 + '@tailwindcss/oxide-linux-x64-gnu': 4.1.18 + '@tailwindcss/oxide-linux-x64-musl': 4.1.18 + '@tailwindcss/oxide-wasm32-wasi': 4.1.18 + '@tailwindcss/oxide-win32-arm64-msvc': 4.1.18 + '@tailwindcss/oxide-win32-x64-msvc': 4.1.18 + + '@tailwindcss/typography@0.5.19(tailwindcss@4.1.18)': + dependencies: + postcss-selector-parser: 6.0.10 + tailwindcss: 4.1.18 + + '@tailwindcss/vite@4.1.18(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': + dependencies: + '@tailwindcss/node': 4.1.18 + '@tailwindcss/oxide': 4.1.18 + tailwindcss: 4.1.18 + vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + + '@types/babel__core@7.20.5': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + '@types/babel__generator': 7.27.0 + '@types/babel__template': 7.4.4 + '@types/babel__traverse': 7.28.0 + + '@types/babel__generator@7.27.0': + dependencies: + '@babel/types': 7.28.5 + + '@types/babel__template@7.4.4': + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + + '@types/babel__traverse@7.28.0': + dependencies: + '@babel/types': 7.28.5 + + '@types/chai@5.2.3': + dependencies: + '@types/deep-eql': 4.0.2 + assertion-error: 2.0.1 + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/deep-eql@4.0.2': {} + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + + '@types/estree@1.0.8': {} + + '@types/fontkit@2.0.8': + dependencies: + '@types/node': 25.0.3 + + '@types/hast@3.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdast@4.0.4': + dependencies: + '@types/unist': 3.0.3 + + '@types/mdx@2.0.13': {} + + '@types/ms@2.1.0': {} + + '@types/nlcst@2.0.3': + dependencies: + '@types/unist': 3.0.3 + + '@types/node@25.0.3': + dependencies: + undici-types: 7.16.0 + + '@types/react-dom@19.2.3(@types/react@19.2.7)': + dependencies: + '@types/react': 19.2.7 + + '@types/react@19.2.7': + dependencies: + csstype: 3.2.3 + + '@types/unist@2.0.11': {} + + '@types/unist@3.0.3': {} + + '@ungap/structured-clone@1.3.0': {} + + '@vitejs/plugin-react@4.7.0(vite@6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': + dependencies: + '@babel/core': 7.28.5 + '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.5) + '@babel/plugin-transform-react-jsx-source': 7.27.1(@babel/core@7.28.5) + '@rolldown/pluginutils': 1.0.0-beta.27 + '@types/babel__core': 7.20.5 + react-refresh: 0.17.0 + vite: 6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + transitivePeerDependencies: + - supports-color + + '@vitest/expect@4.0.16': + dependencies: + '@standard-schema/spec': 1.1.0 + '@types/chai': 5.2.3 + '@vitest/spy': 4.0.16 + '@vitest/utils': 4.0.16 + chai: 6.2.2 + tinyrainbow: 3.0.3 + + '@vitest/mocker@4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2))': + dependencies: + '@vitest/spy': 4.0.16 + estree-walker: 3.0.3 + magic-string: 0.30.21 + optionalDependencies: + vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + + '@vitest/pretty-format@4.0.16': + dependencies: + tinyrainbow: 3.0.3 + + '@vitest/runner@4.0.16': + dependencies: + '@vitest/utils': 4.0.16 + pathe: 2.0.3 + + '@vitest/snapshot@4.0.16': + dependencies: + '@vitest/pretty-format': 4.0.16 + magic-string: 0.30.21 + pathe: 2.0.3 + + '@vitest/spy@4.0.16': {} + + '@vitest/utils@4.0.16': + dependencies: + '@vitest/pretty-format': 4.0.16 + tinyrainbow: 3.0.3 + + '@volar/kit@2.4.27(typescript@5.9.3)': + dependencies: + '@volar/language-service': 2.4.27 + '@volar/typescript': 2.4.27 + typesafe-path: 0.2.2 + typescript: 5.9.3 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/language-core@2.4.27': + dependencies: + '@volar/source-map': 2.4.27 + + '@volar/language-server@2.4.27': + dependencies: + '@volar/language-core': 2.4.27 + '@volar/language-service': 2.4.27 + '@volar/typescript': 2.4.27 + path-browserify: 1.0.1 + request-light: 0.7.0 + vscode-languageserver: 9.0.1 + vscode-languageserver-protocol: 3.17.5 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/language-service@2.4.27': + dependencies: + '@volar/language-core': 2.4.27 + vscode-languageserver-protocol: 3.17.5 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + + '@volar/source-map@2.4.27': {} + + '@volar/typescript@2.4.27': + dependencies: + '@volar/language-core': 2.4.27 + path-browserify: 1.0.1 + vscode-uri: 3.1.0 + + '@vscode/emmet-helper@2.11.0': + dependencies: + emmet: 2.4.11 + jsonc-parser: 2.3.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + '@vscode/l10n@0.0.18': {} + + acorn-jsx@5.3.2(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + + acorn@8.15.0: {} + + ajv-draft-04@1.0.0(ajv@8.17.1): + optionalDependencies: + ajv: 8.17.1 + + ajv@8.17.1: + dependencies: + fast-deep-equal: 3.1.3 + fast-uri: 3.1.0 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + + ansi-align@3.0.1: + dependencies: + string-width: 4.2.3 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + argparse@2.0.1: {} + + aria-query@5.3.2: {} + + array-iterate@2.0.1: {} + + assertion-error@2.0.1: {} + + astring@1.9.0: {} + + astro@5.16.6(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(rollup@4.54.0)(typescript@5.9.3)(yaml@2.8.2): + dependencies: + '@astrojs/compiler': 2.13.0 + '@astrojs/internal-helpers': 0.7.5 + '@astrojs/markdown-remark': 6.3.10 + '@astrojs/telemetry': 3.3.0 + '@capsizecss/unpack': 3.0.1 + '@oslojs/encoding': 1.1.0 + '@rollup/pluginutils': 5.3.0(rollup@4.54.0) + acorn: 8.15.0 + aria-query: 5.3.2 + axobject-query: 4.1.0 + boxen: 8.0.1 + ci-info: 4.3.1 + clsx: 2.1.1 + common-ancestor-path: 1.0.1 + cookie: 1.1.1 + cssesc: 3.0.0 + debug: 4.4.3 + deterministic-object-hash: 2.0.2 + devalue: 5.6.1 + diff: 5.2.0 + dlv: 1.1.3 + dset: 3.1.4 + es-module-lexer: 1.7.0 + esbuild: 0.25.12 + estree-walker: 3.0.3 + flattie: 1.1.1 + fontace: 0.3.1 + github-slugger: 2.0.0 + html-escaper: 3.0.3 + http-cache-semantics: 4.2.0 + import-meta-resolve: 4.2.0 + js-yaml: 4.1.1 + magic-string: 0.30.21 + magicast: 0.5.1 + mrmime: 2.0.1 + neotraverse: 0.6.18 + p-limit: 6.2.0 + p-queue: 8.1.1 + package-manager-detector: 1.6.0 + piccolore: 0.1.3 + picomatch: 4.0.3 + prompts: 2.4.2 + rehype: 13.0.2 + semver: 7.7.3 + shiki: 3.20.0 + smol-toml: 1.6.0 + svgo: 4.0.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tsconfck: 3.1.6(typescript@5.9.3) + ultrahtml: 1.6.0 + unifont: 0.6.0 + unist-util-visit: 5.0.0 + unstorage: 1.17.3 + vfile: 6.0.3 + vite: 6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + vitefu: 1.1.1(vite@6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) + xxhash-wasm: 1.1.0 + yargs-parser: 21.1.1 + yocto-spinner: 0.2.3 + zod: 3.25.76 + zod-to-json-schema: 3.25.1(zod@3.25.76) + zod-to-ts: 1.2.0(typescript@5.9.3)(zod@3.25.76) + optionalDependencies: + sharp: 0.34.5 + transitivePeerDependencies: + - '@azure/app-configuration' + - '@azure/cosmos' + - '@azure/data-tables' + - '@azure/identity' + - '@azure/keyvault-secrets' + - '@azure/storage-blob' + - '@capacitor/preferences' + - '@deno/kv' + - '@netlify/blobs' + - '@planetscale/database' + - '@types/node' + - '@upstash/redis' + - '@vercel/blob' + - '@vercel/functions' + - '@vercel/kv' + - aws4fetch + - db0 + - idb-keyval + - ioredis + - jiti + - less + - lightningcss + - rollup + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - tsx + - typescript + - uploadthing + - yaml + + axobject-query@4.1.0: {} + + bail@2.0.2: {} + + base-64@1.0.0: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.9.11: {} + + boolbase@1.0.0: {} + + boxen@8.0.1: + dependencies: + ansi-align: 3.0.1 + camelcase: 8.0.0 + chalk: 5.6.2 + cli-boxes: 3.0.0 + string-width: 7.2.0 + type-fest: 4.41.0 + widest-line: 5.0.0 + wrap-ansi: 9.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + brotli@1.3.3: + dependencies: + base64-js: 1.5.1 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.9.11 + caniuse-lite: 1.0.30001762 + electron-to-chromium: 1.5.267 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + camelcase@8.0.0: {} + + caniuse-lite@1.0.30001762: {} + + ccount@2.0.1: {} + + chai@6.2.2: {} + + chalk@5.6.2: {} + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chokidar@4.0.3: + dependencies: + readdirp: 4.1.2 + + ci-info@4.3.1: {} + + cli-boxes@3.0.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + clone@2.1.2: {} + + clsx@2.1.1: {} + + collapse-white-space@2.1.0: {} + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + comma-separated-tokens@2.0.3: {} + + commander@11.1.0: {} + + common-ancestor-path@1.0.1: {} + + convert-source-map@2.0.0: {} + + cookie-es@1.2.2: {} + + cookie@1.1.1: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + crossws@0.3.5: + dependencies: + uncrypto: 0.1.3 + + css-select@5.2.2: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 5.0.3 + domutils: 3.2.2 + nth-check: 2.1.1 + + css-tree@2.2.1: + dependencies: + mdn-data: 2.0.28 + source-map-js: 1.2.1 + + css-tree@3.1.0: + dependencies: + mdn-data: 2.12.2 + source-map-js: 1.2.1 + + css-what@6.2.2: {} + + cssesc@3.0.0: {} + + csso@5.0.5: + dependencies: + css-tree: 2.2.1 + + csstype@3.2.3: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.2.0: + dependencies: + character-entities: 2.0.2 + + defu@6.1.4: {} + + dequal@2.0.3: {} + + destr@2.0.5: {} + + detect-libc@2.1.2: {} + + deterministic-object-hash@2.0.2: + dependencies: + base-64: 1.0.0 + + devalue@5.6.1: {} + + devlop@1.1.0: + dependencies: + dequal: 2.0.3 + + dfa@1.2.0: {} + + diff@5.2.0: {} + + dlv@1.1.3: {} + + dom-serializer@2.0.0: + dependencies: + domelementtype: 2.3.0 + domhandler: 5.0.3 + entities: 4.5.0 + + domelementtype@2.3.0: {} + + domhandler@5.0.3: + dependencies: + domelementtype: 2.3.0 + + domutils@3.2.2: + dependencies: + dom-serializer: 2.0.0 + domelementtype: 2.3.0 + domhandler: 5.0.3 + + dset@3.1.4: {} + + electron-to-chromium@1.5.267: {} + + emmet@2.4.11: + dependencies: + '@emmetio/abbreviation': 2.3.3 + '@emmetio/css-abbreviation': 2.1.8 + + emoji-regex@10.6.0: {} + + emoji-regex@8.0.0: {} + + enhanced-resolve@5.18.4: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.0 + + entities@4.5.0: {} + + entities@6.0.1: {} + + es-module-lexer@1.7.0: {} + + esast-util-from-estree@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + unist-util-position-from-estree: 2.0.0 + + esast-util-from-js@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + acorn: 8.15.0 + esast-util-from-estree: 2.0.0 + vfile-message: 4.0.3 + + esbuild@0.25.12: + optionalDependencies: + '@esbuild/aix-ppc64': 0.25.12 + '@esbuild/android-arm': 0.25.12 + '@esbuild/android-arm64': 0.25.12 + '@esbuild/android-x64': 0.25.12 + '@esbuild/darwin-arm64': 0.25.12 + '@esbuild/darwin-x64': 0.25.12 + '@esbuild/freebsd-arm64': 0.25.12 + '@esbuild/freebsd-x64': 0.25.12 + '@esbuild/linux-arm': 0.25.12 + '@esbuild/linux-arm64': 0.25.12 + '@esbuild/linux-ia32': 0.25.12 + '@esbuild/linux-loong64': 0.25.12 + '@esbuild/linux-mips64el': 0.25.12 + '@esbuild/linux-ppc64': 0.25.12 + '@esbuild/linux-riscv64': 0.25.12 + '@esbuild/linux-s390x': 0.25.12 + '@esbuild/linux-x64': 0.25.12 + '@esbuild/netbsd-arm64': 0.25.12 + '@esbuild/netbsd-x64': 0.25.12 + '@esbuild/openbsd-arm64': 0.25.12 + '@esbuild/openbsd-x64': 0.25.12 + '@esbuild/openharmony-arm64': 0.25.12 + '@esbuild/sunos-x64': 0.25.12 + '@esbuild/win32-arm64': 0.25.12 + '@esbuild/win32-ia32': 0.25.12 + '@esbuild/win32-x64': 0.25.12 + + esbuild@0.27.2: + optionalDependencies: + '@esbuild/aix-ppc64': 0.27.2 + '@esbuild/android-arm': 0.27.2 + '@esbuild/android-arm64': 0.27.2 + '@esbuild/android-x64': 0.27.2 + '@esbuild/darwin-arm64': 0.27.2 + '@esbuild/darwin-x64': 0.27.2 + '@esbuild/freebsd-arm64': 0.27.2 + '@esbuild/freebsd-x64': 0.27.2 + '@esbuild/linux-arm': 0.27.2 + '@esbuild/linux-arm64': 0.27.2 + '@esbuild/linux-ia32': 0.27.2 + '@esbuild/linux-loong64': 0.27.2 + '@esbuild/linux-mips64el': 0.27.2 + '@esbuild/linux-ppc64': 0.27.2 + '@esbuild/linux-riscv64': 0.27.2 + '@esbuild/linux-s390x': 0.27.2 + '@esbuild/linux-x64': 0.27.2 + '@esbuild/netbsd-arm64': 0.27.2 + '@esbuild/netbsd-x64': 0.27.2 + '@esbuild/openbsd-arm64': 0.27.2 + '@esbuild/openbsd-x64': 0.27.2 + '@esbuild/openharmony-arm64': 0.27.2 + '@esbuild/sunos-x64': 0.27.2 + '@esbuild/win32-arm64': 0.27.2 + '@esbuild/win32-ia32': 0.27.2 + '@esbuild/win32-x64': 0.27.2 + + escalade@3.2.0: {} + + escape-string-regexp@5.0.0: {} + + estree-util-attach-comments@3.0.0: + dependencies: + '@types/estree': 1.0.8 + + estree-util-build-jsx@3.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@3.0.0: {} + + estree-util-scope@1.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + + estree-util-to-js@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-visit@2.0.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 3.0.3 + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + eventemitter3@5.0.1: {} + + execa@9.6.1: + dependencies: + '@sindresorhus/merge-streams': 4.0.0 + cross-spawn: 7.0.6 + figures: 6.1.0 + get-stream: 9.0.1 + human-signals: 8.0.1 + is-plain-obj: 4.1.0 + is-stream: 4.0.1 + npm-run-path: 6.0.0 + pretty-ms: 9.3.0 + signal-exit: 4.1.0 + strip-final-newline: 4.0.0 + yoctocolors: 2.1.2 + + expect-type@1.3.0: {} + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-uri@3.1.0: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + figures@6.1.0: + dependencies: + is-unicode-supported: 2.1.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + flattie@1.1.1: {} + + fontace@0.3.1: + dependencies: + '@types/fontkit': 2.0.8 + fontkit: 2.0.4 + + fontkit@2.0.4: + dependencies: + '@swc/helpers': 0.5.18 + brotli: 1.3.3 + clone: 2.1.2 + dfa: 1.2.0 + fast-deep-equal: 3.1.3 + restructure: 3.0.2 + tiny-inflate: 1.0.3 + unicode-properties: 1.4.1 + unicode-trie: 2.0.0 + + fsevents@2.3.3: + optional: true + + gensync@1.0.0-beta.2: {} + + get-caller-file@2.0.5: {} + + get-east-asian-width@1.4.0: {} + + get-stream@9.0.1: + dependencies: + '@sec-ant/readable-stream': 0.4.1 + is-stream: 4.0.1 + + github-slugger@2.0.0: {} + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + graceful-fs@4.2.11: {} + + h3@1.15.4: + dependencies: + cookie-es: 1.2.2 + crossws: 0.3.5 + defu: 6.1.4 + destr: 2.0.5 + iron-webcrypto: 1.2.1 + node-mock-http: 1.0.4 + radix3: 1.1.2 + ufo: 1.6.1 + uncrypto: 0.1.3 + + hast-util-from-html@2.0.3: + dependencies: + '@types/hast': 3.0.4 + devlop: 1.1.0 + hast-util-from-parse5: 8.0.3 + parse5: 7.3.0 + vfile: 6.0.3 + vfile-message: 4.0.3 + + hast-util-from-parse5@8.0.3: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + devlop: 1.1.0 + hastscript: 9.0.1 + property-information: 7.1.0 + vfile: 6.0.3 + vfile-location: 5.0.3 + web-namespaces: 2.0.1 + + hast-util-is-element@3.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-parse-selector@4.0.0: + dependencies: + '@types/hast': 3.0.4 + + hast-util-raw@9.1.0: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + '@ungap/structured-clone': 1.3.0 + hast-util-from-parse5: 8.0.3 + hast-util-to-parse5: 8.0.1 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + parse5: 7.3.0 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-estree@3.1.3: + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-attach-comments: 3.0.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-to-html@9.0.5: + dependencies: + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 + + hast-util-to-jsx-runtime@2.3.6: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + '@types/unist': 3.0.3 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + hast-util-whitespace: 3.0.0 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + style-to-js: 1.1.21 + unist-util-position: 5.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + hast-util-to-parse5@8.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + devlop: 1.1.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + web-namespaces: 2.0.1 + zwitch: 2.0.4 + + hast-util-to-text@4.0.2: + dependencies: + '@types/hast': 3.0.4 + '@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.4 + + hastscript@9.0.1: + dependencies: + '@types/hast': 3.0.4 + comma-separated-tokens: 2.0.3 + hast-util-parse-selector: 4.0.0 + property-information: 7.1.0 + space-separated-tokens: 2.0.2 + + html-escaper@3.0.3: {} + + html-void-elements@3.0.0: {} + + http-cache-semantics@4.2.0: {} + + human-signals@8.0.1: {} + + import-meta-resolve@4.2.0: {} + + inline-style-parser@0.2.7: {} + + 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@3.0.0: {} + + is-extglob@2.1.1: {} + + is-fullwidth-code-point@3.0.0: {} + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-hexadecimal@2.0.1: {} + + is-inside-container@1.0.0: + dependencies: + is-docker: 3.0.0 + + is-number@7.0.0: {} + + is-plain-obj@4.1.0: {} + + is-stream@4.0.1: {} + + is-unicode-supported@2.1.0: {} + + is-wsl@3.1.0: + dependencies: + is-inside-container: 1.0.0 + + isexe@2.0.0: {} + + jiti@2.6.1: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.1.0: {} + + json-schema-traverse@1.0.0: {} + + json5@2.2.3: {} + + jsonc-parser@2.3.1: {} + + jsonc-parser@3.3.1: {} + + kleur@3.0.3: {} + + kleur@4.1.5: {} + + lightningcss-android-arm64@1.30.2: + optional: true + + lightningcss-darwin-arm64@1.30.2: + optional: true + + lightningcss-darwin-x64@1.30.2: + optional: true + + lightningcss-freebsd-x64@1.30.2: + optional: true + + lightningcss-linux-arm-gnueabihf@1.30.2: + optional: true + + lightningcss-linux-arm64-gnu@1.30.2: + optional: true + + lightningcss-linux-arm64-musl@1.30.2: + optional: true + + lightningcss-linux-x64-gnu@1.30.2: + optional: true + + lightningcss-linux-x64-musl@1.30.2: + optional: true + + lightningcss-win32-arm64-msvc@1.30.2: + optional: true + + lightningcss-win32-x64-msvc@1.30.2: + optional: true + + lightningcss@1.30.2: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.30.2 + lightningcss-darwin-arm64: 1.30.2 + lightningcss-darwin-x64: 1.30.2 + lightningcss-freebsd-x64: 1.30.2 + lightningcss-linux-arm-gnueabihf: 1.30.2 + lightningcss-linux-arm64-gnu: 1.30.2 + lightningcss-linux-arm64-musl: 1.30.2 + lightningcss-linux-x64-gnu: 1.30.2 + lightningcss-linux-x64-musl: 1.30.2 + lightningcss-win32-arm64-msvc: 1.30.2 + lightningcss-win32-x64-msvc: 1.30.2 + + lodash@4.17.21: {} + + longest-streak@3.1.0: {} + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + magicast@0.5.1: + dependencies: + '@babel/parser': 7.28.5 + '@babel/types': 7.28.5 + source-map-js: 1.2.1 + + markdown-extensions@2.0.0: {} + + markdown-table@3.0.4: {} + + mdast-util-definitions@6.0.0: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + unist-util-visit: 5.0.0 + + mdast-util-find-and-replace@3.0.2: + dependencies: + '@types/mdast': 4.0.4 + escape-string-regexp: 5.0.0 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + mdast-util-from-markdown@2.0.2: + dependencies: + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + mdast-util-to-string: 4.0.0 + micromark: 4.0.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.2 + unist-util-stringify-position: 4.0.0 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-autolink-literal@2.0.1: + dependencies: + '@types/mdast': 4.0.4 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-find-and-replace: 3.0.2 + micromark-util-character: 2.1.1 + + mdast-util-gfm-footnote@2.1.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + micromark-util-normalize-identifier: 2.0.1 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-strikethrough@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-table@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + markdown-table: 3.0.4 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm-task-list-item@2.0.0: + dependencies: + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-gfm@3.1.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-gfm-autolink-literal: 2.0.1 + mdast-util-gfm-footnote: 2.1.0 + mdast-util-gfm-strikethrough: 2.0.0 + mdast-util-gfm-table: 2.0.0 + mdast-util-gfm-task-list-item: 2.0.0 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-expression@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@3.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + '@types/unist': 3.0.3 + ccount: 2.0.1 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-stringify-position: 4.0.0 + vfile-message: 4.0.3 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@3.0.0: + dependencies: + mdast-util-from-markdown: 2.0.2 + mdast-util-mdx-expression: 2.0.1 + mdast-util-mdx-jsx: 3.2.0 + mdast-util-mdxjs-esm: 2.0.1 + mdast-util-to-markdown: 2.1.2 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@2.0.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + devlop: 1.1.0 + mdast-util-from-markdown: 2.0.2 + mdast-util-to-markdown: 2.1.2 + 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.4 + '@types/mdast': 4.0.4 + '@ungap/structured-clone': 1.3.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.1 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.3 + + mdast-util-to-markdown@2.1.2: + 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-classify-character: 2.0.1 + micromark-util-decode-string: 2.0.1 + unist-util-visit: 5.0.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.12.2: {} + + merge2@1.4.1: {} + + micromark-core-commonmark@2.0.3: + dependencies: + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-factory-destination: 2.0.1 + micromark-factory-label: 2.0.1 + micromark-factory-space: 2.0.1 + 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-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.2 + + micromark-extension-gfm-autolink-literal@2.1.0: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-footnote@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-normalize-identifier: 2.0.1 + micromark-util-sanitize-uri: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-strikethrough@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-util-chunked: 2.0.1 + micromark-util-classify-character: 2.0.1 + micromark-util-resolve-all: 2.0.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-table@2.1.1: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm-tagfilter@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-gfm-task-list-item@2.1.0: + dependencies: + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-gfm@3.0.0: + dependencies: + micromark-extension-gfm-autolink-literal: 2.1.0 + micromark-extension-gfm-footnote: 2.1.0 + micromark-extension-gfm-strikethrough: 2.1.0 + micromark-extension-gfm-table: 2.1.1 + micromark-extension-gfm-tagfilter: 2.0.0 + micromark-extension-gfm-task-list-item: 2.1.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-expression@3.0.1: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-extension-mdx-jsx@3.0.2: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + estree-util-is-identifier-name: 3.0.0 + micromark-factory-mdx-expression: 2.0.3 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + micromark-extension-mdx-md@2.0.0: + dependencies: + micromark-util-types: 2.0.2 + + micromark-extension-mdxjs-esm@3.0.0: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-extension-mdxjs@3.0.0: + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + micromark-extension-mdx-expression: 3.0.1 + micromark-extension-mdx-jsx: 3.0.2 + micromark-extension-mdx-md: 2.0.0 + micromark-extension-mdxjs-esm: 3.0.0 + micromark-util-combine-extensions: 2.0.1 + micromark-util-types: 2.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.2 + + 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.2 + + micromark-factory-mdx-expression@2.0.3: + dependencies: + '@types/estree': 1.0.8 + devlop: 1.1.0 + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-events-to-acorn: 2.0.3 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + unist-util-position-from-estree: 2.0.0 + vfile-message: 4.0.3 + + micromark-factory-space@2.0.1: + dependencies: + micromark-util-character: 2.1.1 + micromark-util-types: 2.0.2 + + micromark-factory-title@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-factory-whitespace@2.0.1: + dependencies: + micromark-factory-space: 2.0.1 + micromark-util-character: 2.1.1 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + micromark-util-character@2.1.1: + dependencies: + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + + 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.2 + + micromark-util-combine-extensions@2.0.1: + dependencies: + micromark-util-chunked: 2.0.1 + micromark-util-types: 2.0.2 + + 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.2.0 + micromark-util-character: 2.1.1 + micromark-util-decode-numeric-character-reference: 2.0.2 + micromark-util-symbol: 2.0.1 + + micromark-util-encode@2.0.1: {} + + micromark-util-events-to-acorn@2.0.3: + dependencies: + '@types/estree': 1.0.8 + '@types/unist': 3.0.3 + devlop: 1.1.0 + estree-util-visit: 2.0.0 + micromark-util-symbol: 2.0.1 + micromark-util-types: 2.0.2 + vfile-message: 4.0.3 + + 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.2 + + 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.2 + + micromark-util-symbol@2.0.1: {} + + micromark-util-types@2.0.2: {} + + micromark@4.0.2: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.2.0 + devlop: 1.1.0 + micromark-core-commonmark: 2.0.3 + micromark-factory-space: 2.0.1 + 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-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.2 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mrmime@2.0.1: {} + + ms@2.1.3: {} + + muggle-string@0.4.1: {} + + nanoid@3.3.11: {} + + neotraverse@0.6.18: {} + + nlcst-to-string@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + + node-fetch-native@1.6.7: {} + + node-mock-http@1.0.4: {} + + node-releases@2.0.27: {} + + normalize-path@3.0.0: {} + + npm-check-updates@19.2.1: {} + + npm-run-path@6.0.0: + dependencies: + path-key: 4.0.0 + unicorn-magic: 0.3.0 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + obug@2.1.1: {} + + ofetch@1.5.1: + dependencies: + destr: 2.0.5 + node-fetch-native: 1.6.7 + ufo: 1.6.1 + + ohash@2.0.11: {} + + oniguruma-parser@0.12.1: {} + + oniguruma-to-es@4.3.4: + dependencies: + oniguruma-parser: 0.12.1 + regex: 6.1.0 + regex-recursion: 6.0.2 + + p-limit@6.2.0: + dependencies: + yocto-queue: 1.2.2 + + p-queue@8.1.1: + dependencies: + eventemitter3: 5.0.1 + p-timeout: 6.1.4 + + p-timeout@6.1.4: {} + + package-manager-detector@1.6.0: {} + + pako@0.2.9: {} + + 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.2.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse-latin@7.0.0: + dependencies: + '@types/nlcst': 2.0.3 + '@types/unist': 3.0.3 + nlcst-to-string: 4.0.0 + unist-util-modify-children: 4.0.0 + unist-util-visit-children: 3.0.0 + vfile: 6.0.3 + + parse-ms@4.0.0: {} + + parse5@7.3.0: + dependencies: + entities: 6.0.1 + + path-browserify@1.0.1: {} + + path-key@3.1.1: {} + + path-key@4.0.0: {} + + pathe@2.0.3: {} + + piccolore@0.1.3: {} + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + postcss-selector-parser@6.0.10: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss@8.5.6: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prettier@3.7.4: {} + + pretty-ms@9.3.0: + dependencies: + parse-ms: 4.0.0 + + prismjs@1.30.0: {} + + prompts@2.4.2: + dependencies: + kleur: 3.0.3 + sisteransi: 1.0.5 + + property-information@7.1.0: {} + + queue-microtask@1.2.3: {} + + radix3@1.1.2: {} + + react-dom@19.2.3(react@19.2.3): + dependencies: + react: 19.2.3 + scheduler: 0.27.0 + + react-refresh@0.17.0: {} + + react@19.2.3: {} + + readdirp@4.1.2: {} + + recma-build-jsx@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-build-jsx: 3.0.1 + vfile: 6.0.3 + + recma-jsx@1.0.1(acorn@8.15.0): + dependencies: + acorn: 8.15.0 + acorn-jsx: 5.3.2(acorn@8.15.0) + estree-util-to-js: 2.0.0 + recma-parse: 1.0.0 + recma-stringify: 1.0.0 + unified: 11.0.5 + + recma-parse@1.0.0: + dependencies: + '@types/estree': 1.0.8 + esast-util-from-js: 2.0.1 + unified: 11.0.5 + vfile: 6.0.3 + + recma-stringify@1.0.0: + dependencies: + '@types/estree': 1.0.8 + estree-util-to-js: 2.0.0 + unified: 11.0.5 + vfile: 6.0.3 + + regex-recursion@6.0.2: + dependencies: + regex-utilities: 2.3.0 + + regex-utilities@2.3.0: {} + + regex@6.1.0: + dependencies: + regex-utilities: 2.3.0 + + rehype-parse@9.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-from-html: 2.0.3 + unified: 11.0.5 + + rehype-raw@7.0.0: + dependencies: + '@types/hast': 3.0.4 + hast-util-raw: 9.1.0 + vfile: 6.0.3 + + rehype-recma@1.0.0: + dependencies: + '@types/estree': 1.0.8 + '@types/hast': 3.0.4 + hast-util-to-estree: 3.1.3 + transitivePeerDependencies: + - supports-color + + rehype-stringify@10.0.1: + dependencies: + '@types/hast': 3.0.4 + hast-util-to-html: 9.0.5 + unified: 11.0.5 + + rehype@13.0.2: + dependencies: + '@types/hast': 3.0.4 + rehype-parse: 9.0.1 + rehype-stringify: 10.0.1 + unified: 11.0.5 + + remark-gfm@4.0.1: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-gfm: 3.1.0 + micromark-extension-gfm: 3.0.0 + remark-parse: 11.0.0 + remark-stringify: 11.0.0 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-mdx@3.1.1: + dependencies: + mdast-util-mdx: 3.0.0 + micromark-extension-mdxjs: 3.0.0 + transitivePeerDependencies: + - supports-color + + remark-parse@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-from-markdown: 2.0.2 + micromark-util-types: 2.0.2 + unified: 11.0.5 + transitivePeerDependencies: + - supports-color + + remark-rehype@11.1.2: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.4 + mdast-util-to-hast: 13.2.1 + unified: 11.0.5 + vfile: 6.0.3 + + remark-smartypants@3.0.2: + dependencies: + retext: 9.0.0 + retext-smartypants: 6.2.0 + unified: 11.0.5 + unist-util-visit: 5.0.0 + + remark-stringify@11.0.0: + dependencies: + '@types/mdast': 4.0.4 + mdast-util-to-markdown: 2.1.2 + unified: 11.0.5 + + request-light@0.5.8: {} + + request-light@0.7.0: {} + + require-directory@2.1.1: {} + + require-from-string@2.0.2: {} + + restructure@3.0.2: {} + + retext-latin@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + parse-latin: 7.0.0 + unified: 11.0.5 + + retext-smartypants@6.2.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unist-util-visit: 5.0.0 + + retext-stringify@4.0.0: + dependencies: + '@types/nlcst': 2.0.3 + nlcst-to-string: 4.0.0 + unified: 11.0.5 + + retext@9.0.0: + dependencies: + '@types/nlcst': 2.0.3 + retext-latin: 4.0.0 + retext-stringify: 4.0.0 + unified: 11.0.5 + + reusify@1.1.0: {} + + rollup@4.54.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.54.0 + '@rollup/rollup-android-arm64': 4.54.0 + '@rollup/rollup-darwin-arm64': 4.54.0 + '@rollup/rollup-darwin-x64': 4.54.0 + '@rollup/rollup-freebsd-arm64': 4.54.0 + '@rollup/rollup-freebsd-x64': 4.54.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.54.0 + '@rollup/rollup-linux-arm-musleabihf': 4.54.0 + '@rollup/rollup-linux-arm64-gnu': 4.54.0 + '@rollup/rollup-linux-arm64-musl': 4.54.0 + '@rollup/rollup-linux-loong64-gnu': 4.54.0 + '@rollup/rollup-linux-ppc64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-gnu': 4.54.0 + '@rollup/rollup-linux-riscv64-musl': 4.54.0 + '@rollup/rollup-linux-s390x-gnu': 4.54.0 + '@rollup/rollup-linux-x64-gnu': 4.54.0 + '@rollup/rollup-linux-x64-musl': 4.54.0 + '@rollup/rollup-openharmony-arm64': 4.54.0 + '@rollup/rollup-win32-arm64-msvc': 4.54.0 + '@rollup/rollup-win32-ia32-msvc': 4.54.0 + '@rollup/rollup-win32-x64-gnu': 4.54.0 + '@rollup/rollup-win32-x64-msvc': 4.54.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + sax@1.4.3: {} + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.7.3: {} + + sharp@0.34.5: + dependencies: + '@img/colour': 1.0.0 + detect-libc: 2.1.2 + semver: 7.7.3 + optionalDependencies: + '@img/sharp-darwin-arm64': 0.34.5 + '@img/sharp-darwin-x64': 0.34.5 + '@img/sharp-libvips-darwin-arm64': 1.2.4 + '@img/sharp-libvips-darwin-x64': 1.2.4 + '@img/sharp-libvips-linux-arm': 1.2.4 + '@img/sharp-libvips-linux-arm64': 1.2.4 + '@img/sharp-libvips-linux-ppc64': 1.2.4 + '@img/sharp-libvips-linux-riscv64': 1.2.4 + '@img/sharp-libvips-linux-s390x': 1.2.4 + '@img/sharp-libvips-linux-x64': 1.2.4 + '@img/sharp-libvips-linuxmusl-arm64': 1.2.4 + '@img/sharp-libvips-linuxmusl-x64': 1.2.4 + '@img/sharp-linux-arm': 0.34.5 + '@img/sharp-linux-arm64': 0.34.5 + '@img/sharp-linux-ppc64': 0.34.5 + '@img/sharp-linux-riscv64': 0.34.5 + '@img/sharp-linux-s390x': 0.34.5 + '@img/sharp-linux-x64': 0.34.5 + '@img/sharp-linuxmusl-arm64': 0.34.5 + '@img/sharp-linuxmusl-x64': 0.34.5 + '@img/sharp-wasm32': 0.34.5 + '@img/sharp-win32-arm64': 0.34.5 + '@img/sharp-win32-ia32': 0.34.5 + '@img/sharp-win32-x64': 0.34.5 + optional: true + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + shiki@3.20.0: + dependencies: + '@shikijs/core': 3.20.0 + '@shikijs/engine-javascript': 3.20.0 + '@shikijs/engine-oniguruma': 3.20.0 + '@shikijs/langs': 3.20.0 + '@shikijs/themes': 3.20.0 + '@shikijs/types': 3.20.0 + '@shikijs/vscode-textmate': 10.0.2 + '@types/hast': 3.0.4 + + siginfo@2.0.0: {} + + signal-exit@4.1.0: {} + + sisteransi@1.0.5: {} + + smol-toml@1.6.0: {} + + source-map-js@1.2.1: {} + + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} + + stackback@0.0.2: {} + + std-env@3.10.0: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@7.2.0: + dependencies: + emoji-regex: 10.6.0 + get-east-asian-width: 1.4.0 + strip-ansi: 7.1.2 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.1.2: + dependencies: + ansi-regex: 6.2.2 + + strip-final-newline@4.0.0: {} + + style-to-js@1.1.21: + dependencies: + style-to-object: 1.0.14 + + style-to-object@1.0.14: + dependencies: + inline-style-parser: 0.2.7 + + svgo@4.0.0: + dependencies: + commander: 11.1.0 + css-select: 5.2.2 + css-tree: 3.1.0 + css-what: 6.2.2 + csso: 5.0.5 + picocolors: 1.1.1 + sax: 1.4.3 + + tailwindcss@4.1.18: {} + + tapable@2.3.0: {} + + tiny-inflate@1.0.3: {} + + tinybench@2.9.0: {} + + tinyexec@1.0.2: {} + + tinyglobby@0.2.15: + dependencies: + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + + tinyrainbow@3.0.3: {} + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + tsconfck@3.1.6(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + + tslib@2.8.1: {} + + type-fest@4.41.0: {} + + typesafe-path@0.2.2: {} + + typescript-auto-import-cache@0.3.6: + dependencies: + semver: 7.7.3 + + typescript@5.9.3: {} + + ufo@1.6.1: {} + + ultrahtml@1.6.0: {} + + uncrypto@0.1.3: {} + + undici-types@7.16.0: {} + + unicode-properties@1.4.1: + dependencies: + base64-js: 1.5.1 + unicode-trie: 2.0.0 + + unicode-trie@2.0.0: + dependencies: + pako: 0.2.9 + tiny-inflate: 1.0.3 + + unicorn-magic@0.3.0: {} + + unified@11.0.5: + dependencies: + '@types/unist': 3.0.3 + bail: 2.0.2 + devlop: 1.1.0 + extend: 3.0.2 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 6.0.3 + + unifont@0.6.0: + dependencies: + css-tree: 3.1.0 + ofetch: 1.5.1 + ohash: 2.0.11 + + 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 + + unist-util-modify-children@4.0.0: + dependencies: + '@types/unist': 3.0.3 + array-iterate: 2.0.1 + + unist-util-position-from-estree@2.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-remove-position@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-visit: 5.0.0 + + unist-util-stringify-position@4.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-children@3.0.0: + dependencies: + '@types/unist': 3.0.3 + + unist-util-visit-parents@6.0.2: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + + unist-util-visit@5.0.0: + dependencies: + '@types/unist': 3.0.3 + unist-util-is: 6.0.1 + unist-util-visit-parents: 6.0.2 + + unstorage@1.17.3: + dependencies: + anymatch: 3.1.3 + chokidar: 4.0.3 + destr: 2.0.5 + h3: 1.15.4 + lru-cache: 10.4.3 + node-fetch-native: 1.6.7 + ofetch: 1.5.1 + ufo: 1.6.1 + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + util-deprecate@1.0.2: {} + + vfile-location@5.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile: 6.0.3 + + vfile-message@4.0.3: + dependencies: + '@types/unist': 3.0.3 + unist-util-stringify-position: 4.0.0 + + vfile@6.0.3: + dependencies: + '@types/unist': 3.0.3 + vfile-message: 4.0.3 + + vite@6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2): + dependencies: + esbuild: 0.25.12 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.54.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 25.0.3 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + yaml: 2.8.2 + + vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2): + dependencies: + esbuild: 0.27.2 + fdir: 6.5.0(picomatch@4.0.3) + picomatch: 4.0.3 + postcss: 8.5.6 + rollup: 4.54.0 + tinyglobby: 0.2.15 + optionalDependencies: + '@types/node': 25.0.3 + fsevents: 2.3.3 + jiti: 2.6.1 + lightningcss: 1.30.2 + yaml: 2.8.2 + + vitefu@1.1.1(vite@6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)): + optionalDependencies: + vite: 6.4.1(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + + vitest@4.0.16(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2): + dependencies: + '@vitest/expect': 4.0.16 + '@vitest/mocker': 4.0.16(vite@7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2)) + '@vitest/pretty-format': 4.0.16 + '@vitest/runner': 4.0.16 + '@vitest/snapshot': 4.0.16 + '@vitest/spy': 4.0.16 + '@vitest/utils': 4.0.16 + es-module-lexer: 1.7.0 + expect-type: 1.3.0 + magic-string: 0.30.21 + obug: 2.1.1 + pathe: 2.0.3 + picomatch: 4.0.3 + std-env: 3.10.0 + tinybench: 2.9.0 + tinyexec: 1.0.2 + tinyglobby: 0.2.15 + tinyrainbow: 3.0.3 + vite: 7.3.0(@types/node@25.0.3)(jiti@2.6.1)(lightningcss@1.30.2)(yaml@2.8.2) + why-is-node-running: 2.3.0 + optionalDependencies: + '@types/node': 25.0.3 + transitivePeerDependencies: + - jiti + - less + - lightningcss + - msw + - sass + - sass-embedded + - stylus + - sugarss + - terser + - tsx + - yaml + + volar-service-css@0.0.67(@volar/language-service@2.4.27): + dependencies: + vscode-css-languageservice: 6.3.9 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.27 + + volar-service-emmet@0.0.67(@volar/language-service@2.4.27): + dependencies: + '@emmetio/css-parser': 0.4.1 + '@emmetio/html-matcher': 1.3.0 + '@vscode/emmet-helper': 2.11.0 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.27 + + volar-service-html@0.0.67(@volar/language-service@2.4.27): + dependencies: + vscode-html-languageservice: 5.6.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.27 + + volar-service-prettier@0.0.67(@volar/language-service@2.4.27)(prettier@3.7.4): + dependencies: + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.27 + prettier: 3.7.4 + + volar-service-typescript-twoslash-queries@0.0.67(@volar/language-service@2.4.27): + dependencies: + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.27 + + volar-service-typescript@0.0.67(@volar/language-service@2.4.27): + dependencies: + path-browserify: 1.0.1 + semver: 7.7.3 + typescript-auto-import-cache: 0.3.6 + vscode-languageserver-textdocument: 1.0.12 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + optionalDependencies: + '@volar/language-service': 2.4.27 + + volar-service-yaml@0.0.67(@volar/language-service@2.4.27): + dependencies: + vscode-uri: 3.1.0 + yaml-language-server: 1.19.2 + optionalDependencies: + '@volar/language-service': 2.4.27 + + vscode-css-languageservice@6.3.9: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-html-languageservice@5.6.1: + dependencies: + '@vscode/l10n': 0.0.18 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + + vscode-json-languageservice@4.1.8: + dependencies: + jsonc-parser: 3.3.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-nls: 5.2.0 + vscode-uri: 3.1.0 + + vscode-jsonrpc@8.2.0: {} + + vscode-languageserver-protocol@3.17.5: + dependencies: + vscode-jsonrpc: 8.2.0 + vscode-languageserver-types: 3.17.5 + + vscode-languageserver-textdocument@1.0.12: {} + + vscode-languageserver-types@3.17.5: {} + + vscode-languageserver@9.0.1: + dependencies: + vscode-languageserver-protocol: 3.17.5 + + vscode-nls@5.2.0: {} + + vscode-uri@3.1.0: {} + + web-namespaces@2.0.1: {} + + which-pm-runs@1.1.0: {} + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + why-is-node-running@2.3.0: + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + + widest-line@5.0.0: + dependencies: + string-width: 7.2.0 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@9.0.2: + dependencies: + ansi-styles: 6.2.3 + string-width: 7.2.0 + strip-ansi: 7.1.2 + + xxhash-wasm@1.1.0: {} + + y18n@5.0.8: {} + + yallist@3.1.1: {} + + yaml-language-server@1.19.2: + dependencies: + '@vscode/l10n': 0.0.18 + ajv: 8.17.1 + ajv-draft-04: 1.0.0(ajv@8.17.1) + lodash: 4.17.21 + prettier: 3.7.4 + request-light: 0.5.8 + vscode-json-languageservice: 4.1.8 + vscode-languageserver: 9.0.1 + vscode-languageserver-textdocument: 1.0.12 + vscode-languageserver-types: 3.17.5 + vscode-uri: 3.1.0 + yaml: 2.7.1 + + yaml@2.7.1: {} + + yaml@2.8.2: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yocto-queue@1.2.2: {} + + yocto-spinner@0.2.3: + dependencies: + yoctocolors: 2.1.2 + + yoctocolors@2.1.2: {} + + zod-to-json-schema@3.25.1(zod@3.25.76): + dependencies: + zod: 3.25.76 + + zod-to-ts@1.2.0(typescript@5.9.3)(zod@3.25.76): + dependencies: + typescript: 5.9.3 + zod: 3.25.76 + + zod@3.25.76: {} + + zod@4.3.5: {} + + zwitch@2.0.4: {} diff --git a/astro/pnpm-workspace.yaml b/astro/pnpm-workspace.yaml new file mode 100644 index 000000000..be4aa341f --- /dev/null +++ b/astro/pnpm-workspace.yaml @@ -0,0 +1,7 @@ +packages: + - packages/* + - apps/* + +onlyBuiltDependencies: + - esbuild + - sharp diff --git a/astro/python/pyautodoc_sidecar/pyproject.toml b/astro/python/pyautodoc_sidecar/pyproject.toml new file mode 100644 index 000000000..65f0be2d7 --- /dev/null +++ b/astro/python/pyautodoc_sidecar/pyproject.toml @@ -0,0 +1,15 @@ +[project] +name = "pyautodoc-sidecar" +version = "0.0.1" +description = "Python sidecar for the libtmux autodoc pipeline." +requires-python = ">=3.10" +dependencies = [ + "docutils", +] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + +[tool.hatch.build.targets.wheel] +packages = ["src/pyautodoc_sidecar"] diff --git a/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/__init__.py b/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/__init__.py new file mode 100644 index 000000000..c45580a53 --- /dev/null +++ b/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/__init__.py @@ -0,0 +1,3 @@ +"""Python sidecar package for autodoc extraction.""" + +from __future__ import annotations diff --git a/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/__main__.py b/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/__main__.py new file mode 100644 index 000000000..f8b99969c --- /dev/null +++ b/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/__main__.py @@ -0,0 +1,9 @@ +"""Entrypoint for running the sidecar as a module.""" + +from __future__ import annotations + +from pyautodoc_sidecar.cli import main + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/cli.py b/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/cli.py new file mode 100644 index 000000000..11ade05eb --- /dev/null +++ b/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/cli.py @@ -0,0 +1,122 @@ +"""Command-line interface for the autodoc sidecar.""" + +from __future__ import annotations + +import argparse +import json +import pathlib +import typing as t + +from pyautodoc_sidecar.introspect import introspect_module, introspect_package +from pyautodoc_sidecar.parse import scan_paths + +PROTOCOL_VERSION = 1 + + +def main(argv: list[str] | None = None) -> int: + """Run the sidecar CLI. + + Parameters + ---------- + argv : list[str] | None + CLI arguments (excluding the program name). + + Returns + ------- + int + Exit status code. + + Examples + -------- + >>> import contextlib + >>> import io + >>> import json + >>> import pathlib + >>> import tempfile + >>> from pyautodoc_sidecar.cli import main + >>> root = pathlib.Path(tempfile.mkdtemp()) + >>> module_path = root / 'demo.py' + >>> _ = module_path.write_text('value = 1') + >>> buffer = io.StringIO() + >>> with contextlib.redirect_stdout(buffer): + ... main(['parse-files', '--root', str(root), '--paths', str(module_path)]) + 0 + >>> payload = json.loads(buffer.getvalue()) + >>> payload['protocolVersion'] + 1 + """ + parser = argparse.ArgumentParser( + description="pyautodoc sidecar utilities for libtmux docs." + ) + subparsers = parser.add_subparsers(dest="command", required=True) + + parse_parser = subparsers.add_parser("parse-files", help="Parse Python files.") + parse_parser.add_argument("--root", required=True) + parse_parser.add_argument("--paths", nargs="+", required=True) + parse_parser.add_argument("--include-private", action="store_true") + + intro_module = subparsers.add_parser( + "introspect-module", help="Introspect a module." + ) + intro_module.add_argument("--module", required=True) + intro_module.add_argument("--root") + intro_module.add_argument("--include-private", action="store_true") + intro_module.add_argument( + "--annotation-format", + choices=["string", "value"], + default="string", + ) + intro_module.add_argument("--mock-import", action="append", default=[]) + intro_module.add_argument("--autodoc-mock", action="store_true") + + intro_package = subparsers.add_parser( + "introspect-package", help="Introspect a package." + ) + intro_package.add_argument("--package", required=True) + intro_package.add_argument("--root") + intro_package.add_argument("--include-private", action="store_true") + intro_package.add_argument( + "--annotation-format", + choices=["string", "value"], + default="string", + ) + intro_package.add_argument("--mock-import", action="append", default=[]) + intro_package.add_argument("--autodoc-mock", action="store_true") + + args = parser.parse_args(argv) + payload: dict[str, t.Any] + + if args.command == "parse-files": + root = pathlib.Path(args.root) + paths = [pathlib.Path(item) for item in args.paths] + modules = scan_paths(root, paths, args.include_private) + payload = {"protocolVersion": PROTOCOL_VERSION, "modules": modules} + elif args.command == "introspect-module": + root = pathlib.Path(args.root) if args.root else None + modules = [ + introspect_module( + args.module, + root=root, + include_private=args.include_private, + annotation_format=args.annotation_format, + mock_imports=args.mock_import, + autodoc_mock=args.autodoc_mock, + ) + ] + payload = {"protocolVersion": PROTOCOL_VERSION, "modules": modules} + elif args.command == "introspect-package": + root = pathlib.Path(args.root) if args.root else None + modules = introspect_package( + args.package, + root=root, + include_private=args.include_private, + annotation_format=args.annotation_format, + mock_imports=args.mock_import, + autodoc_mock=args.autodoc_mock, + ) + payload = {"protocolVersion": PROTOCOL_VERSION, "modules": modules} + else: + raise ValueError(f"Unsupported command: {args.command}") + + print(json.dumps(payload)) + return 0 diff --git a/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/introspect.py b/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/introspect.py new file mode 100644 index 000000000..217bc9ed6 --- /dev/null +++ b/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/introspect.py @@ -0,0 +1,769 @@ +"""Runtime introspection helpers for the autodoc sidecar.""" + +from __future__ import annotations + +import importlib +import inspect +import pathlib +import pkgutil +import sys +import types +import typing as t +from unittest import mock + + +def ensure_sys_path(root: pathlib.Path | None) -> None: + """Ensure the root path is available on sys.path. + + Parameters + ---------- + root : pathlib.Path | None + Root path to prepend to sys.path. + + Examples + -------- + >>> import pathlib + >>> from pyautodoc_sidecar.introspect import ensure_sys_path + >>> root = pathlib.Path('.').resolve() + >>> ensure_sys_path(root) + """ + if root is None: + return + + root_str = str(root) + if root_str not in sys.path: + sys.path.insert(0, root_str) + + +def make_mock_module(name: str) -> types.ModuleType: + """Create a mock module placeholder. + + Parameters + ---------- + name : str + Module name. + + Returns + ------- + types.ModuleType + Module placeholder with lazy attributes. + """ + module = types.ModuleType(name) + module.__dict__["__mock__"] = True + + def __getattr__(_attr: str) -> mock.MagicMock: + return mock.MagicMock() + + module.__getattr__ = __getattr__ # type: ignore[assignment] + module.__path__ = [] # type: ignore[attr-defined] + return module + + +def apply_mock_imports(names: t.Iterable[str]) -> None: + """Register mocked modules in sys.modules. + + Parameters + ---------- + names : Iterable[str] + Module names to mock. + """ + for name in names: + if not name: + continue + parts = name.split(".") + for idx in range(1, len(parts) + 1): + target = ".".join(parts[:idx]) + if target not in sys.modules: + sys.modules[target] = make_mock_module(target) + if idx > 1: + parent_name = ".".join(parts[: idx - 1]) + parent = sys.modules[parent_name] + child = sys.modules[target] + setattr(parent, parts[idx - 1], child) + + +def import_module_with_mocks( + module_name: str, + *, + root: pathlib.Path | None, + mock_imports: t.Sequence[str] | None, + autodoc_mock: bool, +) -> types.ModuleType: + """Import a module with optional mocking for missing deps.""" + ensure_sys_path(root) + if mock_imports: + apply_mock_imports(mock_imports) + + if not autodoc_mock: + return importlib.import_module(module_name) + + attempts = 0 + while True: + try: + return importlib.import_module(module_name) + except ModuleNotFoundError as exc: + missing = exc.name + if not missing or missing == module_name: + raise + apply_mock_imports([missing]) + attempts += 1 + if attempts > 25: + raise + + +def safe_repr(value: t.Any) -> str | None: + """Return a safe repr for a value. + + Parameters + ---------- + value : typing.Any + Value to represent. + + Returns + ------- + str | None + The repr value when available. + + Examples + -------- + >>> safe_repr(1) + '1' + >>> safe_repr(None) is not None + True + """ + try: + return repr(value) + except Exception: + return None + + +def format_annotation_text(annotation: t.Any) -> str | None: + """Format an annotation for display. + + Parameters + ---------- + annotation : typing.Any + Annotation to format. + + Returns + ------- + str | None + Text representation of the annotation. + + Examples + -------- + >>> from pyautodoc_sidecar.introspect import format_annotation_text + >>> format_annotation_text(int) + 'int' + """ + if annotation is inspect._empty: + return None + + try: + return inspect.formatannotation(annotation) + except Exception: + return safe_repr(annotation) + + +def render_docstring(docstring: str | None) -> dict[str, str | None]: + """Render a docstring into metadata fields. + + Parameters + ---------- + docstring : str | None + Raw docstring text. + + Returns + ------- + dict[str, str | None] + Docstring metadata fields. + + Examples + -------- + >>> result = render_docstring('Hello world') + >>> result['summary'] + 'Hello world' + """ + if not docstring: + return { + "docstringRaw": None, + "docstringFormat": "unknown", + "docstringHtml": None, + "summary": None, + } + + cleaned = inspect.cleandoc(docstring) + summary = cleaned.strip().splitlines()[0].strip() if cleaned.strip() else None + html: str | None = None + + try: + from docutils.core import publish_parts + + html = publish_parts(cleaned, writer_name="html")["html_body"] + except Exception: + html = None + + return { + "docstringRaw": cleaned, + "docstringFormat": "rst", + "docstringHtml": html, + "summary": summary, + } + + +def resolve_annotation_values( + obj: t.Any, annotation_format: str +) -> dict[str, t.Any]: + """Resolve evaluated annotation values for an object. + + Parameters + ---------- + obj : typing.Any + Object to introspect. + annotation_format : str + Annotation format selector. + + Returns + ------- + dict[str, typing.Any] + Mapping of annotation names to values. + + Examples + -------- + >>> def sample(x: int) -> str: + ... return str(x) + >>> mapping = resolve_annotation_values(sample, 'string') + >>> mapping == {} + True + """ + if annotation_format != "value": + return {} + + try: + import annotationlib + + return annotationlib.get_annotations(obj, eval_str=True) + except Exception: + try: + return inspect.get_annotations(obj, eval_str=True) + except Exception: + return {} + + +def parameter_kind(kind: inspect._ParameterKind) -> str: + """Map inspect parameter kinds to schema names. + + Parameters + ---------- + kind : inspect._ParameterKind + Parameter kind. + + Returns + ------- + str + Normalized kind string. + + Examples + -------- + >>> import inspect + >>> parameter_kind(inspect.Parameter.KEYWORD_ONLY) + 'keyword-only' + """ + mapping = { + inspect.Parameter.POSITIONAL_ONLY: "positional-only", + inspect.Parameter.POSITIONAL_OR_KEYWORD: "positional-or-keyword", + inspect.Parameter.VAR_POSITIONAL: "var-positional", + inspect.Parameter.KEYWORD_ONLY: "keyword-only", + inspect.Parameter.VAR_KEYWORD: "var-keyword", + } + return mapping.get(kind, "positional-or-keyword") + + +def introspect_parameters( + signature: inspect.Signature, annotation_values: dict[str, t.Any] +) -> list[dict[str, str | None]]: + """Introspect parameters for a callable. + + Parameters + ---------- + signature : inspect.Signature + Callable signature. + annotation_values : dict[str, typing.Any] + Evaluated annotation values when available. + + Returns + ------- + list[dict[str, str | None]] + Parameter records. + + Examples + -------- + >>> import inspect + >>> def sample(x: int, *args): return x + >>> params = introspect_parameters(inspect.signature(sample), {}) + >>> params[0]['name'] + 'x' + """ + params: list[dict[str, str | None]] = [] + for param in signature.parameters.values(): + annotation_value = annotation_values.get(param.name) + params.append( + { + "name": param.name, + "kind": parameter_kind(param.kind), + "default": None + if param.default is inspect._empty + else safe_repr(param.default), + "annotationText": format_annotation_text(param.annotation), + "annotationValue": safe_repr(annotation_value) + if annotation_value is not None + else None, + } + ) + return params + + +def introspect_function( + func: t.Any, + *, + kind: str, + annotation_format: str, +) -> dict[str, t.Any]: + """Introspect a function or method. + + Parameters + ---------- + func : typing.Any + Callable to introspect. + kind : str + "function" or "method". + annotation_format : str + Annotation format selector. + + Returns + ------- + dict[str, typing.Any] + Function record. + + Examples + -------- + >>> def greet(name: str) -> str: + ... return f"hi {name}" + >>> record = introspect_function(greet, kind='function', annotation_format='string') + >>> record['name'] + 'greet' + """ + doc_fields = render_docstring(getattr(func, "__doc__", None)) + signature = None + + try: + signature = inspect.signature(func) + except Exception: + signature = inspect.Signature() + + annotation_values = resolve_annotation_values(func, annotation_format) + params = introspect_parameters(signature, annotation_values) + + return_annotation_value = annotation_values.get("return") + returns = { + "annotationText": format_annotation_text(signature.return_annotation), + "annotationValue": safe_repr(return_annotation_value) + if return_annotation_value is not None + else None, + } + + return { + "kind": kind, + "name": func.__name__, + "qualname": f"{func.__module__}.{func.__qualname__}", + "module": func.__module__, + "signature": str(signature), + "parameters": params, + "returns": returns, + "isAsync": inspect.iscoroutinefunction(func), + "isPrivate": func.__name__.startswith("_"), + **doc_fields, + } + + +def introspect_variable( + name: str, + value: t.Any, + *, + module_name: str, + qualname_prefix: str, + annotation_values: dict[str, t.Any], +) -> dict[str, t.Any]: + """Introspect a variable. + + Parameters + ---------- + name : str + Variable name. + value : typing.Any + Variable value. + module_name : str + Module name for the variable. + qualname_prefix : str + Qualname prefix. + annotation_values : dict[str, typing.Any] + Annotation values mapping. + + Returns + ------- + dict[str, typing.Any] + Variable record. + + Examples + -------- + >>> record = introspect_variable('value', 1, module_name='demo', qualname_prefix='demo', annotation_values={}) + >>> record['name'] + 'value' + """ + annotation_value = annotation_values.get(name) + doc_fields = render_docstring(getattr(value, "__doc__", None)) + return { + "kind": "variable", + "name": name, + "qualname": f"{qualname_prefix}.{name}", + "module": module_name, + "value": safe_repr(value), + "annotationText": format_annotation_text(annotation_value), + "annotationValue": safe_repr(annotation_value) + if annotation_value is not None + else None, + "isPrivate": name.startswith("_"), + **doc_fields, + } + + +def introspect_class( + cls: type, + *, + include_private: bool, + annotation_format: str, +) -> dict[str, t.Any]: + """Introspect a class and its direct members. + + Parameters + ---------- + cls : type + Class to introspect. + include_private : bool + Whether to include private members. + annotation_format : str + Annotation format selector. + + Returns + ------- + dict[str, typing.Any] + Class record. + + Examples + -------- + >>> class Box: + ... value: int = 1 + >>> record = introspect_class(Box, include_private=False, annotation_format='string') + >>> record['name'] + 'Box' + """ + doc_fields = render_docstring(getattr(cls, "__doc__", None)) + bases = [f"{base.__module__}.{base.__qualname__}" for base in cls.__bases__] + annotation_values = resolve_annotation_values(cls, annotation_format) + + methods: list[dict[str, t.Any]] = [] + attributes: list[dict[str, t.Any]] = [] + + for name, value in cls.__dict__.items(): + if not include_private and name.startswith("_"): + continue + + if isinstance(value, staticmethod): + func = value.__func__ + methods.append( + introspect_function(func, kind="method", annotation_format=annotation_format) + ) + continue + + if isinstance(value, classmethod): + func = value.__func__ + methods.append( + introspect_function(func, kind="method", annotation_format=annotation_format) + ) + continue + + if inspect.isfunction(value): + methods.append( + introspect_function(value, kind="method", annotation_format=annotation_format) + ) + continue + + if inspect.isclass(value) or inspect.ismodule(value): + continue + + attributes.append( + introspect_variable( + name, + value, + module_name=cls.__module__, + qualname_prefix=f"{cls.__module__}.{cls.__qualname__}", + annotation_values=annotation_values, + ) + ) + + return { + "kind": "class", + "name": cls.__name__, + "qualname": f"{cls.__module__}.{cls.__qualname__}", + "module": cls.__module__, + "bases": bases, + "methods": methods, + "attributes": attributes, + "isPrivate": cls.__name__.startswith("_"), + **doc_fields, + } + + +def resolve_public_names( + module: t.Any, include_private: bool +) -> list[str] | None: + """Resolve the public names for a module. + + Parameters + ---------- + module : typing.Any + Module object. + include_private : bool + Whether to include private members. + + Returns + ------- + list[str] | None + Public names if defined. + + Examples + -------- + >>> import types + >>> mod = types.SimpleNamespace(__all__=['a', 'b']) + >>> resolve_public_names(mod, False) + ['a', 'b'] + """ + if include_private: + return None + + exports = getattr(module, "__all__", None) + if exports is None: + return None + + if isinstance(exports, (list, tuple, set)): + return [str(item) for item in exports] + + return None + + +def introspect_module( + module_name: str, + *, + root: pathlib.Path | None, + include_private: bool, + annotation_format: str, + mock_imports: t.Sequence[str] | None = None, + autodoc_mock: bool = False, +) -> dict[str, t.Any]: + """Introspect a module. + + Parameters + ---------- + module_name : str + Module name to import. + root : pathlib.Path | None + Root path to add to sys.path. + include_private : bool + Whether to include private members. + annotation_format : str + Annotation format selector. + mock_imports : Sequence[str] | None + Modules to mock during import. + autodoc_mock : bool + Mock missing imports encountered during import. + + Returns + ------- + dict[str, typing.Any] + Module record. + + Examples + -------- + >>> result = introspect_module('json', root=None, include_private=False, annotation_format='string') + >>> result['kind'] + 'module' + """ + module = import_module_with_mocks( + module_name, + root=root, + mock_imports=mock_imports, + autodoc_mock=autodoc_mock, + ) + doc_fields = render_docstring(getattr(module, "__doc__", None)) + exports = resolve_public_names(module, include_private) + + functions: list[dict[str, t.Any]] = [] + classes: list[dict[str, t.Any]] = [] + variables: list[dict[str, t.Any]] = [] + + module_annotations = resolve_annotation_values(module, annotation_format) + + for name, value in inspect.getmembers(module): + if not include_private and name.startswith("_"): + if exports is None or name not in exports: + continue + + if exports is not None and name not in exports: + continue + + if inspect.isclass(value): + classes.append( + introspect_class( + value, + include_private=include_private, + annotation_format=annotation_format, + ) + ) + continue + + if inspect.isfunction(value) or inspect.isbuiltin(value): + if getattr(value, "__module__", module_name) != module_name and exports is None: + continue + functions.append( + introspect_function(value, kind="function", annotation_format=annotation_format) + ) + continue + + if inspect.ismodule(value): + continue + + variables.append( + introspect_variable( + name, + value, + module_name=module_name, + qualname_prefix=module_name, + annotation_values=module_annotations, + ) + ) + + return { + "kind": "module", + "name": module_name.split(".")[-1], + "qualname": module_name, + "isPrivate": module_name.split(".")[-1].startswith("_"), + "classes": classes, + "functions": functions, + "variables": variables, + **doc_fields, + } + + +def walk_package_modules( + package_name: str, + *, + root: pathlib.Path | None, + mock_imports: t.Sequence[str] | None = None, + autodoc_mock: bool = False, +) -> list[str]: + """Walk modules within a package. + + Parameters + ---------- + package_name : str + Package to inspect. + root : pathlib.Path | None + Root path to add to sys.path. + mock_imports : Sequence[str] | None + Modules to mock during import. + autodoc_mock : bool + Mock missing imports encountered during import. + + Returns + ------- + list[str] + Module names including the package itself. + + Examples + -------- + >>> modules = walk_package_modules('json', root=None) + >>> modules[0] + 'json' + """ + package = import_module_with_mocks( + package_name, + root=root, + mock_imports=mock_imports, + autodoc_mock=autodoc_mock, + ) + names = [package.__name__] + + if not hasattr(package, "__path__"): + return names + + for module in pkgutil.walk_packages(package.__path__, prefix=f"{package.__name__}."): + names.append(module.name) + + return names + + +def introspect_package( + package_name: str, + *, + root: pathlib.Path | None, + include_private: bool, + annotation_format: str, + mock_imports: t.Sequence[str] | None = None, + autodoc_mock: bool = False, +) -> list[dict[str, t.Any]]: + """Introspect every module within a package. + + Parameters + ---------- + package_name : str + Package to inspect. + root : pathlib.Path | None + Root path to add to sys.path. + include_private : bool + Whether to include private members. + annotation_format : str + Annotation format selector. + mock_imports : Sequence[str] | None + Modules to mock during import. + autodoc_mock : bool + Mock missing imports encountered during import. + + Returns + ------- + list[dict[str, typing.Any]] + Module records. + + Examples + -------- + >>> modules = introspect_package('json', root=None, include_private=False, annotation_format='string') + >>> modules[0]['kind'] + 'module' + """ + modules = [] + for module_name in walk_package_modules( + package_name, + root=root, + mock_imports=mock_imports, + autodoc_mock=autodoc_mock, + ): + modules.append( + introspect_module( + module_name, + root=root, + include_private=include_private, + annotation_format=annotation_format, + mock_imports=mock_imports, + autodoc_mock=autodoc_mock, + ) + ) + return modules diff --git a/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/parse.py b/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/parse.py new file mode 100644 index 000000000..245aa5516 --- /dev/null +++ b/astro/python/pyautodoc_sidecar/src/pyautodoc_sidecar/parse.py @@ -0,0 +1,543 @@ +"""Python AST parser for the autodoc sidecar.""" + +from __future__ import annotations + +import ast +import pathlib +import typing as t + + +def node_to_source(node: ast.AST | None) -> str | None: + """Return source-like text for an AST node. + + Parameters + ---------- + node : ast.AST | None + Node to render as source. + + Returns + ------- + str | None + Source string for the node when available. + + Examples + -------- + >>> import ast + >>> tree = ast.parse('x = 1') + >>> node_to_source(tree.body[0].value) + '1' + """ + if node is None: + return None + + try: + return ast.unparse(node) + except Exception: + return None + + +def node_location(node: ast.AST) -> dict[str, int | None]: + """Return a location dictionary for a node. + + Parameters + ---------- + node : ast.AST + Node with location metadata. + + Returns + ------- + dict[str, int | None] + Location metadata for the node. + + Examples + -------- + >>> import ast + >>> tree = ast.parse('x = 1') + >>> node_location(tree.body[0])['lineno'] + 1 + """ + lineno = int(getattr(node, "lineno", 1) or 1) + col_offset = int(getattr(node, "col_offset", 0) or 0) + end_lineno = getattr(node, "end_lineno", None) + end_col_offset = getattr(node, "end_col_offset", None) + + return { + "lineno": lineno, + "colOffset": col_offset, + "endLineno": int(end_lineno) if end_lineno is not None else None, + "endColOffset": int(end_col_offset) if end_col_offset is not None else None, + } + + +def is_private(name: str) -> bool: + """Return True when a name should be considered private. + + Parameters + ---------- + name : str + Name to test. + + Returns + ------- + bool + True when the name is private. + + Examples + -------- + >>> is_private('_hidden') + True + >>> is_private('visible') + False + """ + return name.startswith("_") + + +def parse_parameters(args: ast.arguments) -> list[dict[str, str | None]]: + """Parse function parameters into serializable dictionaries. + + Parameters + ---------- + args : ast.arguments + Arguments structure from a function definition. + + Returns + ------- + list[dict[str, str | None]] + Parsed parameter records. + + Examples + -------- + >>> import ast + >>> tree = ast.parse('def f(a, b=1, *args, c=2, **kwargs): pass') + >>> params = parse_parameters(tree.body[0].args) + >>> params[0]['name'] + 'a' + """ + params: list[dict[str, str | None]] = [] + positional = list(args.posonlyargs) + list(args.args) + defaults = [None] * (len(positional) - len(args.defaults)) + list(args.defaults) + + for param, default in zip(positional, defaults, strict=True): + kind = ( + "positional-only" if param in args.posonlyargs else "positional-or-keyword" + ) + params.append( + { + "name": param.arg, + "kind": kind, + "annotation": node_to_source(param.annotation), + "default": node_to_source(default), + } + ) + + if args.vararg is not None: + params.append( + { + "name": args.vararg.arg, + "kind": "var-positional", + "annotation": node_to_source(args.vararg.annotation), + "default": None, + } + ) + + for kwonly, default in zip(args.kwonlyargs, args.kw_defaults, strict=True): + params.append( + { + "name": kwonly.arg, + "kind": "keyword-only", + "annotation": node_to_source(kwonly.annotation), + "default": node_to_source(default), + } + ) + + if args.kwarg is not None: + params.append( + { + "name": args.kwarg.arg, + "kind": "var-keyword", + "annotation": node_to_source(args.kwarg.annotation), + "default": None, + } + ) + + return params + + +def parse_function( + node: ast.FunctionDef | ast.AsyncFunctionDef, qualname: str +) -> dict[str, t.Any]: + r"""Parse a function or method definition. + + Parameters + ---------- + node : ast.FunctionDef | ast.AsyncFunctionDef + Function node to parse. + qualname : str + Fully qualified name for the function. + + Returns + ------- + dict[str, typing.Any] + Parsed function record. + + Examples + -------- + >>> import ast + >>> tree = ast.parse('def f(a: int) -> str:\n return "x"') + >>> parsed = parse_function(tree.body[0], 'mod.f') + >>> parsed['name'] + 'f' + """ + decorators = [ + value + for value in (node_to_source(item) for item in node.decorator_list) + if value + ] + + return { + "kind": "function", + "name": node.name, + "qualname": qualname, + "docstring": ast.get_docstring(node), + "decorators": decorators, + "parameters": parse_parameters(node.args), + "returns": node_to_source(node.returns), + "isAsync": isinstance(node, ast.AsyncFunctionDef), + "isPrivate": is_private(node.name), + "location": node_location(node), + } + + +def parse_assignment( + node: ast.Assign | ast.AnnAssign, qualname: str +) -> list[dict[str, t.Any]]: + """Parse assignment statements into variables. + + Parameters + ---------- + node : ast.Assign | ast.AnnAssign + Assignment node to parse. + qualname : str + Qualname prefix for the variable. + + Returns + ------- + list[dict[str, typing.Any]] + Parsed variable records. + + Examples + -------- + >>> import ast + >>> tree = ast.parse('x: int = 3') + >>> parsed = parse_assignment(tree.body[0], 'mod') + >>> parsed[0]['name'] + 'x' + """ + variables: list[dict[str, t.Any]] = [] + + if isinstance(node, ast.Assign): + targets = [item for item in node.targets if isinstance(item, ast.Name)] + annotation = None + value = node_to_source(node.value) + else: + targets = [node.target] if isinstance(node.target, ast.Name) else [] + annotation = node_to_source(node.annotation) + value = node_to_source(node.value) + + for target in targets: + if not isinstance(target, ast.Name): + continue + name = target.id + variables.append( + { + "kind": "variable", + "name": name, + "qualname": f"{qualname}.{name}" if qualname else name, + "annotation": annotation, + "value": value, + "docstring": None, + "isPrivate": is_private(name), + "location": node_location(node), + } + ) + + return variables + + +def parse_class(node: ast.ClassDef, qualname: str) -> dict[str, t.Any]: + r"""Parse a class definition with methods and attributes. + + Parameters + ---------- + node : ast.ClassDef + Class node to parse. + qualname : str + Qualname for the class. + + Returns + ------- + dict[str, typing.Any] + Parsed class record. + + Examples + -------- + >>> import ast + >>> tree = ast.parse('class Box:\n value: int = 1') + >>> parsed = parse_class(tree.body[0], 'mod.Box') + >>> parsed['name'] + 'Box' + """ + methods: list[dict[str, t.Any]] = [] + attributes: list[dict[str, t.Any]] = [] + + for item in node.body: + if isinstance(item, (ast.FunctionDef, ast.AsyncFunctionDef)): + methods.append(parse_function(item, f"{qualname}.{item.name}")) + elif isinstance(item, (ast.Assign, ast.AnnAssign)): + attributes.extend(parse_assignment(item, qualname)) + + decorators = [ + value + for value in (node_to_source(item) for item in node.decorator_list) + if value + ] + bases = [value for value in (node_to_source(item) for item in node.bases) if value] + + return { + "kind": "class", + "name": node.name, + "qualname": qualname, + "docstring": ast.get_docstring(node), + "bases": bases, + "decorators": decorators, + "methods": methods, + "attributes": attributes, + "isPrivate": is_private(node.name), + "location": node_location(node), + } + + +def parse_import(node: ast.Import | ast.ImportFrom) -> dict[str, t.Any]: + """Parse an import statement. + + Parameters + ---------- + node : ast.Import | ast.ImportFrom + Import node to parse. + + Returns + ------- + dict[str, typing.Any] + Parsed import record. + + Examples + -------- + >>> import ast + >>> tree = ast.parse('from pathlib import Path as P') + >>> parsed = parse_import(tree.body[0]) + >>> parsed['module'] + 'pathlib' + """ + names = [] + for alias in node.names: + if alias.asname: + names.append(f"{alias.name} as {alias.asname}") + else: + names.append(alias.name) + + module = getattr(node, "module", None) + if module is None and isinstance(node, ast.Import) and len(node.names) == 1: + module = node.names[0].name + + return { + "kind": "import", + "module": module, + "names": names, + "level": getattr(node, "level", None), + "location": node_location(node), + } + + +def parse_module( + path: pathlib.Path, module_name: str, include_private: bool +) -> dict[str, t.Any]: + """Parse a module into a serializable record. + + Parameters + ---------- + path : pathlib.Path + File path to the module. + module_name : str + Qualified module name. + include_private : bool + Whether to include private members. + + Returns + ------- + dict[str, typing.Any] + Parsed module record. + + Examples + -------- + >>> import pathlib + >>> import tempfile + >>> root = pathlib.Path(tempfile.mkdtemp()) + >>> module_path = root / 'sample.py' + >>> _ = module_path.write_text('value: int = 1') + >>> parsed = parse_module(module_path, 'sample', True) + >>> parsed['name'] + 'sample' + """ + source = path.read_text(encoding="utf-8") + tree = ast.parse(source) + items: list[dict[str, t.Any]] = [] + imports: list[dict[str, t.Any]] = [] + exports: list[str] = [] + + for node in tree.body: + if isinstance(node, (ast.Import, ast.ImportFrom)): + imports.append(parse_import(node)) + continue + + if isinstance(node, (ast.Assign, ast.AnnAssign)): + variables = parse_assignment(node, module_name) + for variable in variables: + if variable["name"] == "__all__" and variable["value"]: + try: + exports = list(ast.literal_eval(variable["value"])) + except Exception: + exports = [] + continue + + if not include_private and variable["isPrivate"]: + continue + + items.append(variable) + continue + + if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)): + if not include_private and is_private(node.name): + continue + items.append(parse_function(node, f"{module_name}.{node.name}")) + continue + + if isinstance(node, ast.ClassDef): + if not include_private and is_private(node.name): + continue + items.append(parse_class(node, f"{module_name}.{node.name}")) + + module_location = node_location(tree) + + return { + "kind": "module", + "name": module_name.split(".")[-1], + "qualname": module_name, + "path": str(path), + "docstring": ast.get_docstring(tree), + "items": items, + "imports": imports, + "exports": exports, + "isPackage": path.name == "__init__.py", + "location": module_location, + } + + +def walk_python_files( + root: pathlib.Path, paths: list[pathlib.Path] +) -> list[tuple[pathlib.Path, str]]: + """Collect Python files from a list of paths. + + Parameters + ---------- + root : pathlib.Path + Root path used for module names. + paths : list[pathlib.Path] + File or directory paths to scan. + + Returns + ------- + list[tuple[pathlib.Path, str]] + Tuples of file paths and module names. + + Examples + -------- + >>> import pathlib + >>> import tempfile + >>> root = pathlib.Path(tempfile.mkdtemp()) + >>> pkg = root / 'pkg' + >>> pkg.mkdir() + >>> _ = (pkg / '__init__.py').write_text('') + >>> files = walk_python_files(root, [pkg]) + >>> files[0][1] + 'pkg' + """ + ignore_dirs = { + ".git", + ".venv", + "__pycache__", + "node_modules", + "dist", + "build", + ".mypy_cache", + ".pytest_cache", + } + results: list[tuple[pathlib.Path, str]] = [] + + for entry in paths: + if entry.is_file() and entry.suffix == ".py": + file_paths = [entry] + elif entry.is_dir(): + file_paths = [ + item + for item in entry.rglob("*.py") + if not any(part in ignore_dirs for part in item.parts) + ] + else: + file_paths = [] + + for file_path in file_paths: + relative = file_path.relative_to(root) + module_parts = list(relative.with_suffix("").parts) + if module_parts[-1] == "__init__": + module_parts = module_parts[:-1] + module_name = ".".join(module_parts) + results.append((file_path, module_name)) + + return results + + +def scan_paths( + root: pathlib.Path, paths: list[pathlib.Path], include_private: bool +) -> list[dict[str, t.Any]]: + """Scan paths and return parsed modules. + + Parameters + ---------- + root : pathlib.Path + Root used for module name resolution. + paths : list[pathlib.Path] + File or directory paths to scan. + include_private : bool + Whether to include private members. + + Returns + ------- + list[dict[str, typing.Any]] + Parsed module records. + + Examples + -------- + >>> import pathlib + >>> import tempfile + >>> root = pathlib.Path(tempfile.mkdtemp()) + >>> module_path = root / 'demo.py' + >>> _ = module_path.write_text('value = 1') + >>> modules = scan_paths(root, [module_path], True) + >>> modules[0]['qualname'] + 'demo' + """ + modules: list[dict[str, t.Any]] = [] + for file_path, module_name in walk_python_files(root, paths): + modules.append(parse_module(file_path, module_name, include_private)) + return modules + diff --git a/astro/python/pyautodoc_sidecar/uv.lock b/astro/python/pyautodoc_sidecar/uv.lock new file mode 100644 index 000000000..c2e65f343 --- /dev/null +++ b/astro/python/pyautodoc_sidecar/uv.lock @@ -0,0 +1,23 @@ +version = 1 +revision = 3 +requires-python = ">=3.10" + +[[package]] +name = "docutils" +version = "0.22.4" +source = { registry = "https://pypi.org/simple" } +sdist = { url = "https://files.pythonhosted.org/packages/ae/b6/03bb70946330e88ffec97aefd3ea75ba575cb2e762061e0e62a213befee8/docutils-0.22.4.tar.gz", hash = "sha256:4db53b1fde9abecbb74d91230d32ab626d94f6badfc575d6db9194a49df29968", size = 2291750, upload-time = "2025-12-18T19:00:26.443Z" } +wheels = [ + { url = "https://files.pythonhosted.org/packages/02/10/5da547df7a391dcde17f59520a231527b8571e6f46fc8efb02ccb370ab12/docutils-0.22.4-py3-none-any.whl", hash = "sha256:d0013f540772d1420576855455d050a2180186c91c15779301ac2ccb3eeb68de", size = 633196, upload-time = "2025-12-18T19:00:18.077Z" }, +] + +[[package]] +name = "pyautodoc-sidecar" +version = "0.0.1" +source = { editable = "." } +dependencies = [ + { name = "docutils" }, +] + +[package.metadata] +requires-dist = [{ name = "docutils" }] diff --git a/astro/tsconfig.json b/astro/tsconfig.json new file mode 100644 index 000000000..e2ca5a297 --- /dev/null +++ b/astro/tsconfig.json @@ -0,0 +1,50 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "lib": ["DOM", "DOM.Iterable", "ESNext"], + "jsx": "react-jsx", + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true, + "allowJs": true, + "composite": true, + "declaration": true, + "declarationMap": true, + "baseUrl": ".", + "paths": { + "@libtmux/schema": ["./packages/schema/src"], + "@libtmux/py-bridge": ["./packages/py-bridge/src"], + "@libtmux/py-parse": ["./packages/py-parse/src"], + "@libtmux/py-introspect": ["./packages/py-introspect/src"], + "@libtmux/api-model": ["./packages/api-model/src"], + "@libtmux/core": ["./packages/core/src"], + "@libtmux/intersphinx": ["./packages/intersphinx/src"], + "@libtmux/rst-lite": ["./packages/rst-lite/src"], + "@libtmux/astro-autodoc": ["./packages/astro-autodoc/src"], + "@libtmux/astro-intersphinx": ["./packages/astro-intersphinx/src"], + "@libtmux/docs-site": ["./apps/docs"] + }, + "sourceMap": true, + "useDefineForClassFields": true + }, + "files": [], + "references": [ + { "path": "./packages/schema" }, + { "path": "./packages/py-bridge" }, + { "path": "./packages/py-parse" }, + { "path": "./packages/py-introspect" }, + { "path": "./packages/api-model" }, + { "path": "./packages/core" }, + { "path": "./packages/intersphinx" }, + { "path": "./packages/rst-lite" }, + { "path": "./packages/astro-autodoc" }, + { "path": "./packages/astro-intersphinx" }, + { "path": "./apps/docs" } + ] +} diff --git a/notes/architecture.md b/notes/architecture.md new file mode 100644 index 000000000..72e9c5a26 --- /dev/null +++ b/notes/architecture.md @@ -0,0 +1,143 @@ +# Architecture + +## Monorepo layout + +```text +libtmux/ +├- libtmux/ +├- docs/ +├- pyproject.toml +└- astro/ + ├- AGENTS.md + ├- pnpm-workspace.yaml + ├- package.json + ├- tsconfig.json + ├- biome.jsonc + ├- packages/ + │ ├- schema/ + │ ├- py-bridge/ + │ ├- py-parse/ + │ ├- py-introspect/ + │ ├- api-model/ + │ ├- core/ + │ ├- intersphinx/ + │ ├- astro-autodoc/ + │ └- astro-intersphinx/ + ├- python/ + │ └- pyautodoc_sidecar/ + └- apps/ + └- docs/ +``` + +## Dependency tiers + +- Tier 0: `schema` (leaf, shared Zod contracts) +- Tier 1: `py-bridge`, `py-parse`, `py-introspect`, `intersphinx` +- Tier 2: `api-model`, `core` +- Tier 3: `astro-autodoc`, `astro-intersphinx`, `apps/docs` + +Dependencies flow strictly downward. `core` orchestrates; Astro packages are UI +consumers. + +## Data flow + +```text +python files -> py-parse (ast) -> py-introspect (inspect) + \ / + -> api-model (merge, normalize) -> api-index.json -> astro UI +``` + +Current implementation note: `py-parse` is wired to the sidecar, and +`py-introspect` now returns runtime data (signatures, annotations, docstrings) +for module/package imports. + +## Schema firewall + +All cross-boundary payloads live in `schema` and are validated by Zod: + +- `protocolVersion: 1` +- structured errors (no thrown strings) +- source spans for deep links +- stable IDs and anchors +- docstring channels and annotation channels + +## Stable IDs and anchors + +Use Sphinx-shaped identifiers so local links and intersphinx share a key space: + +- `xrefKey = "{domain}:{role}:{fullName}"` +- examples: + - `py:module:libtmux.server` + - `py:class:libtmux.server.Server` + - `py:function:libtmux.server.Server.attach` + +Also store `module`, `qualname`, `name`, `kind`, `anchor`, and `source`. + +## Docstrings and annotations + +- Store `docstringRaw`, `docstringFormat`, `docstringHtml`, `summary`. +- Store `annotationText` always when available. +- Optionally store `annotationValue` when explicitly enabled. +- Default `annotationFormat=STRING` for deterministic output. + +## Python sidecar + +Package: `astro/python/pyautodoc_sidecar` + +CLI subcommands: + +1. `parse-files` + - parse with `ast` + - emit imports, defs, spans, module docstrings, `__all__` hints +2. `introspect-module` + - import module + - `inspect.signature`, member discovery, annotations +3. `introspect-package` + - package walk with allow/deny and includePrivate options + +Docstring rendering: + +- Convert RST to HTML via docutils +- Keep raw docstring always +- Allow TS to pre-rewrite xref roles into plain links when inventories exist + +Import safety: + +- `mockImports` and autodoc-style mocks +- timeouts and bounded output at the subprocess boundary + +## TS packages + +- `py-bridge`: runs `uv run` or `uvx`, handles timeouts and JSON parsing, enforces + Zod validation +- `py-parse`: wraps `parse-files` +- `py-introspect`: wraps `introspect-module` and `introspect-package` +- `api-model`: merges parse + introspect data, computes public surface and + stable ordering +- `core`: orchestration, cache invalidation, file watching +- `intersphinx`: parse `objects.inv` v2, provide resolver +- `astro-autodoc`: virtual module and components +- `astro-intersphinx`: inventory loader and URL resolver + +## Cache strategy + +- Cache `api-index.json` in `.cache/pyautodoc/` +- In dev, watch `libtmux/**/*.py` and inventories for invalidation +- Normalize output for snapshots (stable order, scrub absolute paths) + +## Astro integration + +Virtual modules: + +- `virtual:pyautodoc/api` +- `virtual:pyautodoc/intersphinx` + +Components: + +- ``, ``, ``, ``, + `` + +Routing: + +- `src/pages/api/[...slug].astro` can render all modules using + `getStaticPaths()` from the API index while keeping user-authored pages. diff --git a/notes/plan.md b/notes/plan.md new file mode 100644 index 000000000..509e633a0 --- /dev/null +++ b/notes/plan.md @@ -0,0 +1,82 @@ +# Plan + +## Target outcome + +A tiered PNPM monorepo under `astro/` that powers a rebuilt libtmux docs site in +Astro + Tailwind, and a reusable autodoc engine that matches Sphinx autodoc +where it matters (imports, signatures, docstrings, annotations, members, +inheritance, and intersphinx URL resolution). + +## Non-negotiables + +- Hybrid pipeline with schema firewall: TypeScript orchestrates, Python owns + semantics, Zod enforces contracts. +- Deterministic output, stable IDs, and cacheable artifacts. +- Intersphinx inventories supported end-to-end. +- Astro integration must be flexible: the user authors pages; components provide + the building blocks. + +## Phased build plan + +1. Scaffold `astro/` workspace and enforce house rules (Biome, Vitest workspace, + `update:all`, `ncu`, `type-check`, `biome`, `test`, `build`). +2. Define `schema` package: + - `protocolVersion` on all payloads + - stable IDs and anchors + - error shapes and source spans + - docstring + annotation channels +3. Implement Python sidecar package (`astro/python/pyautodoc_sidecar`): + - `parse-files` (ast-based static parse) + - `introspect-module` and `introspect-package` (inspect, annotations) + - RST docstring rendering via docutils + - import mocking, timeouts, and structured failures +4. Implement `py-bridge` to spawn Python via `uv run` and validate output with + Zod; support `uvx` as an optional tool mode. +5. Implement `py-parse` and `py-introspect` TS wrappers, with unit tests. +6. Build `api-model` with stable ordering and snapshot coverage; output a single + `api-index.json` artifact. +7. Build `core` orchestration (scan, parse, introspect, build index, cache). +8. Build `astro-autodoc` integration and components, including a virtual module + and API page generation helpers. +9. Build `intersphinx` parser and `astro-intersphinx` integration. +10. Add `rst-lite` docstring rendering to avoid Sphinx role warnings and keep + output deterministic. +11. Rebuild the docs site layout in Astro and migrate prose content + incrementally. + +## Status (January 4, 2026) + +- Workspace retiering completed (`packages/*`, `apps/docs`, `python/pyautodoc_sidecar`). +- `schema`, `py-bridge`, `py-parse`, `py-introspect`, and `core` scaffolds in place. +- Sidecar `parse-files` implemented via `ast` parsing; introspection implemented + for modules and packages (signatures, annotations, docstrings). +- `api-model` and `astro-autodoc` now consume introspection output for + signatures and docstring HTML. +- Docs app moved to `astro/apps/docs` with updated configs. +- Added `@libtmux/rst-lite` with snapshot tests for parsing/rendering docstring + RST subsets (headings, lists, directives, inline roles, code blocks). + +## Testing and QA + +- Vitest workspace split: + - unit tests never spawn Python + - one integration test exercises `uv run` for introspection + - snapshot normalization (stable ordering, scrub absolute paths) +- Astro integration gets a single build smoke test. +- CI runs `type-check`, `biome`, `test`, and `build`. + +## Risks and mitigations + +- Import side effects: default to `annotationFormat=STRING`, support mock + modules, and enforce timeouts. +- Non-determinism: stable ordering, stable IDs, and cache invalidation rules. +- Docstring formats: store raw, format, and rendered HTML; keep rendering in + Python. +- Type fidelity: preserve `annotationText`, allow optional `annotationValue`. + +## v1 vs v2 scope + +v1 delivers module/class/function/variable extraction, signatures, docstrings, +member lists, inheritance, stable deep links, and intersphinx resolution. +v2 can add inherited docstrings, source-order member ordering, richer type +rendering, and generating `objects.inv` for Astro outputs.