diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 984d281..e78d9b3 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,7 @@ version: 2 updates: - - package-ecosystem: "cargo" # See documentation for possible values - directory: "/" # Location of package manifests + - package-ecosystem: 'cargo' # See documentation for possible values + directory: '/' # Location of package manifests schedule: - interval: "weekly" + interval: 'weekly' diff --git a/.github/workflows/deploy-docs.yml b/.github/workflows/deploy-docs.yml new file mode 100644 index 0000000..9c40e95 --- /dev/null +++ b/.github/workflows/deploy-docs.yml @@ -0,0 +1,72 @@ +name: Deploy VitePress Docs + +on: + push: + branches: [main] + paths: + - 'docs/**' + - '.github/workflows/deploy-docs.yml' + workflow_dispatch: # Allow manual triggers + +# Sets permissions for GitHub Pages deployment +permissions: + contents: read + pages: write + id-token: write + +# Prevent concurrent deployments +concurrency: + group: pages + cancel-in-progress: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 # For lastUpdated feature + + - name: Retrieve Node.js version + id: node-version + run: | + if [ -f .tool-versions ]; then + echo "node-version=$(grep '^nodejs ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT + else + echo "node-version=20" >> $GITHUB_OUTPUT + fi + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ steps.node-version.outputs.node-version }} + cache: 'npm' + cache-dependency-path: docs/package-lock.json + + - name: Setup Pages + uses: actions/configure-pages@v4 + + - name: Install dependencies + working-directory: docs + run: npm ci + + - name: Build with VitePress + working-directory: docs + run: npm run build + + - name: Upload artifact + uses: actions/upload-pages-artifact@v3 + with: + path: docs/.vitepress/dist + + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + needs: build + runs-on: ubuntu-latest + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/.github/workflows/format.yml b/.github/workflows/format.yml index 1d7b067..8d3a2ec 100644 --- a/.github/workflows/format.yml +++ b/.github/workflows/format.yml @@ -1,4 +1,4 @@ -name: "Run Format" +name: 'Run Format' on: push: @@ -38,7 +38,7 @@ jobs: - name: Set up rust toolchain uses: actions-rust-lang/setup-rust-toolchain@v1 with: - components: "clippy, rustfmt" + components: 'clippy, rustfmt' toolchain: ${{ steps.rust-version.outputs.rust-version }} - name: Fetch dependencies (locked) @@ -49,3 +49,34 @@ jobs: - name: Run cargo clippy run: cargo clippy --workspace --all-targets --all-features -- -D warnings + + format-docs: + runs-on: ubuntu-latest + defaults: + run: + working-directory: docs + + steps: + - uses: actions/checkout@v4 + + - name: Retrieve Node.js version + id: node-version + working-directory: . + run: echo "node-version=$(grep '^nodejs ' .tool-versions | awk '{print $2}')" >> $GITHUB_OUTPUT + shell: bash + + - name: Use Node.js + uses: actions/setup-node@v4 + with: + node-version: ${{ steps.node-version.outputs.node-version }} + cache: 'npm' + cache-dependency-path: docs/package.json + + - name: Install dependencies + run: npm ci + + - name: Run ESLint + run: npm run lint + + - name: Run Prettier (check) + run: npm run format diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ecb2ca5..85ef9e9 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,4 @@ -name: "Run Tests" +name: 'Run Tests' on: push: diff --git a/.tool-versions b/.tool-versions index c5ad365..9934717 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,3 @@ -fasltly v13.3.0 +fasltly v13.0.0 +nodejs 24.12.0 rust 1.91.1 diff --git a/README.md b/README.md index e564d20..8431ad7 100644 --- a/README.md +++ b/README.md @@ -1,234 +1,41 @@ # EdgeZero -EdgeZero is a production-ready toolkit for writing an HTTP workload once and -deploying it across multiple edge providers. The core stays runtime-agnostic so -it compiles cleanly to WebAssembly targets (Fastly Compute@Edge, Cloudflare -Workers) and to native hosts (Axum/Tokio) without code changes. +Production-ready toolkit for portable edge HTTP workloads. Write once, deploy to Fastly Compute, Cloudflare Workers, or native Axum servers. -## Workspace layout - -- `crates/edgezero-core` - routing, request/response primitives, middleware chaining, extractor utilities built on `http`, `tower::Service`, and `matchit` 0.8. Handlers opt into extractors such as `Json`, `Path`, and `ValidatedQuery`, and the crate re-exports HTTP types (`Method`, `StatusCode`, `HeaderMap`, ...). -- `crates/edgezero-macros` - procedural macros that power `#[edgezero_core::action]` and related derive helpers. -- `crates/edgezero-adapter-fastly` - Fastly Compute@Edge bridge that maps Fastly request/response types into the shared model and exposes `FastlyRequestContext` plus logging conveniences. -- `crates/edgezero-adapter-cloudflare` - Cloudflare Workers bridge providing `CloudflareRequestContext` and logger bootstrap helpers. -- `crates/edgezero-adapter-axum` - host-side adapter that wraps `RouterService` in Axum/Tokio services for local development and native deployments (the dev server now runs through this crate). -- `crates/edgezero-cli` - CLI for project scaffolding, the local dev server, and adapter-aware build/deploy helpers. Ships with an optional demo dependency. -- `examples/app-demo` - reference application built on the shared router. Includes `crates/app-demo-core` (routes), `crates/app-demo-adapter-fastly` (Fastly binary + `fastly.toml`), `crates/app-demo-adapter-cloudflare` (Workers entrypoint + `wrangler.toml`), and `crates/app-demo-adapter-axum` (native dev server). - -## Quick start +## Quick Start ```bash -# Install the CLI (from this workspace or a published crate) +# Install the CLI cargo install --path crates/edgezero-cli -# Scaffold a new EdgeZero app targeting Fastly, Cloudflare, and Axum -edgezero new my-app --adapters fastly cloudflare axum +# Create a new project +edgezero new my-app cd my-app -# Start the local Axum-powered dev server +# Start the dev server edgezero dev -# Hit one of the generated endpoints -curl http://127.0.0.1:8787/echo/alice - -# Run your workspace tests -cargo test - -# Optional: explore the demo project bundled with this repo -cargo run -p edgezero-cli --features dev-example -- dev -``` - -To run the demo router from `examples/app-demo`, enable the optional -`dev-example` feature as shown above. Without that feature the CLI always loads -the manifest in your current project directory. - -The demo routes showcase core features: - -- `/` - static response to verify the app is running. -- `/echo/{name}` - path parameter extraction. -- `/headers` - direct `RequestContext` access. -- `/stream` - streaming bodies via `Body::stream`. -- `POST /echo` - JSON extractor + response builder. -- `/info` - shared state injection. - -Handlers stay concise by using the `#[action]` macro re-exported from `edgezero-core`: - -```rust -use edgezero_core::action; -use edgezero_core::extractor::Json; -use edgezero_core::response::Text; - -#[derive(serde::Deserialize)] -struct EchoBody { - name: String, -} - -#[action] -async fn echo_json(Json(body): Json) -> Text { - Text::new(format!("Hello, {}!", body.name)) -} +# Test it +curl http://127.0.0.1:8787/ ``` -## CLI tooling - -The CLI and adapters now expect an `edgezero.toml` manifest alongside your workspace. The manifest -describes the shared app (entry crate, optional middleware list, routes, adapters, and logging) -so Fastly/Cloudflare binaries, the CLI, and any local tooling all agree on configuration. The demo -manifest lives in `examples/app-demo/edgezero.toml`, and the scaffolder emits the same structure for -new projects. - -The `edgezero-cli` crate produces the `edgezero` binary (enabled by the `cli` feature). Run it locally with `cargo run -p edgezero-cli -- `. Key subcommands: - -- `edgezero new` - scaffolds a fully wired workspace (pass `--adapters` to pick your targets). -- `edgezero dev` - starts the local Axum HTTP server using the current project's manifest (pass `--features dev-example` when running from this repository to boot the demo app). -- `edgezero build --adapter fastly` - builds the Fastly example to `wasm32-wasip1` and copies the artifact into `edgezero/pkg/`. -- `edgezero serve --adapter fastly` - shells out to `fastly compute serve` after locating the Fastly manifest. -- `edgezero deploy --adapter fastly` - wraps `fastly compute deploy`. -- `edgezero build --adapter axum` - builds your native entrypoint (useful for containers or local integration tests). -- `edgezero serve --adapter axum` - runs the generated Axum entrypoint with `cargo run`, ideal for local or containerised development. - -Adapters register themselves lazily through their `edgezero_adapter_*::cli` modules. With the Axum adapter available you can generate, serve, and test a native host target without leaving the workspace. +## Documentation -## Logging +Full documentation is available at **[stackpop.github.io/edgezero](https://stackpop.github.io/edgezero/)**. -`edgezero-core` relies on the standard `log` facade. Platform adapters expose helper -functions so you can install the right backend when your app boots: +- [Getting Started](https://stackpop.github.io/edgezero/guide/getting-started) - Project setup and first steps +- [Architecture](https://stackpop.github.io/edgezero/guide/architecture) - How EdgeZero works +- [Configuration](https://stackpop.github.io/edgezero/guide/configuration) - `edgezero.toml` reference +- [CLI Reference](https://stackpop.github.io/edgezero/guide/cli-reference) - All CLI commands -- Fastly: call `edgezero_adapter_fastly::init_logger()` (wraps `log_fastly`). -- Cloudflare Workers: call `edgezero_adapter_cloudflare::init_logger()` (logs via - Workers `console_log!`). -- Axum/native: install a standard logger (`simple_logger`, `tracing-subscriber`, etc.) before booting `edgezero_adapter_axum::AxumDevServer`. -- Other targets: initialise a fallback logger such as `simple_logger` before building - your app. - -The helper `run_app::(include_str!("path/to/edgezero.toml"), req)` in -`edgezero-adapter-fastly` and the Cloudflare equivalent encapsulate manifest loading and logger -initialisation, so the adapters you scaffold only need to call the helper from `main`. - -## Provider builds - -Fastly Compute@Edge (requires the `fastly` CLI and the `wasm32-wasip1` target): - -```bash -rustup target add wasm32-wasip1 -cd edgezero/examples/app-demo -cargo build -p app-demo-adapter-fastly --target wasm32-wasip1 --features fastly -# or from the workspace root: -cargo run -p edgezero-cli -- build --adapter fastly -cargo run -p edgezero-cli -- serve --adapter fastly -``` - -The CLI helpers locate `fastly.toml`, build the Wasm artifact, place it in `edgezero/pkg/`, and run `fastly compute serve` from `examples/app-demo/crates/app-demo-adapter-fastly`. - -Cloudflare Workers (requires `wrangler` and the `wasm32-unknown-unknown` target): - -```bash -rustup target add wasm32-unknown-unknown -cd edgezero/examples/app-demo -cargo build -p app-demo-adapter-cloudflare --target wasm32-unknown-unknown -wrangler dev --config crates/app-demo-adapter-cloudflare/wrangler.toml -``` - -Axum / native hosts: - -```bash -# Build or run using the scaffolded commands -edgezero build --adapter axum -edgezero serve --adapter axum -``` +## Supported Platforms -The Fastly and Cloudflare adapters translate provider request/response shapes into the shared `edgezero-core` model and stash provider metadata in the request extensions so handlers can reach runtime-specific APIs. +| Platform | Target | Status | +| ------------------ | ------------------------ | ------ | +| Fastly Compute | `wasm32-wasip1` | Stable | +| Cloudflare Workers | `wasm32-unknown-unknown` | Stable | +| Axum (Native) | Host | Stable | -## Path parameters - -`edgezero-core` uses matchit 0.8+. Define parameters with `{name}` segments -(`/blog/{slug}`) and catch-alls with `{*rest}`. Legacy Axum-style `:name` -segments are intentionally unsupported. - -## Route listing - -Enable `RouterBuilder::enable_route_listing()` when you want a quick view of the -registered routes. It injects a JSON endpoint at -`DEFAULT_ROUTE_LISTING_PATH` (defaults to `/__edgezero/routes`) that returns an -array of `{ "method": "GET", "path": "/..." }` entries for every handler. -Use `RouterBuilder::enable_route_listing_at("/debug/routes")` to expose the -listing at a custom path. - -## Streaming responses - -Handlers can return `Body::stream` to yield response chunks progressively. The -router keeps the stream intact all the way to the adapters; the Fastly and -Cloudflare bridges buffer chunks sequentially while writing to the provider -runtime APIs, so long-lived streams remain compatible with Wasm targets. Responses compressed with gzip or brotli are transparently -decoded before they reach handlers so you can reformat or transform the -payload before sending it downstream. - -## Proxying upstream services - -`edgezero-core` ships with `ProxyRequest`, `ProxyResponse`, and the `ProxyService` wrapper so edge adapters can forward traffic while reusing the same handler logic: - -```rust -use edgezero_core::http::Uri; -use edgezero_core::proxy::{ProxyRequest, ProxyService}; - -let target: Uri = "https://example.com/api".parse()?; -let proxy_request = ProxyRequest::from_request(request, target); -let response = ProxyService::new(client).forward(proxy_request).await?; -``` - -Use the adapter-specific clients (`edgezero_adapter_fastly::FastlyProxyClient` and `edgezero_adapter_cloudflare::CloudflareProxyClient`) when compiling for those adapters, and swap in lightweight test clients during unit tests. The proxy helpers preserve streaming bodies and transparently decode gzip or brotli payloads before they reach your handler. - -## Testing - -Unit tests live next to the modules they exercise. Run the entire suite with -`cargo test`, or scope to a single crate via `cargo test -p edgezero-core`. -The adapter crates include lightweight host-side tests that validate context -insertion and URI parsing without needing the Wasm toolchains. - -### Coverage (host-only) - -Host-only coverage uses `cargo llvm-cov` and writes LCOV output under -`target/coverage/`. Install the tooling and run the helper script: - -```bash -cargo install cargo-llvm-cov -rustup component add llvm-tools-preview -./scripts/run_coverage.sh -``` - -If `genhtml` (from `lcov`) is available, the script also writes HTML output -under `target/coverage/-html/`. - -When `EDGEZERO_COVERAGE_PACKAGES` is unset, the script auto-discovers workspace -packages that expose a `lib` or `proc-macro` target. Set -`EDGEZERO_COVERAGE_INCLUDE_BINS=1` to include binary-only packages. To target -specific crates, set `EDGEZERO_COVERAGE_PACKAGES`: - -```bash -EDGEZERO_COVERAGE_PACKAGES="edgezero-core edgezero-adapter-axum" ./scripts/run_coverage.sh -``` - -### Wasm runners (Fastly / WASI) - -Some adapter tests target `wasm32-wasip1`; Cargo needs a Wasm runtime to execute -the generated binaries. Install the following tools before running those tests: - -- Wasmtime (executes `wasm32-wasip1` tests) - - macOS: `brew install wasmtime` - - Linux: `curl https://wasmtime.dev/install.sh -sSf | bash` - - Windows: follow for the MSI/winget installers -- Viceroy (Fastly’s local Compute@Edge simulator) - - macOS: `brew install fastly/tap/viceroy` - - Linux & Windows: download the latest release archive from - , extract it, and place the - binary on your `PATH` - -Tell Cargo to use Wasmtime when running the wasm tests: - -```bash -export CARGO_TARGET_WASM32_WASIP1_RUNNER="wasmtime run --dir=." -cargo test -p edgezero-adapter-fastly --features fastly --target wasm32-wasip1 -``` +## License -Streaming responses are covered in the `edgezero-core` router tests and in the -Fastly adapter tests to ensure chunked bodies make it to the provider output. +MIT diff --git a/TODO.md b/TODO.md index 7058458..b9131ca 100644 --- a/TODO.md +++ b/TODO.md @@ -88,6 +88,7 @@ High-level backlog and decisions to drive the next milestones. - [ ] Provider additions: prototype a third adapter (e.g. AWS Lambda@Edge or Vercel Edge Functions) using the stabilized adapter API to validate cross-provider abstractions. - [x] Manifest ergonomics: design an `edgezero.toml` schema that mirrors Spin’s manifest convenience (route triggers, env/secrets, build targets) while remaining provider-agnostic; update CLI scaffolding accordingly. (`crates/edgezero-cli/src/manifest.rs`, templates in `crates/edgezero-cli/src/templates/root/edgezero.toml.hbs`, doc `docs/manifest.md`, app-demo manifest `examples/app-demo/edgezero.toml`) - [ ] Tooling parity: extend `edgezero-cli` with template/plugin style commands (similar to Spin templates) to streamline new app scaffolds and provider-specific wiring. +- [ ] CLI parity backlog: add `edgezero --list-adapters`, standardize exit codes, search up for `edgezero.toml`, respect `RUST_LOG` for dev output, and bake in hot reload for `edgezero dev`. ## Open Design Questions (for later pickup) @@ -489,90 +490,113 @@ High-level backlog and decisions to drive the next milestones. - Assumptions: `RequestContext` parameters are owned values (no reference variants needed) and only one is expected per handler. - Outstanding: None; `cargo test` across the workspace passed after the macro test adjustment. -## Codex Plan (2026-01-09 - Review new tests + coverage) +## Codex Plan (2026-01-27 - Documentation Exposure Review) -- [x] Locate recently added/changed tests and note their intent and scope. -- [x] Review test quality for gaps, brittle assertions, or missing scenarios. -- [x] Evaluate whether coverage measurement is warranted and how to run it here. -- [x] Record findings and recommendations in the Review section with timestamp. +- [x] Diff `main` vs current branch to scope doc-related changes. +- [x] Review updated/new docs for redundancy, inconsistency, missing coverage, and verbosity. +- [x] Check overall documentation thoroughness and note gaps vs the framework surface area. +- [x] Compile review findings with file references and prioritized severity. -## Review (2026-01-09 05:47 UTC) +## Review (2026-01-27 00:20:19 UTC) -- Summary: Reviewed the recent test additions across `edgezero-core`, `edgezero-macros`, and adapter modules (notably the Axum dev-server/proxy integration tests) to assess coverage intent, reliability, and gaps; no code changes were made. -- Assumptions: The tests reviewed reflect the “recently added” scope the request referenced; wasm adapter coverage remains out-of-scope for host-only runs. -- Outstanding: Potential flakiness risks in Axum dev-server integration tests (fixed ports + sleep); coverage gaps noted in response for streaming preservation, invalid manifest cases, and macro output brittleness. +- Summary: Compared `main` against the `feature/vitepress` branch, audited the new VitePress guide set plus CLI/adapter docs, and assembled a prioritized findings list focused on inaccuracies, redundancy, missing coverage, and verbosity. +- Assumptions: Documentation review targets current source behavior (not proposed future APIs); adapter contract expectations should reflect existing Fastly/Cloudflare/Axum implementations unless explicitly labeled aspirational. +- Outstanding: None (review-only; no code changes applied). -## Codex Plan (2026-01-09 - Harden axum dev-server tests + add coverage helper) +## Review (2026-01-27 00:50:51 UTC) -- [x] Update Axum dev-server integration tests to use ephemeral ports and a readiness check instead of fixed ports + sleep. -- [x] Add a `cargo llvm-cov` helper (script and/or CI step) scoped to host-capable crates, documenting the command path. -- [x] Run `cargo test` after code changes. -- [x] Record changes, assumptions, and any remaining gaps in the Review section with timestamp. +- Summary: Aligned guide content with current EdgeZero APIs (App::build_app, adapter entrypoints, middleware signature, proxy clients), corrected routing/streaming/handlers/CLI details, and refreshed manifest/logging docs; added the missing CLI/dev features list to the roadmap. +- Assumptions: Docs now reflect current behavior for Fastly/Cloudflare/Axum adapters and the CLI; future features are captured explicitly in the roadmap rather than implied in guides. +- Outstanding: None (docs-only updates). -## Review (2026-01-09 05:57 UTC) +## Review (2026-01-27 01:02:05 UTC) -- Summary: Reworked the Axum dev-server integration tests to bind an ephemeral listener and retry requests instead of relying on fixed ports + sleeps, added a host-only coverage helper script (`scripts/run_coverage.sh`) with README instructions, and confirmed `cargo test` passes. -- Assumptions: Coverage runs remain host-only (defaulting to `edgezero-core`) unless `EDGEZERO_COVERAGE_PACKAGES` is provided; wasm-only crates are still excluded from coverage runs. -- Outstanding: None. +- Summary: Condensed the proxying guide into a single end-to-end example that uses adapter proxy handles, and added short logging-status callouts to the Fastly, Cloudflare, and Axum adapter docs. +- Assumptions: The proxy handle approach is the preferred public pattern; adapter logging notes should stay concise and match current defaults. +- Outstanding: None (docs-only updates). -## Codex Plan (2026-01-09 - Add HTML coverage output) +## Review (2026-01-27 01:05:18 UTC) -- [x] Update `scripts/run_coverage.sh` to emit an HTML report (LCOV -> HTML) when `genhtml` is available. -- [x] Document the HTML output location and fallback behavior in `README.md`. -- [x] Run `cargo test` after code changes. -- [x] Record changes, assumptions, and any remaining gaps in the Review section with timestamp. +- Summary: Added a dedicated `docs/guide/roadmap.md` page containing the current roadmap and design questions, and linked it in the VitePress sidebar. +- Assumptions: The roadmap page mirrors `TODO.md` and is a public-facing summary of ongoing planning work. +- Outstanding: None (docs-only updates). -## Review (2026-01-09 06:05 UTC) +## Review (2026-01-27 01:09:04 UTC) -- Summary: Updated `scripts/run_coverage.sh` to emit per-crate HTML reports when `genhtml` is available and documented the output location in `README.md`; `cargo test` remains green. -- Assumptions: HTML generation depends on `genhtml` (lcov); without it the script still writes LCOV files and prints install guidance. -- Outstanding: None. +- Summary: Expanded the roadmap page with doc/CLI alignment gaps found during review and added an explicit Spin support item. +- Assumptions: The new roadmap bullets are directional and do not imply implementation order. +- Outstanding: None (docs-only updates). -## Codex Plan (2026-01-09 - Auto-discover crates for coverage) +## Codex Plan (2026-01-27 - Post-Merge Docs Review) -- [x] Update `scripts/run_coverage.sh` to auto-discover workspace packages (e.g., via `cargo metadata`) when `EDGEZERO_COVERAGE_PACKAGES` is unset. -- [x] Ensure the script skips non-library targets that cannot be covered on host (and document behavior). -- [x] Update README coverage instructions accordingly. -- [x] Run `cargo test` after code changes. -- [x] Record changes, assumptions, and any remaining gaps in the Review section with timestamp. +- [x] Diff `main` vs current branch to scope doc-related changes post-merge. +- [x] Re-review docs for redundancy, inconsistencies, missing coverage, thoroughness, and verbosity. +- [x] Summarize findings with file references and prioritized issues. -## Review (2026-01-09 06:10 UTC) +## Review (2026-01-27 01:40:07 UTC) -- Summary: `scripts/run_coverage.sh` now auto-discovers workspace packages via `cargo metadata` when `EDGEZERO_COVERAGE_PACKAGES` is unset, defaulting to lib/proc-macro crates unless `EDGEZERO_COVERAGE_INCLUDE_BINS=1` is set; README coverage guidance updated and `cargo test` still passes. -- Assumptions: Auto-discovery relies on `python3` for JSON parsing; binary-only coverage remains opt-in to avoid host-incompatible targets. -- Outstanding: None. +- Summary: Re-ran the post-merge documentation audit against `main`, flagging the top doc/behavior mismatches (CLI flags, trigger adapter filtering, Fastly backend wording) plus missing manifest field coverage and CLI passthrough docs. +- Assumptions: Review targets current runtime behavior; undocumented manifest fields and adapter filters should be explicitly labeled as reserved/ignored if they remain unimplemented. +- Outstanding: None (review-only; no code changes applied). -## Review (2026-01-09 06:19 UTC) +## Codex Plan (2026-01-27 - Docs Fixes After Review) -- Summary: Fixed `scripts/run_coverage.sh` auto-discovery to pipe `cargo metadata` into `python -c` correctly, ran the script successfully, and re-ran `cargo test` after the change. -- Assumptions: `python3` remains available for JSON parsing; `genhtml` is installed for HTML output. -- Outstanding: None. +- [x] Align CLI usage docs with actual flags (`edgezero new`, passthrough args) and remove/replace unsupported examples. +- [x] Reconcile adapter/proxy docs (Cloudflare vs Fastly outbound request wording; adapter filtering behavior). +- [x] Document or mark reserved manifest fields (`app.version`, `app.kind`) and trim duplicate CLI install instructions. +- [x] Summarize changes with file references in TODO review entry. -## Review (2026-01-09 16:22 UTC) +## Review (2026-01-27 01:58:05 UTC) -- Summary: Added a buffered form test for `RequestContext::form::` to cover the remaining `context.rs` line gap; `cargo test -p edgezero-core` and `cargo llvm-cov -p edgezero-core --show-missing-lines` now report 100% line coverage for `edgezero-core`. -- Assumptions: Line coverage (not region coverage) is the target for this pass. -- Outstanding: None. +- Summary: Aligned CLI and configuration docs with actual flags and manifest behavior, clarified Fastly/Cloudflare outbound request wording, and trimmed duplicate CLI install guidance; captured test failures in `debug.md`. +- Assumptions: Manifest `version` and `kind` remain reserved fields until the parser is extended; adapter filters stay informational unless `app!` adds enforcement. +- Outstanding: `cargo test` failed in `edgezero-adapter-axum` integration tests due to local port bind permissions (see `debug.md`). -## Codex Plan (2026-01-09 - Edgezero-core 100% coverage push) +## Review (2026-01-27 02:13:01 UTC) -- [x] Re-run `cargo llvm-cov -p edgezero-core --no-deps --show-missing-lines` to confirm the current uncovered lines. -- [x] Add targeted unit tests in `edgezero-core` to cover uncovered branches (App, Body, Context, Error, Manifest, Middleware, Response, Router, Handler/Http if needed) and refactor test-only branches that artificially lower coverage. -- [x] Fix test compilation errors surfaced by the coverage run (expect_err Debug bounds in extractor/manifest tests; middleware type inference/lifetime issues). -- [x] Identify the remaining uncovered `edgezero-core` lines (currently in `context.rs`) and add minimal coverage for them. -- [x] Re-run coverage and `cargo test` to verify 100% line coverage for `edgezero-core`. -- [x] Document changes, assumptions, and any remaining gaps in the Review section with timestamp. +- Summary: Cleaned the roadmap to focus on forward-looking items, moved completed work into a "Completed (Recent)" section, removed the stale date header, and tightened Spin wording to "mirror" rather than "match." +- Assumptions: The completed items reflect the current docs/tests baseline; future roadmap entries should be updated as features land. +- Outstanding: `cargo test` failed in `edgezero-adapter-axum` integration tests due to local port bind permissions (see `debug.md`). -## Codex Plan (2026-01-09 - Investigate cargo test failure) +## Review (2026-01-27 02:17:12 UTC) -- [x] Run `cargo test` to capture the failing crate/test output. -- [x] Inspect the failing module(s) to identify the smallest fix or adjustment. -- [x] Apply the minimal code/test change needed to resolve the failure. -- [x] Re-run the relevant tests (`cargo test` and/or `cargo test -p `) to confirm. -- [x] Record the outcome, assumptions, and any remaining issues in the Review section with timestamp. +- Summary: Pruned the open design questions to only unresolved items, and moved resolved topics (platform focus, adapter mapping rules) into the completed section. +- Assumptions: Remaining open questions reflect genuine policy decisions rather than already-shipped behavior. +- Outstanding: `cargo test` failed in `edgezero-adapter-axum` integration tests due to local port bind permissions (see `debug.md`). -## Review (2026-01-09 16:28 UTC) +## Codex Plan (2026-01-27 - Roadmap Cleanup) -- Summary: Added `tempfile` as a dev-dependency for `edgezero-macros` so the included manifest tests compile, and `cargo test` now passes across the workspace. -- Assumptions: Keeping manifest tests in the macro crate is acceptable for now; we rely on workspace-level `tempfile` versioning. -- Outstanding: None. +- [x] Audit `docs/guide/roadmap.md` for redundancy, stale dating, and overlapping bullets. +- [x] Restructure the roadmap into clearer groupings (e.g., near-term vs later, or by theme) and tighten wording. +- [x] Keep Spin-related intent but ensure it reads as “mirrors” and does not overpromise; remove any duplicate items. +- [x] Update TODO review entry with summary and file references. + +## Codex Plan (2026-01-27 - Open Design Questions Cleanup) + +- [x] Review `docs/guide/roadmap.md` "Open Design Questions" and identify which are already resolved. +- [x] Remove resolved items or move them into "Completed (Recent)" with brief phrasing. +- [x] Keep only genuinely open questions, tightening wording to avoid duplication. +- [x] Update TODO review entry with summary and file references. + +## Codex Plan (2026-01-27 - Roadmap Doc Page) + +- [x] Add a dedicated roadmap page under `docs/guide/roadmap.md`. +- [x] Populate it with the roadmap content (including the CLI parity backlog list). +- [x] Wire the roadmap page into the VitePress sidebar/navigation. + +## Codex Plan (2026-01-27 - Roadmap Findings + Spin Support) + +- [x] Add the key doc/CLI gaps found during the review to the roadmap page. +- [x] Add an explicit roadmap item for Spin support (define scope at the doc level). + +## Codex Plan (2026-01-27 - Proxying Snippet + Adapter Logging Callout) + +- [x] Condense proxying guide into a single end-to-end example using adapter proxy handles. +- [x] Add a short logging status callout to the adapter docs (Axum/Cloudflare/Fastly). + +## Codex Plan (2026-01-27 - Docs Alignment + Roadmap Additions) + +- [x] Update guides to reflect current APIs (App::build_app, adapter entrypoints, middleware signature, proxy client usage). +- [x] Correct routing, streaming, handlers, and CLI reference docs to match current behavior. +- [x] Refresh configuration docs to align with manifest schema and loader APIs. +- [x] Add missing-feature backlog (list-adapters, exit codes, manifest search-up, RUST_LOG, hot reload) to the roadmap section. diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 0000000..57a09c3 --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,3 @@ +node_modules +.vitepress/dist +.vitepress/cache diff --git a/docs/.prettierignore b/docs/.prettierignore new file mode 100644 index 0000000..94aa6e0 --- /dev/null +++ b/docs/.prettierignore @@ -0,0 +1,3 @@ +.vitepress/cache +.vitepress/dist +node_modules diff --git a/docs/.prettierrc b/docs/.prettierrc new file mode 100644 index 0000000..364896f --- /dev/null +++ b/docs/.prettierrc @@ -0,0 +1,7 @@ +{ + "semi": false, + "singleQuote": true, + "tabWidth": 2, + "trailingComma": "es5", + "proseWrap": "preserve" +} diff --git a/docs/.vitepress/config.mts b/docs/.vitepress/config.mts new file mode 100644 index 0000000..2133fef --- /dev/null +++ b/docs/.vitepress/config.mts @@ -0,0 +1,67 @@ +import { defineConfig } from 'vitepress' + +// https://vitepress.dev/reference/site-config +export default defineConfig({ + title: 'EdgeZero', + description: 'Production-ready toolkit for portable edge HTTP workloads', + base: '/edgezero/', + themeConfig: { + // https://vitepress.dev/reference/default-theme-config + nav: [ + { text: 'Home', link: '/' }, + { text: 'Guide', link: '/guide/getting-started' }, + { text: 'Adapters', link: '/guide/adapters/overview' }, + { text: 'Reference', link: '/guide/configuration' }, + ], + + sidebar: [ + { + text: 'Introduction', + items: [ + { text: 'What is EdgeZero?', link: '/guide/what-is-edgezero' }, + { text: 'Getting Started', link: '/guide/getting-started' }, + { text: 'Architecture', link: '/guide/architecture' }, + { text: 'Roadmap', link: '/guide/roadmap' }, + ], + }, + { + text: 'Core Concepts', + items: [ + { text: 'Routing', link: '/guide/routing' }, + { text: 'Handlers & Extractors', link: '/guide/handlers' }, + { text: 'Middleware', link: '/guide/middleware' }, + { text: 'Streaming', link: '/guide/streaming' }, + { text: 'Proxying', link: '/guide/proxying' }, + ], + }, + { + text: 'Adapters', + items: [ + { text: 'Overview', link: '/guide/adapters/overview' }, + { text: 'Fastly Compute', link: '/guide/adapters/fastly' }, + { text: 'Cloudflare Workers', link: '/guide/adapters/cloudflare' }, + { text: 'Axum (Native)', link: '/guide/adapters/axum' }, + ], + }, + { + text: 'Reference', + items: [ + { + text: 'Configuration (edgezero.toml)', + link: '/guide/configuration', + }, + { text: 'CLI Reference', link: '/guide/cli-reference' }, + ], + }, + ], + + socialLinks: [ + { icon: 'github', link: 'https://github.com/stackpop/edgezero' }, + ], + + footer: { + message: 'Released under the MIT License.', + copyright: 'Copyright 2025-present Stackpop', + }, + }, +}) diff --git a/docs/adapter-contract.md b/docs/adapter-contract.md deleted file mode 100644 index da35df7..0000000 --- a/docs/adapter-contract.md +++ /dev/null @@ -1,132 +0,0 @@ -# Provider Adapter Contract - -This document defines the expectations EdgeZero places on provider adapters. The -current implementations (Fastly Compute@Edge and Cloudflare Workers) satisfy -these rules; new targets should follow the same contract so that the shared -`edgezero-core` services behave consistently. - -## Goals - -Adapters translate provider-specific HTTP primitives into the portable `App` -in `edgezero-core`. They must preserve request semantics, stream responses -without buffering, expose provider context, and offer a proxy bridge so handlers -can forward traffic without knowing which platform they are on. - -## Request conversion - -Each adapter exposes an `into_core_request` helper that accepts the provider's -request type and returns `edgezero_core::http::Request`. The conversion must: - -- Preserve the HTTP method exactly (`GET`, `POST`, etc.). -- Parse the full URI (path and query string) into an `http::Uri`. Reject invalid - URIs with `EdgeError::bad_request`. -- Copy all headers into the core request. Provider-specific headers may be - filtered only when they clash with the platform defaults (e.g. Fastly's host - override). -- Consume the request body into an `edgezero_core::body::Body`. If the provider offers - a streaming API, it should be exposed via `Body::Stream`; otherwise a single - buffered chunk is acceptable. -- Insert a provider context struct (e.g. `FastlyRequestContext`) into the request - extensions. The context should expose metadata such as client IP addresses or - environment handles so handlers can reach platform APIs. - -## Response conversion - -Adapters also expose `from_core_response` (or equivalent) to transform an -`edgezero_core::http::Response` into the provider response type. Implementations must: - -- Map HTTP status codes verbatim. -- Copy headers, respecting casing rules enforced by the provider. -- Preserve streaming bodies. `Body::Stream` should be written chunk-by-chunk to - the provider output without buffering the entire payload. -- Handle encoding helpers (`decode_gzip_stream`, `decode_brotli_stream`) where a - provider requires transparent decompression. - -## Dispatch helper - -Adapters surface a `dispatch` function that bridges from the provider event loop -into the shared router (`App::router().oneshot(...)`). It should: - -1. Convert the incoming provider request with `into_core_request`. -2. Await the router future. -3. Convert the resulting `Response` back into the provider type. -4. Map any `EdgeError` into the provider's error type so failures surface as - HTTP 5xx responses instead of panicking. - -This helper is what demo entrypoints and adapters call when wiring their -platform-specific main functions. - -## Proxy integration - -Adapters implement `edgezero_core::proxy::ProxyClient` so handlers can forward outbound -requests. The client must: - -- Accept a `ProxyRequest` created with `ProxyRequest::from_request`. -- Build and send an outbound provider request, reusing headers and streaming the - body without buffering. -- Convert the provider response into a `ProxyResponse`, again preserving - streaming behaviour and normalising encodings. -- Attach a diagnostic header (e.g. `x-edgezero-proxy`) identifying which adapter - forwarded the call. -- Surface provider errors as `EdgeError::internal` so applications can decide - how to respond. - -## Logging initialisation - -Each adapter exports an `init_logger` helper for platform-specific logging -backends (`log_fastly` or `console_log!`). Applications should call it before -building the router. New adapters should provide a comparable helper so apps -consistently opt into logging. - -## Contract tests - -To keep the contract enforceable, each adapter includes integration tests that -validate request/response conversions and the dispatch helper. Fastly and -Cloudflare now ship `tests/contract.rs` suites that exercise: - -- `into_core_request` for method, URI, header, body, and context propagation. -- `from_core_response` for status propagation and streamed body writes. -- `dispatch` for routed handlers, body passthrough, and streaming responses. - -Because the Fastly SDK links against the Compute@Edge host functions, the -contract tests compile only for `wasm32-wasip1`. Run them with: - -```bash -rustup target add wasm32-wasip1 # once per workstation -cargo test -p edgezero-adapter-fastly --features fastly --target wasm32-wasip1 --tests -``` - -Provide a Wasm runner (Wasmtime or Viceroy) via -`CARGO_TARGET_WASM32_WASIP1_RUNNER` if you want to execute the binaries instead -of running `--no-run`. - -Cloudflare's adapter relies on `wasm32-unknown-unknown`. The contract suite lives -in `crates/edgezero-adapter-cloudflare/tests/contract.rs` and uses -`wasm-bindgen-test` to run under the Workers runtime shims. Execute it with: - -```bash -rustup target add wasm32-unknown-unknown # once per workstation -cargo test -p edgezero-adapter-cloudflare --features cloudflare --target wasm32-unknown-unknown --tests -``` - -Configure a wasm-bindgen test runner via `wasm-bindgen-test-runner` (Node.js) or -`wasm-pack` depending on your tooling; the suite asserts the same request, -response, and streaming guarantees as the Fastly tests. - -## Onboarding new adapters - -When bringing up another adapter: - -1. Implement request/response conversion functions that follow the rules above. -2. Provide a context type exposing the adapter's metadata and insert it in - `into_core_request`. -3. Implement a `dispatch` wrapper plus logging helper. -4. Wire up a `ProxyClient` that streams bodies and normalises encodings. -5. Copy the contract test suite, swapping in the new adapter types. Ensure the - tests are gated to the target architecture if the adapter SDK does not - compile for native hosts. -6. Register the adapter with `edgezero-adapter::register_adapter` (typically in a - `cli` module using the `ctor` crate) so the CLI can discover it dynamically. - -Adapters that fulfil these steps can be dropped into the EdgeZero CLI without -requiring changes to application code. diff --git a/docs/eslint.config.js b/docs/eslint.config.js new file mode 100644 index 0000000..50481a7 --- /dev/null +++ b/docs/eslint.config.js @@ -0,0 +1,20 @@ +import js from '@eslint/js' +import tseslint from 'typescript-eslint' + +export default [ + { + ignores: ['.vitepress/cache/**', '.vitepress/dist/**', 'node_modules/**'], + }, + js.configs.recommended, + ...tseslint.configs.recommended, + { + files: ['**/*.ts', '**/*.mts'], + languageOptions: { + parserOptions: { + projectService: { + allowDefaultProject: ['.vitepress/*.mts'], + }, + }, + }, + }, +] diff --git a/docs/guide/adapters/axum.md b/docs/guide/adapters/axum.md new file mode 100644 index 0000000..82d1b65 --- /dev/null +++ b/docs/guide/adapters/axum.md @@ -0,0 +1,193 @@ +# Axum (Native) + +Run EdgeZero applications natively using Axum and Tokio for local development, testing, and container deployments. + +## Overview + +The Axum adapter provides: + +- **Local development server** - Fast iteration without Wasm compilation +- **Native testing** - Run tests with standard `cargo test` +- **Container deployments** - Deploy to any platform supporting native binaries + +## Project Setup + +When scaffolding with `edgezero new my-app`, the Axum adapter includes: + +``` +crates/my-app-adapter-axum/ +├── Cargo.toml +├── axum.toml +└── src/ + └── main.rs +``` + +### Entrypoint + +The Axum entrypoint wires the adapter: + +```rust +use edgezero_adapter_axum::AxumDevServer; +use edgezero_core::app::Hooks; +use my_app_core::App; + +fn main() { + let app = App::build_app(); + let router = app.router().clone(); + if let Err(err) = AxumDevServer::new(router).run() { + eprintln!("axum adapter failed: {err}"); + std::process::exit(1); + } +} +``` + +## Development Server + +The `edgezero dev` command uses the Axum adapter: + +```bash +edgezero dev +``` + +This starts a server at `http://127.0.0.1:8787` with standard logging to stdout. + +### Manual Start + +Run the Axum entrypoint directly: + +```bash +# Using the CLI +edgezero serve --adapter axum + +# Or directly with cargo +cargo run -p my-app-adapter-axum +``` + +## Building + +Build a native release binary: + +```bash +# Using the CLI +edgezero build --adapter axum + +# Or directly +cargo build -p my-app-adapter-axum --release +``` + +The binary is placed in `target/release/my-app-adapter-axum`. + +## Proxy Client + +The Axum adapter provides a native HTTP client for proxying: + +```rust +use edgezero_adapter_axum::AxumProxyClient; +use edgezero_core::proxy::ProxyService; + +let client = AxumProxyClient::default(); +let response = ProxyService::new(client).forward(request).await?; +``` + +This uses `reqwest` under the hood for outbound HTTP requests. + +## Logging + +The Axum adapter's `run_app` helper installs `simple_logger` and reads logging configuration +from `edgezero.toml` (level and `echo_stdout`). If you want a different logger, wire your own +entrypoint using `App::build_app()` and `AxumDevServer`. + +::: tip Logging status +`run_app` wires logging automatically; custom entrypoints should install a logger explicitly. +::: + +## Testing + +The Axum adapter enables standard Rust testing: + +```rust +#[cfg(test)] +mod tests { + use super::*; + use edgezero_core::app::Hooks; + use edgezero_core::http::{Request, Method}; + + #[tokio::test] + async fn test_handler() { + let app = App::build_app(); + let router = app.router(); + + let request = Request::builder() + .method(Method::GET) + .uri("/hello") + .body(Body::empty()) + .unwrap(); + + let response = router.oneshot(request).await.unwrap(); + assert_eq!(response.status(), 200); + } +} +``` + +Run tests: + +```bash +cargo test -p my-app-core +cargo test -p my-app-adapter-axum +``` + +## Container Deployment + +Build and deploy as a standard container: + +```dockerfile +FROM rust:1.75 as builder +WORKDIR /app +COPY . . +RUN cargo build -p my-app-adapter-axum --release + +FROM debian:bookworm-slim +COPY --from=builder /app/target/release/my-app-adapter-axum /usr/local/bin/ +EXPOSE 8787 +CMD ["my-app-adapter-axum"] +``` + +## Configuration + +Configure the Axum adapter in `edgezero.toml`. See [Configuration](/guide/configuration) for the full +manifest reference. + +The `axum.toml` file is used by the Axum CLI helper to locate the crate and display the port. +The runtime currently binds to `127.0.0.1:8787` regardless of the `axum.toml` port value. + +## Development Workflow + +A typical development workflow: + +1. **Start dev server**: `edgezero dev` +2. **Make changes** to handlers in `my-app-core` +3. **Test locally** with curl or browser +4. **Run tests**: `cargo test` +5. **Build for edge**: `edgezero build --adapter fastly` +6. **Deploy**: `edgezero deploy --adapter fastly` + +## Differences from Edge Adapters + +| Aspect | Axum | Fastly/Cloudflare | +| ----------- | -------------- | ----------------- | +| Compilation | Native | Wasm | +| Cold start | ~0ms | ~0ms (Wasm) | +| Memory | Unlimited | 128MB typical | +| Filesystem | Full access | Sandboxed | +| Network | Direct | Backend/fetch | +| Concurrency | Multi-threaded | Single-threaded | + +::: tip Development Parity +While Axum provides a convenient development environment, always test on actual edge platforms before deploying. Some edge-specific features (KV stores, geolocation) aren't available in the Axum adapter. +::: + +## Next Steps + +- Deploy to [Fastly Compute](/guide/adapters/fastly) for production +- Deploy to [Cloudflare Workers](/guide/adapters/cloudflare) as an alternative +- Explore [Configuration](/guide/configuration) for manifest options diff --git a/docs/guide/adapters/cloudflare.md b/docs/guide/adapters/cloudflare.md new file mode 100644 index 0000000..a71ce60 --- /dev/null +++ b/docs/guide/adapters/cloudflare.md @@ -0,0 +1,207 @@ +# Cloudflare Workers + +Deploy EdgeZero applications to Cloudflare Workers using WebAssembly. + +## Prerequisites + +- [Wrangler CLI](https://developers.cloudflare.com/workers/wrangler/install-and-update/) +- Rust `wasm32-unknown-unknown` target: `rustup target add wasm32-unknown-unknown` + +## Project Setup + +When scaffolding with `edgezero new my-app`, the Cloudflare adapter includes: + +``` +crates/my-app-adapter-cloudflare/ +├── Cargo.toml +├── wrangler.toml +└── src/ + └── main.rs +``` + +### wrangler.toml + +The Wrangler manifest configures your Worker: + +```toml +name = "my-app" +main = "build/worker/shim.mjs" +compatibility_date = "2024-01-01" + +[build] +command = "cargo build --release --target wasm32-unknown-unknown" +``` + +### Entrypoint + +The Cloudflare entrypoint wires the adapter: + +```rust +use edgezero_adapter_cloudflare::dispatch; +use edgezero_core::app::Hooks; +use my_app_core::App; +use worker::*; + +#[event(fetch)] +async fn main(req: Request, env: Env, ctx: Context) -> Result { + let app = App::build_app(); + dispatch(&app, req, env, ctx).await +} +``` + +## Building + +Build for Cloudflare's Wasm target: + +```bash +# Using the CLI +edgezero build --adapter cloudflare + +# Or directly with cargo +cargo build -p my-app-adapter-cloudflare --target wasm32-unknown-unknown --release +``` + +## Local Development + +Run locally with Wrangler: + +```bash +# Using the CLI +edgezero serve --adapter cloudflare + +# Or directly +wrangler dev --config crates/my-app-adapter-cloudflare/wrangler.toml +``` + +This starts a local server at `http://127.0.0.1:8787`. + +## Deployment + +Deploy to Cloudflare Workers: + +```bash +# Using the CLI +edgezero deploy --adapter cloudflare + +# Or directly +wrangler publish --config crates/my-app-adapter-cloudflare/wrangler.toml +``` + +## Fetch API + +Cloudflare Workers use the global `fetch` API for outbound requests: + +```rust +use edgezero_adapter_cloudflare::CloudflareProxyClient; +use edgezero_core::proxy::ProxyService; + +let client = CloudflareProxyClient; +let response = ProxyService::new(client).forward(request).await?; +``` + +Unlike Fastly, there's no backend configuration needed - Workers can fetch any URL directly. + +## Logging + +EdgeZero does not install a Cloudflare logger by default. Use your preferred logger (for example +`console_log` or your own `log` implementation), and view output in Wrangler or the Cloudflare +dashboard. + +::: tip Logging status +Cloudflare logging is opt-in; install a logger (such as `console_log`) in your entrypoint if you +need structured output. +::: + +## Context Access + +Access Cloudflare-specific APIs via the request context extensions: + +```rust +use edgezero_core::context::RequestContext; +use edgezero_adapter_cloudflare::CloudflareRequestContext; + +async fn handler(ctx: RequestContext) -> Result { + if let Some(cf_ctx) = CloudflareRequestContext::get(ctx.request()) { + // Access Cloudflare-specific data + let env = cf_ctx.env(); + let ctx = cf_ctx.ctx(); + // ... + } + + // ... +} +``` + +## Environment Variables & Secrets + +Define variables in `wrangler.toml`: + +```toml +[vars] +API_URL = "https://api.example.com" + +# Secrets are set via wrangler CLI +# wrangler secret put API_KEY +``` + +Access in handlers via the Cloudflare context or environment bindings. + +## KV Storage + +Use Cloudflare KV for edge storage: + +```toml +# wrangler.toml +[[kv_namespaces]] +binding = "MY_KV" +id = "abc123" +``` + +Access via the Cloudflare environment bindings in your handler. + +## Durable Objects + +For stateful edge computing, configure Durable Objects: + +```toml +# wrangler.toml +[durable_objects] +bindings = [ + { name = "COUNTER", class_name = "Counter" } +] +``` + +## Streaming + +Cloudflare Workers support streaming via `ReadableStream`. The adapter automatically converts `Body::stream` to Cloudflare's streaming format. + +See the [Streaming guide](/guide/streaming) for examples and patterns. + +## Testing + +Run contract tests for the Cloudflare adapter: + +```bash +cargo test -p edgezero-adapter-cloudflare --features cloudflare --target wasm32-unknown-unknown +``` + +Note: Some tests require `wasm-bindgen-test-runner` for execution. + +## Manifest Configuration + +Configure the Cloudflare adapter in `edgezero.toml`. See [Configuration](/guide/configuration) for the full manifest reference. + +## Comparison with Fastly + +| Feature | Cloudflare Workers | Fastly Compute | +| ----------------- | ------------------------ | ----------------------------------- | +| Target | `wasm32-unknown-unknown` | `wasm32-wasip1` | +| Outbound requests | Global `fetch` | Dynamic backends (derived from URI) | +| Storage | KV, Durable Objects, R2 | KV Store, Object Store | +| Logging | `console.log` | Log endpoints | +| CLI | Wrangler | Fastly CLI | + +## Next Steps + +- Learn about [Fastly Compute](/guide/adapters/fastly) as an alternative +- Explore the [Axum adapter](/guide/adapters/axum) for local development diff --git a/docs/guide/adapters/fastly.md b/docs/guide/adapters/fastly.md new file mode 100644 index 0000000..ead2d83 --- /dev/null +++ b/docs/guide/adapters/fastly.md @@ -0,0 +1,182 @@ +# Fastly Compute@Edge + +Deploy EdgeZero applications to Fastly's Compute@Edge platform using WebAssembly. + +## Prerequisites + +- [Fastly CLI](https://developer.fastly.com/learning/compute/#install-the-fastly-cli) +- Rust `wasm32-wasip1` target: `rustup target add wasm32-wasip1` +- [Wasmtime](https://wasmtime.dev/) or [Viceroy](https://github.com/fastly/Viceroy) for local testing + +## Project Setup + +When scaffolding with `edgezero new my-app`, the Fastly adapter includes: + +``` +crates/my-app-adapter-fastly/ +├── Cargo.toml +├── fastly.toml +└── src/ + └── main.rs +``` + +### fastly.toml + +The Fastly manifest configures your service: + +```toml +manifest_version = 2 +name = "my-app" +language = "rust" +authors = ["you@example.com"] + +[local_server] + [local_server.backends] + [local_server.backends."origin"] + url = "https://your-origin.example.com" +``` + +### Entrypoint + +The Fastly entrypoint wires the adapter: + +```rust +use edgezero_adapter_fastly::dispatch; +use edgezero_core::app::Hooks; +use my_app_core::App; + +#[fastly::main] +fn main(req: fastly::Request) -> Result { + let app = App::build_app(); + dispatch(&app, req) +} +``` + +## Building + +Build for Fastly's Wasm target: + +```bash +# Using the CLI +edgezero build --adapter fastly + +# Or directly with cargo +cargo build -p my-app-adapter-fastly --target wasm32-wasip1 --release +``` + +The compiled Wasm binary is placed in `target/wasm32-wasip1/release/`. + +## Local Development + +Run locally with Viceroy (Fastly's local simulator): + +```bash +# Using the CLI +edgezero serve --adapter fastly + +# Or directly +fastly compute serve --skip-build +``` + +This starts a local server at `http://127.0.0.1:7676`. + +## Deployment + +Deploy to Fastly Compute@Edge: + +```bash +# Using the CLI +edgezero deploy --adapter fastly + +# Or directly +fastly compute deploy +``` + +## Backends + +EdgeZero's Fastly proxy client uses **dynamic backends** derived from the target URI (host + scheme). +You do not need to predeclare backends in `fastly.toml` for EdgeZero proxying. + +```rust +use edgezero_adapter_fastly::FastlyProxyClient; +use edgezero_core::proxy::ProxyService; + +let client = FastlyProxyClient; +let response = ProxyService::new(client).forward(request).await?; +``` + +## Logging + +Fastly uses endpoint-based logging. Configure logging in `edgezero.toml`: + +```toml +[adapters.fastly.logging] +endpoint = "stdout" +level = "info" +echo_stdout = true +``` + +To initialize logging manually, call `init_logger` with explicit settings: + +```rust +use edgezero_adapter_fastly::init_logger; +use log::LevelFilter; + +fn main() { + init_logger("stdout", LevelFilter::Info, true).expect("init logger"); +} +``` + +::: tip Logging status +Fastly logging is wired when you call `init_logger` (or `run_app`); otherwise no logger is installed. +::: + +## Context Access + +Access Fastly-specific APIs via the request context extensions: + +```rust +use edgezero_core::context::RequestContext; +use edgezero_adapter_fastly::FastlyRequestContext; + +async fn handler(ctx: RequestContext) -> Result { + // Access Fastly context from extensions + if let Some(fastly_ctx) = FastlyRequestContext::get(ctx.request()) { + let client_ip = fastly_ctx.client_ip; + // ... + } + + // ... +} +``` + +## Streaming + +Fastly supports native streaming via `stream_to_client`. The adapter automatically converts `Body::stream` to Fastly's streaming APIs. + +See the [Streaming guide](/guide/streaming) for examples and patterns. + +## Testing + +Run contract tests for the Fastly adapter: + +```bash +# Set up the Wasm runner +export CARGO_TARGET_WASM32_WASIP1_RUNNER="wasmtime run --dir=." + +# Run tests +cargo test -p edgezero-adapter-fastly --features fastly --target wasm32-wasip1 +``` + +::: tip Viceroy Issues +If Viceroy reports keychain access errors on macOS, use Wasmtime as the test runner instead. +::: + +## Manifest Configuration + +Configure the Fastly adapter in `edgezero.toml`. See [Configuration](/guide/configuration) for the full manifest reference. + +## Next Steps + +- Learn about [Cloudflare Workers](/guide/adapters/cloudflare) as an alternative deployment target +- Explore [Configuration](/guide/configuration) for manifest details diff --git a/docs/guide/adapters/overview.md b/docs/guide/adapters/overview.md new file mode 100644 index 0000000..e705f9d --- /dev/null +++ b/docs/guide/adapters/overview.md @@ -0,0 +1,107 @@ +# Adapter Overview + +Adapters bridge provider-specific HTTP primitives into EdgeZero's portable model. This document defines the contract that all adapters must fulfill. + +## Goals + +Adapters translate provider-specific HTTP primitives into the portable `App` in `edgezero-core`. They must: + +- Preserve request semantics +- Stream responses without buffering where the provider supports it +- Expose provider context +- Offer a proxy bridge so handlers can forward traffic without knowing which platform they are on + +## Request Conversion + +Each adapter exposes an `into_core_request` helper that accepts the provider's request type and returns `edgezero_core::http::Request`. The conversion must: + +- **Preserve the HTTP method** exactly (`GET`, `POST`, etc.) +- **Parse the full URI** (path and query string) into an `http::Uri`. Reject invalid URIs with `EdgeError::bad_request` +- **Copy all headers** into the core request. Provider-specific headers may be filtered only when they clash with platform defaults +- **Consume the request body** into an `edgezero_core::body::Body`. Adapters may buffer inbound bodies today; streaming input should be preserved where available +- **Insert a provider context struct** (e.g., `FastlyRequestContext`) into the request extensions. The context should expose metadata such as client IP addresses or environment handles so handlers can reach platform APIs + +## Response Conversion + +Adapters also expose `from_core_response` (or equivalent) to transform an `edgezero_core::http::Response` into the provider response type. Implementations must: + +- **Map HTTP status codes** verbatim +- **Copy headers**, respecting casing rules enforced by the provider +- **Preserve streaming bodies** - `Body::Stream` should be written chunk-by-chunk to the provider output without buffering the entire payload +- **Handle encoding helpers** (`decode_gzip_stream`, `decode_brotli_stream`) where a provider requires transparent decompression + +## Dispatch Helper + +Adapters surface a `dispatch` function that bridges from the provider event loop into the shared router (`App::router().oneshot(...)`). It should: + +1. Convert the incoming provider request with `into_core_request` +2. Await the router future +3. Convert the resulting `Response` back into the provider type +4. Map any `EdgeError` into the provider's error type so failures surface as provider errors (often 5xx) instead of panicking + +This helper is what demo entrypoints and adapters call when wiring their platform-specific main functions. + +## Proxy Integration + +Adapters implement `edgezero_core::proxy::ProxyClient` so handlers can forward outbound requests. The client must: + +- Accept a `ProxyRequest` created with `ProxyRequest::from_request` +- Build and send an outbound provider request, reusing headers and streaming the body without buffering +- Convert the provider response into a `ProxyResponse`, again preserving streaming behaviour and normalising encodings +- Attach a diagnostic header (e.g., `x-edgezero-proxy`) identifying which adapter forwarded the call (Fastly and Cloudflare do this today) +- Surface provider errors as `EdgeError::internal` so applications can decide how to respond + +## Logging Initialisation + +Each adapter exports an `init_logger` helper for platform-specific logging backends. Fastly wires +`log_fastly`, Cloudflare currently no-ops, and Axum uses `simple_logger` in its `run_app` helper. +New adapters should provide a comparable helper so apps consistently opt into logging. + +## Contract Tests + +To keep the contract enforceable, each adapter includes integration tests that validate request/response conversions and the dispatch helper: + +- `into_core_request` for method, URI, header, body, and context propagation +- `from_core_response` for status propagation and streamed body writes +- `dispatch` for routed handlers, body passthrough, and streaming responses + +### Fastly Tests + +Because the Fastly SDK links against the Compute@Edge host functions, the contract tests compile only for `wasm32-wasip1`. Run them with: + +```bash +rustup target add wasm32-wasip1 +cargo test -p edgezero-adapter-fastly --features fastly --target wasm32-wasip1 --tests +``` + +Provide a Wasm runner (Wasmtime or Viceroy) via `CARGO_TARGET_WASM32_WASIP1_RUNNER` if you want to execute the binaries instead of running `--no-run`. + +### Cloudflare Tests + +Cloudflare's adapter relies on `wasm32-unknown-unknown`. The contract suite uses `wasm-bindgen-test` to run under the Workers runtime shims: + +```bash +rustup target add wasm32-unknown-unknown +cargo test -p edgezero-adapter-cloudflare --features cloudflare --target wasm32-unknown-unknown --tests +``` + +## Onboarding New Adapters + +When bringing up another adapter: + +1. **Implement request/response conversion functions** that follow the rules above +2. **Provide a context type** exposing the adapter's metadata and insert it in `into_core_request` +3. **Implement a `dispatch` wrapper** plus logging helper +4. **Wire up a `ProxyClient`** that streams bodies and normalises encodings +5. **Copy the contract test suite**, swapping in the new adapter types. Ensure the tests are gated to the target architecture if the adapter SDK does not compile for native hosts +6. **Register the adapter** with `edgezero-adapter::register_adapter` (typically in a `cli` module using the `ctor` crate) so the CLI can discover it dynamically + +Adapters that fulfil these steps can be dropped into the EdgeZero CLI without requiring changes to application code. + +## Available Adapters + +| Adapter | Platform | Target | Status | +| ---------------------------------------- | ------------------- | ------------------------ | ------ | +| [Fastly](/guide/adapters/fastly) | Fastly Compute@Edge | `wasm32-wasip1` | Stable | +| [Cloudflare](/guide/adapters/cloudflare) | Cloudflare Workers | `wasm32-unknown-unknown` | Stable | +| [Axum](/guide/adapters/axum) | Native (Tokio) | Host | Stable | diff --git a/docs/guide/architecture.md b/docs/guide/architecture.md new file mode 100644 index 0000000..50599a7 --- /dev/null +++ b/docs/guide/architecture.md @@ -0,0 +1,132 @@ +# Architecture + +EdgeZero is organized as a Cargo workspace with distinct crates for core functionality, platform adapters, and tooling. + +## Workspace Layout + +``` +edgezero/ +├── crates/ +│ ├── edgezero-core/ # Core routing, extractors, middleware +│ ├── edgezero-macros/ # Procedural macros (#[action], app!) +│ ├── edgezero-adapter/ # Shared adapter traits and registry +│ ├── edgezero-adapter-fastly/ # Fastly Compute@Edge bridge +│ ├── edgezero-adapter-cloudflare/ # Cloudflare Workers bridge +│ ├── edgezero-adapter-axum/ # Native Axum/Tokio bridge +│ └── edgezero-cli/ # CLI for scaffolding and dev server +└── examples/ + └── app-demo/ # Reference application +``` + +## Core Crate + +`edgezero-core` provides the runtime-agnostic foundation: + +- **Routing** - `RouterService` with path parameter matching via `matchit` +- **Request/Response** - Portable `http::Request` and `http::Response` types +- **Body** - Unified body type supporting buffered and streaming modes +- **Extractors** - `Json`, `Path`, `Query`, `Form`, `Headers`, `Host`, `ForwardedHost`, and `Validated*` variants +- **Middleware** - Composable middleware chain with async support +- **Manifest** - `edgezero.toml` parsing and validation +- **Compression** - Shared gzip/brotli stream decoders + +Handlers in your core crate only depend on `edgezero-core`, keeping them portable. + +## Macros Crate + +`edgezero-macros` provides compile-time code generation: + +- **`#[action]`** - Transforms async functions into handlers with automatic extractor wiring +- **`app!`** - Generates router setup from your `edgezero.toml` manifest + +Example usage: + +```rust +// In your core crate's lib.rs +mod handlers; + +edgezero_core::app!("../../edgezero.toml"); +``` + +## Adapter Crates + +Adapters translate between provider-specific types and the portable core model: + +### edgezero-adapter-fastly + +- Converts Fastly `Request` to `edgezero_core::http::Request` +- Maps core responses back to Fastly `Response` +- Provides `FastlyRequestContext` for accessing Fastly-specific APIs +- Implements `FastlyProxyClient` for upstream requests + +### edgezero-adapter-cloudflare + +- Converts Workers `Request` to core request +- Maps responses to Workers `Response` +- Provides `CloudflareRequestContext` for Workers APIs +- Implements `CloudflareProxyClient` for fetch operations + +### edgezero-adapter-axum + +- Wraps `RouterService` in Axum/Tokio services +- Powers the local development server +- Supports native container deployments + +## CLI Crate + +`edgezero-cli` provides the `edgezero` binary: + +- **`edgezero new`** - Scaffolds a new project with templates +- **`edgezero dev`** - Runs the local Axum dev server +- **`edgezero build`** - Builds for a specific adapter target +- **`edgezero serve`** - Runs provider-specific local servers (Viceroy, wrangler dev) +- **`edgezero deploy`** - Deploys to production + +## Data Flow + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Provider Runtime │ +│ (Fastly Compute / Cloudflare Workers / Axum Server) │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Adapter │ +│ - into_core_request(): Provider Request → Core Request │ +│ - from_core_response(): Core Response → Provider Response │ +│ - dispatch(): Full request lifecycle │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ edgezero-core │ +│ - RouterService matches routes │ +│ - Middleware chain executes │ +│ - Handler runs with extracted params │ +│ - Response built and returned │ +└─────────────────────────────────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Your Core Crate │ +│ - handlers.rs: Business logic │ +│ - lib.rs: App definition via app! macro │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Feature Flags + +Adapter crates use feature flags to gate provider SDKs and CLI integration: + +| Feature | Crate | Purpose | +| ------------- | --------------------------- | -------------------------------------- | +| `fastly` | edgezero-adapter-fastly | Fastly SDK integration | +| `cloudflare` | edgezero-adapter-cloudflare | Workers SDK integration | +| `cli` | adapter crates | Register adapters and scaffolding data | +| `dev-example` | edgezero-cli | Bundled demo app for development | + +## Next Steps + +- Learn about the [Adapter Contract](/guide/adapters/overview) for extending EdgeZero +- Explore [Configuration](/guide/configuration) to customize your app diff --git a/docs/guide/cli-reference.md b/docs/guide/cli-reference.md new file mode 100644 index 0000000..0ff70db --- /dev/null +++ b/docs/guide/cli-reference.md @@ -0,0 +1,226 @@ +# CLI Reference + +The `edgezero` CLI provides commands for scaffolding, development, building, and deployment. + +## Installation + +Follow the [Getting Started](/guide/getting-started) guide to install the CLI. + +## Commands + +### edgezero new + +Scaffold a new EdgeZero project: + +```bash +edgezero new [options] +``` + +**Arguments:** + +- `` - Project name (used for directory and crate names) + +**Options:** + +- `--dir ` - Directory to create the project in (default: current directory) +- `--local-core` - Use local path dependency for edgezero-core (development only) + +**Examples:** + +```bash +# Create project with all registered adapters +edgezero new my-app + +# Create in a specific directory +edgezero new my-app --dir /path/to/projects +``` + +**Generated structure:** + +``` +my-app/ +├── Cargo.toml +├── edgezero.toml +├── crates/ +│ ├── my-app-core/ +│ ├── my-app-adapter-fastly/ +│ ├── my-app-adapter-cloudflare/ +│ └── my-app-adapter-axum/ +``` + +The scaffolder includes all adapters registered at CLI build time. + +### edgezero dev + +Start the local development server: + +```bash +edgezero dev +``` + +**Example:** + +```bash +edgezero dev +# Server starts at http://127.0.0.1:8787 +``` + +If `edgezero.toml` defines an Axum adapter command, `edgezero dev` delegates to it. Otherwise it +starts the built-in dev server (default routes). + +### edgezero build + +Build for a specific adapter: + +```bash +edgezero build --adapter +``` + +**Arguments:** + +- `--adapter ` - Target adapter (`fastly`, `cloudflare`, `axum`) + +**Examples:** + +```bash +# Build for Fastly +edgezero build --adapter fastly + +# Build for Cloudflare +edgezero build --adapter cloudflare + +# Build native binary +edgezero build --adapter axum +``` + +The command executes the `build` command from `[adapters..commands]` in `edgezero.toml`, or falls back to the built-in adapter helper. + +Any arguments after `--` are forwarded to the adapter command: + +```bash +edgezero build --adapter fastly -- --flag value +``` + +### edgezero serve + +Run the provider-specific local server: + +```bash +edgezero serve --adapter +``` + +**Arguments:** + +- `--adapter ` - Target adapter (`fastly`, `cloudflare`, `axum`) + +**Examples:** + +```bash +# Run Fastly's Viceroy +edgezero serve --adapter fastly + +# Run Wrangler dev server +edgezero serve --adapter cloudflare + +# Run native Axum server +edgezero serve --adapter axum +``` + +**Provider behavior:** + +- **Fastly**: Runs `fastly compute serve` +- **Cloudflare**: Runs `wrangler dev` +- **Axum**: Runs `cargo run -p ` + +### edgezero deploy + +Deploy to production: + +```bash +edgezero deploy --adapter +``` + +**Arguments:** + +- `--adapter ` - Target adapter (`fastly`, `cloudflare`) + +**Examples:** + +```bash +# Deploy to Fastly +edgezero deploy --adapter fastly + +# Deploy to Cloudflare +edgezero deploy --adapter cloudflare +``` + +**Provider behavior:** + +- **Fastly**: Runs `fastly compute deploy` +- **Cloudflare**: Runs `wrangler publish` + +::: warning +The `axum` adapter doesn't support `deploy` - use standard container/binary deployment instead. +::: + +## Environment Variables + +The CLI respects these environment variables: + +| Variable | Description | +| ------------------- | ------------------------------------------- | +| `EDGEZERO_MANIFEST` | Path to manifest (default: `edgezero.toml`) | + +## Working Directory + +All commands expect to run from the project root where `edgezero.toml` is located. If the file is +missing, the CLI falls back to built-in adapters (when compiled in) instead of manifest-driven +commands. + +## Adapter Discovery + +Adapters register themselves via the `edgezero-adapter` registry at build time. There is currently +no `edgezero --list-adapters` command; the scaffolder includes all adapters that were compiled in. + +Built-in adapters (default CLI build): + +- `fastly` - Fastly Compute@Edge +- `cloudflare` - Cloudflare Workers +- `axum` - Native Axum/Tokio + +## Troubleshooting + +### Missing Wasm Target + +``` +error: target may not be installed +``` + +Install the required target: + +```bash +rustup target add wasm32-wasip1 # For Fastly +rustup target add wasm32-unknown-unknown # For Cloudflare +``` + +### Manifest Not Found + +If you rely on manifest-driven commands, ensure `edgezero.toml` exists or set `EDGEZERO_MANIFEST`. +When no manifest is present, the CLI falls back to built-in adapter implementations (if compiled +in) instead of using manifest commands. + +### Provider CLI Not Found + +``` +error: fastly: command not found +``` + +Install the provider CLI: + +- Fastly: https://developer.fastly.com/learning/compute/ +- Cloudflare: `npm install -g wrangler` + +## Next Steps + +- Configure your project with [edgezero.toml](/guide/configuration) +- Deploy to [Fastly](/guide/adapters/fastly) or [Cloudflare](/guide/adapters/cloudflare) diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md new file mode 100644 index 0000000..c0d9275 --- /dev/null +++ b/docs/guide/configuration.md @@ -0,0 +1,335 @@ +# Configuration + +The `edgezero.toml` manifest describes an EdgeZero application, providing a single source of truth for routing, middleware, adapters, and environment configuration. + +## Overview + +New workspaces scaffolded with `edgezero new` include this manifest by default. The manifest drives both runtime routing and CLI commands. + +```toml +[app] +name = "my-app" +entry = "crates/my-app-core" +middleware = ["edgezero_core::middleware::RequestLogger"] + +[[triggers.http]] +id = "root" +path = "/" +methods = ["GET"] +handler = "my_app_core::handlers::root" + +[adapters.fastly] +# Fastly-specific configuration + +[adapters.cloudflare] +# Cloudflare-specific configuration +``` + +## App Section + +The `[app]` section defines application metadata: + +```toml +[app] +name = "demo" +entry = "crates/demo-core" +middleware = ["edgezero_core::middleware::RequestLogger"] +``` + +| Field | Required | Description | +| ------------ | -------- | -------------------------------------------------------------------- | +| `name` | No | Display name for the application (defaults to "EdgeZero App") | +| `entry` | No | Path to the core crate containing handlers (recommended for tooling) | +| `version` | No | Reserved for future compatibility; currently ignored | +| `kind` | No | Reserved for future compatibility; currently ignored | +| `middleware` | No | List of middleware to apply globally | + +### Middleware + +Manifest-driven middleware are applied in order before routes: + +```toml +[app] +middleware = [ + "edgezero_core::middleware::RequestLogger", + "my_app_core::cors::Cors" +] +``` + +Each item must be: + +- A publicly accessible path +- Either a unit struct or zero-argument constructor +- Implementing `edgezero_core::middleware::Middleware` + +## HTTP Triggers + +The `[[triggers.http]]` array defines routes: + +```toml +[[triggers.http]] +id = "root" +path = "/" +methods = ["GET"] +handler = "my_app_core::handlers::root" + +[[triggers.http]] +id = "echo" +path = "/echo/{name}" +methods = ["GET", "POST"] +handler = "my_app_core::handlers::echo" +adapters = ["fastly", "cloudflare"] +body-mode = "buffered" +``` + +| Field | Required | Description | +| ------------- | -------- | ------------------------------------------------------------ | +| `id` | No | Stable identifier for tooling | +| `path` | Yes | URI template (`{param}` for params, `{*rest}` for catch-all) | +| `methods` | No | Allowed HTTP methods (defaults to `GET`) | +| `handler` | No | Path to handler function (required for `app!` route wiring) | +| `adapters` | No | Intended adapter filter (metadata; `app!` currently ignores) | +| `description` | No | Human-readable description for docs or tooling | +| `body-mode` | No | `buffered` or `stream` | + +::: tip Adapter filters +The `adapters` field is currently metadata for tooling; `app!` wires all triggers regardless of adapter. +::: + +## Environment Section + +Declare environment variables and secrets: + +```toml +[environment] + +[[environment.variables]] +name = "API_BASE_URL" +env = "API_BASE_URL" +value = "https://example.com/api" + +[[environment.secrets]] +name = "API_TOKEN" +adapters = ["fastly", "cloudflare"] +env = "API_TOKEN" +``` + +### Variables + +| Field | Required | Description | +| ------------- | -------- | ------------------------------------ | +| `name` | Yes | Variable name in application | +| `description` | No | Human-readable description | +| `env` | No | Environment key (defaults to `name`) | +| `value` | No | Default value | +| `adapters` | No | Limit to specific adapters | + +Variables with a default `value` are injected when running CLI commands. + +### Secrets + +| Field | Required | Description | +| ------------- | -------- | ------------------------------------ | +| `name` | Yes | Secret name in application | +| `description` | No | Human-readable description | +| `env` | No | Environment key (defaults to `name`) | +| `adapters` | No | Limit to specific adapters | + +Secrets must be present in the environment; missing secrets abort CLI commands with an error. + +## Adapters Section + +Each adapter has its own configuration block: + +```toml +[adapters.fastly.adapter] +crate = "crates/demo-adapter-fastly" +manifest = "crates/demo-adapter-fastly/fastly.toml" + +[adapters.fastly.build] +target = "wasm32-wasip1" +profile = "release" + +[adapters.fastly.commands] +build = "cargo build --release --target wasm32-wasip1 -p demo-adapter-fastly" +serve = "fastly compute serve -C crates/demo-adapter-fastly" +deploy = "fastly compute deploy -C crates/demo-adapter-fastly" + +[adapters.fastly.logging] +endpoint = "stdout" +level = "info" +echo_stdout = true +``` + +### Adapter Metadata + +| Field | Description | +| ---------- | ------------------------------------------------------ | +| `crate` | Path to adapter crate | +| `manifest` | Path to provider manifest (fastly.toml, wrangler.toml) | + +### Build Configuration + +| Field | Description | +| ---------- | -------------------------------- | +| `target` | Rust compilation target | +| `profile` | Build profile (`release`, `dev`) | +| `features` | Cargo features to enable | + +### Commands + +| Field | Description | +| -------- | ---------------------------------------------- | +| `build` | Command for `edgezero build --adapter ` | +| `serve` | Command for `edgezero serve --adapter ` | +| `deploy` | Command for `edgezero deploy --adapter ` | + +When commands are omitted, the CLI falls back to built-in adapter helpers. + +### Logging + +Logging can be configured per adapter under `[adapters..logging]` or via a top-level +`[logging.]` block. If both are present, the adapter-specific block takes precedence. + +| Field | Adapters | Description | +| ------------- | ------------ | ----------------------------------------------------------- | +| `endpoint` | Fastly | Log endpoint name | +| `level` | All | Log level: `trace`, `debug`, `info`, `warn`, `error`, `off` | +| `echo_stdout` | Fastly, Axum | Mirror logs to stdout | + +Note: Cloudflare logging is not wired to a built-in logger yet. + +## Full Example + +```toml +[app] +name = "my-app" +entry = "crates/my-app-core" +middleware = [ + "edgezero_core::middleware::RequestLogger", + "my_app_core::middleware::Cors" +] + +[[triggers.http]] +id = "root" +path = "/" +methods = ["GET"] +handler = "my_app_core::handlers::root" + +[[triggers.http]] +id = "echo" +path = "/echo/{name}" +methods = ["GET"] +handler = "my_app_core::handlers::echo" + +[[triggers.http]] +id = "api" +path = "/api/{*rest}" +methods = ["GET", "POST", "PUT", "DELETE"] +handler = "my_app_core::handlers::api_proxy" +body-mode = "stream" + +[environment] + +[[environment.variables]] +name = "API_URL" +value = "https://api.example.com" + +[[environment.secrets]] +name = "API_KEY" + +[adapters.fastly.adapter] +crate = "crates/my-app-adapter-fastly" +manifest = "crates/my-app-adapter-fastly/fastly.toml" + +[adapters.fastly.build] +target = "wasm32-wasip1" +profile = "release" + +[adapters.fastly.commands] +build = "cargo build --release --target wasm32-wasip1 -p my-app-adapter-fastly" +serve = "fastly compute serve -C crates/my-app-adapter-fastly" +deploy = "fastly compute deploy -C crates/my-app-adapter-fastly" + +[adapters.fastly.logging] +endpoint = "stdout" +level = "info" +echo_stdout = true + +[adapters.cloudflare.adapter] +crate = "crates/my-app-adapter-cloudflare" +manifest = "crates/my-app-adapter-cloudflare/wrangler.toml" + +[adapters.cloudflare.build] +target = "wasm32-unknown-unknown" +profile = "release" + +[adapters.cloudflare.commands] +build = "cargo build --release --target wasm32-unknown-unknown -p my-app-adapter-cloudflare" +serve = "wrangler dev --config crates/my-app-adapter-cloudflare/wrangler.toml" +deploy = "wrangler publish --config crates/my-app-adapter-cloudflare/wrangler.toml" + +[adapters.cloudflare.logging] +level = "info" + +[adapters.axum.adapter] +crate = "crates/my-app-adapter-axum" +manifest = "crates/my-app-adapter-axum/axum.toml" + +[adapters.axum.commands] +build = "cargo build --release -p my-app-adapter-axum" +serve = "cargo run -p my-app-adapter-axum" +``` + +## Using the Manifest + +### app! Macro + +Generate router wiring from the manifest: + +```rust +// In your core crate's lib.rs +mod handlers; + +edgezero_core::app!("../../edgezero.toml"); +``` + +The macro: + +- Parses HTTP triggers +- Generates route registration +- Wires middleware from the manifest +- Creates the `App` struct that implements `Hooks` (use `App::build_app()`) + +### ManifestLoader + +Load the manifest programmatically: + +```rust +use edgezero_core::manifest::ManifestLoader; +use std::path::Path; + +let manifest = ManifestLoader::from_path(Path::new("edgezero.toml"))?; +let app_name = manifest + .manifest() + .app + .name + .as_deref() + .unwrap_or("EdgeZero App"); +println!("App name: {}", app_name); +``` + +## Validation + +`ManifestLoader` validates: + +- Non-empty string fields when present (names, paths, commands) +- Supported HTTP methods and `body-mode` values +- Well-formed logging levels and adapter logging config + +Errors are surfaced at startup or during macro expansion. + +## Next Steps + +- Learn about [CLI commands](/guide/cli-reference) +- Explore [adapter-specific configuration](/guide/adapters/overview) diff --git a/docs/guide/getting-started.md b/docs/guide/getting-started.md new file mode 100644 index 0000000..9befc2a --- /dev/null +++ b/docs/guide/getting-started.md @@ -0,0 +1,119 @@ +# Getting Started + +This guide walks you through creating your first EdgeZero application. + +## Prerequisites + +- Rust toolchain (stable; see `.tool-versions` in the repo) +- For Fastly: `wasm32-wasip1` target and the Fastly CLI +- For Cloudflare: `wasm32-unknown-unknown` target and Wrangler + +## Installation + +Install the EdgeZero CLI from the workspace or a published crate: + +```bash +cargo install --path crates/edgezero-cli +``` + +## Create a New Project + +Scaffold a new EdgeZero app: + +```bash +edgezero new my-app +cd my-app +``` + +This generates a workspace with: + +- `crates/my-app-core` - Your shared handlers and routing logic +- `crates/my-app-adapter-fastly` - Fastly Compute entrypoint +- `crates/my-app-adapter-cloudflare` - Cloudflare Workers entrypoint +- `crates/my-app-adapter-axum` - Native Axum entrypoint +- `edgezero.toml` - Manifest describing routes, middleware, and adapter config + +## Start the Dev Server + +Run the local Axum-powered development server: + +```bash +edgezero dev +``` + +Your app is now running at `http://127.0.0.1:8787`. Try the generated endpoints: + +```bash +# Root endpoint +curl http://127.0.0.1:8787/ + +# Path parameter extraction +curl http://127.0.0.1:8787/echo/alice + +# JSON echo +curl -X POST http://127.0.0.1:8787/echo \ + -H "Content-Type: application/json" \ + -d '{"name": "Bob"}' +``` + +## Project Structure + +A scaffolded project looks like this: + +``` +my-app/ +├── Cargo.toml # Workspace manifest +├── edgezero.toml # EdgeZero configuration +├── crates/ +│ ├── my-app-core/ +│ │ ├── Cargo.toml +│ │ └── src/ +│ │ ├── lib.rs # App definition with edgezero_core::app! +│ │ └── handlers.rs # Your route handlers +│ ├── my-app-adapter-fastly/ +│ │ ├── Cargo.toml +│ │ ├── fastly.toml +│ │ └── src/main.rs +│ ├── my-app-adapter-cloudflare/ +│ │ ├── Cargo.toml +│ │ ├── wrangler.toml +│ │ └── src/main.rs +│ └── my-app-adapter-axum/ +│ ├── Cargo.toml +│ ├── axum.toml +│ └── src/main.rs +``` + +## Writing Your First Handler + +Handlers use the `#[action]` macro for ergonomic extractor support: + +```rust +use edgezero_core::action; +use edgezero_core::extractor::Json; +use edgezero_core::response::Text; + +#[derive(serde::Deserialize)] +struct EchoBody { + name: String, +} + +#[action] +async fn echo_json(Json(body): Json) -> Text { + Text::new(format!("Hello, {}!", body.name)) +} +``` + +## Running Tests + +Run your workspace tests with: + +```bash +cargo test +``` + +## Next Steps + +- Learn about [Routing](/guide/routing) to define your endpoints +- Explore [Handlers & Extractors](/guide/handlers) for type-safe request handling +- Deploy to [Fastly](/guide/adapters/fastly) or [Cloudflare](/guide/adapters/cloudflare) diff --git a/docs/guide/handlers.md b/docs/guide/handlers.md new file mode 100644 index 0000000..f13e4ef --- /dev/null +++ b/docs/guide/handlers.md @@ -0,0 +1,413 @@ +# Handlers & Extractors + +EdgeZero provides ergonomic handler definitions using the `#[action]` macro and type-safe extractors. + +## The #[action] Macro + +The `#[action]` macro transforms async functions into EdgeZero handlers with automatic extractor wiring: + +```rust +use edgezero_core::action; +use edgezero_core::extractor::Json; +use edgezero_core::response::Text; + +#[derive(serde::Deserialize)] +struct CreateUser { + name: String, + email: String, +} + +#[action] +async fn create_user(Json(body): Json) -> Text { + Text::new(format!("Created user: {}", body.name)) +} +``` + +The macro: + +- Generates the `FromRequest` boilerplate for each extractor +- Handles async execution +- Converts the return type into a proper response + +## Built-in Extractors + +### Path Parameters + +Extract typed parameters from the URL path: + +```rust +use edgezero_core::extractor::Path; + +// Single parameter +#[action] +async fn get_user(Path(id): Path) -> Text { + Text::new(format!("User ID: {}", id)) +} + +// Multiple parameters via struct +#[derive(serde::Deserialize)] +struct PostPath { + user_id: u64, + post_id: u64, +} + +#[action] +async fn get_post(Path(params): Path) -> Text { + Text::new(format!("User {} Post {}", params.user_id, params.post_id)) +} +``` + +### Query Parameters + +Extract query string parameters: + +```rust +use edgezero_core::extractor::Query; + +#[derive(serde::Deserialize)] +struct Pagination { + page: Option, + limit: Option, +} + +#[action] +async fn list_items(Query(params): Query) -> Text { + let page = params.page.unwrap_or(1); + let limit = params.limit.unwrap_or(10); + Text::new(format!("Page {} with {} items", page, limit)) +} +``` + +### JSON Body + +Parse JSON request bodies: + +```rust +use edgezero_core::extractor::Json; + +#[derive(serde::Deserialize)] +struct LoginRequest { + username: String, + password: String, +} + +#[action] +async fn login(Json(body): Json) -> Text { + Text::new(format!("Logging in: {}", body.username)) +} +``` + +### Validated Extractors + +Use `validator` crate integration for input validation: + +```rust +use edgezero_core::extractor::{ValidatedJson, ValidatedQuery}; +use validator::Validate; + +#[derive(serde::Deserialize, Validate)] +struct CreatePost { + #[validate(length(min = 1, max = 200))] + title: String, + #[validate(length(min = 1))] + content: String, +} + +#[action] +async fn create_post(ValidatedJson(body): ValidatedJson) -> Text { + Text::new(format!("Created post: {}", body.title)) +} +``` + +If validation fails, EdgeZero automatically returns a 400 Bad Request with error details. + +### Headers + +Extract request headers directly: + +```rust +use edgezero_core::extractor::Headers; + +#[action] +async fn check_auth(Headers(headers): Headers) -> Text { + let token = headers + .get("authorization") + .and_then(|v| v.to_str().ok()) + .unwrap_or("none"); + Text::new(format!("Auth: {}", token)) +} +``` + +### Form Data + +Parse URL-encoded form bodies: + +```rust +use edgezero_core::extractor::Form; + +#[derive(serde::Deserialize)] +struct ContactForm { + name: String, + email: String, +} + +#[action] +async fn submit_form(Form(data): Form) -> Text { + Text::new(format!("Received from: {}", data.email)) +} +``` + +Use `ValidatedForm` for form data with validation, and `ValidatedPath` for validated path parameters. + +### Host Extractors + +Extract the hostname from request headers: + +```rust +use edgezero_core::extractor::{Host, ForwardedHost}; + +// Extract from the Host header (falls back to "localhost") +#[action] +async fn check_host(Host(host): Host) -> Text { + Text::new(format!("Host: {}", host)) +} + +// Extract from X-Forwarded-Host first, then Host header +// Use this when behind a reverse proxy or load balancer +#[action] +async fn check_forwarded(ForwardedHost(host): ForwardedHost) -> Text { + Text::new(format!("Effective host: {}", host)) +} +``` + +### Request Context + +For full request access, handlers can receive `RequestContext` directly (no `#[action]` needed): + +```rust +use edgezero_core::context::RequestContext; +use edgezero_core::error::EdgeError; + +async fn inspect(ctx: RequestContext) -> Result, EdgeError> { + let method = ctx.request().method(); + let path = ctx.request().uri().path(); + let user_agent = ctx.request().headers() + .get("user-agent") + .and_then(|v| v.to_str().ok()) + .unwrap_or("unknown"); + + Ok(Text::new(format!("{} {} from {}", method, path, user_agent))) +} +``` + +`RequestContext` provides these methods: + +| Method | Returns | +| ---------------- | ------------------------------------------ | +| `request()` | `&Request` - full HTTP request | +| `path_params()` | `&PathParams` - raw path parameters | +| `path::()` | Deserialize path params to `T` | +| `query::()` | Deserialize query string to `T` | +| `json::()` | Deserialize JSON body to `T` | +| `form::()` | Deserialize form body to `T` | +| `body()` | `&Body` - raw request body | +| `into_request()` | `Request` - consume context, take request | +| `proxy_handle()` | `Option` - adapter proxy hook | + +## Response Types + +### Text Responses + +```rust +use edgezero_core::response::Text; + +#[action] +async fn hello() -> Text<&'static str> { + Text::new("Hello, World!") +} +``` + +### JSON Responses + +Build JSON responses using `Body::json`: + +```rust +use edgezero_core::body::Body; +use edgezero_core::http::{Response, StatusCode}; +use edgezero_core::error::EdgeError; + +#[derive(serde::Serialize)] +struct User { + id: u64, + name: String, +} + +#[action] +async fn get_user() -> Result { + let user = User { id: 1, name: "Alice".into() }; + let body = Body::json(&user).map_err(EdgeError::internal)?; + + Ok(Response::builder() + .status(StatusCode::OK) + .header("content-type", "application/json") + .body(body) + .unwrap()) +} +``` + +### Status Codes + +```rust +use edgezero_core::http::StatusCode; +use edgezero_core::response::Text; + +#[action] +async fn not_found() -> (StatusCode, Text<&'static str>) { + (StatusCode::NOT_FOUND, Text::new("Resource not found")) +} +``` + +### Custom Headers + +```rust +use edgezero_core::body::Body; +use edgezero_core::http::{HeaderValue, Response, StatusCode}; + +#[action] +async fn with_headers() -> Response { + let mut response = Response::builder() + .status(StatusCode::OK) + .body(Body::from("Response with custom header")) + .unwrap(); + response + .headers_mut() + .insert("x-custom", HeaderValue::from_static("value")); + response +} +``` + +## Combining Extractors + +You can use multiple extractors in a single handler: + +```rust +#[action] +async fn update_user( + Path(id): Path, + Query(params): Query, + Json(body): Json, +) -> Text { + Text::new(format!("Updated user {} with name {}", id, body.name)) +} +``` + +## Error Handling + +Extractors return `EdgeError` on failure, which automatically converts to appropriate HTTP responses: + +| Error | Status Code | +| --------------------- | ------------------------ | +| JSON parse error | 400 Bad Request | +| Validation error | 422 Unprocessable Entity | +| Missing path param | 400 Bad Request | +| Type conversion error | 400 Bad Request | + +For custom error handling, return `Result`: + +```rust +use edgezero_core::error::EdgeError; + +#[action] +async fn fallible(Json(body): Json) -> Result, EdgeError> { + if body.invalid { + return Err(EdgeError::bad_request("Invalid request")); + } + Ok(Text::new("Success")) +} +``` + +### EdgeError Methods + +`EdgeError` provides factory methods for common HTTP errors: + +```rust +use edgezero_core::error::EdgeError; + +// Client errors +EdgeError::bad_request("Invalid input") // 400 +EdgeError::not_found("/missing/path") // 404 +EdgeError::method_not_allowed(&method, &allowed) // 405 +EdgeError::validation("Field too short") // 422 + +// Server errors +EdgeError::internal("Unexpected failure") // 500 +EdgeError::internal(some_error) // 500 (from any error type) +``` + +## Custom Extractors + +Implement the `FromRequest` trait to create custom extractors: + +```rust +use async_trait::async_trait; +use edgezero_core::context::RequestContext; +use edgezero_core::error::EdgeError; +use edgezero_core::extractor::FromRequest; + +pub struct BearerToken(pub String); + +#[async_trait(?Send)] +impl FromRequest for BearerToken { + async fn from_request(ctx: &RequestContext) -> Result { + let header = ctx.request().headers() + .get("authorization") + .and_then(|v| v.to_str().ok()) + .ok_or_else(|| EdgeError::bad_request("Missing Authorization header"))?; + + let token = header + .strip_prefix("Bearer ") + .ok_or_else(|| EdgeError::bad_request("Invalid Bearer token format"))?; + + Ok(BearerToken(token.to_string())) + } +} + +// Use in handlers: +#[action] +async fn protected(BearerToken(token): BearerToken) -> Text { + Text::new(format!("Authenticated with token: {}...", &token[..8])) +} +``` + +## Custom Response Types + +Implement `IntoResponse` for custom response types: + +```rust +use edgezero_core::body::Body; +use edgezero_core::http::{Response, StatusCode}; +use edgezero_core::response::IntoResponse; + +pub struct HtmlResponse(pub String); + +impl IntoResponse for HtmlResponse { + fn into_response(self) -> Response { + Response::builder() + .status(StatusCode::OK) + .header("content-type", "text/html; charset=utf-8") + .body(Body::from(self.0)) + .unwrap() + } +} + +// Use in handlers: +#[action] +async fn page() -> HtmlResponse { + HtmlResponse("

Hello

".to_string()) +} +``` + +## Next Steps + +- Learn about [Middleware](/guide/middleware) for request/response processing +- Explore [Streaming](/guide/streaming) for large response bodies diff --git a/docs/guide/middleware.md b/docs/guide/middleware.md new file mode 100644 index 0000000..a038ac3 --- /dev/null +++ b/docs/guide/middleware.md @@ -0,0 +1,230 @@ +# Middleware + +EdgeZero supports composable middleware for cross-cutting concerns like logging, authentication, and CORS. + +## Defining Middleware + +Middleware implements the `Middleware` trait: + +```rust +use async_trait::async_trait; +use edgezero_core::context::RequestContext; +use edgezero_core::error::EdgeError; +use edgezero_core::http::Response; +use edgezero_core::middleware::{Middleware, Next}; + +pub struct RequestLogger; + +#[async_trait(?Send)] +impl Middleware for RequestLogger { + async fn handle( + &self, + ctx: RequestContext, + next: Next<'_>, + ) -> Result { + let method = ctx.request().method().clone(); + let path = ctx.request().uri().path().to_string(); + let start = std::time::Instant::now(); + + let response = next.run(ctx).await?; + + let elapsed_ms = start.elapsed().as_secs_f64() * 1000.0; + tracing::info!( + "request method={} path={} status={} elapsed_ms={:.2}", + method, + path, + response.status().as_u16(), + elapsed_ms + ); + + Ok(response) + } +} +``` + +## Registering Middleware + +### Via Manifest + +Define middleware in `edgezero.toml`: + +```toml +[app] +name = "my-app" +entry = "crates/my-app-core" +middleware = [ + "edgezero_core::middleware::RequestLogger", + "my_app_core::middleware::Auth" +] +``` + +Middleware are applied in order before routes are matched. + +### Programmatically + +Register middleware when building the router: + +```rust +use edgezero_core::router::RouterService; + +let router = RouterService::builder() + .middleware(RequestLogger) + .middleware(CorsMiddleware::default()) + .get("/hello", hello) + .build(); +``` + +## Middleware Order + +Middleware execute in registration order for requests, and reverse order for responses: + +``` +Request Flow: + Client → Logger → Auth → CORS → Handler + +Response Flow: + Handler → CORS → Auth → Logger → Client +``` + +## Common Patterns + +### Authentication + +```rust +use edgezero_core::body::Body; + +pub struct AuthMiddleware { + secret: String, +} + +#[async_trait(?Send)] +impl Middleware for AuthMiddleware { + async fn handle( + &self, + ctx: RequestContext, + next: Next<'_>, + ) -> Result { + // Check authorization header + let auth_header = ctx.request().headers().get("authorization"); + + match auth_header { + Some(value) if self.verify_token(value) => { + // Token valid, continue to handler + next.run(ctx).await + } + _ => { + // Return 401 Unauthorized + let response = Response::builder() + .status(401) + .body(Body::from("Unauthorized")) + .map_err(EdgeError::internal)?; + Ok(response) + } + } + } +} +``` + +### CORS + +```rust +pub struct CorsMiddleware { + allowed_origins: Vec, +} + +#[async_trait(?Send)] +impl Middleware for CorsMiddleware { + async fn handle( + &self, + ctx: RequestContext, + next: Next<'_>, + ) -> Result { + let origin = ctx + .request() + .headers() + .get("origin") + .and_then(|v| v.to_str().ok()) + .map(String::from); + + let mut response = next.run(ctx).await?; + + if let Some(origin) = origin { + if self.allowed_origins.contains(&origin) { + response.headers_mut().insert( + "access-control-allow-origin", + origin.parse().unwrap(), + ); + } + } + + Ok(response) + } +} +``` + +### Request Timing + +```rust +pub struct TimingMiddleware; + +#[async_trait(?Send)] +impl Middleware for TimingMiddleware { + async fn handle( + &self, + ctx: RequestContext, + next: Next<'_>, + ) -> Result { + let start = std::time::Instant::now(); + + let mut response = next.run(ctx).await?; + + let duration = start.elapsed(); + response.headers_mut().insert( + "x-response-time", + format!("{}ms", duration.as_millis()).parse().unwrap(), + ); + + Ok(response) + } +} +``` + +## Early Returns + +Middleware can short-circuit the chain by not calling `next`: + +```rust +use edgezero_core::body::Body; + +impl Middleware for RateLimiter { + async fn handle( + &self, + ctx: RequestContext, + next: Next<'_>, + ) -> Result { + if self.is_rate_limited(&ctx) { + // Don't call next - return immediately + let response = Response::builder() + .status(429) + .body(Body::from("Too Many Requests")) + .map_err(EdgeError::internal)?; + return Ok(response); + } + + next.run(ctx).await + } +} +``` + +## Built-in Middleware + +EdgeZero provides these middleware out of the box: + +| Middleware | Purpose | +| --------------- | ---------------------------------------------- | +| `RequestLogger` | Logs request method, path, and response status | + +## Next Steps + +- Learn about [Streaming](/guide/streaming) for progressive responses +- Explore [Proxying](/guide/proxying) for upstream forwarding diff --git a/docs/guide/proxying.md b/docs/guide/proxying.md new file mode 100644 index 0000000..e6502f3 --- /dev/null +++ b/docs/guide/proxying.md @@ -0,0 +1,63 @@ +# Proxying + +EdgeZero provides helpers for forwarding requests to upstream services while staying +provider-agnostic. + +## End-to-End Example + +This example forwards the incoming request upstream, adjusts headers on the way in and out, and +returns a friendly 502 on proxy errors. It uses the adapter-provided proxy handle inserted by each +adapter. + +```rust +use edgezero_core::action; +use edgezero_core::body::Body; +use edgezero_core::context::RequestContext; +use edgezero_core::error::EdgeError; +use edgezero_core::http::{Response, StatusCode, Uri}; +use edgezero_core::proxy::ProxyRequest; + +#[action] +async fn proxy_with_auth(RequestContext(ctx): RequestContext) -> Result { + let target: Uri = "https://api.example.com".parse().unwrap(); + + let handle = ctx + .proxy_handle() + .ok_or_else(|| EdgeError::internal("proxy client not configured"))?; + + let mut proxy_request = ProxyRequest::from_request(ctx.into_request(), target); + proxy_request.headers_mut().insert( + "authorization", + "Bearer secret-token".parse().unwrap(), + ); + proxy_request.headers_mut().remove("cookie"); + + match handle.forward(proxy_request).await { + Ok(mut response) => { + response + .headers_mut() + .insert("x-proxy-by", "edgezero".parse().unwrap()); + Ok(response) + } + Err(err) => { + tracing::error!("proxy failed: {}", err); + let response = Response::builder() + .status(StatusCode::BAD_GATEWAY) + .body(Body::from("Bad Gateway")) + .map_err(EdgeError::internal)?; + Ok(response) + } + } +} +``` + +## Notes + +- Fastly and Cloudflare preserve streaming bodies; Axum buffers outbound bodies before sending. +- Fastly and Cloudflare automatically decode `gzip`/`br` responses for you. +- If you need a direct client (for tests or custom wiring), use the adapter clients + (`FastlyProxyClient`, `CloudflareProxyClient`, `AxumProxyClient::default()`). + +## Next Steps + +- Learn about [Fastly](/guide/adapters/fastly) and [Cloudflare](/guide/adapters/cloudflare) adapter specifics diff --git a/docs/guide/roadmap.md b/docs/guide/roadmap.md new file mode 100644 index 0000000..e5ce1b8 --- /dev/null +++ b/docs/guide/roadmap.md @@ -0,0 +1,39 @@ +# Roadmap + +This page captures upcoming EdgeZero work and longer-term bets. Items here are directional and may +shift as the roadmap evolves. + +## Near-Term Priorities + +- Tooling parity: extend `edgezero-cli` with template/plugin style commands (similar to Spin + templates) to streamline new app scaffolds and provider-specific wiring. +- CLI parity backlog: add `edgezero --list-adapters`, standardize exit codes, search up for + `edgezero.toml`, respect `RUST_LOG` for dev output, and bake in hot reload for `edgezero dev`. +- Adapter behavior matrix: document which adapters buffer bodies, which preserve streaming, and + where proxy headers/automatic decompression apply so expectations match runtime behavior. +- Example coverage: add focused guides for `axum.toml`, manifest `description` fields, logging + precedence, and route listing + body-mode behavior to reduce ambiguity. +- Spin support: add first-class Spin adapter support and document how EdgeZero manifests mirror + Spin-compatible deployments. +- Provider additions: prototype a third adapter (e.g. AWS Lambda@Edge or Vercel Edge Functions) + using the stabilized adapter API to validate cross-provider abstractions. + +## Completed (Recent) + +- Adapter stability: formalised the provider adapter contract and shipped shared docs + integration + tests so new targets plug in safely. +- Manifest ergonomics: established the `edgezero.toml` schema and CLI scaffolding for route + triggers, env/secrets, and build targets. +- Documentation baseline: published a single-source-of-truth docs set aligned with current APIs + (App::build_app entrypoints, adapter dispatch signatures, middleware signature, proxy handle + usage). +- Platform focus: Fastly Compute@Edge and Cloudflare Workers are the primary edge targets, with Axum + serving local development and native deployment needs. +- Core contracts: request/response mapping rules are now captured in the adapter contract docs. + +## Open Design Questions (for later pickup) + +- Minimum Rust version (MSRV) target and upgrade cadence. +- Caching/edge-specific headers: how much to standardize (e.g., Surrogate-Control)? +- Config overlays: strategy for environment-specific overrides in `edgezero.toml`. +- Contribution guidelines and governance model. diff --git a/docs/guide/routing.md b/docs/guide/routing.md new file mode 100644 index 0000000..98649ee --- /dev/null +++ b/docs/guide/routing.md @@ -0,0 +1,166 @@ +# Routing + +EdgeZero uses `matchit` 0.8+ for high-performance path matching with support for parameters and catch-all segments. + +## Defining Routes + +Routes are typically defined in your `edgezero.toml` manifest and wired automatically via the `app!` macro: + +```toml +[[triggers.http]] +id = "hello" +path = "/hello" +methods = ["GET"] +handler = "my_app_core::handlers::hello" + +[[triggers.http]] +id = "echo" +path = "/echo/{name}" +methods = ["GET", "POST"] +handler = "my_app_core::handlers::echo" +``` + +You can also build routes programmatically using convenience methods: + +```rust +use edgezero_core::router::RouterService; + +let router = RouterService::builder() + .get("/hello", hello_handler) + .get("/echo/{name}", echo_handler) + .post("/echo", echo_json_handler) + .build(); +``` + +Or with explicit method specification: + +```rust +use edgezero_core::router::RouterService; +use edgezero_core::http::Method; + +let router = RouterService::builder() + .route("/hello", Method::GET, hello_handler) + .route("/echo/{name}", Method::GET, echo_handler) + .route("/echo", Method::POST, echo_json_handler) + .build(); +``` + +## Path Parameters + +Define parameters with `{name}` segments: + +```rust +use edgezero_core::action; +use edgezero_core::extractor::Path; +use edgezero_core::response::Text; + +#[action] +async fn greet(Path(name): Path) -> Text { + Text::new(format!("Hello, {}!", name)) +} +``` + +For routes like `/users/{id}/posts/{post_id}`, extract multiple parameters: + +```rust +#[derive(serde::Deserialize)] +struct PostParams { + id: u64, + post_id: u64, +} + +#[action] +async fn get_post(Path(params): Path) -> Text { + Text::new(format!("User {} Post {}", params.id, params.post_id)) +} +``` + +## Catch-All Segments + +Use `{*rest}` for catch-all routes that match any remaining path: + +```rust +// Route: /files/{*path} +// Matches: /files/docs/readme.md -> path = "docs/readme.md" + +#[action] +async fn serve_file(Path(path): Path) -> Text { + Text::new(format!("Serving: {}", path)) +} +``` + +## HTTP Methods + +Specify allowed methods in your route definition: + +```toml +[[triggers.http]] +path = "/resource" +methods = ["GET", "POST", "PUT", "DELETE"] +handler = "my_app_core::handlers::resource" +``` + +Or programmatically: + +```rust +RouterService::builder() + .get("/resource", get_resource) + .post("/resource", create_resource) + .put("/resource/{id}", update_resource) + .delete("/resource/{id}", delete_resource) + .build() +``` + +EdgeZero automatically returns `405 Method Not Allowed` for requests that match a path but use an unsupported method. + +## Route Listing + +Enable route listing for debugging: + +```rust +let router = RouterService::builder() + .enable_route_listing() + .get("/hello", hello) + .build(); +``` + +This exposes a JSON endpoint at `/__edgezero/routes`: + +```json +[ + { "method": "GET", "path": "/hello" }, + { "method": "GET", "path": "/__edgezero/routes" } +] +``` + +Customize the listing path: + +```rust +RouterService::builder() + .enable_route_listing_at("/debug/routes") +``` + +## Path Syntax + +EdgeZero uses matchit's path syntax: + +| Pattern | Example | Matches | +| ----------- | ---------------- | ---------------------------- | +| `/static` | `/static` | Exact match only | +| `/{param}` | `/users/{id}` | Single segment: `/users/123` | +| `/{*catch}` | `/files/{*path}` | Rest of path: `/files/a/b/c` | + +::: warning Legacy Syntax +Axum-style `:name` parameters are **not supported**. Use `{name}` instead. +::: + +## Route Priority + +Routes are matched by specificity (static segments first, then parameters, then catch-alls). If two +routes have the same specificity, the first registered wins. Avoid ambiguous patterns that share +the same shape (for example, two routes that both look like `/users/{id}`). + +## Next Steps + +- Learn about [Handlers & Extractors](/guide/handlers) for processing requests +- Explore [Middleware](/guide/middleware) for cross-cutting concerns diff --git a/docs/guide/streaming.md b/docs/guide/streaming.md new file mode 100644 index 0000000..b0792a4 --- /dev/null +++ b/docs/guide/streaming.md @@ -0,0 +1,135 @@ +# Streaming + +EdgeZero supports streaming responses for large payloads, real-time data, and server-sent events. + +## Streaming Responses + +Use `Body::stream` to yield response chunks progressively: + +```rust +use edgezero_core::action; +use edgezero_core::body::Body; +use edgezero_core::http::Response; +use bytes::Bytes; +use futures::stream; + +#[action] +async fn stream_data() -> Response { + let chunks = vec![ + Bytes::from_static(b"Hello"), + Bytes::from_static(b" "), + Bytes::from_static(b"World"), + ]; + + let body = Body::stream(stream::iter(chunks)); + + Response::builder() + .status(200) + .header("content-type", "text/plain") + .body(body) + .unwrap() +} +``` + +## How Streaming Works + +The router keeps streams intact through the adapter layer: + +1. Your handler returns `Body::stream(...)` with a `Stream` of chunks +2. The adapter writes chunks sequentially to the provider's output API +3. Fastly uses `stream_to_client`, Cloudflare uses `ReadableStream` +4. The client receives data as it becomes available + +## Server-Sent Events + +Stream events to clients with SSE: + +```rust +use edgezero_core::action; +use edgezero_core::body::Body; +use edgezero_core::http::Response; +use bytes::Bytes; + +#[action] +async fn events() -> Response { + let events = async_stream::stream! { + for i in 0..10 { + let payload = format!("data: Event {}\n\n", i); + yield Bytes::from(payload); + } + }; + + Response::builder() + .status(200) + .header("content-type", "text/event-stream") + .header("cache-control", "no-cache") + .body(Body::stream(events)) + .unwrap() +} +``` + +## Body Modes + +Routes can specify their body handling mode in the manifest. This is parsed today and reserved +for future enforcement by adapters and router helpers: + +```toml +[[triggers.http]] +path = "/upload" +methods = ["POST"] +handler = "my_app::handlers::upload" +body-mode = "buffered" # or "stream" +``` + +| Mode | Behavior | +| ---------- | ----------------------------------------------------- | +| `buffered` | Body is fully read into memory before handler runs | +| `stream` | Body is passed as a stream for progressive processing | + +## Transparent Decompression + +EdgeZero automatically decompresses gzip and brotli responses from upstream services: + +```rust +// Proxied response with Content-Encoding: gzip is automatically decoded +let response = proxy.forward(request).await?; +// response.body is now decompressed +``` + +This happens transparently in the adapter layer using shared decoders from `edgezero-core`. + +## Memory Considerations + +Streaming is essential for: + +- Large file downloads +- Video/audio content +- Real-time data feeds +- Responses larger than available memory + +::: warning Platform Limits +Edge platforms have memory constraints. A Fastly Compute instance has ~128MB by default. Always stream large responses rather than buffering. +::: + +## Chunked Transfer + +When the response size is unknown, EdgeZero uses chunked transfer encoding: + +```rust +#[action] +async fn dynamic_content() -> Response { + let stream = generate_content_stream(); + + // No Content-Length header needed + Response::builder() + .status(200) + .header("content-type", "application/octet-stream") + .body(Body::stream(stream)) + .unwrap() +} +``` + +## Next Steps + +- Learn about [Proxying](/guide/proxying) for forwarding requests upstream +- Explore adapter-specific streaming in [Fastly](/guide/adapters/fastly) and [Cloudflare](/guide/adapters/cloudflare) guides diff --git a/docs/guide/what-is-edgezero.md b/docs/guide/what-is-edgezero.md new file mode 100644 index 0000000..3583d69 --- /dev/null +++ b/docs/guide/what-is-edgezero.md @@ -0,0 +1,48 @@ +# What is EdgeZero? + +EdgeZero is a production-ready toolkit for writing an HTTP workload once and deploying it across multiple edge providers. The core stays runtime-agnostic so it compiles cleanly to WebAssembly targets (Fastly Compute@Edge, Cloudflare Workers) and to native hosts (Axum/Tokio) without code changes. + +## Key Features + +EdgeZero provides developers with: + +- **Portable HTTP workloads** - Write your business logic once using the shared `edgezero-core` primitives, then compile to any supported target +- **Multiple deployment targets** - Deploy to Fastly Compute@Edge, Cloudflare Workers, or native Axum servers from the same codebase +- **Type-safe extractors** - Use ergonomic extractors like `Json`, `Path`, and `ValidatedQuery` for clean handler code +- **Streaming support** - Stream responses progressively with `Body::stream` for long-lived or chunked responses +- **Proxy helpers** - Forward traffic upstream with built-in `ProxyRequest` and `ProxyService` abstractions +- **CLI tooling** - Scaffold projects, run dev servers, and deploy with the `edgezero` CLI + +## How It Works + +EdgeZero separates your application into layers: + +1. **Core logic** - Your handlers and business logic live in a shared crate that depends only on `edgezero-core` +2. **Adapters** - Thin bridge crates translate provider-specific request/response types into the portable model +3. **Entrypoints** - Minimal main functions that wire the adapter to your core app + +This architecture means you can: + +- Develop locally with the Axum adapter's dev server +- Test your handlers in isolation without provider SDKs +- Deploy the same logic to multiple edge platforms + +## Supported Platforms + +| Platform | Target | Status | +| ------------------- | ------------------------ | ------ | +| Fastly Compute@Edge | `wasm32-wasip1` | Stable | +| Cloudflare Workers | `wasm32-unknown-unknown` | Stable | +| Axum/Tokio (native) | Native host | Stable | + +## Use Cases + +- **Edge APIs** - Low-latency JSON APIs running close to users +- **Proxy services** - Request forwarding with header manipulation +- **A/B testing** - Edge-side traffic splitting and experimentation +- **Content transformation** - HTML/CSS rewriting at the edge +- **Multi-cloud deployment** - Avoid vendor lock-in by targeting multiple providers + +## Next Steps + +Continue to [Getting Started](/guide/getting-started) to set up your first EdgeZero project. diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 0000000..96650af --- /dev/null +++ b/docs/index.md @@ -0,0 +1,29 @@ +--- +layout: home + +hero: + name: 'EdgeZero' + text: 'Write Once, Deploy Everywhere' + tagline: 'Production-ready toolkit for portable edge HTTP workloads' + actions: + - theme: brand + text: Get Started + link: /guide/getting-started + - theme: alt + text: View on GitHub + link: https://github.com/stackpop/edgezero + +features: + - title: Write Once, Deploy Anywhere + details: Build your HTTP workload once with runtime-agnostic core code that compiles to WebAssembly or native targets without changes. + - title: Fastly Compute@Edge Support + details: Deploy to Fastly Compute@Edge with zero-cold-start WASM binaries using the wasm32-wasip1 target. + - title: Cloudflare Workers Support + details: Run on Cloudflare Workers with seamless wrangler integration and wasm32-unknown-unknown compilation. + - title: Native Development (Axum) + details: Develop locally with a full-featured Axum/Tokio dev server, then deploy to containers or native hosts. + - title: Type-Safe Extractors + details: 'Use ergonomic extractors like Json, Path, Query, and validated variants with the #[action] macro for clean handler code' + - title: Streaming & Proxying + details: Stream responses progressively with Body::stream and forward traffic upstream with built-in proxy helpers. +--- diff --git a/docs/manifest.md b/docs/manifest.md deleted file mode 100644 index 3ead92f..0000000 --- a/docs/manifest.md +++ /dev/null @@ -1,197 +0,0 @@ -# edgezero.toml Manifest - -The `edgezero.toml` file describes an EdgeZero application, mirroring the -ergonomics of Spin's manifest while remaining provider agnostic. New workspaces -scaffolded with `edgezero new` now include this manifest by default. - -## Top-level structure - -```toml -[app] -name = "demo" -version = "0.1.0" -kind = "http" -entry = "crates/demo-core" -middleware = ["edgezero_core::middleware::RequestLogger"] - -[[triggers.http]] -id = "root" -path = "/" -methods = ["GET"] -handler = "demo_core::handlers::root" -adapters = ["fastly", "cloudflare"] -body-mode = "buffered" - -[environment] - -[[environment.variables]] -name = "API_BASE_URL" -env = "API_BASE_URL" -value = "https://example.com/api" - -[[environment.secrets]] -name = "API_TOKEN" -adapters = ["fastly", "cloudflare"] -env = "API_TOKEN" - -[adapters.fastly.adapter] -crate = "crates/demo-adapter-fastly" -manifest = "crates/demo-adapter-fastly/fastly.toml" - -[adapters.fastly.build] -target = "wasm32-wasip1" -profile = "release" - -[adapters.fastly.commands] -build = "cargo build --release --target wasm32-wasip1 -p demo-adapter-fastly" -serve = "fastly compute serve -C crates/demo-adapter-fastly" -deploy = "fastly compute deploy -C crates/demo-adapter-fastly" - -[adapters.fastly.logging] -endpoint = "stdout" -level = "info" -echo_stdout = true - - -[adapters.cloudflare.adapter] -crate = "crates/demo-adapter-cloudflare" -manifest = "crates/demo-adapter-cloudflare/wrangler.toml" - -[adapters.cloudflare.build] -target = "wasm32-unknown-unknown" -profile = "release" - -[adapters.cloudflare.commands] -build = "cargo build --release --target wasm32-unknown-unknown -p demo-adapter-cloudflare" -serve = "wrangler dev --config crates/demo-adapter-cloudflare/wrangler.toml" -deploy = "wrangler publish --config crates/demo-adapter-cloudflare/wrangler.toml" - -[adapters.cloudflare.logging] -level = "info" -``` - -### `[app]` - -Metadata about the application: the display name, the crate that exposes the router (`entry`), -and an optional `middleware = ["path::to::Middleware"]` list of zero-argument constructors -that are registered before routes are added. Each entry must resolve to a type or function -implementing `edgezero_core::middleware::Middleware`, letting global behaviour (logging, CORS, auth guards, -etc.) live alongside the manifest instead of being hard-wired in Rust. - -### `app.middleware` - -Manifest-driven equivalent of `RouterService::builder().middleware(...)`. Middleware are applied -in order before the request is handed to route handlers. For example: - -```toml -[app] -name = "app-demo" -entry = "crates/app-demo-core" -middleware = [ - "edgezero_core::middleware::RequestLogger", - "app_demo_core::cors::Cors" -] -``` - -Each item must be publicly accessible and expose a unit struct or zero-argument constructor that -implements `Middleware`. - -### `[[triggers.http]]` - -Defines HTTP routes and their handlers. Fields: - -- `id`: Stable identifier for the route (optional but useful for tooling). -- `path`: URI template understood by `edgezero-core`. -- `methods`: Allowed HTTP methods (defaults to `GET` if omitted). -- `handler`: Path to the handler function (for reference/documentation). -- `adapters`: Which adapters expose the route. Empty means “all adapters”. -- `body-mode`: Either `buffered` or `stream` to document expected behaviour. - -### `[environment]` - -Declares environment variables and secrets shared across adapters. Each entry -supports a human-friendly description, the upstream environment key (`env`, -defaulting to the `name`), an optional default `value`, and a provider filter. -When running provider commands through `edgezero-cli`, variables with a default -`value` are injected into the child process and secrets must already be present -in the environment; missing secrets will cause the command to abort with a -helpful error message. - -### `[adapters.]` - -Describes how a provider adapter is built and invoked. - -- `[adapters..adapter]`: Points at the adapter crate and any provider - manifest (e.g. `fastly.toml`, `wrangler.toml`). -- `[adapters..build]`: Build target, profile, and optional feature list. -- `[adapters..commands]`: Convenience commands for build/serve/deploy. - -The EdgeZero CLI will, when present, run these commands for `build`, `serve`, -and `deploy` before falling back to the adapter's built-in behaviour. That lets -you customise provider tooling (e.g. add flags) without recompiling the CLI. - -### `[adapters..logging]` - -Optional logging configuration nested under each adapter. Current fields: - -- `endpoint` (Fastly only): Name passed to `init_logger` (defaults to `stdout`). -- `level`: Log level (`trace`, `debug`, `info`, `warn`, `error`, `off`). Defaults to `info`. -- `echo_stdout` (Fastly only): Whether to mirror logs to stdout. Defaults to `true`. - -The Fastly adapter in the demo looks these values up before installing its -logger, and the CLI scaffolding emits the same pattern for new projects. Other -adapters can obtain provider-specific settings via -`Manifest::logging_or_default("provider")`, which guarantees a concrete log -level while leaving optional values available for provider-specific defaults at -runtime. - -Manifest parsing lives in `edgezero-core::manifest`, and CLI commands now verify -that a provider is declared before invoking adapter-specific tooling. Additional -provider metadata (extra environment bindings, secrets per provider, extra -commands) can be layered under these sections without breaking existing tooling -thanks to permissive deserialisation defaults. - -`ManifestLoader` validates basic manifest constraints (non-empty trigger paths -and handlers, well-formed logging levels, etc.) so mistakes are caught early at -startup or during macro expansion. - -`edgezero-core::ManifestLoader` provides a shared parser so applications can load -the manifest at runtime. The demo app uses this loader to build its router from -the manifest, and the CLI reuses the same types when executing provider -commands. - -## CLI integration - -`edgezero build|serve|deploy --adapter ` looks up the provider entry in -`edgezero.toml`. If a `[adapters..commands]` block supplies a `build`, -`serve`, or `deploy` command, the CLI executes it from the manifest directory. -This allows each adapter to decide how artifacts are produced (for example, -invoking `cargo build --target wasm32-wasip1` for Fastly or `wrangler dev` for -Cloudflare). When commands are omitted, the CLI falls back to the built-in -helpers shipped with the adapters (currently the Fastly adapter). - -The example app under `examples/app-demo` ships an `edgezero.toml` manifest that -drives both runtime routing and CLI commands. `app-demo-core` reads the manifest -at startup to register HTTP routes (rather than hard-coding paths in Rust), and -running `edgezero build --adapter fastly` from the workspace root invokes the -Fastly build command specified in the manifest. - -## Generating routers via macro - -Use `edgezero_core::app!("path/to/edgezero.toml", AppName);` inside your -crate to generate a `Hooks` implementation and `build_router` function directly -from the manifest. The `AppName` argument is optional; when omitted the macro -emits a struct named `App`. The macro understands the HTTP trigger list (including -methods and handler paths) and emits the wiring automatically. It also accepts -crate-qualified handler paths such as `app_demo_core::handlers::root`, rewriting -them to the local `crate::…` form the compiler expects. The demo app’s `lib.rs` -shows the minimal usage: - -```rust -mod handlers; - -edgezero_core::app!("../../edgezero.toml"); -``` - -Handlers referenced in the manifest can therefore use either `crate::` or the -crate name prefix; both get normalised during macro expansion. diff --git a/docs/package-lock.json b/docs/package-lock.json new file mode 100644 index 0000000..b3f44e2 --- /dev/null +++ b/docs/package-lock.json @@ -0,0 +1,3973 @@ +{ + "name": "edgezero-docs", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "edgezero-docs", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "@eslint/js": "^9.17.0", + "@types/node": "^24.10", + "eslint": "^9.17.0", + "prettier": "^3.4.2", + "typescript-eslint": "^8.18.2", + "vitepress": "^1.5.0" + } + }, + "node_modules/@algolia/abtesting": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.13.0.tgz", + "integrity": "sha512-Zrqam12iorp3FjiKMXSTpedGYznZ3hTEOAr2oCxI8tbF8bS1kQHClyDYNq/eV0ewMNLyFkgZVWjaS+8spsOYiQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.47.0.tgz", + "integrity": "sha512-aOpsdlgS9xTEvz47+nXmw8m0NtUiQbvGWNuSEb7fA46iPL5FxOmOUZkh8PREBJpZ0/H8fclSc7BMJCVr+Dn72w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.47.0.tgz", + "integrity": "sha512-EcF4w7IvIk1sowrO7Pdy4Ako7x/S8+nuCgdk6En+u5jsaNQM4rTT09zjBPA+WQphXkA2mLrsMwge96rf6i7Mow==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.47.0.tgz", + "integrity": "sha512-Wzg5Me2FqgRDj0lFuPWFK05UOWccSMsIBL2YqmTmaOzxVlLZ+oUqvKbsUSOE5ud8Fo1JU7JyiLmEXBtgDKzTwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.47.0.tgz", + "integrity": "sha512-Ci+cn/FDIsDxSKMRBEiyKrqybblbk8xugo6ujDN1GSTv9RIZxwxqZYuHfdLnLEwLlX7GB8pqVyqrUSlRnR+sJA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.47.0.tgz", + "integrity": "sha512-gsLnHPZmWcX0T3IigkDL2imCNtsQ7dR5xfnwiFsb+uTHCuYQt+IwSNjsd8tok6HLGLzZrliSaXtB5mfGBtYZvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.47.0.tgz", + "integrity": "sha512-PDOw0s8WSlR2fWFjPQldEpmm/gAoUgLigvC3k/jCSi/DzigdGX6RdC0Gh1RR1P8Cbk5KOWYDuL3TNzdYwkfDyA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.47.0.tgz", + "integrity": "sha512-b5hlU69CuhnS2Rqgsz7uSW0t4VqrLMLTPbUpEl0QVz56rsSwr1Sugyogrjb493sWDA+XU1FU5m9eB8uH7MoI0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.47.0.tgz", + "integrity": "sha512-WvwwXp5+LqIGISK3zHRApLT1xkuEk320/EGeD7uYy+K8WwDd5OjXnhjuXRhYr1685KnkvWkq1rQ/ihCJjOfHpQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.47.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.47.0.tgz", + "integrity": "sha512-j2EUFKAlzM0TE4GRfkDE3IDfkVeJdcbBANWzK16Tb3RHz87WuDfQ9oeEW6XiRE1/bEkq2xf4MvZesvSeQrZRDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.47.0.tgz", + "integrity": "sha512-+kTSE4aQ1ARj2feXyN+DMq0CIDHJwZw1kpxIunedkmpWUg8k3TzFwWsMCzJVkF2nu1UcFbl7xsIURz3Q3XwOXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.47.0.tgz", + "integrity": "sha512-Ja+zPoeSA2SDowPwCNRbm5Q2mzDvVV8oqxCQ4m6SNmbKmPlCfe30zPfrt9ho3kBHnsg37pGucwOedRIOIklCHw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.47.0.tgz", + "integrity": "sha512-N6nOvLbaR4Ge+oVm7T4W/ea1PqcSbsHR4O58FJ31XtZjFPtOyxmnhgCmGCzP9hsJI6+x0yxJjkW5BMK/XI8OvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.47.0.tgz", + "integrity": "sha512-z1oyLq5/UVkohVXNDEY70mJbT/sv/t6HYtCvCwNrOri6pxBJDomP9R83KOlwcat+xqBQEdJHjbrPh36f1avmZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", + "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.28.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", + "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.6" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", + "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.9.1", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", + "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "eslint-visitor-keys": "^3.4.3" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", + "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/config-array": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", + "integrity": "sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.2" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.3.tgz", + "integrity": "sha512-Kr+LPIUVKz2qkx1HAMH8q1q6azbqBAsXJUxBl/ODDuVPX45Z9DfwB8tPjTi6nNZ8BuM3nbJxC5zCAg5elnBUTQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^10.0.1", + "globals": "^14.0.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.2.tgz", + "integrity": "sha512-q1mjIoW1VX4IvSocvM/vbTiveKC4k9eLrajNEuSsmjymSDEbpGddtpfOoN7YGAqBK3NG+uqo8ia4PDTt8buCYA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + } + }, + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" + }, + "engines": { + "node": ">=18.18.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.68", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.68.tgz", + "integrity": "sha512-bQPl1zuZlX6AnofreA1v7J+hoPncrFMppqGboe/SH54jZO37meiBUGBqNOxEpc0HKfZGxJaVVJwZd4gdMYu3hw==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", + "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz", + "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz", + "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz", + "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz", + "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz", + "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz", + "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz", + "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz", + "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz", + "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz", + "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz", + "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz", + "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz", + "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz", + "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz", + "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz", + "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", + "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz", + "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz", + "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz", + "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz", + "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz", + "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz", + "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", + "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@shikijs/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", + "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", + "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^3.1.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", + "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", + "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", + "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", + "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", + "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.15", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/markdown-it": { + "version": "14.1.2", + "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", + "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/linkify-it": "^5", + "@types/mdurl": "^2" + } + }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/@types/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "24.10.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.9.tgz", + "integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.54.0.tgz", + "integrity": "sha512-hAAP5io/7csFStuOmR782YmTthKBJ9ND3WVL60hcOjvtGFb+HJxH4O5huAcmcZ9v9G8P+JETiZ/G1B8MALnWZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/type-utils": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "ignore": "^7.0.5", + "natural-compare": "^1.4.0", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^8.54.0", + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.54.0.tgz", + "integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.54.0.tgz", + "integrity": "sha512-YPf+rvJ1s7MyiWM4uTRhE4DvBXrEV+d8oC3P9Y2eT7S+HBS0clybdMIPnhiATi9vZOYDc7OQ1L/i6ga6NFYK/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.54.0", + "@typescript-eslint/types": "^8.54.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.54.0.tgz", + "integrity": "sha512-27rYVQku26j/PbHYcVfRPonmOlVI6gihHtXFbTdB5sb6qA0wdAQAbyXFVarQ5t4HRojIz64IV90YtsjQSSGlQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.54.0.tgz", + "integrity": "sha512-dRgOyT2hPk/JwxNMZDsIXDgyl9axdJI3ogZ2XWhBPsnZUv+hPesa5iuhdYt2gzwA9t8RE5ytOJ6xB0moV0Ujvw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.54.0.tgz", + "integrity": "sha512-hiLguxJWHjjwL6xMBwD903ciAwd7DmK30Y9Axs/etOkftC3ZNN9K44IuRD/EB08amu+Zw6W37x9RecLkOo3pMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/types": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.54.0.tgz", + "integrity": "sha512-PDUI9R1BVjqu7AUDsRBbKMtwmjWcn4J3le+5LpcFgWULN3LvHC5rkc9gCVxbrsrGmO1jfPybN5s6h4Jy+OnkAA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.54.0.tgz", + "integrity": "sha512-BUwcskRaPvTk6fzVWgDPdUndLjB87KYDrN5EYGetnktoeAvPtO4ONHlAZDnj5VFnUANg0Sjm7j4usBlnoVMHwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.54.0", + "@typescript-eslint/tsconfig-utils": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/visitor-keys": "8.54.0", + "debug": "^4.4.3", + "minimatch": "^9.0.5", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.4.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", + "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.54.0.tgz", + "integrity": "sha512-9Cnda8GS57AQakvRyG0PTejJNlA2xhvyNtEVIMlDWOOeEyBkYWhGPnfrIAnqxLMTSTo6q8g12XVjjev5l1NvMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.54.0", + "@typescript-eslint/types": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.54.0.tgz", + "integrity": "sha512-VFlhGSl4opC0bprJiItPQ1RfUhGDIBokcPwaFH4yiBCaNPeld/9VeXbiPO1cLyorQi1G1vL+ecBk1x8o1axORA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.54.0", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.27.tgz", + "integrity": "sha512-gnSBQjZA+//qDZen+6a2EdHqJ68Z7uybrMf3SPjEGgG4dicklwDVmMC1AeIHxtLVPT7sn6sH1KOO+tS6gwOUeQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/shared": "3.5.27", + "entities": "^7.0.0", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.27.tgz", + "integrity": "sha512-oAFea8dZgCtVVVTEC7fv3T5CbZW9BxpFzGGxC79xakTr6ooeEqmRuvQydIiDAkglZEAd09LgVf1RoDnL54fu5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.27.tgz", + "integrity": "sha512-sHZu9QyDPeDmN/MRoshhggVOWE5WlGFStKFwu8G52swATgSny27hJRWteKDSUUzUH+wp+bmeNbhJnEAel/auUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.28.5", + "@vue/compiler-core": "3.5.27", + "@vue/compiler-dom": "3.5.27", + "@vue/compiler-ssr": "3.5.27", + "@vue/shared": "3.5.27", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.6", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.27.tgz", + "integrity": "sha512-Sj7h+JHt512fV1cTxKlYhg7qxBvack+BGncSpH+8vnN+KN95iPIcqB5rsbblX40XorP+ilO7VIKlkuu3Xq2vjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.27.tgz", + "integrity": "sha512-vvorxn2KXfJ0nBEnj4GYshSgsyMNFnIQah/wczXlsNXt+ijhugmW+PpJ2cNPe4V6jpnBcs0MhCODKllWG+nvoQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.27.tgz", + "integrity": "sha512-fxVuX/fzgzeMPn/CLQecWeDIFNt3gQVhxM0rW02Tvp/YmZfXQgcTXlakq7IMutuZ/+Ogbn+K0oct9J3JZfyk3A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.27", + "@vue/shared": "3.5.27" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.27.tgz", + "integrity": "sha512-/QnLslQgYqSJ5aUmb5F0z0caZPGHRB8LEAQ1s81vHFM5CBfnun63rxhvE/scVb/j3TbBuoZwkJyiLCkBluMpeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.27", + "@vue/runtime-core": "3.5.27", + "@vue/shared": "3.5.27", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.27.tgz", + "integrity": "sha512-qOz/5thjeP1vAFc4+BY3Nr6wxyLhpeQgAE/8dDtKo6a6xdk+L4W46HDZgNmLOBUDEkFXV3G7pRiUqxjX0/2zWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.27", + "@vue/shared": "3.5.27" + }, + "peerDependencies": { + "vue": "3.5.27" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.27.tgz", + "integrity": "sha512-dXr/3CgqXsJkZ0n9F3I4elY8wM9jMJpP3pvRG52r6m0tu/MsAFIe6JpXVGeNMd/D9F4hQynWT8Rfuj0bdm9kFQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", + "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vueuse/core": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/acorn": { + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/algoliasearch": { + "version": "5.47.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.47.0.tgz", + "integrity": "sha512-AGtz2U7zOV4DlsuYV84tLp2tBbA7RPtLA44jbVH4TTpDcc1dIWmULjHSsunlhscbzDydnjuFlNhflR3nV4VJaQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.13.0", + "@algolia/client-abtesting": "5.47.0", + "@algolia/client-analytics": "5.47.0", + "@algolia/client-common": "5.47.0", + "@algolia/client-insights": "5.47.0", + "@algolia/client-personalization": "5.47.0", + "@algolia/client-query-suggestions": "5.47.0", + "@algolia/client-search": "5.47.0", + "@algolia/ingestion": "1.47.0", + "@algolia/monitoring": "1.47.0", + "@algolia/recommend": "5.47.0", + "@algolia/requester-browser-xhr": "5.47.0", + "@algolia/requester-fetch": "5.47.0", + "@algolia/requester-node-http": "5.47.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "license": "Python-2.0" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true, + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "9.39.2", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.2.tgz", + "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.1", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.1", + "@eslint/js": "9.39.2", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.6", + "debug": "^4.3.2", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^8.0.0", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } + } + }, + "node_modules/eslint-scope": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "acorn": "^8.15.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^4.2.1" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz", + "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/file-entry-cache": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "flat-cache": "^4.0.0" + }, + "engines": { + "node": ">=16.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "license": "MIT", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", + "dev": true, + "license": "MIT", + "dependencies": { + "flatted": "^3.2.9", + "keyv": "^4.5.4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/flatted": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", + "dev": true, + "license": "ISC" + }, + "node_modules/focus-trap": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", + "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tabbable": "^6.4.0" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ignore": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-yaml": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "dev": true, + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", + "dev": true, + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true, + "license": "MIT" + }, + "node_modules/oniguruma-to-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", + "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, + "node_modules/optionator": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.5" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/preact": { + "version": "10.28.2", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.28.2.tgz", + "integrity": "sha512-lbteaWGzGHdlIuiJ0l2Jq454m6kcpI1zNje6d8MlGAFlYvP2GO4ibnat7P74Esfz4sPTdM6UxtTwh/d3pwM9JA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz", + "integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==", + "dev": true, + "license": "MIT", + "bin": { + "prettier": "bin/prettier.cjs" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.56.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", + "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.56.0", + "@rollup/rollup-android-arm64": "4.56.0", + "@rollup/rollup-darwin-arm64": "4.56.0", + "@rollup/rollup-darwin-x64": "4.56.0", + "@rollup/rollup-freebsd-arm64": "4.56.0", + "@rollup/rollup-freebsd-x64": "4.56.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", + "@rollup/rollup-linux-arm-musleabihf": "4.56.0", + "@rollup/rollup-linux-arm64-gnu": "4.56.0", + "@rollup/rollup-linux-arm64-musl": "4.56.0", + "@rollup/rollup-linux-loong64-gnu": "4.56.0", + "@rollup/rollup-linux-loong64-musl": "4.56.0", + "@rollup/rollup-linux-ppc64-gnu": "4.56.0", + "@rollup/rollup-linux-ppc64-musl": "4.56.0", + "@rollup/rollup-linux-riscv64-gnu": "4.56.0", + "@rollup/rollup-linux-riscv64-musl": "4.56.0", + "@rollup/rollup-linux-s390x-gnu": "4.56.0", + "@rollup/rollup-linux-x64-gnu": "4.56.0", + "@rollup/rollup-linux-x64-musl": "4.56.0", + "@rollup/rollup-openbsd-x64": "4.56.0", + "@rollup/rollup-openharmony-arm64": "4.56.0", + "@rollup/rollup-win32-arm64-msvc": "4.56.0", + "@rollup/rollup-win32-ia32-msvc": "4.56.0", + "@rollup/rollup-win32-x64-gnu": "4.56.0", + "@rollup/rollup-win32-x64-msvc": "4.56.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/shiki": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", + "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/langs": "2.5.0", + "@shikijs/themes": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/ts-api-utils": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", + "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.12" + }, + "peerDependencies": { + "typescript": ">=4.8.4" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "license": "MIT", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "dev": true, + "license": "Apache-2.0", + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/typescript-eslint": { + "version": "8.54.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.54.0.tgz", + "integrity": "sha512-CKsJ+g53QpsNPqbzUsfKVgd3Lny4yKZ1pP4qN3jdMOg/sisIDLGyDMezycquXLE5JsEU0wp3dGNdzig0/fmSVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.54.0", + "@typescript-eslint/parser": "8.54.0", + "@typescript-eslint/typescript-estree": "8.54.0", + "@typescript-eslint/utils": "8.54.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <6.0.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vitepress": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", + "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.27", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.27.tgz", + "integrity": "sha512-aJ/UtoEyFySPBGarREmN4z6qNKpbEguYHMmXSiOGk69czc+zhs0NF6tEFrY8TZKAl8N/LYAkd4JHVd5E/AsSmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.27", + "@vue/compiler-sfc": "3.5.27", + "@vue/runtime-dom": "3.5.27", + "@vue/server-renderer": "3.5.27", + "@vue/shared": "3.5.27" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + } + } +} diff --git a/docs/package.json b/docs/package.json new file mode 100644 index 0000000..559e652 --- /dev/null +++ b/docs/package.json @@ -0,0 +1,24 @@ +{ + "name": "edgezero-docs", + "version": "1.0.0", + "description": "Documentation site for EdgeZero", + "license": "MIT", + "type": "module", + "scripts": { + "dev": "vitepress dev", + "build": "vitepress build", + "preview": "vitepress preview", + "format": "prettier --check .", + "format:write": "prettier --write .", + "lint": "eslint .", + "lint:fix": "eslint . --fix" + }, + "devDependencies": { + "@types/node": "^24.10", + "eslint": "^9.17.0", + "@eslint/js": "^9.17.0", + "typescript-eslint": "^8.18.2", + "prettier": "^3.4.2", + "vitepress": "^1.5.0" + } +}