From b04f7c16193a4aef354653f8265304665cf03135 Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Thu, 26 Mar 2026 15:19:10 +0100 Subject: [PATCH 1/7] Add documentation for API reference, configuration, and testing strategies - Created `api.md` for comprehensive API reference including interfaces, attributes, and types. - Added `configuration.md` detailing appsettings structure, environment variables, and module-specific settings. - Introduced `e2e-tests.md` for end-to-end testing setup and usage with Playwright. - Documented integration tests in `integration-tests.md`, covering HTTP endpoint testing with `WebApplicationFactory`. - Provided an overview of testing strategies in `overview.md`, outlining unit, integration, and end-to-end tests. - Added `unit-tests.md` detailing unit testing practices, including service and validator tests, fake data generators, and event handler testing. --- .github/workflows/ci.yml | 107 +- biome.json | 25 +- .../2026-03-26-documentation-site-design.md | 43 + docs/plans/2026-03-26-documentation-site.md | 462 +++ docs/site/.gitignore | 3 + docs/site/.vitepress/config.ts | 139 + docs/site/advanced/deployment.md | 225 ++ docs/site/advanced/interceptors.md | 155 + docs/site/advanced/source-generator.md | 244 ++ docs/site/advanced/type-generation.md | 269 ++ docs/site/cli/doctor.md | 117 + docs/site/cli/new-feature.md | 75 + docs/site/cli/new-module.md | 105 + docs/site/cli/new-project.md | 78 + docs/site/cli/overview.md | 43 + docs/site/frontend/components.md | 183 + docs/site/frontend/overview.md | 129 + docs/site/frontend/pages.md | 178 + docs/site/frontend/styling.md | 199 + docs/site/frontend/vite.md | 226 ++ docs/site/getting-started/introduction.md | 179 + .../site/getting-started/project-structure.md | 406 +++ docs/site/getting-started/quick-start.md | 298 ++ docs/site/guide/contracts.md | 322 ++ docs/site/guide/database.md | 362 ++ docs/site/guide/endpoints.md | 417 +++ docs/site/guide/events.md | 297 ++ docs/site/guide/inertia.md | 394 ++ docs/site/guide/menus.md | 230 ++ docs/site/guide/modules.md | 329 ++ docs/site/guide/permissions.md | 252 ++ docs/site/guide/settings.md | 251 ++ docs/site/index.md | 35 + docs/site/package.json | 13 + docs/site/public/favicon.svg | 4 + docs/site/reference/api.md | 525 +++ docs/site/reference/configuration.md | 201 + docs/site/testing/e2e-tests.md | 292 ++ docs/site/testing/integration-tests.md | 233 ++ docs/site/testing/overview.md | 88 + docs/site/testing/unit-tests.md | 195 + package-lock.json | 3233 +++++++++++------ package.json | 3 +- 43 files changed, 10478 insertions(+), 1086 deletions(-) create mode 100644 docs/plans/2026-03-26-documentation-site-design.md create mode 100644 docs/plans/2026-03-26-documentation-site.md create mode 100644 docs/site/.gitignore create mode 100644 docs/site/.vitepress/config.ts create mode 100644 docs/site/advanced/deployment.md create mode 100644 docs/site/advanced/interceptors.md create mode 100644 docs/site/advanced/source-generator.md create mode 100644 docs/site/advanced/type-generation.md create mode 100644 docs/site/cli/doctor.md create mode 100644 docs/site/cli/new-feature.md create mode 100644 docs/site/cli/new-module.md create mode 100644 docs/site/cli/new-project.md create mode 100644 docs/site/cli/overview.md create mode 100644 docs/site/frontend/components.md create mode 100644 docs/site/frontend/overview.md create mode 100644 docs/site/frontend/pages.md create mode 100644 docs/site/frontend/styling.md create mode 100644 docs/site/frontend/vite.md create mode 100644 docs/site/getting-started/introduction.md create mode 100644 docs/site/getting-started/project-structure.md create mode 100644 docs/site/getting-started/quick-start.md create mode 100644 docs/site/guide/contracts.md create mode 100644 docs/site/guide/database.md create mode 100644 docs/site/guide/endpoints.md create mode 100644 docs/site/guide/events.md create mode 100644 docs/site/guide/inertia.md create mode 100644 docs/site/guide/menus.md create mode 100644 docs/site/guide/modules.md create mode 100644 docs/site/guide/permissions.md create mode 100644 docs/site/guide/settings.md create mode 100644 docs/site/index.md create mode 100644 docs/site/package.json create mode 100644 docs/site/public/favicon.svg create mode 100644 docs/site/reference/api.md create mode 100644 docs/site/reference/configuration.md create mode 100644 docs/site/testing/e2e-tests.md create mode 100644 docs/site/testing/integration-tests.md create mode 100644 docs/site/testing/overview.md create mode 100644 docs/site/testing/unit-tests.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index be469bab..e79d0766 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,59 +6,104 @@ on: pull_request: branches: [main] -concurrency: - group: ci-${{ github.ref }} - cancel-in-progress: true +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true jobs: - lint: + build: runs-on: ubuntu-latest + steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v4 - - name: Setup Node.js - uses: actions/setup-node@v6 + - name: Setup .NET + uses: actions/setup-dotnet@v4 with: - node-version: '22' - cache: 'npm' + dotnet-version: '10.0.x' - - name: Install npm dependencies - run: npm ci + - name: Download Tailwind CSS + run: bash tools/download-tailwind.sh - - name: Lint and format check - run: npm run check + - name: Restore + run: dotnet restore - build: + - name: Build + run: dotnet build --no-restore + + test-sqlite: runs-on: ubuntu-latest - needs: lint + needs: build + steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v4 - name: Setup .NET - uses: actions/setup-dotnet@v5 + uses: actions/setup-dotnet@v4 with: dotnet-version: '10.0.x' - - name: Setup Node.js - uses: actions/setup-node@v6 + - name: Download Tailwind CSS + run: bash tools/download-tailwind.sh + + - name: Test + run: dotnet test --verbosity normal + + test-postgresql: + runs-on: ubuntu-latest + needs: build + + services: + postgres: + image: postgres:16 + env: + POSTGRES_USER: test + POSTGRES_PASSWORD: test + POSTGRES_DB: simplemodule_test + ports: + - 5432:5432 + options: >- + --health-cmd pg_isready + --health-interval 10s + --health-timeout 5s + --health-retries 5 + + steps: + - uses: actions/checkout@v4 + + - name: Setup .NET + uses: actions/setup-dotnet@v4 with: - node-version: '22' - cache: 'npm' + dotnet-version: '10.0.x' - name: Download Tailwind CSS run: bash tools/download-tailwind.sh - - name: Restore .NET - run: dotnet restore + - name: Test with PostgreSQL + run: dotnet test --verbosity normal + env: + Database__DefaultConnection: "Host=localhost;Database=simplemodule_test;Username=test;Password=test" - - name: Build .NET - run: dotnet build --no-restore + - name: Verify migrations are up-to-date + working-directory: template/SimpleModule.Host + env: + Database__DefaultConnection: "Host=localhost;Database=simplemodule_test;Username=test;Password=test" + run: dotnet ef migrations has-pending-model-changes - - name: Install npm dependencies - run: npm ci + publish: + runs-on: ubuntu-latest + needs: [test-sqlite, test-postgresql] + if: github.ref == 'refs/heads/main' - - name: Build frontend - run: npm run build + steps: + - uses: actions/checkout@v4 - - name: Test - run: dotnet test --no-build + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: '10.0.x' + + - name: Download Tailwind CSS + run: bash tools/download-tailwind.sh + + - name: Publish + run: dotnet publish template/SimpleModule.Host/SimpleModule.Host.csproj -c Release diff --git a/biome.json b/biome.json index 450cc98d..002781e1 100644 --- a/biome.json +++ b/biome.json @@ -38,14 +38,23 @@ } } }, + "overrides": [ + { + "includes": ["modules/*/src/*/types.ts"], + "linter": { + "rules": { + "suspicious": { + "noExplicitAny": "off" + }, + "complexity": { + "noBannedTypes": "off", + "noEmptyTypeParameters": "off" + } + } + } + } + ], "files": { - "includes": [ - "modules/**", - "packages/**", - "template/**", - "tests/**", - "!**/wwwroot", - "!**/types.ts" - ] + "includes": ["modules/**", "packages/**", "template/**", "tests/**", "docs/**", "!**/wwwroot"] } } diff --git a/docs/plans/2026-03-26-documentation-site-design.md b/docs/plans/2026-03-26-documentation-site-design.md new file mode 100644 index 00000000..e362ba58 --- /dev/null +++ b/docs/plans/2026-03-26-documentation-site-design.md @@ -0,0 +1,43 @@ +# Documentation Site Design + +**Date:** 2026-03-26 +**Status:** Approved +**Framework:** VitePress +**URL:** docs.simplemodule.dev + +## Overview + +Public-facing documentation site for SimpleModule framework users. Markdown-based, powered by VitePress, hosted at docs.simplemodule.dev. + +## Audience + +Developers who install SimpleModule via NuGet/npm and build modular monolith applications with it. + +## Technology Choice + +VitePress — lightest, fastest, best out-of-box developer docs experience, markdown-first. + +## Site Structure + +``` +docs/site/ +├── .vitepress/config.ts — Nav, sidebar, theme configuration +├── public/favicon.svg — Site favicon +├── index.md — Landing/hero page +├── getting-started/ — Introduction, quick start, project structure +├── guide/ — Core concepts (modules, endpoints, contracts, DB, events, permissions, menus, settings, inertia) +├── frontend/ — React + Inertia architecture, pages, components, styling, Vite +├── cli/ — sm command reference +├── testing/ — Test stack, unit/integration/e2e patterns +├── advanced/ — Source generator, type generation, interceptors, deployment +└── reference/ — Configuration options, API reference +``` + +## Key Decisions + +- No versioning (pre-1.0, single version) +- Built-in local search (miniSearch) +- Dark/light mode toggle +- GitHub link in header +- Edit on GitHub links per page +- Separate npm workspace in docs/site/ diff --git a/docs/plans/2026-03-26-documentation-site.md b/docs/plans/2026-03-26-documentation-site.md new file mode 100644 index 00000000..6c80116c --- /dev/null +++ b/docs/plans/2026-03-26-documentation-site.md @@ -0,0 +1,462 @@ +# Documentation Site Implementation Plan + +> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. + +**Goal:** Create a VitePress-powered documentation site for SimpleModule framework users, hosted at docs.simplemodule.dev. + +**Architecture:** VitePress docs site in `docs/site/` as an npm workspace. Markdown content organized into getting-started, guide, frontend, cli, testing, advanced, and reference sections. VitePress config handles nav, sidebar, search, and theme. + +**Tech Stack:** VitePress 2.x, Node.js, Markdown + +--- + +### Task 1: Scaffold VitePress project + +**Files:** +- Create: `docs/site/package.json` +- Create: `docs/site/.vitepress/config.ts` +- Create: `docs/site/index.md` +- Modify: `package.json` (add workspace) +- Modify: `biome.json` (add docs to includes) + +**Step 1: Create package.json for docs site** + +Create `docs/site/package.json`: +```json +{ + "name": "docs", + "private": true, + "scripts": { + "dev": "vitepress dev", + "build": "vitepress build", + "preview": "vitepress preview" + }, + "devDependencies": { + "vitepress": "^2.0.0" + } +} +``` + +**Step 2: Add docs/site workspace to root package.json** + +In root `package.json`, add `"docs/site"` to the `workspaces` array. + +**Step 3: Add docs to biome.json includes** + +In `biome.json`, add `"docs/**"` to the `files.includes` array. + +**Step 4: Create VitePress config** + +Create `docs/site/.vitepress/config.ts` with: +- Site title: "SimpleModule" +- Description: "Modular monolith framework for .NET" +- Navigation: Getting Started, Guide, Frontend, CLI, Testing, Advanced, Reference +- Sidebar: grouped by section with all pages +- Social links: GitHub repo +- Search: local (miniSearch) +- Edit link: GitHub source +- Dark/light mode (default) + +**Step 5: Create landing page** + +Create `docs/site/index.md` with VitePress hero layout: +- Title: SimpleModule +- Tagline: Modular monolith framework for .NET with compile-time module discovery +- Actions: Get Started, View on GitHub +- Features: Compile-time Discovery, React + Inertia.js, Module Isolation, CLI Tooling + +**Step 6: Install dependencies** + +Run: `npm install` + +**Step 7: Verify dev server starts** + +Run: `cd docs/site && npx vitepress dev --port 5173` +Expected: VitePress dev server running with hero landing page + +--- + +### Task 2: Getting Started section + +**Files:** +- Create: `docs/site/getting-started/introduction.md` +- Create: `docs/site/getting-started/quick-start.md` +- Create: `docs/site/getting-started/project-structure.md` + +**Step 1: Write introduction.md** + +Cover: +- What is SimpleModule (modular monolith framework for .NET) +- Why modular monoliths (vs microservices, vs traditional monolith) +- Key features: compile-time discovery, React+Inertia frontend, module isolation, CLI tooling +- How it works (high-level): Module attribute → source generator → auto-registration +- When to use SimpleModule + +**Step 2: Write quick-start.md** + +Cover: +- Prerequisites (.NET 10 SDK, Node.js) +- `sm new project MyApp` to scaffold +- `dotnet build && npm install` +- `npm run dev` to start development +- Creating first module: `sm new module Products` +- Creating first feature: `sm new feature Products/Browse` +- Verifying it works in browser at https://localhost:5001 + +**Step 3: Write project-structure.md** + +Cover: +- Directory layout explanation (framework/, modules/, packages/, template/, cli/, tests/) +- What each framework project does (Core, Generator, Database, Blazor, Hosting) +- Module structure pattern (src/Name, src/Name.Contracts, tests/Name.Tests) +- Frontend packages (@simplemodule/client, @simplemodule/ui, @simplemodule/theme-default) +- Host app and how it wires everything together +- Solution file and build configuration + +--- + +### Task 3: Guide section — Core concepts + +**Files:** +- Create: `docs/site/guide/modules.md` +- Create: `docs/site/guide/endpoints.md` +- Create: `docs/site/guide/contracts.md` +- Create: `docs/site/guide/database.md` + +**Step 1: Write modules.md** + +Cover: +- What is a module (self-contained feature unit) +- The `IModule` interface and its virtual methods +- The `[Module]` attribute: Name, Version, RoutePrefix, ViewPrefix +- Module lifecycle: ConfigureServices → ConfigureMiddleware → ConfigureEndpoints → ConfigureMenu → ConfigurePermissions → ConfigureSettings +- How discovery works (source generator scans at compile time) +- Generated code: `AddModules()`, `MapModuleEndpoints()`, `CollectModuleMenuItems()` +- Creating a module manually vs `sm new module` +- Example: ProductsModule walkthrough + +**Step 2: Write endpoints.md** + +Cover: +- `IEndpoint` interface for API endpoints +- `IViewEndpoint` interface for Inertia view endpoints +- The `Map(IEndpointRouteBuilder app)` method +- Auto-discovery: source generator finds all implementors +- Parameter binding rules (GET/HEAD/OPTIONS/DELETE vs POST/PUT/PATCH) +- Implicit vs explicit binding (`[FromForm]`, `[FromBody]`, `[FromQuery]`, etc.) +- Form binding limitations (List workaround) +- Example: CRUD endpoints for Products + +**Step 3: Write contracts.md** + +Cover: +- Why contracts: module isolation, no direct dependencies +- Contracts project structure (Name.Contracts.csproj) +- `IContracts` interface pattern +- `[Dto]` attribute for shared types +- TypeScript generation from DTOs +- Cross-module dependency: depend on contracts, never implementations +- Example: Orders depending on IProductContracts + +**Step 4: Write database.md** + +Cover: +- Multi-provider support: SQLite, PostgreSQL, SQL Server +- Schema isolation: table prefixes (SQLite) vs schemas (PostgreSQL/SQL Server) +- Module DbContext pattern: one DbContext per module +- `ModuleDbContextInfo` registration +- EF Core interceptor DI pattern (resolve at interception time, not constructor) +- Entity configurations +- Development: `EnsureCreated()` vs production migrations +- Example: ProductsDbContext + +--- + +### Task 4: Guide section — Communication & cross-cutting + +**Files:** +- Create: `docs/site/guide/events.md` +- Create: `docs/site/guide/permissions.md` +- Create: `docs/site/guide/menus.md` +- Create: `docs/site/guide/settings.md` +- Create: `docs/site/guide/inertia.md` + +**Step 1: Write events.md** + +Cover: +- Event bus overview: `IEventBus.PublishAsync()` +- `IEvent` marker interface +- `IEventHandler` implementation +- Partial success semantics: all handlers run, exceptions collected +- `AggregateException` behavior +- Handler best practices: stateless, independent, idempotent, no long-running work +- Exception handling pattern (try/catch in handlers for non-critical work) +- Testing partial failure scenarios + +**Step 2: Write permissions.md** + +Cover: +- Permission system overview +- `ConfigurePermissions(PermissionRegistryBuilder builder)` +- `[RequirePermission("name")]` attribute +- Policy-based authorization via claims +- Defining module permissions +- Checking permissions in endpoints + +**Step 3: Write menus.md** + +Cover: +- `IMenuRegistry` and `IMenuBuilder` +- `ConfigureMenu(IMenuBuilder menus)` on module +- Adding menu items with labels, icons, routes +- Menu ordering and grouping +- How the frontend renders menus + +**Step 4: Write settings.md** + +Cover: +- Module settings infrastructure +- `ConfigureSettings(ISettingsBuilder settings)` on module +- Defining and accessing settings +- Settings storage and retrieval + +**Step 5: Write inertia.md** + +Cover: +- What is Inertia.js and how SimpleModule uses it +- Request flow: ASP.NET → Blazor SSR → React hydration +- `Inertia.Render("Module/Page", props)` call +- How props are serialized and delivered +- Shared data and partial reloads +- Error handling (404, 500 responses) + +--- + +### Task 5: Frontend section + +**Files:** +- Create: `docs/site/frontend/overview.md` +- Create: `docs/site/frontend/pages.md` +- Create: `docs/site/frontend/components.md` +- Create: `docs/site/frontend/styling.md` +- Create: `docs/site/frontend/vite.md` + +**Step 1: Write overview.md** + +Cover: +- Architecture: React 19 + Inertia.js + Blazor SSR +- How module frontends work: each module builds pages.js via Vite library mode +- ClientApp as the Inertia bootstrap +- Dynamic page resolution by route name +- Type safety via generated TypeScript interfaces + +**Step 2: Write pages.md** + +Cover: +- Pages registry pattern: `Pages/index.ts` +- The `pages` record mapping route names to lazy imports +- Why this is critical: missing entry = silent 404 +- `validate-pages` script and CI integration +- Adding new pages: match `Inertia.Render("Name/Page")` key +- Example: Products module pages + +**Step 3: Write components.md** + +Cover: +- @simplemodule/ui package +- Radix UI + Tailwind wrappers +- Importing: `@simplemodule/ui/components`, `@simplemodule/ui/lib/utils` +- Available components +- Adding new components: `npm run ui:add` +- Component registry + +**Step 4: Write styling.md** + +Cover: +- Tailwind CSS 4.x configuration +- @simplemodule/theme-default package +- Theme customization +- Dark mode support +- Module-specific styles +- Global styles in template/SimpleModule.Host/Styles/ + +**Step 5: Write vite.md** + +Cover: +- Vite library mode for modules +- Module vite.config.ts pattern +- Externalizing React, React-DOM, @inertiajs/react +- Development workflow: `npm run dev` orchestrator +- Build modes: dev (unminified) vs prod (minified) +- Build orchestrator and watch mode +- Source maps in development + +--- + +### Task 6: CLI section + +**Files:** +- Create: `docs/site/cli/overview.md` +- Create: `docs/site/cli/new-project.md` +- Create: `docs/site/cli/new-module.md` +- Create: `docs/site/cli/new-feature.md` +- Create: `docs/site/cli/doctor.md` + +**Step 1: Write overview.md** + +Cover: +- The `sm` command: what it is, how to install +- Available commands summary table +- Global options + +**Step 2: Write new-project.md** + +Cover: `sm new project` — what it scaffolds, options, example output + +**Step 3: Write new-module.md** + +Cover: `sm new module ` — what it creates, project structure, options, example + +**Step 4: Write new-feature.md** + +Cover: `sm new feature ` — what it adds, options, example + +**Step 5: Write doctor.md** + +Cover: `sm doctor [--fix]` — what it validates, auto-fix behavior, common issues + +--- + +### Task 7: Testing section + +**Files:** +- Create: `docs/site/testing/overview.md` +- Create: `docs/site/testing/unit-tests.md` +- Create: `docs/site/testing/integration-tests.md` +- Create: `docs/site/testing/e2e-tests.md` + +**Step 1: Write overview.md** + +Cover: +- Test stack: xUnit.v3, FluentAssertions, Bogus, NSubstitute +- Test project conventions: `tests/.Tests/` +- Running tests: `dotnet test` commands +- CI strategy: SQLite + PostgreSQL +- Test naming: `Method_Scenario_Expected` + +**Step 2: Write unit-tests.md** + +Cover: +- Unit test patterns +- FakeDataGenerators (Bogus) +- Mocking with NSubstitute +- Testing event handlers +- Testing services in isolation + +**Step 3: Write integration-tests.md** + +Cover: +- `SimpleModuleWebApplicationFactory` +- In-memory SQLite by default +- Test authentication: `CreateAuthenticatedClient(params Claim[] claims)` +- Claims via `X-Test-Claims` header +- Testing endpoints end-to-end +- PostgreSQL integration tests in CI + +**Step 4: Write e2e-tests.md** + +Cover: +- Playwright setup and configuration +- Running E2E tests: `npm run test:e2e`, `npm run test:e2e:ui` +- Writing browser tests +- CI integration + +--- + +### Task 8: Advanced section + +**Files:** +- Create: `docs/site/advanced/source-generator.md` +- Create: `docs/site/advanced/type-generation.md` +- Create: `docs/site/advanced/interceptors.md` +- Create: `docs/site/advanced/deployment.md` + +**Step 1: Write source-generator.md** + +Cover: +- How the Roslyn IIncrementalGenerator works +- What it discovers: [Module] classes, IEndpoint/IViewEndpoint implementors, [Dto] types +- What it generates: AddModules(), MapModuleEndpoints(), CollectModuleMenuItems(), JSON serializers, TypeScript interfaces, Razor assembly discovery +- netstandard2.0 constraint and why +- Debugging the generator + +**Step 2: Write type-generation.md** + +Cover: +- [Dto] attribute → source generator → embedded TypeScript +- `tools/extract-ts-types.mjs` pipeline +- `npm run generate:types` command +- Output: `ClientApp/types/` directory +- Using generated types in React components +- [NoDtoGeneration] escape hatch + +**Step 3: Write interceptors.md** + +Cover: +- EF Core SaveChangesInterceptor pattern +- Circular dependency problem with DI +- Solution: inject IServiceProvider, resolve at interception time +- Correct vs incorrect patterns (with code examples) +- When to use interceptors vs other approaches + +**Step 4: Write deployment.md** + +Cover: +- Docker and docker-compose setup +- CI/CD pipeline overview (build → test-sqlite → test-postgresql → publish) +- Production database setup (PostgreSQL recommended) +- Environment configuration +- Health checks + +--- + +### Task 9: Reference section + +**Files:** +- Create: `docs/site/reference/configuration.md` +- Create: `docs/site/reference/api.md` + +**Step 1: Write configuration.md** + +Cover: +- appsettings.json structure +- Database provider configuration +- Module-specific settings +- Environment variables +- Development vs production configuration + +**Step 2: Write api.md** + +Cover: +- Core interfaces: IModule, IEndpoint, IViewEndpoint, IEventBus, IEventHandler, IEvent, IMenuRegistry +- Core attributes: [Module], [Dto], [NoDtoGeneration], [RequirePermission] +- Core types: PagedResult, ModuleDbContextInfo +- Extension methods: AddModules(), MapModuleEndpoints() + +--- + +### Task 10: Final verification + +**Step 1: Run VitePress build** + +Run: `cd docs/site && npx vitepress build` +Expected: Build succeeds with no errors, all pages rendered + +**Step 2: Preview the built site** + +Run: `cd docs/site && npx vitepress preview --port 5173` +Expected: All navigation works, sidebar links resolve, search works + +**Step 3: Verify all internal links** + +Check that all markdown cross-references resolve correctly. diff --git a/docs/site/.gitignore b/docs/site/.gitignore new file mode 100644 index 00000000..cb536060 --- /dev/null +++ b/docs/site/.gitignore @@ -0,0 +1,3 @@ +.vitepress/dist +.vitepress/cache +node_modules diff --git a/docs/site/.vitepress/config.ts b/docs/site/.vitepress/config.ts new file mode 100644 index 00000000..5b858200 --- /dev/null +++ b/docs/site/.vitepress/config.ts @@ -0,0 +1,139 @@ +import { defineConfig } from 'vitepress'; + +export default defineConfig({ + title: 'SimpleModule', + description: 'Modular monolith framework for .NET with compile-time module discovery', + + head: [['link', { rel: 'icon', href: '/favicon.svg' }]], + + cleanUrls: true, + + ignoreDeadLinks: [ + /localhost/, + ], + + themeConfig: { + logo: '/favicon.svg', + + nav: [ + { text: 'Guide', link: '/getting-started/introduction' }, + { text: 'Frontend', link: '/frontend/overview' }, + { text: 'CLI', link: '/cli/overview' }, + { text: 'Testing', link: '/testing/overview' }, + { text: 'Reference', link: '/reference/api' }, + ], + + sidebar: { + '/getting-started/': [ + { + text: 'Getting Started', + items: [ + { text: 'Introduction', link: '/getting-started/introduction' }, + { text: 'Quick Start', link: '/getting-started/quick-start' }, + { text: 'Project Structure', link: '/getting-started/project-structure' }, + ], + }, + ], + + '/guide/': [ + { + text: 'Core Concepts', + items: [ + { text: 'Modules', link: '/guide/modules' }, + { text: 'Endpoints', link: '/guide/endpoints' }, + { text: 'Contracts & DTOs', link: '/guide/contracts' }, + { text: 'Database', link: '/guide/database' }, + ], + }, + { + text: 'Communication', + items: [ + { text: 'Event Bus', link: '/guide/events' }, + { text: 'Permissions', link: '/guide/permissions' }, + { text: 'Menus', link: '/guide/menus' }, + { text: 'Settings', link: '/guide/settings' }, + { text: 'Inertia.js Integration', link: '/guide/inertia' }, + ], + }, + ], + + '/frontend/': [ + { + text: 'Frontend', + items: [ + { text: 'Overview', link: '/frontend/overview' }, + { text: 'Pages Registry', link: '/frontend/pages' }, + { text: 'UI Components', link: '/frontend/components' }, + { text: 'Styling & Theming', link: '/frontend/styling' }, + { text: 'Vite Build System', link: '/frontend/vite' }, + ], + }, + ], + + '/cli/': [ + { + text: 'CLI', + items: [ + { text: 'Overview', link: '/cli/overview' }, + { text: 'sm new project', link: '/cli/new-project' }, + { text: 'sm new module', link: '/cli/new-module' }, + { text: 'sm new feature', link: '/cli/new-feature' }, + { text: 'sm doctor', link: '/cli/doctor' }, + ], + }, + ], + + '/testing/': [ + { + text: 'Testing', + items: [ + { text: 'Overview', link: '/testing/overview' }, + { text: 'Unit Tests', link: '/testing/unit-tests' }, + { text: 'Integration Tests', link: '/testing/integration-tests' }, + { text: 'E2E Tests', link: '/testing/e2e-tests' }, + ], + }, + ], + + '/advanced/': [ + { + text: 'Advanced', + items: [ + { text: 'Source Generator', link: '/advanced/source-generator' }, + { text: 'Type Generation', link: '/advanced/type-generation' }, + { text: 'EF Core Interceptors', link: '/advanced/interceptors' }, + { text: 'Deployment', link: '/advanced/deployment' }, + ], + }, + ], + + '/reference/': [ + { + text: 'Reference', + items: [ + { text: 'API Reference', link: '/reference/api' }, + { text: 'Configuration', link: '/reference/configuration' }, + ], + }, + ], + }, + + socialLinks: [ + { icon: 'github', link: 'https://github.com/anthropics/SimpleModule' }, + ], + + editLink: { + pattern: 'https://github.com/anthropics/SimpleModule/edit/main/docs/site/:path', + text: 'Edit this page on GitHub', + }, + + search: { + provider: 'local', + }, + + footer: { + message: 'Released under the MIT License.', + copyright: 'Copyright 2026 SimpleModule Contributors', + }, + }, +}); diff --git a/docs/site/advanced/deployment.md b/docs/site/advanced/deployment.md new file mode 100644 index 00000000..89d83021 --- /dev/null +++ b/docs/site/advanced/deployment.md @@ -0,0 +1,225 @@ +--- +outline: deep +--- + +# Deployment + +SimpleModule applications deploy as standard ASP.NET applications. This guide covers Docker deployment, the CI/CD pipeline, and production configuration. + +## Docker + +### Dockerfile + +The project includes a multi-stage Dockerfile that produces a minimal runtime image: + +```dockerfile +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build +WORKDIR /src + +# Copy project files and restore (layer caching) +COPY Directory.Build.props Directory.Packages.props ./ +COPY *.slnx ./ +COPY framework/SimpleModule.Core/*.csproj framework/SimpleModule.Core/ +COPY framework/SimpleModule.Database/*.csproj framework/SimpleModule.Database/ +COPY framework/SimpleModule.Generator/*.csproj framework/SimpleModule.Generator/ +COPY framework/SimpleModule.Blazor/*.csproj framework/SimpleModule.Blazor/ +COPY template/SimpleModule.Host/*.csproj template/SimpleModule.Host/ +# ... module project files ... +RUN dotnet restore template/SimpleModule.Host/SimpleModule.Host.csproj + +# Build and publish +COPY . . +RUN dotnet publish template/SimpleModule.Host/SimpleModule.Host.csproj \ + -c Release -o /app/publish + +FROM mcr.microsoft.com/dotnet/aspnet:10.0 AS runtime +WORKDIR /app +COPY --from=build /app/publish . +EXPOSE 8080 +ENTRYPOINT ["dotnet", "SimpleModule.Host.dll"] +``` + +Key points: +- **Multi-stage build** -- the SDK image is only used for building; the final image uses the smaller `aspnet` runtime image +- **Layer caching** -- project files are copied and restored before the full source copy, so NuGet restore is cached across builds +- The runtime container exposes port **8080** + +### Docker Compose + +For local testing or simple deployments, use `docker-compose.yml`: + +```yaml +services: + api: + build: . + ports: + - "8080:8080" + environment: + - ASPNETCORE_ENVIRONMENT=Production + - Database__DefaultConnection=Host=postgres;Database=simplemodule;Username=simplemodule;Password=simplemodule + depends_on: + postgres: + condition: service_healthy + + postgres: + image: postgres:16 + environment: + POSTGRES_USER: simplemodule + POSTGRES_PASSWORD: simplemodule + POSTGRES_DB: simplemodule + ports: + - "5432:5432" + volumes: + - pgdata:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U simplemodule"] + interval: 10s + timeout: 5s + retries: 5 + +volumes: + pgdata: +``` + +Start with: + +```bash +docker compose up -d +``` + +The API service waits for PostgreSQL to pass its health check before starting. + +## CI/CD Pipeline + +The GitHub Actions workflow (`.github/workflows/ci.yml`) runs on every push to `main` and every pull request. It has four jobs: + +### 1. Build + +``` +build → Restore → Build +``` + +Verifies the solution compiles without errors. + +### 2. Test (SQLite) + +``` +test-sqlite → Run all tests with in-memory SQLite +``` + +Runs the full test suite using SQLite (the default provider). This is fast and catches most issues. + +### 3. Test (PostgreSQL) + +``` +test-postgresql → Run all tests against PostgreSQL service container +``` + +Runs the same test suite against a real PostgreSQL 16 instance. This catches provider-specific issues like SQL dialect differences. The job also verifies that EF Core migrations are up to date: + +```bash +dotnet ef migrations has-pending-model-changes +``` + +The connection string is passed via environment variable: + +``` +Database__DefaultConnection=Host=localhost;Database=simplemodule_test;Username=test;Password=test +``` + +### 4. Publish + +``` +publish → dotnet publish -c Release (only on main branch) +``` + +Runs only when tests pass on the `main` branch. Produces the release artifacts. + +### Pipeline Diagram + +``` +build ──┬── test-sqlite ────┬── publish (main only) + └── test-postgresql ─┘ +``` + +The test jobs run in parallel after build succeeds. Publish runs after both test jobs pass. + +## Production Build + +Build a release-optimized application with: + +```bash +dotnet publish template/SimpleModule.Host/SimpleModule.Host.csproj -c Release +``` + +This produces a self-contained deployment package in the default publish directory. + +## Production Database + +PostgreSQL is the recommended database for production. Configure it via `appsettings.json` or environment variables: + +### Using appsettings.json + +```json +{ + "Database": { + "DefaultConnection": "Host=your-server;Database=simplemodule;Username=app;Password=secret", + "Provider": "PostgreSQL" + } +} +``` + +### Using Environment Variables + +Environment variables override `appsettings.json`. Use the `__` (double underscore) separator for nested keys: + +```bash +export Database__DefaultConnection="Host=your-server;Database=simplemodule;Username=app;Password=secret" +export Database__Provider="PostgreSQL" +``` + +::: warning +For production, always use environment variables or a secrets manager for connection strings. Never commit credentials to source control. +::: + +## Environment Configuration + +### Key Environment Variables + +| Variable | Description | Example | +|----------|-------------|---------| +| `ASPNETCORE_ENVIRONMENT` | Runtime environment | `Production`, `Development` | +| `Database__DefaultConnection` | Database connection string | `Host=...;Database=...` | +| `Database__Provider` | Database provider | `Sqlite`, `PostgreSQL` | + +### Docker Environment + +When running in Docker, pass environment variables via `docker-compose.yml` or `docker run`: + +```bash +docker run -d \ + -p 8080:8080 \ + -e ASPNETCORE_ENVIRONMENT=Production \ + -e Database__DefaultConnection="Host=db;Database=simplemodule;Username=app;Password=secret" \ + simplemodule +``` + +## Database Initialization + +SimpleModule uses `EnsureCreated()` by default, which creates the database schema if it does not exist. For production environments with evolving schemas, use EF Core migrations per module: + +```bash +# Add a migration for a specific module's DbContext +dotnet ef migrations add InitialCreate \ + --project modules/Products/src/Products \ + --startup-project template/SimpleModule.Host + +# Apply migrations +dotnet ef database update \ + --project modules/Products/src/Products \ + --startup-project template/SimpleModule.Host +``` + +::: tip +Each module has its own DbContext and schema. In PostgreSQL, modules use separate schemas for isolation. In SQLite, modules use table prefixes. +::: diff --git a/docs/site/advanced/interceptors.md b/docs/site/advanced/interceptors.md new file mode 100644 index 00000000..3b7a5390 --- /dev/null +++ b/docs/site/advanced/interceptors.md @@ -0,0 +1,155 @@ +--- +outline: deep +--- + +# EF Core Interceptors + +SimpleModule supports EF Core `SaveChangesInterceptor` for cross-cutting concerns like audit logging, soft deletes, and timestamp management. The source generator auto-discovers interceptors and wires them into the DbContext pipeline. + +## Overview + +A `SaveChangesInterceptor` hooks into the EF Core save pipeline, allowing you to inspect or modify entities before or after they are persisted. Common use cases include: + +- Setting `CreatedAt`/`UpdatedAt` timestamps +- Recording audit log entries +- Enforcing business rules before save +- Publishing domain events after save + +## The Circular Dependency Problem + +When an interceptor depends on a service that itself depends on a `DbContext`, you get a circular dependency that causes a deadlock during DI construction: + +``` +SaveChangesInterceptor + → ISettingsContracts (constructor injection) + → SettingsService + → SettingsDbContext (deadlock!) +``` + +This happens because: + +1. EF Core resolves all registered `ISaveChangesInterceptor` instances during DbContext options construction +2. If an interceptor's constructor requires a service that transitively depends on a DbContext, the DI container tries to build the DbContext to satisfy the service, which tries to build the interceptors, which tries to build the service... + +## The Solution: Lazy Resolution + +Inject `IServiceProvider?` as an optional parameter and resolve dependencies at interception time -- not at construction time. + +### Correct Pattern + +```csharp +public sealed class AuditInterceptor( + IServiceProvider? serviceProvider = null +) : SaveChangesInterceptor +{ + public override ValueTask> SavingChangesAsync( + DbContextEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = default) + { + // Resolve at interception time -- safe, no circular dependency + var settings = serviceProvider?.GetService(); + if (settings is not null) + { + // Use the service + } + + return base.SavingChangesAsync(eventData, result, cancellationToken); + } +} +``` + +### Anti-Pattern + +```csharp +// WRONG: Causes circular dependency during DI construction +public sealed class BadInterceptor( + ISettingsContracts settings // Don't do this! +) : SaveChangesInterceptor +{ + public override ValueTask> SavingChangesAsync( + DbContextEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = default) + { + // settings was injected in constructor -- this triggers circular dependency + var value = settings.GetValue("key"); + return base.SavingChangesAsync(eventData, result, cancellationToken); + } +} +``` + +## Guidelines + +### Constructor Parameters + +- **Never** inject services that transitively depend on a DbContext into the interceptor constructor +- **Do** inject `IServiceProvider?` as an optional dependency when runtime service resolution is needed +- **Do** inject simple services (like `ILogger`, `TimeProvider`) that have no DbContext dependency + +### Service Resolution Timing + +- **Only** resolve services within interception methods: `SavingChangesAsync`, `SavedChangesAsync`, or `SaveChangesFailedAsync` +- The framework resolves all registered `ISaveChangesInterceptor` instances **lazily** during DbContext options construction + +### Null Safety + +Making `IServiceProvider?` optional (with `= null`) ensures the interceptor works in unit tests where DI may not be available: + +```csharp +public sealed class TimestampInterceptor( + IServiceProvider? serviceProvider = null +) : SaveChangesInterceptor +{ + public override ValueTask> SavingChangesAsync( + DbContextEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = default) + { + if (eventData.Context is null) + return base.SavingChangesAsync(eventData, result, cancellationToken); + + var now = serviceProvider?.GetService()?.GetUtcNow() + ?? DateTimeOffset.UtcNow; + + foreach (var entry in eventData.Context.ChangeTracker.Entries()) + { + if (entry.State == EntityState.Added && entry.Entity is IHasCreatedAt created) + { + created.CreatedAt = now; + } + + if (entry.State == EntityState.Modified && entry.Entity is IHasUpdatedAt updated) + { + updated.UpdatedAt = now; + } + } + + return base.SavingChangesAsync(eventData, result, cancellationToken); + } +} +``` + +## Auto-Discovery + +The source generator automatically discovers classes that implement `SaveChangesInterceptor` (or `ISaveChangesInterceptor`) in module assemblies. Discovered interceptors are registered in the generated DbContext configuration -- you do not need to manually register them. + +The generator records each interceptor's constructor parameters to ensure correct DI wiring: + +```csharp +// From DiscoveryData +internal readonly record struct InterceptorInfoRecord( + string FullyQualifiedName, + string ModuleName, + ImmutableArray ConstructorParamTypeFqns +); +``` + +## Summary + +| Do | Don't | +|----|-------| +| Inject `IServiceProvider?` as optional | Inject services that depend on DbContext | +| Resolve services inside interception methods | Resolve services in the constructor | +| Use `?.GetService()` for null safety | Assume services are always available | +| Keep constructors minimal | Add complex initialization logic to constructors | diff --git a/docs/site/advanced/source-generator.md b/docs/site/advanced/source-generator.md new file mode 100644 index 00000000..5024cb1f --- /dev/null +++ b/docs/site/advanced/source-generator.md @@ -0,0 +1,244 @@ +--- +outline: deep +--- + +# Source Generator + +SimpleModule uses a Roslyn **incremental source generator** (`IIncrementalGenerator`) to discover modules, endpoints, DTOs, and other framework constructs at compile time. This eliminates runtime reflection entirely -- everything is wired up in generated code before your application starts. + +## Why IIncrementalGenerator? + +The Roslyn SDK offers two generator APIs: + +| API | Caching | Performance | +|-----|---------|-------------| +| `ISourceGenerator` | None -- runs on every keystroke | Slow in large solutions | +| `IIncrementalGenerator` | Built-in incremental caching | Only re-generates when inputs change | + +SimpleModule uses `IIncrementalGenerator` because modular monoliths reference many assemblies. Without incremental caching, the generator would re-scan every referenced assembly on every keystroke, causing noticeable IDE lag. + +The generator extracts an **equatable data model** (`DiscoveryData`) from the compilation. The incremental pipeline compares the new data model against the cached one and skips re-generation when nothing has changed. + +## The netstandard2.0 Constraint + +Roslyn analyzers and source generators **must** target `netstandard2.0`. This is a hard requirement from the Roslyn compiler infrastructure -- the generator runs inside the compiler process, which loads analyzers as netstandard2.0 assemblies. + +The generator's project file reflects this: + +```xml + + + netstandard2.0 + latest + true + + + + + + +``` + +::: warning +If you add dependencies to the generator project, they must also be netstandard2.0 compatible. Modern .NET APIs like `Span` or `ImmutableArray` are available through the `Microsoft.CodeAnalysis.CSharp` package. +::: + +## What It Discovers + +The generator scans both referenced assemblies and the current compilation for: + +| Construct | How It's Found | +|-----------|----------------| +| **Modules** | Classes decorated with `[Module]` attribute | +| **API Endpoints** | Classes implementing `IEndpoint` | +| **View Endpoints** | Classes implementing `IViewEndpoint` | +| **DTO Types** | Public types in `*.Contracts` assemblies (opt-out with `[NoDtoGeneration]`) | +| **DbContexts** | Classes inheriting from `DbContext` in module assemblies | +| **Entity Configurations** | Classes implementing `IEntityTypeConfiguration` | +| **Contract Interfaces** | Interfaces in `*.Contracts` assemblies with implementations | +| **Permission Classes** | Sealed classes implementing `IModulePermissions` | +| **Interceptors** | Classes implementing `ISaveChangesInterceptor` | +| **Vogen Value Objects** | Types with Vogen value object markers | + +All discovered data is collected into a `DiscoveryData` record: + +```csharp +internal readonly record struct DiscoveryData( + ImmutableArray Modules, + ImmutableArray DtoTypes, + ImmutableArray DbContexts, + ImmutableArray EntityConfigs, + ImmutableArray Dependencies, + ImmutableArray IllegalReferences, + ImmutableArray ContractInterfaces, + ImmutableArray ContractImplementations, + ImmutableArray PermissionClasses, + ImmutableArray Interceptors, + ImmutableArray VogenValueObjects +); +``` + +## What It Generates + +The generator feeds `DiscoveryData` through a pipeline of **emitters**, each responsible for generating a specific source file: + +### Core Extension Methods + +| Emitter | Generated File | Purpose | +|---------|---------------|---------| +| `ModuleExtensionsEmitter` | `ModuleExtensions.g.cs` | `AddModules()` -- registers all module services, contract implementations, permissions, and JSON serializers | +| `EndpointExtensionsEmitter` | `EndpointExtensions.g.cs` | `MapModuleEndpoints()` -- maps all `IEndpoint` and `IViewEndpoint` implementations with route groups and authorization | +| `MenuExtensionsEmitter` | `MenuExtensions.g.cs` | `CollectModuleMenuItems()` -- collects menu items from all modules that implement `ConfigureMenu` | +| `SettingsExtensionsEmitter` | `SettingsExtensions.g.cs` | Collects settings definitions from modules that implement `ConfigureSettings` | +| `HostingExtensionsEmitter` | `HostingExtensions.g.cs` | Top-level `AddSimpleModule()` that orchestrates all registrations | + +### Frontend Integration + +| Emitter | Generated File | Purpose | +|---------|---------------|---------| +| `TypeScriptDefinitionsEmitter` | `DtoTypeScript_{Module}.g.cs` | Embeds TypeScript interface definitions as comments in C# files, one per module | +| `ViewPagesEmitter` | `ViewPages.g.cs` | Page constants for all `IViewEndpoint` views | +| `PageRegistryEmitter` | `PageRegistry.g.cs` | Registry mapping page names to module assemblies | + +### Database + +| Emitter | Generated File | Purpose | +|---------|---------------|---------| +| `HostDbContextEmitter` | `HostDbContext.g.cs` | Generates the host DbContext that aggregates module DbSets | +| `DbContextRegistryEmitter` | `DbContextRegistry.g.cs` | Registers `ModuleDbContextInfo` for each module's DbContext | +| `ValueConverterConventionsEmitter` | `ValueConverterConventions.g.cs` | Applies EF Core value converters for Vogen value objects | + +### Infrastructure + +| Emitter | Generated File | Purpose | +|---------|---------------|---------| +| `JsonResolverEmitter` | `ModulesJsonResolver.g.cs` | AOT-compatible JSON type info resolver for all DTO types | +| `RazorComponentExtensionsEmitter` | `RazorComponentExtensions.g.cs` | Discovers assemblies containing Razor components | +| `DiagnosticEmitter` | _(diagnostics only)_ | Reports compiler warnings/errors for illegal module references and other issues | + +## The Discovery-Emit Pipeline + +The generator follows a two-phase architecture: + +``` +Compilation + │ + ▼ +SymbolDiscovery.Extract(compilation) + │ Scans all referenced assemblies + │ Finds [Module] classes, IEndpoint types, [Dto] types, etc. + │ Returns equatable DiscoveryData + │ + ▼ +Incremental Cache Check + │ Compares new DiscoveryData with cached version + │ If equal → skip generation (no IDE lag) + │ If changed → proceed to emission + │ + ▼ +Emitters[].Emit(context, data) + │ Each emitter generates one source file + │ ModuleExtensionsEmitter → ModuleExtensions.g.cs + │ EndpointExtensionsEmitter → EndpointExtensions.g.cs + │ TypeScriptDefinitionsEmitter → DtoTypeScript_*.g.cs + │ ... (14 emitters total) + │ + ▼ +Generated source added to compilation +``` + +The main generator class is minimal -- it delegates all work to `SymbolDiscovery` and the emitter array: + +```csharp +[Generator] +public class ModuleDiscovererGenerator : IIncrementalGenerator +{ + private static readonly IEmitter[] Emitters = + [ + new DiagnosticEmitter(), + new ModuleExtensionsEmitter(), + new EndpointExtensionsEmitter(), + new MenuExtensionsEmitter(), + new SettingsExtensionsEmitter(), + new RazorComponentExtensionsEmitter(), + new ViewPagesEmitter(), + new PageRegistryEmitter(), + new JsonResolverEmitter(), + new TypeScriptDefinitionsEmitter(), + new HostingExtensionsEmitter(), + new HostDbContextEmitter(), + new ValueConverterConventionsEmitter(), + new DbContextRegistryEmitter(), + ]; + + public void Initialize(IncrementalGeneratorInitializationContext context) + { + var dataProvider = context.CompilationProvider.Select( + static (compilation, _) => SymbolDiscovery.Extract(compilation) + ); + + context.RegisterSourceOutput( + dataProvider, + static (spc, data) => + { + if (data.Modules.Length == 0) + return; + + foreach (var emitter in Emitters) + { + emitter.Emit(spc, data); + } + } + ); + } +} +``` + +## Module Dependency Ordering + +The generator performs **topological sorting** of modules based on their contract dependencies. This ensures that when `AddModules()` calls `ConfigureServices` on each module, dependencies are initialized before dependents. The generated code includes phase comments: + +```csharp +// Phase 1: No dependencies +((IModule)s_Dashboard_DashboardModule).ConfigureServices(services, configuration); + +// Phase 2: Depends on Products +((IModule)s_Orders_OrdersModule).ConfigureServices(services, configuration); +``` + +## Debugging Tips + +### Inspecting Generated Files + +Generated source files are written to the `obj/` directory during build. To find them: + +```bash +# Find all generated files for the host project +find template/SimpleModule.Host/obj -name "*.g.cs" | head -20 +``` + +Or in Visual Studio, expand **Dependencies > Analyzers > SimpleModule.Generator** in Solution Explorer to see all generated files. + +### Common Issues + +::: tip Generator Not Running +If you don't see generated files, verify that the host project references the generator correctly: +```xml + +``` +::: + +::: tip Module Not Discovered +The generator scans referenced assemblies for `[Module]` classes. Ensure your module project is referenced by the host project and the class has the `[Module]` attribute. +::: + +::: tip Stale Generated Code +If the generator seems to produce outdated code, clean and rebuild: +```bash +dotnet clean +dotnet build +``` +The incremental cache occasionally needs a full rebuild to reset. +::: diff --git a/docs/site/advanced/type-generation.md b/docs/site/advanced/type-generation.md new file mode 100644 index 00000000..6cb4577e --- /dev/null +++ b/docs/site/advanced/type-generation.md @@ -0,0 +1,269 @@ +--- +outline: deep +--- + +# Type Generation + +SimpleModule automatically generates TypeScript interfaces from your C# DTO types. This ensures your React frontend always has accurate type definitions that match the server-side data shapes, with zero manual synchronization. + +## The Pipeline + +Type generation is a three-stage pipeline: + +``` +C# DTO types in *.Contracts assemblies + │ + ▼ +Source Generator (compile time) + │ Reads public types from Contracts assemblies + │ Maps C# types to TypeScript types + │ Embeds TS interfaces as comments in DtoTypeScript_{Module}.g.cs + │ + ▼ +extract-ts-types.mjs (build tool) + │ Reads generated .g.cs files from obj/ directory + │ Extracts TypeScript interfaces from comment blocks + │ Writes types.ts into each module's src/ directory + │ + ▼ +modules/{Module}/src/{Module}/types.ts + Ready for import in React components +``` + +## Marking Types for Generation + +### Convention-Based Discovery + +By default, **all public types** in `*.Contracts` assemblies are treated as DTOs and included in TypeScript generation. You do not need to add any attributes to your types. + +For example, this class in `Products.Contracts`: + +```csharp +namespace SimpleModule.Products.Contracts; + +public class Product +{ + public ProductId Id { get; set; } + public string Name { get; set; } = string.Empty; + public decimal Price { get; set; } +} +``` + +is automatically discovered and generates a TypeScript interface. + +### The `[Dto]` Attribute + +The `[Dto]` attribute can be used to explicitly mark types for generation in assemblies that are not `*.Contracts` assemblies: + +```csharp +using SimpleModule.Core; + +[Dto] +public class CustomResponse +{ + public string Message { get; set; } = string.Empty; + public int Code { get; set; } +} +``` + +The attribute targets classes and structs: + +```csharp +[AttributeUsage( + AttributeTargets.Class | AttributeTargets.Struct, + AllowMultiple = false, + Inherited = false +)] +public sealed class DtoAttribute : Attribute { } +``` + +### The `[NoDtoGeneration]` Escape Hatch + +To exclude a type in a Contracts assembly from TypeScript generation, apply `[NoDtoGeneration]`: + +```csharp +using SimpleModule.Core; + +namespace SimpleModule.Products.Contracts; + +[NoDtoGeneration] +public class InternalHelper +{ + // This type will not appear in types.ts +} +``` + +This attribute can be applied to classes, structs, and interfaces: + +```csharp +[AttributeUsage( + AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, + AllowMultiple = false, + Inherited = false +)] +public sealed class NoDtoGenerationAttribute : Attribute { } +``` + +::: tip When to use [NoDtoGeneration] +Use it for types that live in a Contracts assembly but are not meant for the frontend -- for example, contract interfaces like `IProductContracts`, internal helper types, or types used only for inter-module communication. +::: + +## Running Type Generation + +Generate TypeScript types with: + +```bash +npm run generate:types +``` + +This runs the `extract-ts-types.mjs` tool, which reads the source generator's output files from the `obj/` directory and writes `types.ts` files into each module. + +::: info +You must build the .NET project before running type generation, since the tool reads the generated `.g.cs` files from the build output. +::: + +## Output Location + +Each module gets its own `types.ts` file at: + +``` +modules/{ModuleName}/src/{ModuleName}/types.ts +``` + +For example, the Products module produces: + +``` +modules/Products/src/Products/types.ts +``` + +The file is marked as auto-generated and should not be edited manually: + +```typescript +// Auto-generated from [Dto] types — do not edit +export interface CreateProductRequest { + name: string; + price: number; +} + +export interface Product { + id: number; + name: string; + price: number; +} + +export interface UpdateProductRequest { + name: string; + price: number; +} +``` + +## Type Mapping + +The source generator maps C# types to TypeScript types using the following rules: + +### Primitive Types + +| C# Type | TypeScript Type | +|---------|----------------| +| `string` | `string` | +| `int`, `long`, `short`, `byte` | `number` | +| `float`, `double`, `decimal` | `number` | +| `bool` | `boolean` | +| `DateTime`, `DateTimeOffset`, `DateOnly`, `TimeOnly` | `string` | +| `Guid` | `string` | + +### Nullable Types + +`Nullable` (or `T?`) maps to `T | null`: + +| C# Type | TypeScript Type | +|---------|----------------| +| `int?` | `number \| null` | +| `string?` | `string \| null` | +| `DateTime?` | `string \| null` | + +### Collection Types + +Generic collections map to TypeScript arrays: + +| C# Type | TypeScript Type | +|---------|----------------| +| `List` | `T[]` | +| `IList` | `T[]` | +| `IEnumerable` | `T[]` | +| `IReadOnlyList` | `T[]` | +| `ICollection` | `T[]` | + +### DTO References + +When a property references another `[Dto]` type, the generator resolves it to the TypeScript interface name rather than `any`. + +### Value Objects + +Vogen value objects (strongly-typed IDs, etc.) are mapped to their **underlying primitive type**. For example, a `ProductId` wrapping `int` maps to `number` in TypeScript. + +### Unknown Types + +Any type not recognized by the mapping rules falls back to `any`. + +## Using Generated Types in React + +Import the generated types directly in your React components: + +```tsx +import type { Product, CreateProductRequest } from '../types'; + +interface BrowseProps { + products: Product[]; +} + +export default function Browse({ products }: BrowseProps) { + return ( + + + + + + + + + {products.map((product) => ( + + + + + ))} + +
NamePrice
{product.name}${product.price.toFixed(2)}
+ ); +} +``` + +## How It Works Internally + +The source generator embeds TypeScript interfaces as **comments inside C# files**. This approach allows the TS definitions to travel through the normal build pipeline without affecting compilation: + +```csharp +// +#if SIMPLEMODULE_TS +/* +// @module Products + +export interface Product { + id: number; + name: string; + price: number; +} + +*/ +#endif +``` + +The `extract-ts-types.mjs` tool then: + +1. Reads all `DtoTypeScript_*.g.cs` files from the generated output directory +2. Extracts the module name from the `// @module` comment +3. Parses the TypeScript interfaces from the comment block +4. Writes a `types.ts` file to the module's source directory + +Property names are automatically converted from `PascalCase` (C#) to `camelCase` (TypeScript) during generation, matching the default `System.Text.Json` serialization behavior. diff --git a/docs/site/cli/doctor.md b/docs/site/cli/doctor.md new file mode 100644 index 00000000..ca6bccbc --- /dev/null +++ b/docs/site/cli/doctor.md @@ -0,0 +1,117 @@ +--- +outline: deep +--- + +# sm doctor + +Validates your project structure and conventions against SimpleModule requirements. Reports issues as PASS, WARN, or FAIL, and can auto-fix common problems. + +## Usage + +```bash +sm doctor [--fix] +``` + +## Options + +| Option | Description | +|--------|-------------| +| `--fix` | Auto-fix missing `.slnx` entries and project references | + +## Checks Performed + +The doctor command runs five categories of checks: + +### 1. Solution Structure + +Verifies the foundational directory layout exists: + +| Check | Severity | +|-------|----------| +| `.slnx` solution file exists | FAIL | +| `src/` directory exists | FAIL | +| `src/modules/` directory exists | FAIL | +| `tests/` directory exists | FAIL | + +### 2. Project References + +For each discovered module, verifies the host/API `.csproj` has a `` to the module's implementation project. + +| Check | Severity | Auto-fixable | +|-------|----------|-------------| +| API project references each module | FAIL | Yes | + +### 3. Solution File Entries + +For each discovered module, verifies the `.slnx` file contains folder entries for the module's projects (contracts, implementation, tests). + +| Check | Severity | Auto-fixable | +|-------|----------|-------------| +| Module entries exist in `.slnx` | FAIL | Yes | + +### 4. .csproj Conventions + +Validates project file conventions for each module: + +| Check | Severity | +|-------|----------| +| Module `.csproj` has `` | FAIL | +| Contracts `.csproj` only references Core (not other modules) | WARN | +| Generator project targets `netstandard2.0` | FAIL | + +### 5. Module Pattern + +Validates that each module follows the expected file structure: + +| Check | Severity | +|-------|----------| +| `{Module}Module.cs` exists | WARN | +| `{Module}Constants.cs` exists | WARN | +| `{Module}DbContext.cs` exists | WARN | +| `Endpoints/` directory exists | WARN | + +## Auto-Fix Behavior + +When you pass `--fix`, the doctor attempts to repair the following issues: + +- **Missing `.slnx` entries** -- adds folder entries for the module's three projects +- **Missing project references** -- adds a `` in the host `.csproj` pointing to the module + +After auto-fixing, all checks are re-run and the results table reflects the current state. + +```bash +sm doctor --fix +# Attempting auto-fix... +# Fixed: added Invoices to .slnx +# Fixed: added Invoices reference to API csproj +``` + +::: info +Auto-fix only handles structural wiring (slnx entries and project references). It does not create missing files like `Module.cs` or `DbContext.cs` -- use `sm new module` for that. +::: + +## Output + +The doctor displays a table with three columns: + +``` +| Status | Check | Details | +|--------|------------------------|--------------------------------------| +| PASS | Solution file | .slnx exists | +| PASS | Directory src/ | exists | +| FAIL | API -> Invoices | missing project reference in API | +| WARN | Invoices/Endpoints/ | Endpoints directory missing | +``` + +The command exits with code `0` when all checks pass (warnings are allowed) and code `1` when any check fails. + +## CI Integration + +Add `sm doctor` to your CI pipeline to catch structural issues early: + +```yaml +- name: Validate project structure + run: sm doctor +``` + +The non-zero exit code on failure makes it suitable for CI gates. Pair it with `--fix` in a pre-commit hook for local development if desired. diff --git a/docs/site/cli/new-feature.md b/docs/site/cli/new-feature.md new file mode 100644 index 00000000..0ba01912 --- /dev/null +++ b/docs/site/cli/new-feature.md @@ -0,0 +1,75 @@ +--- +outline: deep +--- + +# sm new feature + +Adds a new endpoint to an existing module. The generated endpoint implements `IEndpoint` and is automatically discovered by the Roslyn source generator -- no manual registration needed. + +## Usage + +```bash +sm new feature [name] +``` + +If you omit required options, the CLI prompts you interactively with selection menus. + +## Options + +| Option | Description | +|--------|-------------| +| `[name]` | Feature name in PascalCase (e.g., `UpdateInvoice`). Prompted if omitted. | +| `-m, --module ` | Target module name. If omitted, presents a selection list of existing modules. | +| `--method ` | HTTP method: `GET`, `POST`, `PUT`, or `DELETE`. Prompted if omitted. | +| `-r, --route ` | Route pattern (e.g., `/{id}`). Defaults to `/{id}` when prompted. | +| `--validator` | Include a request validator class alongside the endpoint. | + +## What Gets Created + +Running `sm new feature UpdateInvoice --module Invoices --method PUT --route /{id} --validator` generates: + +``` +modules/Invoices/src/Invoices/Endpoints/Invoices/ + UpdateInvoiceEndpoint.cs # IEndpoint implementation + UpdateInvoiceRequestValidator.cs # Request validator (when --validator is used) +``` + +### Endpoint Auto-Discovery + +The generated endpoint implements `IEndpoint`, which the Roslyn source generator scans at compile time. There is no need to manually register routes -- just build the project and the endpoint is live. + +### Validator (Optional) + +When you pass `--validator`, the CLI generates a companion validator class that validates the request before the endpoint logic runs. + +## Interactive Mode + +When run without flags, the CLI walks you through each option: + +```bash +sm new feature +# ? Feature name (PascalCase): UpdateInvoice +# ? Select a module: Invoices +# ? HTTP method: PUT +# ? Route pattern: /{id} +# ? Include a validator? Yes +``` + +## Requirements + +- Must be run from within a SimpleModule project (the CLI looks for a `.slnx` file) +- At least one module must exist. If no modules are found, the CLI directs you to run `sm new module` first + +::: tip View Endpoints +If your feature is a page (view endpoint using `Inertia.Render`), remember to add a corresponding entry in your module's `Pages/index.ts`. See the [Pages Registry Pattern](/guide/modules#pages-registry) for details. +::: + +## Example + +```bash +# Fully specified +sm new feature CreateInvoice --module Invoices --method POST --route / --validator + +# Interactive +sm new feature +``` diff --git a/docs/site/cli/new-module.md b/docs/site/cli/new-module.md new file mode 100644 index 00000000..ccaeb075 --- /dev/null +++ b/docs/site/cli/new-module.md @@ -0,0 +1,105 @@ +--- +outline: deep +--- + +# sm new module + +Creates a new module following the three-project pattern: implementation, contracts, and tests. The CLI also wires the module into the solution file and adds a project reference to the host. + +## Usage + +```bash +sm new module [name] +``` + +If you omit the name, the CLI prompts you interactively. + +## Options + +| Option | Description | +|--------|-------------| +| `[name]` | Module name in PascalCase (e.g., `Invoices`). Must start with an uppercase letter. Prompted if omitted. | + +## What Gets Created + +Running `sm new module Invoices` generates the following structure. The CLI automatically derives the singular name (e.g., `Invoices` becomes `Invoice`) for class names. + +### Contracts Project + +``` +modules/Invoices/src/Invoices.Contracts/ + Invoices.Contracts.csproj # References Core only + IInvoiceContracts.cs # Public contract interface + Invoice.cs # [Dto] type for cross-module use + Events/ + InvoiceCreatedEvent.cs # Domain event +``` + +### Implementation Project + +``` +modules/Invoices/src/Invoices/ + Invoices.csproj # References Core + Contracts + InvoicesModule.cs # IModule with [Module("Invoices")] + InvoicesConstants.cs # Module constants (permissions, etc.) + InvoicesDbContext.cs # EF Core DbContext + InvoiceService.cs # Service implementing IInvoiceContracts + Endpoints/Invoices/ + GetAllEndpoint.cs # IEndpoint (auto-discovered) +``` + +### Test Project + +``` +modules/Invoices/tests/Invoices.Tests/ + Invoices.Tests.csproj # xUnit test project + GlobalUsings.cs # Common test usings + Unit/ + InvoiceServiceTests.cs # Service unit test skeleton + Integration/ + InvoicesEndpointTests.cs # Endpoint integration test skeleton +``` + +## Three-Project Pattern + +Each module follows a strict separation: + +| Project | Purpose | Dependencies | +|---------|---------|-------------| +| `Invoices.Contracts` | Public API surface -- interfaces, DTOs, events | Core only | +| `Invoices` | Implementation -- services, DbContext, endpoints | Core + Contracts | +| `Invoices.Tests` | Unit and integration tests | Implementation + Tests.Shared | + +This ensures modules communicate only through contracts, never through implementation details. + +## Automatic Wiring + +After creating the files, the CLI automatically: + +1. **Adds entries to `.slnx`** -- all three projects appear in the solution with proper folder grouping +2. **Adds a `ProjectReference`** in the host/API `.csproj` pointing to the module's implementation project + +This means the module is immediately discoverable by the Roslyn source generator on the next build. + +## Post-Creation Steps + +After the CLI finishes: + +```bash +dotnet build # source generator discovers the new module +dotnet run --project src/MyApp.Api +``` + +::: tip +Run `sm doctor` after creating a module to verify all references and solution entries are correct. +::: + +## Example + +```bash +# Interactive mode +sm new module + +# Direct mode +sm new module Invoices +``` diff --git a/docs/site/cli/new-project.md b/docs/site/cli/new-project.md new file mode 100644 index 00000000..faf03072 --- /dev/null +++ b/docs/site/cli/new-project.md @@ -0,0 +1,78 @@ +--- +outline: deep +--- + +# sm new project + +Scaffolds a complete SimpleModule solution with all the foundational projects and configuration files. + +## Usage + +```bash +sm new project [name] +``` + +If you omit the name, the CLI prompts you interactively. + +## Options + +| Option | Description | +|--------|-------------| +| `[name]` | Project name in PascalCase (e.g., `MyApp`). Prompted if omitted. | +| `-o, --output ` | Output directory. Defaults to the current directory. | + +## What Gets Created + +Running `sm new project MyApp` generates the following structure: + +``` +MyApp/ + MyApp.slnx # Solution file + Directory.Build.props # Shared MSBuild properties + Directory.Packages.props # Central package management + global.json # SDK version pinning + src/ + MyApp.Api/ + MyApp.Api.csproj # Host/API project + Program.cs # Entry point with generated extensions + MyApp.Core/ + MyApp.Core.csproj # Core framework (IModule, IEndpoint, etc.) + MyApp.Database/ + MyApp.Database.csproj # Database infrastructure + MyApp.Generator/ + MyApp.Generator.csproj # Roslyn source generator (netstandard2.0) + modules/ # Empty directory for modules + tests/ + MyApp.Tests.Shared/ + MyApp.Tests.Shared.csproj # Shared test infrastructure +``` + +## Project Details + +- **Api** -- the host application that calls generated `AddModules()` and `MapModuleEndpoints()` extension methods +- **Core** -- defines the `IModule` interface, `[Module]` attribute, `IEndpoint`, `[Dto]`, event bus, and menu system +- **Database** -- multi-provider database support with schema isolation per module +- **Generator** -- Roslyn incremental source generator targeting `netstandard2.0` for compile-time module discovery +- **Tests.Shared** -- `WebApplicationFactory` base class, fake data generators, and test authentication + +## After Scaffolding + +```bash +cd MyApp +sm new module Products # create your first module +dotnet build # build the solution +``` + +::: warning +The CLI will refuse to create a project in a non-empty directory. Choose a clean location or use the `--output` flag to specify a different path. +::: + +## Example + +```bash +# Create in current directory +sm new project MyApp + +# Create in a specific directory +sm new project MyApp --output ~/projects +``` diff --git a/docs/site/cli/overview.md b/docs/site/cli/overview.md new file mode 100644 index 00000000..0c9c9228 --- /dev/null +++ b/docs/site/cli/overview.md @@ -0,0 +1,43 @@ +--- +outline: deep +--- + +# CLI Overview + +The `sm` CLI tool streamlines common development tasks in SimpleModule projects. It scaffolds new solutions, modules, and features, and validates your project structure against framework conventions. + +## Installation + +Install the CLI as a global .NET tool: + +```bash +dotnet tool install -g SimpleModule.Cli +``` + +Once installed, the `sm` command is available globally from any terminal. + +## Commands + +| Command | Description | +|---------|-------------| +| `sm new project [name]` | Scaffold a new SimpleModule solution | +| `sm new module [name]` | Create a module with contracts, endpoints, and tests | +| `sm new feature [name]` | Add a feature endpoint to an existing module | +| `sm doctor [--fix]` | Validate project structure and conventions | + +All `sm new` commands support interactive prompts. If you omit required arguments, the CLI will ask for them using an interactive selection UI powered by [Spectre.Console](https://spectreconsole.net/). + +## How It Works + +The CLI is built with `Spectre.Console.Cli` and packaged as a .NET tool (`PackAsTool`). It discovers your solution by searching for a `.slnx` file in the current directory or parent directories. Most commands require being run from within an existing SimpleModule project. + +::: tip +Run `sm doctor` after creating modules or making structural changes to verify everything is wired up correctly. +::: + +## Next Steps + +- [Scaffold a new project](./new-project) with `sm new project` +- [Create a module](./new-module) with `sm new module` +- [Add a feature](./new-feature) with `sm new feature` +- [Validate your project](./doctor) with `sm doctor` diff --git a/docs/site/frontend/components.md b/docs/site/frontend/components.md new file mode 100644 index 00000000..042f87a4 --- /dev/null +++ b/docs/site/frontend/components.md @@ -0,0 +1,183 @@ +--- +outline: deep +--- + +# UI Components + +The `@simplemodule/ui` package provides a comprehensive set of React components built on [Radix UI](https://www.radix-ui.com/) primitives with Tailwind CSS styling. It follows the shadcn/ui approach -- components live in your workspace as source code, not as an opaque npm dependency. + +## Package Overview + +The package is located at `packages/SimpleModule.UI/` and uses two export paths: + +```ts +// Import components +import { Button, Card, Dialog } from '@simplemodule/ui'; + +// Import utilities +import { cn } from '@simplemodule/ui/lib/utils'; +``` + +## The `cn` Utility + +The `cn` function combines `clsx` and `tailwind-merge` to safely merge Tailwind classes: + +```ts +import { cn } from '@simplemodule/ui/lib/utils'; + +
+``` + +This avoids class conflicts (e.g., `p-4` and `p-2` don't both end up in the DOM -- `tailwind-merge` resolves them). + +## Available Components + +### Layout + +| Component | Description | +|---|---| +| `Container` | Centered max-width container with variant sizes | +| `Grid` | CSS grid wrapper with configurable columns | +| `Stack` | Flexbox stack (vertical/horizontal) with gap control | +| `PageShell` | Full page wrapper with title, description, and breadcrumbs | +| `PageHeader` | Page header with title and actions slot | +| `Section` | Semantic section wrapper | +| `Separator` | Visual divider (horizontal or vertical) | +| `AspectRatio` | Maintains a fixed aspect ratio for content | +| `ScrollArea` | Custom scrollbar container | +| `Resizable` | Resizable panel layout (`ResizablePanel`, `ResizableHandle`, `ResizablePanelGroup`) | + +### Data Display + +| Component | Description | +|---|---| +| `Card` | Surface container (`Card`, `CardHeader`, `CardContent`, `CardFooter`, `CardTitle`, `CardDescription`) | +| `Table` | Styled table (`Table`, `TableHeader`, `TableBody`, `TableRow`, `TableHead`, `TableCell`) | +| `DataGrid` | Feature-rich data table with sorting, filtering, and pagination | +| `DataGridPage` | Full-page data grid with built-in search and actions | +| `Badge` | Inline status indicator with variants | +| `Avatar` | User avatar with image and fallback | +| `HoverCard` | Card shown on hover | +| `Tooltip` | Contextual tooltip | +| `Calendar` | Date calendar display | +| `Chart` | Recharts wrapper with theming (`ChartContainer`, `ChartTooltip`, `ChartLegend`) | +| `Skeleton` | Loading placeholder | +| `Spinner` | Loading spinner with size variants | +| `Progress` | Progress bar | + +### Navigation + +| Component | Description | +|---|---| +| `Breadcrumb` | Breadcrumb navigation (`Breadcrumb`, `BreadcrumbList`, `BreadcrumbItem`, `BreadcrumbLink`, `BreadcrumbPage`, `BreadcrumbSeparator`) | +| `Tabs` | Tab navigation (`Tabs`, `TabsList`, `TabsTrigger`, `TabsContent`) | +| `Sidebar` | Application sidebar (`Sidebar`, `SidebarHeader`, `SidebarContent`, `SidebarMenu`, `SidebarMenuItem`, etc.) | +| `DropdownMenu` | Dropdown menu (`DropdownMenu`, `DropdownMenuTrigger`, `DropdownMenuContent`, `DropdownMenuItem`, etc.) | +| `Command` | Command palette / search (`Command`, `CommandInput`, `CommandList`, `CommandItem`, etc.) | +| `Popover` | Floating content panel | + +### Forms + +| Component | Description | +|---|---| +| `Button` | Button with variants (default, destructive, outline, secondary, ghost, link) | +| `Input` | Text input with variants | +| `Textarea` | Multi-line text input | +| `Checkbox` | Checkbox input | +| `RadioGroup` | Radio button group (`RadioGroup`, `RadioGroupItem`) | +| `Select` | Dropdown select (`Select`, `SelectTrigger`, `SelectValue`, `SelectContent`, `SelectItem`) | +| `Switch` | Toggle switch | +| `Slider` | Range slider | +| `Label` | Form label | +| `Field` | Form field wrapper with label, description, and error (`Field`, `FieldGroup`, `FieldDescription`, `FieldError`) | +| `DatePicker` | Date picker combining calendar and popover | +| `Toggle` | Toggle button | +| `ToggleGroup` | Group of toggle buttons | + +### Feedback + +| Component | Description | +|---|---| +| `Alert` | Alert message with variants (`Alert`, `AlertTitle`, `AlertDescription`) | +| `Dialog` | Modal dialog (`Dialog`, `DialogTrigger`, `DialogContent`, `DialogHeader`, `DialogTitle`, `DialogDescription`, `DialogFooter`) | +| `Sheet` | Slide-out panel (`Sheet`, `SheetTrigger`, `SheetContent`, `SheetHeader`, `SheetTitle`, `SheetDescription`) | +| `Toast` | Toast notification (`Toast`, `ToastProvider`, `ToastViewport`, `ToastTitle`, `ToastDescription`, `ToastAction`) | +| `Accordion` | Collapsible content sections (`Accordion`, `AccordionItem`, `AccordionTrigger`, `AccordionContent`) | +| `Collapsible` | Single collapsible section | + +## Usage Examples + +### Page with Cards + +```tsx +import { Card, CardContent, PageShell } from '@simplemodule/ui'; +import type { Product } from '../types'; + +export default function Browse({ products }: { products: Product[] }) { + return ( + +
+ {products.map((p) => ( + + + {p.name} + ${p.price.toFixed(2)} + + + ))} +
+
+ ); +} +``` + +### Form with Field Components + +```tsx +import { Button, Field, FieldError, FieldGroup, Input, Label } from '@simplemodule/ui'; + +function CreateForm() { + return ( +
+ + + + + Name is required + + + +
+ ); +} +``` + +### Data Grid Page + +```tsx +import { DataGridPage } from '@simplemodule/ui'; + +export default function Manage({ products }) { + return ( + + ); +} +``` + +## Dependencies + +The UI package relies on these key dependencies: + +- **Radix UI** -- Accessible, unstyled primitives (dialog, dropdown, select, tabs, etc.) +- **class-variance-authority (CVA)** -- Type-safe component variants +- **clsx + tailwind-merge** -- Class name composition +- **cmdk** -- Command palette +- **react-day-picker** -- Calendar and date picker +- **recharts** -- Charting library + +React and React-DOM are peer dependencies -- they are provided by the host application, not bundled. diff --git a/docs/site/frontend/overview.md b/docs/site/frontend/overview.md new file mode 100644 index 00000000..dedea30e --- /dev/null +++ b/docs/site/frontend/overview.md @@ -0,0 +1,129 @@ +--- +outline: deep +--- + +# Frontend Overview + +SimpleModule's frontend is built on **React 19** served through **Inertia.js** with a **Blazor SSR** shell. Each module ships its own self-contained page bundle, and the ClientApp bootstraps Inertia to dynamically load pages from any module at runtime. + +## Architecture + +The frontend architecture follows a modular pattern that mirrors the backend: + +``` +Browser Request + --> ASP.NET route handler calls Inertia.Render("Products/Browse", props) + --> Inertia middleware renders Blazor SSR shell with JSON props + --> React ClientApp dynamically imports Products.pages.js + --> Component hydrates with server-provided props +``` + +Each module compiles its React pages into a single ES module bundle (`{ModuleName}.pages.js`) using Vite in library mode. The host application's ClientApp is the Inertia bootstrap that resolves and loads these bundles on demand. + +## Tech Stack + +| Technology | Version | Purpose | +|---|---|---| +| React | 19 | UI rendering | +| Inertia.js | 2.x | SPA-like navigation without a client-side router | +| Vite | 6.x | Build tooling and dev server | +| Tailwind CSS | 4.x | Utility-first styling | +| TypeScript | 5.8 | Type safety | +| Biome | 2.x | Linting and formatting | + +## How Module Frontends Work + +Every module that has a UI builds a `{ModuleName}.pages.js` file into its `wwwroot/` directory. This file exports a `pages` record that maps route names to React components: + +```ts +// modules/Products/src/Products/Pages/index.ts +export const pages: Record = { + 'Products/Browse': () => import('../Views/Browse'), + 'Products/Manage': () => import('../Views/Manage'), + 'Products/Create': () => import('../Views/Create'), + 'Products/Edit': () => import('../Views/Edit'), +}; +``` + +These bundles externalize shared dependencies (React, React-DOM, Inertia) so they are not duplicated across modules. The shared libraries are vendored once by the ClientApp build. + +## ClientApp: The Inertia Bootstrap + +The ClientApp at `template/SimpleModule.Host/ClientApp/app.tsx` is the entry point for the entire frontend. It creates an Inertia app and delegates page resolution to `@simplemodule/client`: + +```tsx +import { createInertiaApp, router } from '@inertiajs/react'; +import { resolvePage } from '@simplemodule/client/resolve-page'; +import { createRoot } from 'react-dom/client'; + +createInertiaApp({ + resolve: resolvePage, + setup({ el, App, props }) { + createRoot(el).render(); + }, +}); +``` + +The ClientApp also handles non-Inertia error responses (404, 500, etc.) by intercepting invalid responses on the `router` and displaying a toast notification instead of the default Inertia error. + +## Dynamic Page Resolution + +When Inertia needs to render a page, `resolvePage` splits the route name to determine which module bundle to load: + +```ts +// @simplemodule/client/resolve-page.ts +export async function resolvePage(name: string) { + const moduleName = name.split('/')[0]; + const mod = await import(`/_content/${moduleName}/${moduleName}.pages.js`); + const page = mod.pages[name]; + + // Support lazy page entries: () => import('./SomePage') + if (typeof page === 'function') { + const resolved = await page(); + return resolved.default ? resolved : { default: resolved }; + } + + return page.default ? page : { default: page }; +} +``` + +For a route name like `Products/Browse`: +1. The module name `Products` is extracted from the first segment +2. The bundle `/_content/Products/Products.pages.js` is dynamically imported +3. The `pages` record is looked up for the key `Products/Browse` +4. Lazy entries (functions) are resolved, eager entries are returned directly + +A cache-buster query parameter is appended from a `` tag when present, ensuring browsers pick up new builds without stale caches. + +## The @simplemodule/client Package + +The `@simplemodule/client` package (`packages/SimpleModule.Client/`) provides the core frontend infrastructure: + +| Export | Purpose | +|---|---| +| `@simplemodule/client/resolve-page` | Page resolution for Inertia's `resolve` callback | +| `@simplemodule/client/module` | `defineModuleConfig()` -- unified Vite config factory for modules | +| `@simplemodule/client/vite` | Vendor build plugin and externalization helpers | + +## Type Safety + +The source generator discovers C# types marked with the `[Dto]` attribute and embeds TypeScript interface definitions. The `tools/extract-ts-types.mjs` script extracts these into `.ts` files under `ClientApp/types/`, giving React components full type safety over server-provided props: + +```tsx +import type { Product } from '../types'; + +export default function Browse({ products }: { products: Product[] }) { + return ( + + {products.map((p) => ( + + + {p.name} + ${p.price.toFixed(2)} + + + ))} + + ); +} +``` diff --git a/docs/site/frontend/pages.md b/docs/site/frontend/pages.md new file mode 100644 index 00000000..95c9baf9 --- /dev/null +++ b/docs/site/frontend/pages.md @@ -0,0 +1,178 @@ +--- +outline: deep +--- + +# Pages Registry + +Every module that renders UI must maintain a **pages registry** -- a `Pages/index.ts` file that maps route names to React components. This is the bridge between C# endpoints and the React frontend. + +## The Pattern + +Each module exports a `pages` record from `Pages/index.ts`: + +```ts +// modules/Products/src/Products/Pages/index.ts +export const pages: Record = { + 'Products/Browse': () => import('../Views/Browse'), + 'Products/Manage': () => import('../Views/Manage'), + 'Products/Create': () => import('../Views/Create'), + 'Products/Edit': () => import('../Views/Edit'), +}; +``` + +Each key matches the component name passed to `Inertia.Render()` on the server side, and each value is a lazy import pointing to the React component. + +## How It Connects to C# Endpoints + +On the backend, view endpoints call `Inertia.Render` with a component name: + +```csharp +// modules/Products/src/Products/Views/BrowseEndpoint.cs +public class BrowseEndpoint : IViewEndpoint +{ + public void Map(IEndpointRouteBuilder app) + { + app.MapGet( + "/browse", + async (IProductContracts products) => + Inertia.Render( + "Products/Browse", // <-- This key must exist in Pages/index.ts + new { products = await products.GetAllProductsAsync() } + ) + ) + .AllowAnonymous(); + } +} +``` + +The string `"Products/Browse"` is the key that `resolvePage` looks up in the module's `pages` record. If the key does not exist, the page silently fails to load. + +::: danger Missing entries cause silent 404s +If you add a new `IViewEndpoint` with `Inertia.Render("Products/Something")` but forget to add a matching entry in `Pages/index.ts`: + +- The endpoint compiles and runs fine on the server +- Navigating to that page results in a **silent client-side 404** -- no error in the console, no error response shown to the user +- This can go unnoticed for hours until QA or a user discovers it + +**Always add the pages registry entry immediately when creating a new view endpoint.** +::: + +## The Rule + +For every `IViewEndpoint` that calls `Inertia.Render("ModuleName/PageName", ...)`, there must be a matching entry in that module's `Pages/index.ts`: + +```ts +'ModuleName/PageName': () => import('../Views/PageName'), +``` + +## Adding a New Page Step-by-Step + +1. **Create the C# endpoint** implementing `IViewEndpoint`: + + ```csharp + public class DetailsEndpoint : IViewEndpoint + { + public void Map(IEndpointRouteBuilder app) + { + app.MapGet("/{id}", (int id, IProductContracts products) => + Inertia.Render("Products/Details", new { product = ... })); + } + } + ``` + +2. **Create the React component** in the module's `Views/` directory: + + ```tsx + // modules/Products/src/Products/Views/Details.tsx + import { PageShell } from '@simplemodule/ui'; + import type { Product } from '../types'; + + export default function Details({ product }: { product: Product }) { + return ( + + {/* ... */} + + ); + } + ``` + +3. **Register in `Pages/index.ts`** immediately: + + ```ts + export const pages: Record = { + 'Products/Browse': () => import('../Views/Browse'), + 'Products/Manage': () => import('../Views/Manage'), + 'Products/Create': () => import('../Views/Create'), + 'Products/Edit': () => import('../Views/Edit'), + 'Products/Details': () => import('../Views/Details'), // [!code ++] + }; + ``` + +4. **Validate** with the validation script: + + ```bash + npm run validate-pages + ``` + +## Validating Page Registrations {#validate-pages} + +The `validate-pages` script automatically checks that all C# endpoints have corresponding TypeScript entries and vice versa. + +### What It Does + +1. Scans all `.cs` files in each module's `src/{ModuleName}/` directory +2. Finds all `Inertia.Render("ComponentName/...")` calls via regex +3. Reads the module's `Pages/index.ts` file +4. Extracts all keys from the `pages` record +5. Compares the two lists and reports mismatches + +### Running It + +```bash +npm run validate-pages +``` + +On success: + +``` +=== Pages Registry Validation === + +All modules have valid Pages/index.ts registrations +``` + +On failure: + +``` +=== Pages Registry Validation === + +Module: Products + Missing in Pages/index.ts: Products/Details + +Found 1 module(s) with mismatches +Please update the Pages/index.ts files to match C# endpoints. +``` + +### Exit Codes + +| Code | Meaning | +|---|---| +| `0` | All modules have valid registrations | +| `1` | Mismatches found (missing or extra entries) | + +### CI Integration + +The script exits with code 1 on failure, making it suitable for CI pipelines. Add it as a step in your build workflow to catch missing registrations before they reach production. + +## Lazy vs Eager Imports + +The pages registry supports both lazy and eager component imports: + +```ts +// Lazy (recommended) -- component is loaded on demand +'Products/Browse': () => import('../Views/Browse'), + +// Eager -- component is bundled into the pages.js file +'Products/Browse': Browse, +``` + +Lazy imports are recommended because they allow Vite to code-split within the module bundle. The `resolvePage` function handles both patterns transparently. diff --git a/docs/site/frontend/styling.md b/docs/site/frontend/styling.md new file mode 100644 index 00000000..6361bcdf --- /dev/null +++ b/docs/site/frontend/styling.md @@ -0,0 +1,199 @@ +--- +outline: deep +--- + +# Styling + +SimpleModule uses **Tailwind CSS 4** for styling, with a centralized design system defined in the `@simplemodule/theme-default` package. The theme provides semantic color tokens, component classes, and full dark mode support. + +## Tailwind CSS 4 Setup + +The global stylesheet lives at `template/SimpleModule.Host/Styles/app.css` and imports Tailwind along with the theme: + +```css +@import "tailwindcss"; +@import "../../../packages/SimpleModule.Theme.Default/theme.css"; +@source "../../../packages/SimpleModule.UI/"; +@source "../../../packages/SimpleModule.Client/"; +@source "../../../modules/**/Views/**/*.tsx"; +@source "../../../modules/**/Pages/**/*.tsx"; +``` + +The `@source` directives tell Tailwind where to scan for class usage, ensuring that utility classes used in module components, UI package files, and Blazor Razor components are all included in the final CSS output. + +## The Default Theme + +The `@simplemodule/theme-default` package (`packages/SimpleModule.Theme.Default/`) provides a complete design system via CSS custom properties inside a `@theme` block. This is the single source of truth for all colors, shadows, and visual tokens. + +### Color Tokens + +#### Primary & Accent + +| Token | Light | Purpose | +|---|---|---| +| `--color-primary` | `#16a34a` | Primary actions, links, focus rings | +| `--color-primary-hover` | `#15803d` | Primary hover state | +| `--color-primary-light` | `#4ade80` | Light primary variant | +| `--color-primary-subtle` | `rgba(22, 163, 74, 0.08)` | Subtle backgrounds | +| `--color-accent` | `#166534` | Gradient endpoints, deep emphasis | + +#### Semantic Colors + +| Category | Tokens | +|---|---| +| **Success** | `success`, `success-light`, `success-bg`, `success-text` | +| **Danger** | `danger`, `danger-hover`, `danger-bg`, `danger-text` | +| **Warning** | `warning`, `warning-bg`, `warning-border`, `warning-text` | +| **Info** | `info`, `info-bg` | + +#### Surfaces & Text + +| Token | Light | Dark | +|---|---|---| +| `--color-surface` | `#ffffff` | `#0f172a` | +| `--color-surface-raised` | `#f8fafc` | `#1e293b` | +| `--color-surface-sunken` | `#f1f5f9` | `#0b1120` | +| `--color-text` | `#0f172a` | `#f1f5f9` | +| `--color-text-secondary` | `#475569` | `#94a3b8` | +| `--color-text-muted` | `#94a3b8` | `#64748b` | + +All tokens are accessible as Tailwind utilities. For example, `bg-surface`, `text-primary`, `border-border-strong`. + +### Shadows + +The theme defines themeable shadows used by button components: + +```css +--shadow-primary: 0 4px 14px rgba(22, 163, 74, 0.35); +--shadow-primary-hover: 0 6px 20px rgba(22, 163, 74, 0.5); +--shadow-danger: 0 4px 14px rgba(225, 29, 72, 0.25); +--shadow-danger-hover: 0 6px 20px rgba(225, 29, 72, 0.4); +``` + +## Dark Mode {#dark-mode} + +Dark mode is built into the theme via the `.dark` class selector. When the `.dark` class is present on a parent element, all color tokens automatically switch to their dark variants: + +```css +.dark { + --color-surface: #0f172a; + --color-surface-raised: #1e293b; + --color-text: #f1f5f9; + --color-text-secondary: #94a3b8; + --color-border: #1e293b; + /* ... all tokens are overridden */ +} +``` + +Because the theme uses CSS custom properties, dark mode works without any JavaScript -- just toggling the `.dark` class switches the entire color palette. + +## Built-in Component Classes + +The theme provides pre-built CSS component classes in the `@layer components` block: + +### Buttons + +```css +.btn-primary /* Gradient background, white text, elevated shadow */ +.btn-secondary /* Surface background, bordered */ +.btn-ghost /* Transparent, subtle hover */ +.btn-danger /* Red background, elevated shadow */ +.btn-outline /* Transparent, primary border */ +.btn-sm /* Small size modifier */ +.btn-lg /* Large size modifier */ +``` + +### Cards & Surfaces + +```css +.glass-card /* Glassmorphism effect with backdrop blur */ +.card /* Standard card with border and hover effect */ +.dash-card /* Dashboard stat card */ +.panel /* Section panel with gradient accent bar */ +``` + +### Badges & Alerts + +```css +.badge-success .badge-danger .badge-warning .badge-info +.alert-success .alert-danger .alert-warning .alert-info +``` + +### Navigation & Layout + +```css +.nav-link-active /* Active navigation link */ +.nav-link-inactive /* Inactive navigation link */ +.app-layout /* Root flex layout */ +.app-sidebar /* Fixed sidebar with collapse support */ +.app-content /* Main content area */ +``` + +### Utilities + +```css +.gradient-text /* Gradient-filled text */ +.gradient-border /* Gradient border using mask technique */ +.bg-mesh /* Animated background gradient mesh */ +.spinner /* CSS loading spinner */ +``` + +## Typography + +The theme sets two font families: + +- **Body text**: `"DM Sans"`, system-ui, sans-serif +- **Headings**: `"Sora"`, "DM Sans", system-ui, sans-serif +- **Code**: `"JetBrains Mono"`, "Fira Code", monospace + +Headings (`h1`-`h6`) are styled in the base layer with tight tracking and bold weight. + +## Theme Customization + +To customize the theme, you have two options: + +### Override tokens in your app.css + +Add overrides after the theme import: + +```css +@import "tailwindcss"; +@import "../../../packages/SimpleModule.Theme.Default/theme.css"; + +@theme { + --color-primary: #2563eb; /* Switch to blue */ + --color-primary-hover: #1d4ed8; + --color-accent: #1e40af; +} +``` + +### Create a custom theme package + +Create a new package modeled after `@simplemodule/theme-default` with your own `theme.css` file, and update the import in `app.css`. + +## Module-Specific Styles + +Modules can include their own CSS alongside their components. The `@source` directives in `app.css` scan module directories for Tailwind class usage: + +```css +@source "../../../modules/**/Views/**/*.tsx"; +@source "../../../modules/**/Pages/**/*.tsx"; +``` + +For module-specific styles that go beyond Tailwind utilities, add a CSS file in the module's source directory and import it in your component or include it in the module's Vite build. + +## Biome Configuration for CSS + +Biome is configured to understand Tailwind CSS directives like `@theme`, `@layer`, and `@source`: + +```json +{ + "css": { + "parser": { + "tailwindDirectives": true + } + } +} +``` + +This prevents false-positive lint/format errors on Tailwind-specific CSS syntax. diff --git a/docs/site/frontend/vite.md b/docs/site/frontend/vite.md new file mode 100644 index 00000000..708ef631 --- /dev/null +++ b/docs/site/frontend/vite.md @@ -0,0 +1,226 @@ +--- +outline: deep +--- + +# Vite Configuration + +SimpleModule uses **Vite 6** in library mode to build each module's frontend as a standalone ES module. This page covers the build configuration, the development workflow, and the orchestrators that coordinate everything. + +## Library Mode for Modules + +Each module builds its React pages into a single `{ModuleName}.pages.js` file using Vite's [library mode](https://vite.dev/guide/build.html#library-mode). This means: + +- The output is a reusable ES module, not a full application bundle +- Shared dependencies (React, Inertia) are externalized, not bundled into each module +- The host application loads module bundles dynamically at runtime + +### Why Library Mode? + +In a modular monolith, each module is independently deployable. If every module bundled its own copy of React, you would ship React N times. Library mode externalizes shared dependencies so they are loaded once from vendored copies. + +## Module `vite.config.ts` + +Every module uses the `defineModuleConfig` helper from `@simplemodule/client/module`: + +```ts +// modules/Products/src/Products/vite.config.ts +import { defineModuleConfig } from '@simplemodule/client/module'; + +export default defineModuleConfig(__dirname); +``` + +This single line generates a complete Vite configuration. The helper derives everything from the module directory: + +| Setting | Value | Source | +|---|---|---| +| Entry point | `Pages/index.ts` | Convention | +| Output file | `{Name}.pages.js` | Directory name | +| Output directory | `wwwroot/` | Convention | +| Format | ES module | Fixed | +| Externals | React, React-DOM, Inertia | `defaultVendors` | +| Source maps | Enabled in dev, disabled in prod | `VITE_MODE` env var | +| Minification | Disabled in dev, esbuild in prod | `VITE_MODE` env var | + +### Under the Hood + +The `defineModuleConfig` function generates this Vite config: + +```ts +function defineModuleConfig(dir: string): UserConfig { + const name = basename(dir); + const isDev = process.env.VITE_MODE !== 'prod'; + + return defineConfig({ + plugins: [react()], + define: { + 'process.env.NODE_ENV': JSON.stringify(isDev ? 'development' : 'production'), + }, + build: { + lib: { + entry: resolve(dir, 'Pages/index.ts'), + formats: ['es'], + fileName: () => `${name}.pages.js`, + }, + sourcemap: isDev, + minify: isDev ? false : 'esbuild', + outDir: 'wwwroot', + emptyOutDir: false, + rollupOptions: { + external: defaultVendors.map((v) => v.pkg), + output: { + assetFileNames: `${name.toLowerCase()}[extname]`, + }, + }, + }, + }); +} +``` + +### Custom Overrides + +For non-standard requirements, use Vite's `mergeConfig`: + +```ts +import { mergeConfig } from 'vite'; +import { defineModuleConfig } from '@simplemodule/client/module'; + +export default mergeConfig(defineModuleConfig(__dirname), { + // Custom overrides here +}); +``` + +## Externalization + +Shared dependencies are externalized so they are not bundled into each module. The `defaultVendors` list defines what gets externalized: + +```ts +export const defaultVendors: VendorEntry[] = [ + { pkg: 'react', file: 'react', externals: [] }, + { pkg: 'react-dom', file: 'react-dom', externals: ['react'] }, + { pkg: 'react/jsx-runtime', file: 'react-jsx-runtime', externals: ['react'] }, + { pkg: 'react-dom/client', file: 'react-dom-client', externals: ['react', 'react-dom'] }, + { + pkg: '@inertiajs/react', + file: 'inertiajs-react', + externals: ['react', 'react-dom', 'react/jsx-runtime', 'react-dom/client'], + }, +]; +``` + +The `vendorBuildPlugin` builds these shared libraries into standalone ESM files under `wwwroot/js/vendor/`. Each module's `import` statements for React or Inertia are rewritten to import maps or resolved paths pointing at these vendored copies. + +## Module `package.json` + +Each module declares React and React-DOM as **peer dependencies** since they are provided by the host: + +```json +{ + "private": true, + "name": "@simplemodule/products", + "version": "0.0.0", + "scripts": { + "build": "cross-env VITE_MODE=prod vite build", + "build:dev": "cross-env VITE_MODE=dev vite build", + "watch": "cross-env VITE_MODE=dev vite build --watch" + }, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" + } +} +``` + +Three scripts are standard: +- **`build`** -- Production build (minified, no source maps) +- **`build:dev`** -- Development build (unminified, with source maps) +- **`watch`** -- Development build with file watching + +## Development Workflow + +### `npm run dev` + +The `npm run dev` command starts the complete development environment using the **dev orchestrator** (`tools/dev-orchestrator.mjs`). It launches three types of processes in parallel: + +1. **`dotnet run`** -- The ASP.NET backend on `https://localhost:5001` +2. **Module watches** -- `vite build --watch` for every module with a `vite.config.ts` +3. **ClientApp watch** -- `vite build --watch` for the host ClientApp + +``` +npm run dev + | + ├── dotnet run --project template/SimpleModule.Host + ├── npm run watch (in modules/Products/src/Products/) + ├── npm run watch (in modules/Orders/src/Orders/) + ├── npm run watch (in modules/Users/src/Users/) + └── npm run watch (in template/SimpleModule.Host/ClientApp/) +``` + +The orchestrator auto-discovers all modules that have a Vite config, so adding a new module automatically includes it in the dev workflow. + +::: tip Development experience +- **Edit a module file** -- Vite rebuilds that module in milliseconds (unminified, readable output) +- **Edit ClientApp** -- Vite rebuilds the host app +- **Refresh browser** -- See changes immediately +- **Browser dev tools** -- Source maps let you debug original TypeScript +- **Ctrl+C** -- Gracefully stops all processes (dotnet, all watchers) +::: + +### Error Handling + +The orchestrator treats `dotnet run` as critical -- if the backend crashes, all processes shut down. Module watch failures are non-fatal; other modules and the backend continue running. + +## Build Modes + +### Development Build + +```bash +npm run dev:build +# or +npm run build:dev +``` + +- Unminified output (readable JavaScript) +- Source maps enabled +- `process.env.NODE_ENV` set to `'development'` +- Controlled by `VITE_MODE=dev` + +### Production Build + +```bash +npm run build +``` + +- Minified with esbuild +- No source maps +- `process.env.NODE_ENV` set to `'production'` +- Controlled by `VITE_MODE=prod` + +## Build Orchestrator + +The production build uses the **build orchestrator** (`tools/build-orchestrator.mjs`) which: + +1. Discovers all buildable workspaces (modules + ClientApp) +2. Builds all workspaces **in parallel** for performance +3. Passes the `VITE_MODE` environment variable to each build +4. Reports success or failure for each workspace +5. Exits with code 1 if any build fails + +```bash +# Build all modules in production mode +npm run build + +# Build all modules in development mode +npm run dev:build +``` + +## Build Output + +After building, each module's `wwwroot/` directory contains: + +``` +modules/Products/src/Products/wwwroot/ + Products.pages.js # The module's page bundle + products.css # Any CSS assets (named from module) +``` + +These files are served as static content via ASP.NET's `_content/{ModuleName}/` path, which is how `resolvePage` finds them at `/_content/Products/Products.pages.js`. diff --git a/docs/site/getting-started/introduction.md b/docs/site/getting-started/introduction.md new file mode 100644 index 00000000..1db47118 --- /dev/null +++ b/docs/site/getting-started/introduction.md @@ -0,0 +1,179 @@ +--- +outline: deep +--- + +# Introduction + +SimpleModule is a **modular monolith framework for .NET** that combines compile-time module discovery, database schema isolation, and a modern React frontend into a single, cohesive development experience. + +You build feature modules as self-contained units. A Roslyn source generator wires them together at compile time. No reflection, no runtime scanning, no XML configuration. Everything is statically known before your app starts. + +## Why a Modular Monolith? + +Most teams face a choice between two extremes: a traditional monolith where everything is tangled together, or microservices where everything is distributed. Modular monoliths give you a middle path that captures the best of both. + +### Over Traditional Monoliths + +A traditional monolith starts simple but degrades as it grows. Feature code bleeds across boundaries. A change in billing breaks product search. Nobody knows which database tables belong to which feature. + +SimpleModule enforces structure from day one: + +- **Module isolation** -- each module gets its own database schema, its own permissions, its own settings. You cannot accidentally reach into another module's internals. +- **Contract-based communication** -- modules talk through explicit interfaces and events. Dependencies are visible in the project graph, not hidden in shared database tables. +- **Independent development** -- teams can work on separate modules without merge conflicts in shared code. Each module has its own test project, its own frontend bundle, its own release cycle. + +### Over Microservices + +Microservices solve real problems but introduce operational complexity that many teams don't need: + +- **No network boundaries** -- method calls between modules are in-process. No serialization overhead, no retry logic, no service discovery. +- **Shared transactions** -- when two modules need to participate in the same operation, they share a database transaction. No distributed sagas, no eventual consistency headaches. +- **Simpler operations** -- one deployment artifact, one process to monitor, one log stream to search. You don't need Kubernetes to ship a CRUD app. +- **Easy extraction** -- if a module outgrows the monolith, the contract boundary is already in place. Moving to a separate service means swapping the in-process contract implementation for an HTTP client. + +## Key Features + +### Compile-Time Discovery + +SimpleModule includes a Roslyn incremental source generator that scans your assemblies at build time. It finds: + +- Classes decorated with `[Module]` that implement `IModule` +- Endpoint classes implementing `IEndpoint` or `IViewEndpoint` +- Data transfer objects marked with `[Dto]` + +The generator emits extension methods -- `AddModules()`, `MapModuleEndpoints()`, `CollectModuleMenuItems()` -- that your host app calls in `Program.cs`. There is no reflection at runtime. The generated code is plain C# that you can inspect in your IDE. + +```csharp +// Program.cs — calls generated extension methods +var builder = WebApplication.CreateBuilder(args); +builder.AddModules(); // registers all module services + +var app = builder.Build(); +app.MapModuleEndpoints(); // maps all discovered endpoints +app.CollectModuleMenuItems(); // builds the navigation menu +``` + +### React + Inertia.js Frontend + +Each module ships its own React pages as a Vite library-mode bundle. The host app uses Blazor SSR to deliver the initial HTML with serialized props, then React hydrates on the client. + +This means: + +- **Server-driven navigation** -- Inertia.js handles page transitions. No client-side router to configure. +- **Module-scoped bundles** -- each module builds a `{ModuleName}.pages.js` file. The host app dynamically imports the right bundle based on the route. +- **Full React ecosystem** -- use any React library. The framework doesn't limit what you can do on the client. + +```typescript +// modules/Products/src/Products/Pages/index.ts +export const pages: Record = { + 'Products/Browse': () => import('../Views/Browse'), + 'Products/Manage': () => import('../Views/Manage'), + 'Products/Create': () => import('../Views/Create'), +}; +``` + +### Module Isolation + +Every module is a self-contained unit with clear boundaries: + +| Concern | Isolation mechanism | +|---------|-------------------| +| Database | Separate schema (PostgreSQL/SQL Server) or table prefix (SQLite) | +| Permissions | Module-scoped permission definitions | +| Settings | Module-scoped settings with typed access | +| Menus | Each module registers its own menu items | +| Frontend | Separate Vite bundle per module | +| Communication | Contracts (interfaces) and events only | + +::: warning No Backdoors +Modules cannot reference each other's implementation projects. They depend on `.Contracts` projects only, which contain interfaces and `[Dto]` types. The compiler enforces this boundary. +::: + +### CLI Tooling + +The `sm` command-line tool handles scaffolding and project health: + +```bash +sm new project MyApp # scaffold a new SimpleModule solution +sm new module Products # create a module with contracts, endpoints, tests +sm new feature Products/Browse # add a feature to an existing module +sm doctor --fix # validate project structure, auto-fix issues +``` + +### Multi-Provider Database + +SimpleModule supports multiple database providers with automatic schema isolation: + +| Provider | Isolation strategy | Use case | +|----------|-------------------|----------| +| SQLite | Table prefixes (`Products_Items`) | Local development, testing | +| PostgreSQL | Schemas (`products.items`) | Production | +| SQL Server | Schemas (`products.items`) | Production | + +Each module registers its database context through `ModuleDbContextInfo`. The framework handles schema creation and provider-specific configuration. + +## How It Works + +The core workflow is straightforward: + +**1. Define a module** + +```csharp +[Module("Products", RoutePrefix = "products")] +public sealed class ProductsModule : IModule +{ + public static void ConfigureServices( + IServiceCollection services, + IConfiguration configuration) + { + services.AddScoped(); + } +} +``` + +**2. Add endpoints** + +```csharp +public sealed class BrowseProducts : IViewEndpoint +{ + public static void Map(IEndpointRouteBuilder app) => + app.MapGet("/", Handler); + + private static IResult Handler(IProductContracts products) => + Inertia.Render("Products/Browse", new + { + Products = products.GetAll() + }); +} +``` + +**3. Build the project** + +The Roslyn source generator runs during compilation. It discovers `ProductsModule`, finds `BrowseProducts`, and generates the wiring code. You can see the generated files in your IDE under Analyzers. + +**4. Everything is registered** + +The generated `AddModules()` method calls `ProductsModule.ConfigureServices()`. The generated `MapModuleEndpoints()` method maps `BrowseProducts` under the `/products` route prefix. The generated `CollectModuleMenuItems()` method gathers any menu items the module registered. All at compile time. All type-safe. + +::: tip Zero Configuration +You don't write registration code, startup configuration, or reflection-based discovery logic. Add a class, implement an interface, build. The generator handles the rest. +::: + +## Tech Stack + +| Layer | Technology | +|-------|-----------| +| Runtime | .NET 10 | +| Frontend | React 19, Inertia.js | +| Server rendering | Blazor SSR | +| Build tooling | Vite, Tailwind CSS 4 | +| Source generation | Roslyn incremental generators | +| Component library | Radix UI | +| Testing | xUnit.v3, FluentAssertions, Bogus | +| Database | SQLite, PostgreSQL, SQL Server via EF Core | +| CLI | System.CommandLine | + +## Next Steps + +- [Quick Start](./quick-start) -- install the CLI and create your first project in under five minutes +- [Project Structure](./project-structure) -- understand how a SimpleModule solution is organized diff --git a/docs/site/getting-started/project-structure.md b/docs/site/getting-started/project-structure.md new file mode 100644 index 00000000..b9086bec --- /dev/null +++ b/docs/site/getting-started/project-structure.md @@ -0,0 +1,406 @@ +--- +outline: deep +--- + +# Project Structure + +A SimpleModule solution follows a consistent directory layout that separates the framework, your feature modules, frontend packages, and the host application. + +## Top-Level Layout + +``` +MyApp/ +├── framework/ # Core framework packages +│ ├── SimpleModule.Core/ +│ ├── SimpleModule.Generator/ +│ ├── SimpleModule.Database/ +│ ├── SimpleModule.Blazor/ +│ ├── SimpleModule.Hosting/ +│ └── SimpleModule.DevTools/ +├── modules/ # Your feature modules +│ ├── Products/ +│ ├── Orders/ +│ └── Settings/ +├── packages/ # Frontend npm packages +│ ├── SimpleModule.Client/ +│ ├── SimpleModule.UI/ +│ └── SimpleModule.Theme.Default/ +├── template/ +│ └── SimpleModule.Host/ # The host application +│ ├── ClientApp/ +│ ├── Program.cs +│ └── wwwroot/ +├── cli/ +│ └── SimpleModule.Cli/ # The sm CLI tool +├── tests/ # Framework-level test projects +├── SimpleModule.slnx # Solution file +├── package.json # Root npm workspace config +└── Directory.Build.props # Shared MSBuild properties +``` + +## Framework Projects + +The `framework/` directory contains the core packages that power SimpleModule. You reference these but rarely modify them. + +### SimpleModule.Core + +The foundation. Defines all the interfaces and attributes that modules implement: + +- **`IModule`** -- the module contract. Implement this to define a module. +- **`[Module]` attribute** -- marks a class as a module with metadata (name, route prefix). +- **`IEndpoint` / `IViewEndpoint`** -- endpoint contracts. `IEndpoint` for API routes, `IViewEndpoint` for pages that render React views. +- **`[Dto]` attribute** -- marks types for source generator processing (JSON serialization, TypeScript extraction). +- **`IMenuRegistry`** -- register navigation menu items from your module. +- **`IEventBus`** -- publish events for cross-module communication. +- **`IEventHandler`** -- handle events from other modules. +- **`Inertia`** -- server-side helpers for rendering React pages with props. + +### SimpleModule.Generator + +A Roslyn incremental source generator targeting **netstandard2.0** (required by the compiler toolchain). It runs at build time and scans referenced assemblies to discover: + +- Classes with `[Module]` implementing `IModule` +- Classes implementing `IEndpoint` or `IViewEndpoint` +- Types decorated with `[Dto]` + +It generates: + +| Generated method | Purpose | +|-----------------|---------| +| `AddModules()` | Calls each module's `ConfigureServices` | +| `MapModuleEndpoints()` | Maps all discovered endpoints with route prefixes | +| `CollectModuleMenuItems()` | Builds the navigation menu from module registrations | +| JSON serializer contexts | AOT-friendly serialization for `[Dto]` types | +| TypeScript interfaces | Embedded TS definitions extracted by build tooling | +| Razor component discovery | Assembly metadata for Blazor SSR | + +::: tip Inspecting Generated Code +In Visual Studio or Rider, expand **Dependencies > Analyzers > SimpleModule.Generator** to see exactly what code the generator produces. This is useful for debugging registration issues. +::: + +### SimpleModule.Database + +Multi-provider database support built on EF Core. Handles: + +- **Provider abstraction** -- SQLite, PostgreSQL, and SQL Server behind a unified configuration API +- **Schema isolation** -- each module's tables are automatically namespaced (table prefixes for SQLite, schemas for PostgreSQL/SQL Server) +- **`ModuleDbContextInfo`** -- metadata that each module registers to declare its database context and schema name + +### SimpleModule.Blazor + +The Blazor SSR shell that serves as the bridge between ASP.NET and React. It renders the initial HTML page with serialized Inertia props, which React then hydrates on the client side. + +### SimpleModule.Hosting + +Module registration infrastructure. Provides the runtime plumbing that the generated `AddModules()` and `MapModuleEndpoints()` methods call into. Handles service collection extensions, endpoint routing integration, and module lifecycle management. + +### SimpleModule.DevTools + +Development utilities including hot reload support, diagnostic middleware, and developer experience tooling that is excluded from production builds. + +## Module Structure + +Every module follows a **three-project pattern**: implementation, contracts, and tests. + +``` +modules/Products/ +├── src/ +│ ├── Products/ # Implementation (private) +│ │ ├── Products.csproj +│ │ ├── ProductsModule.cs # Module class with [Module] attribute +│ │ ├── Data/ +│ │ │ ├── ProductsDbContext.cs # EF Core DbContext +│ │ │ └── Entities/ # Database entities (internal) +│ │ ├── Endpoints/ +│ │ │ └── Products/ +│ │ │ ├── BrowseProducts.cs # GET /products +│ │ │ ├── CreateProduct.cs # POST /products/create +│ │ │ └── ManageProduct.cs # GET /products/{id} +│ │ ├── Services/ +│ │ │ └── ProductService.cs # IProductContracts implementation +│ │ ├── Pages/ +│ │ │ └── index.ts # React page registry +│ │ ├── Views/ +│ │ │ ├── Browse.tsx # React page components +│ │ │ ├── Create.tsx +│ │ │ └── Manage.tsx +│ │ ├── vite.config.ts # Vite library mode config +│ │ └── package.json # npm package with peer deps +│ └── Products.Contracts/ # Public API (shared) +│ ├── Products.Contracts.csproj +│ ├── IProductContracts.cs # Contract interface +│ └── Dtos/ +│ └── ProductDto.cs # [Dto] types +└── tests/ + └── Products.Tests/ # Test project + ├── Products.Tests.csproj + └── Endpoints/ + └── BrowseProductsTests.cs +``` + +### Implementation Project (`Products/`) + +This is the private implementation. No other module should reference this project directly. It contains: + +- **Module class** -- the `[Module]`-decorated class that registers services +- **Endpoints** -- classes implementing `IEndpoint` or `IViewEndpoint`, auto-discovered by the generator +- **Data layer** -- EF Core DbContext, entities, and migrations (all `internal`) +- **Services** -- implementations of the contract interface +- **Frontend** -- React pages, Vite config, and the page registry + +The `.csproj` file uses `Microsoft.NET.Sdk` with a framework reference to ASP.NET: + +```xml + + + net10.0 + + + + + + + + +``` + +### Contracts Project (`Products.Contracts/`) + +The public face of the module. Other modules depend on this project when they need to interact with Products. It contains only: + +- **Contract interface** (`IProductContracts`) -- methods other modules can call +- **DTO types** marked with `[Dto]` -- data shapes shared across module boundaries + +```csharp +// IProductContracts.cs +public interface IProductContracts +{ + Task> GetAllAsync(CancellationToken cancellationToken); + Task GetByIdAsync(int id, CancellationToken cancellationToken); + Task CreateAsync(CreateProductRequest request, CancellationToken cancellationToken); +} +``` + +```csharp +// Dtos/ProductDto.cs +[Dto] +public sealed record ProductDto(int Id, string Name, decimal Price, string? Description); + +[Dto] +public sealed record CreateProductRequest(string Name, decimal Price, string? Description); +``` + +::: warning Contracts Are the Boundary +The contracts project must never reference the implementation project. It depends only on `SimpleModule.Core`. This ensures that modules cannot access each other's internals -- the compiler enforces the boundary. +::: + +### Test Project (`Products.Tests/`) + +An xUnit.v3 test project with access to the shared test infrastructure: + +```csharp +public sealed class BrowseProductsTests( + SimpleModuleWebApplicationFactory factory) + : IClassFixture +{ + [Fact] + public async Task BrowseProducts_WithProducts_ReturnsAll() + { + // Arrange + var client = factory.CreateAuthenticatedClient(); + + // Act + var response = await client.GetAsync("/products"); + + // Assert + response.StatusCode.Should().Be(HttpStatusCode.OK); + } +} +``` + +Test naming convention: `Method_Scenario_Expected` with underscores (configured in `.editorconfig`). + +## The Host Application + +The host app lives at `template/SimpleModule.Host/` and is the entry point that ties everything together. + +### Program.cs + +The host's `Program.cs` calls the generated extension methods: + +```csharp +var builder = WebApplication.CreateBuilder(args); + +// Generated: registers all module services +builder.AddModules(); + +var app = builder.Build(); + +// Generated: maps all discovered endpoints with route prefixes +app.MapModuleEndpoints(); + +// Generated: collects menu items from all modules +app.CollectModuleMenuItems(); + +app.Run(); +``` + +These three method calls replace what would otherwise be dozens of manual registration lines. The source generator produces them based on what it discovers in your module assemblies. + +### ClientApp + +The React entry point at `template/SimpleModule.Host/ClientApp/`: + +``` +ClientApp/ +├── app.tsx # Inertia bootstrap + page resolver +├── types/ # Generated TypeScript interfaces from [Dto] types +└── ... +``` + +The page resolver in `app.tsx` dynamically imports module bundles based on the route name. When Inertia navigates to `Products/Browse`, it resolves to `/_content/Products/Products.pages.js` and loads the corresponding React component. + +```typescript +// Simplified page resolution logic +const moduleName = pageName.split('/')[0]; +const bundle = await import(`/_content/${moduleName}/${moduleName}.pages.js`); +const component = bundle.pages[pageName]; +``` + +### wwwroot + +Static assets and module page bundles are served from `wwwroot/`. Each module's Vite build outputs its `{ModuleName}.pages.js` bundle here (via the `_content/{ModuleName}/` convention). + +## Frontend Packages + +The `packages/` directory contains shared npm packages used by modules and the host app. These are managed via npm workspaces. + +### @simplemodule/client + +Vite plugin and utilities for module builds: + +- **Vite plugin** -- configures library mode builds with the right externals (React, ReactDOM, @inertiajs/react) +- **Page resolution** -- utility for resolving page components from module bundles +- **Vendoring** -- handles shared dependency bundling + +### @simplemodule/ui + +A component library built on Radix UI with Tailwind CSS styling: + +```tsx +import { Button } from '@simplemodule/ui/components'; +import { cn } from '@simplemodule/ui/lib/utils'; +``` + +Provides pre-built, accessible components: buttons, dialogs, tables, forms, dropdowns, and more. All styled with Tailwind CSS and customizable through the theme package. + +### @simplemodule/theme-default + +The default Tailwind CSS theme. Provides base styles, color tokens, and design system foundations that the UI components and your module pages consume. + +## Cross-Module Communication + +Modules communicate through two mechanisms: + +### Contracts (Synchronous) + +Module A depends on Module B's contracts project and calls its interface methods: + +``` +modules/Orders/src/Orders.csproj + └── references → modules/Products/src/Products.Contracts/ +``` + +```csharp +// In an Orders endpoint +public sealed class CreateOrder : IEndpoint +{ + public static void Map(IEndpointRouteBuilder app) => + app.MapPost("/", Handler); + + private static async Task Handler( + CreateOrderRequest request, + IProductContracts products, // injected from Products module + IOrderContracts orders, + CancellationToken cancellationToken) + { + var product = await products.GetByIdAsync(request.ProductId, cancellationToken); + if (product is null) + return TypedResults.NotFound(); + + var order = await orders.CreateAsync(request, cancellationToken); + return TypedResults.Created($"/orders/{order.Id}", order); + } +} +``` + +### Events (Asynchronous) + +For loose coupling, modules publish events through `IEventBus`: + +```csharp +// Publisher (in Orders module) +await eventBus.PublishAsync(new OrderCreatedEvent(order.Id, order.ProductId)); + +// Handler (in Products module, or any module) +public sealed class UpdateStockOnOrderCreated : IEventHandler +{ + public async Task HandleAsync( + OrderCreatedEvent @event, + CancellationToken cancellationToken) + { + // Reduce stock for the ordered product + } +} +``` + +Event handlers run sequentially and failures are isolated -- if one handler throws, the others still execute. + +## Solution File + +The `SimpleModule.slnx` file at the root ties every project together. When you use `sm new module`, it automatically adds the new module's three projects to the solution. + +```bash +dotnet build # builds everything +dotnet test # tests everything +``` + +## Build Configuration + +### Directory.Build.props + +Shared MSBuild properties applied to all projects in the solution: + +- **`TreatWarningsAsErrors`** enabled globally +- **`AnalysisLevel=latest-all`** with **`AnalysisMode=All`** for comprehensive code analysis +- Suppressed rules are listed in `.editorconfig` +- File-scoped namespaces enforced as errors + +### npm Workspaces + +The root `package.json` defines npm workspaces covering: + +- `modules/*/src/*` -- module frontend code +- `packages/*` -- shared frontend packages +- `template/SimpleModule.Host/ClientApp` -- the host app's React entry point + +This allows a single `npm install` at the root to resolve all dependencies, and commands like `npm run build` to build everything. + +## Adding a New Module Manually + +If you prefer not to use the CLI, here are the steps: + +1. Create the directory structure under `modules//` +2. Create the contracts project (`.Contracts.csproj`) referencing only `SimpleModule.Core` +3. Create the implementation project (`.csproj`) referencing Core and Contracts, with `` +4. Create the test project (`.Tests.csproj`) +5. Add a `[Module]` class implementing `IModule` +6. Add endpoints implementing `IEndpoint` or `IViewEndpoint` +7. Set up the frontend: `package.json`, `vite.config.ts`, `Pages/index.ts`, React components +8. Add a `ProjectReference` in `template/SimpleModule.Host/SimpleModule.Host.csproj` +9. Add all three projects to `SimpleModule.slnx` +10. Run `dotnet build` -- the source generator picks up the new module automatically + +::: tip Use the CLI +`sm new module ` does all of this in seconds and ensures nothing is missed. Use `sm doctor --fix` afterward to verify everything is wired correctly. +::: diff --git a/docs/site/getting-started/quick-start.md b/docs/site/getting-started/quick-start.md new file mode 100644 index 00000000..3d4bd379 --- /dev/null +++ b/docs/site/getting-started/quick-start.md @@ -0,0 +1,298 @@ +--- +outline: deep +--- + +# Quick Start + +Get a working SimpleModule application running in under five minutes. + +## Prerequisites + +Before you begin, make sure you have: + +- [**.NET 10 SDK**](https://dotnet.microsoft.com/download/dotnet/10.0) (or later) +- [**Node.js 20+**](https://nodejs.org/) (LTS recommended) +- **npm** (comes with Node.js) + +Verify your setup: + +```bash +dotnet --version # should print 10.0.x or higher +node --version # should print v20.x or higher +npm --version # should print 10.x or higher +``` + +## Option A: Using the CLI (Recommended) + +The `sm` CLI is the fastest way to get started. It scaffolds a complete solution with all the right project references, build configuration, and frontend wiring. + +### Install the CLI + +```bash +dotnet tool install -g SimpleModule.Cli +``` + +### Create a New Project + +```bash +sm new project MyApp +cd MyApp +``` + +This generates a full solution with the host app, framework references, frontend packages, and a sample module. + +### Build and Run + +```bash +dotnet build +npm install +npm run dev +``` + +The `npm run dev` command starts everything at once: + +- **ASP.NET backend** on `https://localhost:5001` +- **Vite watchers** for all module frontends (unminified, with source maps) +- **ClientApp watcher** for the main React entry point + +::: tip Hot Reload +Edit any TypeScript/React file and Vite rebuilds instantly. Refresh your browser to see changes. Edit C# files and the dotnet process picks up changes via hot reload. +::: + +Open [https://localhost:5001](https://localhost:5001) in your browser. You should see the SimpleModule dashboard. + +## Option B: Manual Setup + +If you prefer to clone the template directly: + +```bash +git clone https://github.com/anthropics/SimpleModule-template.git MyApp +cd MyApp +dotnet build +npm install +npm run dev +``` + +Open [https://localhost:5001](https://localhost:5001). + +## Creating Your First Module + +With the CLI installed and your project running, add a new module: + +```bash +sm new module Products +``` + +This creates three projects following the standard module pattern: + +``` +modules/Products/ +├── src/ +│ ├── Products/ # Module implementation +│ │ ├── Products.csproj +│ │ ├── ProductsModule.cs # [Module] class with ConfigureServices +│ │ ├── Endpoints/ # API and view endpoints +│ │ ├── Pages/ +│ │ │ └── index.ts # React page registry +│ │ ├── Views/ # React page components +│ │ ├── vite.config.ts # Vite library mode build +│ │ └── package.json +│ └── Products.Contracts/ # Public interface for other modules +│ ├── Products.Contracts.csproj +│ ├── IProductContracts.cs # Contract interface +│ └── ProductDto.cs # Shared DTOs with [Dto] attribute +└── tests/ + └── Products.Tests/ # xUnit test project + └── Products.Tests.csproj +``` + +The CLI also: + +- Adds `ProjectReference` entries to the host app +- Registers all projects in `SimpleModule.slnx` +- Sets up the Vite build configuration +- Creates a starter endpoint and React page + +### The Generated Module Class + +```csharp +[Module("Products", RoutePrefix = "products")] +public sealed class ProductsModule : IModule +{ + public static void ConfigureServices( + IServiceCollection services, + IConfiguration configuration) + { + services.AddScoped(); + } +} +``` + +### The Generated Contract + +```csharp +// In Products.Contracts +public interface IProductContracts +{ + Task> GetAllAsync(CancellationToken cancellationToken); +} + +[Dto] +public sealed record ProductDto(int Id, string Name, decimal Price); +``` + +::: info The `[Dto]` Attribute +Marking a type with `[Dto]` tells the source generator to include it in JSON serializer context generation and TypeScript type extraction. Always use it on types that cross module boundaries. +::: + +## Adding a Feature + +Add a browsing feature to the Products module: + +```bash +sm new feature Products/Browse +``` + +This scaffolds: + +- A C# endpoint class (`Endpoints/Products/BrowseProducts.cs`) +- A React page component (`Views/Browse.tsx`) +- An entry in the page registry (`Pages/index.ts`) + +### The Endpoint + +```csharp +public sealed class BrowseProducts : IViewEndpoint +{ + public static void Map(IEndpointRouteBuilder app) => + app.MapGet("/", Handler); + + private static async Task Handler( + IProductContracts products, + CancellationToken cancellationToken) + { + var items = await products.GetAllAsync(cancellationToken); + return Inertia.Render("Products/Browse", new { Products = items }); + } +} +``` + +### The React Page + +```tsx +import { Head } from '@inertiajs/react'; + +interface Props { + products: Array<{ + id: number; + name: string; + price: number; + }>; +} + +export default function Browse({ products }: Props) { + return ( + <> + +

Products

+
    + {products.map((product) => ( +
  • + {product.name} - ${product.price} +
  • + ))} +
+ + ); +} +``` + +### The Page Registry + +```typescript +// modules/Products/src/Products/Pages/index.ts +export const pages: Record = { + 'Products/Browse': () => import('../Views/Browse'), +}; +``` + +::: danger Don't Forget the Page Registry +Every `IViewEndpoint` that calls `Inertia.Render("Products/Something", ...)` **must** have a matching entry in `Pages/index.ts`. If you forget, the endpoint works on the server but silently 404s on the client with no error message. + +Run `npm run validate-pages` to catch mismatches. +::: + +## Running Tests + +Run the full test suite: + +```bash +dotnet test +``` + +Run tests for a specific module: + +```bash +dotnet test --filter "FullyQualifiedName~Products" +``` + +Run a single test method: + +```bash +dotnet test --filter "FullyQualifiedName~BrowseProducts_ReturnsOk" +``` + +The test infrastructure provides: + +- **`SimpleModuleWebApplicationFactory`** -- pre-configured with in-memory SQLite and a test auth scheme +- **`CreateAuthenticatedClient()`** -- returns an `HttpClient` with auth claims injected via headers +- **`FakeDataGenerators`** -- Bogus-based fakers for all module DTOs + +```csharp +public sealed class BrowseProductsTests( + SimpleModuleWebApplicationFactory factory) : IClassFixture +{ + [Fact] + public async Task BrowseProducts_ReturnsOk() + { + var client = factory.CreateAuthenticatedClient(); + var response = await client.GetAsync("/products"); + response.StatusCode.Should().Be(HttpStatusCode.OK); + } +} +``` + +## Docker + +Run the full stack with Docker Compose: + +```bash +docker compose up +``` + +This starts: + +- The SimpleModule host app on **http://localhost:8080** +- A **PostgreSQL** instance for production-like database behavior + +::: tip Development vs Production Database +During local development with `npm run dev`, the app uses SQLite by default -- no database server needed. Docker Compose switches to PostgreSQL to match production behavior. +::: + +## Development Workflow Summary + +| Command | What it does | +|---------|-------------| +| `npm run dev` | Start backend + all frontend watchers | +| `npm run build` | Production build (minified, optimized) | +| `npm run dev:build` | Dev build (unminified, source maps) | +| `npm run check` | Lint + format check (Biome) | +| `npm run check:fix` | Auto-fix lint + formatting | +| `npm run validate-pages` | Verify all endpoints have page registry entries | +| `dotnet test` | Run all tests | +| `dotnet build` | Build the solution | +| `sm doctor --fix` | Validate and fix project structure | + +## Next Steps + +- [Project Structure](./project-structure) -- understand how the solution is organized diff --git a/docs/site/guide/contracts.md b/docs/site/guide/contracts.md new file mode 100644 index 00000000..e6ee3760 --- /dev/null +++ b/docs/site/guide/contracts.md @@ -0,0 +1,322 @@ +--- +outline: deep +--- + +# Contracts + +Contracts are the public API of a module. They define the interface through which other modules interact with your module, without exposing implementation details. This is the key mechanism that keeps modules decoupled in a SimpleModule application. + +## Why Contracts? + +In a modular monolith, modules must communicate, but direct dependencies between module implementations would create tight coupling. The contracts pattern solves this: + +- Modules depend on **interfaces**, never implementations +- Each module exposes a `.Contracts` project with its public types +- The source generator auto-registers the implementation at startup +- At runtime, DI resolves the concrete service behind the interface + +This means you can refactor a module's internals freely without breaking other modules, as long as the contract interface stays stable. + +## Contracts Project Structure + +Each module has a separate contracts project alongside its main project: + +``` +modules/Products/ + src/ + Products.Contracts/ # Public API -- other modules depend on this + Products.Contracts.csproj + IProductContracts.cs # The interface + Product.cs # Shared DTO + ProductId.cs # Strongly-typed ID + CreateProductRequest.cs # Request DTO + UpdateProductRequest.cs # Request DTO + Products/ # Private implementation -- no one depends on this + ProductsModule.cs + ProductService.cs # Implements IProductContracts + ... +``` + +The contracts project uses `Microsoft.NET.Sdk` (not Razor) and references only `SimpleModule.Core`: + +```xml + + + net10.0 + Library + + + + + +``` + +::: tip +Keep contracts projects minimal. They should contain only the interface, DTOs, events, and strongly-typed IDs. No business logic, no database dependencies, no ASP.NET references. +::: + +## The `IContracts` Interface + +Each module exposes a single contract interface that defines all operations other modules can call: + +```csharp +namespace SimpleModule.Products.Contracts; + +public interface IProductContracts +{ + Task> GetAllProductsAsync(); + Task GetProductByIdAsync(ProductId id); + Task> GetProductsByIdsAsync(IEnumerable ids); + Task CreateProductAsync(CreateProductRequest request); + Task UpdateProductAsync(ProductId id, UpdateProductRequest request); + Task DeleteProductAsync(ProductId id); +} +``` + +The source generator discovers the concrete implementation of this interface and auto-registers it as a scoped service. You never write `services.AddScoped()` by hand -- the generated `AddModules()` method handles it. + +### Orders Contract + +Here is another example showing the Orders module contract: + +```csharp +namespace SimpleModule.Orders.Contracts; + +public interface IOrderContracts +{ + Task> GetAllOrdersAsync(); + Task GetOrderByIdAsync(OrderId id); + Task CreateOrderAsync(CreateOrderRequest request); + Task UpdateOrderAsync(OrderId id, UpdateOrderRequest request); + Task DeleteOrderAsync(OrderId id); +} +``` + +## Shared DTO Types + +DTOs (Data Transfer Objects) defined in contracts projects are the data shapes shared between modules. They should be plain classes with public properties: + +```csharp +namespace SimpleModule.Products.Contracts; + +public class Product +{ + public ProductId Id { get; set; } + public string Name { get; set; } = string.Empty; + public decimal Price { get; set; } +} +``` + +```csharp +namespace SimpleModule.Products.Contracts; + +public class CreateProductRequest +{ + public string Name { get; set; } = string.Empty; + public decimal Price { get; set; } +} +``` + +### Strongly-Typed IDs + +Modules use [Vogen](https://github.com/SteveDunn/Vogen) to define strongly-typed IDs that prevent accidental misuse of raw integer or GUID values: + +```csharp +using Vogen; + +namespace SimpleModule.Products.Contracts; + +[ValueObject( + conversions: Conversions.SystemTextJson | Conversions.EfCoreValueConverter)] +public readonly partial struct ProductId; +``` + +The `Conversions` flags generate JSON and EF Core value converters automatically, so the ID type works seamlessly in API serialization and database queries. + +::: tip +Strongly-typed IDs belong in the contracts project, not in Core. They are domain types specific to their module. +::: + +## Cross-Module Dependencies + +When one module needs to use another module's data, it depends on the contracts project -- never the implementation. + +For example, the Orders module uses `ProductId` from Products and `UserId` from Users: + +```csharp +// Orders.Contracts/OrderItem.cs +using SimpleModule.Products.Contracts; + +namespace SimpleModule.Orders.Contracts; + +public class OrderItem +{ + public ProductId ProductId { get; set; } + public int Quantity { get; set; } +} +``` + +```csharp +// Orders.Contracts/Order.cs +using SimpleModule.Users.Contracts; + +namespace SimpleModule.Orders.Contracts; + +public class Order +{ + public OrderId Id { get; set; } + public UserId UserId { get; set; } + public List Items { get; set; } = new(); + public decimal Total { get; set; } + public DateTime CreatedAt { get; set; } +} +``` + +The project file makes the dependency explicit: + +```xml + + + + + + + + +``` + +The source generator detects these inter-module dependencies and ensures modules are registered in the correct order (Products before Orders). + +## The `[Dto]` Attribute + +The `[Dto]` attribute marks types for automatic TypeScript interface generation: + +```csharp +[AttributeUsage( + AttributeTargets.Class | AttributeTargets.Struct, + AllowMultiple = false, + Inherited = false)] +public sealed class DtoAttribute : Attribute { } +``` + +### Convention-Based Discovery + +By convention, **all public types in `*.Contracts` assemblies are treated as DTOs** and will have TypeScript interfaces generated automatically. You do not need to apply `[Dto]` to types in contracts projects. + +The `[Dto]` attribute is needed for types **outside** contracts assemblies that should still participate in TypeScript generation: + +```csharp +// In a non-contracts project -- [Dto] is required here +using SimpleModule.Core; + +namespace SimpleModule.AuditLogs.Contracts; + +[Dto] +public class DashboardStats +{ + public int TotalEntries { get; set; } + public int UniqueUsers { get; set; } + public double AverageDurationMs { get; set; } + public double ErrorRate { get; set; } + public Dictionary BySource { get; set; } = new(); + public List TopUsers { get; set; } = []; + public List Timeline { get; set; } = []; +} + +[Dto] +public class NamedCount +{ + public string Name { get; set; } = ""; + public int Count { get; set; } +} + +[Dto] +public class TimelinePoint +{ + public string Date { get; set; } = ""; + public int Http { get; set; } + public int Domain { get; set; } + public int Changes { get; set; } +} +``` + +## TypeScript Generation Pipeline + +The TypeScript generation pipeline converts C# DTO types into TypeScript interfaces that your React components can use: + +1. **Source generator** scans for `[Dto]` types and public types in `*.Contracts` assemblies +2. **TypeScript definitions are embedded** in the generated source as string resources +3. **`extract-ts-types.mjs`** extracts the definitions and writes `.ts` files to each module + +The generated TypeScript files are placed in each module's source directory as `types.ts`: + +```typescript +// Auto-generated from [Dto] types -- do not edit +export interface CreateProductRequest { + name: string; + price: number; +} + +export interface Product { + id: number; + name: string; + price: number; +} + +export interface UpdateProductRequest { + name: string; + price: number; +} +``` + +::: warning +These files are auto-generated. Do not edit them manually -- your changes will be overwritten on the next build. +::: + +## The `[NoDtoGeneration]` Escape Hatch + +Some public types in contracts assemblies should not have TypeScript interfaces generated. For example, event types or internal marker interfaces. Use `[NoDtoGeneration]` to exclude them: + +```csharp +[NoDtoGeneration] +public sealed record OrderCreatedEvent( + OrderId OrderId, UserId UserId, decimal Total) : IEvent; +``` + +The attribute can be applied to classes, structs, and interfaces: + +```csharp +[AttributeUsage( + AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, + AllowMultiple = false, + Inherited = false)] +public sealed class NoDtoGenerationAttribute : Attribute { } +``` + +## Events in Contracts + +Domain events are also defined in contracts projects so that handlers in other modules can subscribe to them: + +```csharp +using SimpleModule.Core.Events; +using SimpleModule.Users.Contracts; + +namespace SimpleModule.Orders.Contracts.Events; + +public sealed record OrderCreatedEvent( + OrderId OrderId, UserId UserId, decimal Total) : IEvent; +``` + +Any module can implement `IEventHandler` to react when an order is created, without depending on the Orders implementation. + +## Summary + +| Concept | Where It Lives | Purpose | +|---------|---------------|---------| +| `IContracts` | Contracts project | Public interface for inter-module calls | +| DTO classes | Contracts project | Shared data shapes | +| Strongly-typed IDs | Contracts project | Type-safe identifiers (Vogen) | +| Events (`IEvent`) | Contracts project | Cross-module event definitions | +| `[Dto]` | On types outside contracts | Opt-in to TypeScript generation | +| `[NoDtoGeneration]` | On types inside contracts | Opt-out of TypeScript generation | diff --git a/docs/site/guide/database.md b/docs/site/guide/database.md new file mode 100644 index 00000000..bac2b913 --- /dev/null +++ b/docs/site/guide/database.md @@ -0,0 +1,362 @@ +--- +outline: deep +--- + +# Database + +SimpleModule provides a multi-provider database layer with automatic schema isolation per module. Each module gets its own `DbContext` and its own namespace in the database, whether that is table prefixes (SQLite) or schemas (PostgreSQL, SQL Server). + +## Multi-Provider Support + +Three database providers are supported out of the box: + +| Provider | Schema Isolation | Detection Heuristic | +|----------|-----------------|---------------------| +| **SQLite** | Table name prefixes (`Products_Products`) | Connection string contains `Data Source=` | +| **PostgreSQL** | Database schemas (`products.Products`) | Connection string contains `Host=` | +| **SQL Server** | Database schemas (`products.Products`) | Connection string contains `Initial Catalog=`, `Server=.\`, or `Server=(` | + +The provider is auto-detected from the connection string. You can also set it explicitly in configuration: + +```json +{ + "Database": { + "DefaultConnection": "Data Source=app.db", + "Provider": "Sqlite" + } +} +``` + +Valid provider values: `"Sqlite"`, `"PostgreSql"`, `"SqlServer"`. + +### Per-Module Connection Strings + +By default, all modules share the `DefaultConnection`. If you need a module to use a separate database, add it to `ModuleConnections`: + +```json +{ + "Database": { + "DefaultConnection": "Host=localhost;Database=myapp;...", + "ModuleConnections": { + "AuditLogs": "Host=localhost;Database=myapp_audit;..." + } + } +} +``` + +When a module has its own connection string, schema isolation is skipped for that module since it already has a dedicated database. + +## One DbContext Per Module + +Each module registers its own `DbContext` using the `AddModuleDbContext` extension method. This method handles provider detection, connection string resolution, and interceptor registration: + +```csharp +[Module( + ProductsConstants.ModuleName, + RoutePrefix = ProductsConstants.RoutePrefix, + ViewPrefix = "/products" +)] +public class ProductsModule : IModule +{ + public void ConfigureServices( + IServiceCollection services, IConfiguration configuration) + { + services.AddModuleDbContext( + configuration, ProductsConstants.ModuleName); + } +} +``` + +### The ProductsDbContext + +Here is a complete example of a module DbContext: + +```csharp +public class ProductsDbContext( + DbContextOptions options, + IOptions dbOptions +) : DbContext(options) +{ + public DbSet Products => Set(); + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder.ApplyConfiguration(new ProductConfiguration()); + modelBuilder.ApplyModuleSchema("Products", dbOptions.Value); + } + + protected override void ConfigureConventions( + ModelConfigurationBuilder configurationBuilder) + { + configurationBuilder + .Properties() + .HaveConversion(); + } +} +``` + +Key points: + +1. The constructor uses **primary constructor** syntax with `DbContextOptions` and `IOptions` +2. `OnModelCreating` applies entity configurations and then calls `ApplyModuleSchema` to set up schema isolation +3. `ConfigureConventions` registers Vogen value converters for strongly-typed IDs + +::: warning +Always call `modelBuilder.ApplyModuleSchema(moduleName, dbOptions.Value)` in `OnModelCreating`. Without it, your module's tables will not be isolated and may collide with other modules. +::: + +## Schema Isolation + +The `ApplyModuleSchema` extension method automatically applies the correct isolation strategy based on the database provider: + +**SQLite** -- Prefixes all table names with the module name: + +``` +Products_Products +Products_Categories +Orders_Orders +Orders_OrderItems +``` + +**PostgreSQL / SQL Server** -- Creates separate schemas: + +```sql +products.Products +products.Categories +orders.Orders +orders.OrderItems +``` + +The implementation: + +```csharp +public static void ApplyModuleSchema( + this ModelBuilder modelBuilder, + string moduleName, + DatabaseOptions dbOptions) +{ + var hasOwnConnection = dbOptions.ModuleConnections.ContainsKey(moduleName); + if (hasOwnConnection) + return; // Module has its own database, no prefix needed + + var provider = DatabaseProviderDetector.Detect(connectionString); + + if (provider == DatabaseProvider.Sqlite) + { + var prefix = $"{moduleName}_"; + foreach (var entity in modelBuilder.Model.GetEntityTypes()) + { + var tableName = entity.GetTableName(); + if (tableName is not null + && !tableName.StartsWith(prefix, StringComparison.Ordinal)) + { + entity.SetTableName($"{prefix}{tableName}"); + } + } + } + else + { + var schema = moduleName.ToLowerInvariant(); + foreach (var entity in modelBuilder.Model.GetEntityTypes()) + { + entity.SetSchema(schema); + } + } +} +``` + +## Entity Configurations + +Use `IEntityTypeConfiguration` to define entity mappings. Keep these in an `EntityConfigurations` directory in your module: + +```csharp +public class ProductConfiguration : IEntityTypeConfiguration +{ + public void Configure(EntityTypeBuilder builder) + { + builder.HasKey(p => p.Id); + builder.Property(p => p.Id).ValueGeneratedOnAdd(); + builder.Property(p => p.Name).IsRequired(); + builder.Property(p => p.Price).HasColumnType("decimal(18,2)"); + + builder.HasData(GenerateSeedProducts()); + } + + private static Product[] GenerateSeedProducts() + { + var id = 0; + var faker = new Faker() + .UseSeed(54321) + .RuleFor(p => p.Id, _ => ProductId.From(++id)) + .RuleFor(p => p.Name, f => f.Commerce.ProductName()) + .RuleFor(p => p.Price, f => decimal.Parse( + f.Commerce.Price(10, 1000), + CultureInfo.InvariantCulture)); + + return faker.Generate(10).ToArray(); + } +} +``` + +Apply configurations in `OnModelCreating`: + +```csharp +protected override void OnModelCreating(ModelBuilder modelBuilder) +{ + modelBuilder.ApplyConfiguration(new ProductConfiguration()); + modelBuilder.ApplyModuleSchema("Products", dbOptions.Value); +} +``` + +::: tip +The example above uses [Bogus](https://github.com/bchavez/Bogus) to generate deterministic seed data with `UseSeed()`. This is useful for development and testing. +::: + +## ModuleDbContextInfo + +When you call `AddModuleDbContext`, a `ModuleDbContextInfo` record is automatically registered in DI: + +```csharp +public sealed record ModuleDbContextInfo(string ModuleName, Type DbContextType); +``` + +The framework uses this at startup to discover all module databases and run initialization (e.g., `EnsureCreated()` or migrations). You generally do not interact with this type directly. + +## EF Core Interceptor DI Pattern + +SaveChanges interceptors can cause circular dependencies when they depend on services that themselves depend on a `DbContext`. For example: + +``` +SaveChangesInterceptor -> ISettingsContracts -> SettingsService -> SettingsDbContext +``` + +This creates a deadlock during DI construction. The solution is to resolve runtime dependencies at interception time, not in the constructor. + +### Correct Pattern + +```csharp +public sealed class AuditSaveChangesInterceptor( + IAuditContext auditContext, + AuditChannel channel, + IServiceProvider? serviceProvider = null // Nullable for optionality +) : SaveChangesInterceptor +{ + public override async ValueTask> SavingChangesAsync( + DbContextEventData eventData, + InterceptionResult result, + CancellationToken cancellationToken = default) + { + if (eventData.Context is null) + return await base.SavingChangesAsync( + eventData, result, cancellationToken); + + // Resolve settings at interception time, not constructor time + ISettingsContracts? settings = null; + if (serviceProvider is not null) + { + settings = serviceProvider.GetService(); + } + + if (settings is not null) + { + var raw = await settings.GetSettingAsync( + "auditlogs.capture.changes", SettingScope.System); + if (string.Equals( + raw, "false", StringComparison.OrdinalIgnoreCase)) + return await base.SavingChangesAsync( + eventData, result, cancellationToken); + } + + // ... interceptor logic + return await base.SavingChangesAsync( + eventData, result, cancellationToken); + } +} +``` + +### Anti-Pattern + +```csharp +// WRONG: direct dependency on a service that depends on DbContext +public sealed class BadInterceptor( + ISettingsContracts settings // Circular dependency! +) : SaveChangesInterceptor { } +``` + +::: danger +Never inject services that transitively depend on a `DbContext` into interceptor constructors. Inject `IServiceProvider?` as an optional dependency and resolve the service inside the interception method. +::: + +### How Interceptors Are Registered + +The `AddModuleDbContext` method automatically resolves all registered `ISaveChangesInterceptor` instances and adds them to the DbContext options: + +```csharp +// Inside AddModuleDbContext +options.AddInterceptors(sp.GetServices()); +``` + +Register your interceptor in the module's `ConfigureServices`: + +```csharp +services.AddScoped(); +``` + +All interceptors registered this way apply to **all** module DbContexts. If your interceptor should skip its own module's context, check the context type: + +```csharp +var contextType = eventData.Context.GetType(); +if (contextType == typeof(AuditLogsDbContext)) + return await base.SavingChangesAsync(eventData, result, cancellationToken); +``` + +## Development: EnsureCreated + +In non-production environments, the framework automatically initializes databases at startup. This means you can iterate quickly without running migrations manually during development. + +::: tip +For development, `EnsureCreated()` is called automatically. You do not need to run any migration commands to get started. +::: + +## Production: EF Core Migrations + +For production deployments, use EF Core migrations scoped to each module: + +```bash +# Generate a migration for the Products module +dotnet ef migrations add InitialCreate \ + --project modules/Products/src/Products \ + --startup-project template/SimpleModule.Host \ + --context ProductsDbContext + +# Apply migrations +dotnet ef database update \ + --project modules/Products/src/Products \ + --startup-project template/SimpleModule.Host \ + --context ProductsDbContext +``` + +Each module manages its own migration history independently, since each has its own `DbContext` with its own schema. + +## Configuration Reference + +Full `Database` configuration section: + +```json +{ + "Database": { + "DefaultConnection": "Data Source=app.db", + "Provider": "Sqlite", + "ModuleConnections": { + "AuditLogs": "Host=localhost;Database=audit;Username=app;Password=..." + } + } +} +``` + +| Property | Type | Description | +|----------|------|-------------| +| `DefaultConnection` | `string` | Connection string shared by all modules (required) | +| `Provider` | `string?` | Explicit provider override. Auto-detected from connection string if omitted | +| `ModuleConnections` | `Dictionary` | Per-module connection strings for database isolation | diff --git a/docs/site/guide/endpoints.md b/docs/site/guide/endpoints.md new file mode 100644 index 00000000..fbe521d1 --- /dev/null +++ b/docs/site/guide/endpoints.md @@ -0,0 +1,417 @@ +--- +outline: deep +--- + +# Endpoints + +Endpoints are the HTTP entry points into your module. SimpleModule provides two endpoint interfaces: `IEndpoint` for API endpoints that return JSON, and `IViewEndpoint` for Inertia view endpoints that render React pages. Both are auto-discovered by the source generator -- you never register them manually. + +## `IEndpoint` -- API Endpoints + +The `IEndpoint` interface has a single method: + +```csharp +public interface IEndpoint +{ + void Map(IEndpointRouteBuilder app); +} +``` + +Inside `Map`, you use ASP.NET Minimal API methods (`MapGet`, `MapPost`, `MapPut`, `MapDelete`) to define your route. The `app` parameter is already scoped to your module's `RoutePrefix`, so you define routes relative to that prefix. + +### Example: Full CRUD + +Here is the complete set of API endpoints from the Products module. The module's `RoutePrefix` is `"/api/products"`, so `"/"` maps to `/api/products` and `"/{id}"` maps to `/api/products/{id}`. + +**GET all products:** + +```csharp +public class GetAllEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder app) => + app.MapGet( + "/", + (IProductContracts productContracts) => + CrudEndpoints.GetAll(productContracts.GetAllProductsAsync) + ) + .RequirePermission(ProductsPermissions.View); +} +``` + +**GET by ID:** + +```csharp +public class GetByIdEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder app) => + app.MapGet( + "/{id}", + (ProductId id, IProductContracts productContracts) => + CrudEndpoints.GetById( + () => productContracts.GetProductByIdAsync(id)) + ) + .RequirePermission(ProductsPermissions.View); +} +``` + +**POST (create):** + +```csharp +public class CreateEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder app) => + app.MapPost( + "/", + (CreateProductRequest request, IProductContracts productContracts) => + { + var validation = CreateRequestValidator.Validate(request); + if (!validation.IsValid) + { + throw new ValidationException(validation.Errors); + } + + return CrudEndpoints.Create( + () => productContracts.CreateProductAsync(request), + p => $"{ProductsConstants.RoutePrefix}/{p.Id}" + ); + } + ) + .RequirePermission(ProductsPermissions.Create); +} +``` + +**PUT (update):** + +```csharp +public class UpdateEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder app) => + app.MapPut( + "/{id}", + (ProductId id, UpdateProductRequest request, + IProductContracts productContracts) => + { + var validation = UpdateRequestValidator.Validate(request); + if (!validation.IsValid) + { + throw new ValidationException(validation.Errors); + } + + return CrudEndpoints.Update( + () => productContracts.UpdateProductAsync(id, request)); + } + ) + .RequirePermission(ProductsPermissions.Update); +} +``` + +**DELETE:** + +```csharp +public class DeleteEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder app) => + app.MapDelete( + "/{id}", + (ProductId id, IProductContracts productContracts) => + CrudEndpoints.Delete( + () => productContracts.DeleteProductAsync(id)) + ) + .RequirePermission(ProductsPermissions.Delete); +} +``` + +## `IViewEndpoint` -- Inertia View Endpoints + +The `IViewEndpoint` interface is identical in shape to `IEndpoint`: + +```csharp +public interface IViewEndpoint +{ + void Map(IEndpointRouteBuilder app); +} +``` + +The difference is semantic. View endpoints use `Inertia.Render()` to return server-side props that hydrate React components. They are grouped under the module's `ViewPrefix` instead of `RoutePrefix`, and they are excluded from API documentation (Swagger/OpenAPI). + +### Example: Browse View + +```csharp +public class BrowseEndpoint : IViewEndpoint +{ + public void Map(IEndpointRouteBuilder app) + { + app.MapGet( + "/browse", + async (IProductContracts products) => + Inertia.Render( + "Products/Browse", + new { products = await products.GetAllProductsAsync() } + ) + ) + .AllowAnonymous(); + } +} +``` + +The first argument to `Inertia.Render` is the component name (e.g., `"Products/Browse"`). This must match an entry in the module's `Pages/index.ts` registry on the frontend side. + +### Example: Create View with Form Handling + +View endpoints often handle both the GET (render the form) and POST (process the submission): + +```csharp +public class CreateEndpoint : IViewEndpoint +{ + public void Map(IEndpointRouteBuilder app) + { + app.MapGet("/create", () => Inertia.Render("Products/Create")); + + app.MapPost( + "/", + async ( + [FromForm] string name, + [FromForm] decimal price, + IProductContracts products + ) => + { + var request = new CreateProductRequest + { + Name = name, Price = price + }; + await products.CreateProductAsync(request); + return TypedResults.Redirect("/products/manage"); + } + ) + .DisableAntiforgery(); + } +} +``` + +### Example: Edit View with GET, POST, and DELETE + +```csharp +public class EditEndpoint : IViewEndpoint +{ + public void Map(IEndpointRouteBuilder app) + { + app.MapGet( + "/{id}/edit", + async (ProductId id, IProductContracts products) => + { + var product = await products.GetProductByIdAsync(id); + if (product is null) + return TypedResults.NotFound(); + return Inertia.Render("Products/Edit", new { product }); + } + ); + + app.MapPost( + "/{id}", + async ( + ProductId id, + [FromForm] string name, + [FromForm] decimal price, + IProductContracts products + ) => + { + var request = new UpdateProductRequest + { + Name = name, Price = price + }; + await products.UpdateProductAsync(id, request); + return TypedResults.Redirect($"/products/{id}/edit"); + } + ) + .DisableAntiforgery(); + + app.MapDelete( + "/{id}", + async (ProductId id, IProductContracts products) => + { + await products.DeleteProductAsync(id); + return TypedResults.Redirect("/products/manage"); + } + ); + } +} +``` + +::: warning +When adding a new `IViewEndpoint`, you **must** also register the corresponding component in your module's `Pages/index.ts`. The Inertia component name in `Inertia.Render("Products/Edit", ...)` must have a matching key in the pages record. If you forget, the page will silently 404 on the client side with no error. + +Run `npm run validate-pages` to verify all endpoints have matching frontend entries. +::: + +## Auto-Discovery + +The source generator automatically discovers all classes implementing `IEndpoint` or `IViewEndpoint` in your module's assembly. You do not need to register them anywhere. + +The generated code creates route groups with the appropriate prefixes: + +- `IEndpoint` classes are grouped under the module's `RoutePrefix` (e.g., `/api/products`) with `RequireAuthorization()` applied by default +- `IViewEndpoint` classes are grouped under the module's `ViewPrefix` (e.g., `/products`) with `RequireAuthorization()` and `ExcludeFromDescription()` applied by default + +To allow anonymous access to a specific endpoint, chain `.AllowAnonymous()` after the route definition. + +## Parameter Binding + +SimpleModule endpoints use ASP.NET Minimal API parameter binding. Understanding the implicit binding rules saves you from writing unnecessary attributes. + +### Binding Source Priority + +| HTTP Method | Binding order (implicit) | +|-------------|--------------------------| +| GET, HEAD, OPTIONS, DELETE | Route > Query > Header > DI services | +| POST, PUT, PATCH | Route > Query > Header > Body (JSON) > DI services | + +### Implicit Binding (No Attribute Needed) + +Most parameters bind automatically without any attributes: + +```csharp +// Route parameter: int id binds from {id} in the route template +app.MapGet("/{id}", (ProductId id) => ...); + +// Query parameter: string? search binds from ?search=... +app.MapGet("/", (string? search, int page = 1) => ...); + +// JSON body: complex type binds from request body for POST/PUT +app.MapPost("/", (CreateProductRequest request) => ...); + +// DI services: auto-injected when registered in the container +app.MapGet("/", (IProductContracts products) => ...); + +// Special types: auto-bound by the framework +app.MapGet("/", (HttpContext context, CancellationToken ct, + ClaimsPrincipal user) => ...); +``` + +::: tip +DI services are auto-injected. You do **not** need `[FromServices]` -- it is noise. +::: + +### When Attributes Are Required + +**`[FromForm]`** is required for scalar form data. It is never implicit: + +```csharp +// CORRECT: scalar form fields require [FromForm] +app.MapPost("/", async ( + [FromForm] string name, + [FromForm] decimal price, + IProductContracts products) => ...); +``` + +**`[FromQuery]`** when a parameter name conflicts with a route parameter, or to rename: + +```csharp +app.MapGet("/{id}", (int id, [FromQuery(Name = "v")] int? version) => ...); +``` + +**`[FromHeader]`** for HTTP headers: + +```csharp +app.MapGet("/", ([FromHeader(Name = "X-Tenant-Id")] string tenantId) => ...); +``` + +### Optional Parameters + +Parameters are **required by default**. A missing required parameter returns 400 Bad Request. Make parameters optional with: + +```csharp +// Nullable type: null if missing +app.MapGet("/", (int? page) => ...); + +// Default value: uses default if missing +app.MapGet("/", (int page = 1, int pageSize = 25) => ...); + +// Arrays from query strings: missing key gives empty array +app.MapGet("/tags", (int[] ids) => ...); // ?ids=1&ids=2 +``` + +### Form Binding Limitations + +::: danger +`[FromForm]` does **not** support `List` or `string[]` from repeated form keys in Minimal APIs. Use `ReadFormAsync()` instead: + +```csharp +app.MapPost("/", async (HttpContext context) => +{ + var form = await context.Request.ReadFormAsync(); + var permissions = form["permissions"] + .Where(p => !string.IsNullOrWhiteSpace(p)) + .Select(p => p!) + .ToList(); +}); +``` +::: + +## Correct Patterns + +```csharp +// API: complex type auto-binds from JSON body, service auto-injected +app.MapPost("/", async (CreateProductRequest request, + IProductContracts products) => ...); + +// API: route param + body + DI +app.MapPut("/{id}", async (int id, UpdateProductRequest request, + IProductContracts products) => ...); + +// View: scalar form data requires [FromForm] +app.MapPost("/", async ([FromForm] string name, + [FromForm] decimal price, + IProductContracts products) => ...); + +// Query: arrays bind from repeated keys +app.MapGet("/tags", (int[] q) => $"tag1: {q[0]}, tag2: {q[1]}"); +``` + +## Anti-Patterns (Avoid) + +```csharp +// BAD: manual form reading for scalar values (use [FromForm] instead) +app.MapPost("/", async (HttpContext context) => +{ + var form = await context.Request.ReadFormAsync(); + var name = form["name"].ToString(); // Use [FromForm] string name +}); + +// BAD: manual JSON deserialization (let model binding handle it) +app.MapPost("/", async (HttpContext context) => +{ + var body = await JsonSerializer.DeserializeAsync( + context.Request.Body); +}); + +// BAD: [FromServices] is unnecessary noise +app.MapGet("/", ([FromServices] IProductContracts products) => ...); +// GOOD: DI services auto-inject +app.MapGet("/", (IProductContracts products) => ...); +``` + +## File Organization + +By convention, endpoints are organized in the module's directory structure: + +``` +modules/Products/src/Products/ + Endpoints/ + Products/ + GetAllEndpoint.cs + GetByIdEndpoint.cs + CreateEndpoint.cs + CreateRequestValidator.cs + UpdateEndpoint.cs + UpdateRequestValidator.cs + DeleteEndpoint.cs + Views/ + BrowseEndpoint.cs + ManageEndpoint.cs + CreateEndpoint.cs + EditEndpoint.cs +``` + +- `Endpoints/` contains `IEndpoint` classes (API), organized by resource +- `Views/` contains `IViewEndpoint` classes (Inertia pages) +- Validators sit alongside their corresponding endpoint diff --git a/docs/site/guide/events.md b/docs/site/guide/events.md new file mode 100644 index 00000000..f06847d3 --- /dev/null +++ b/docs/site/guide/events.md @@ -0,0 +1,297 @@ +--- +outline: deep +--- + +# Event Bus + +The event bus enables decoupled communication between modules. Instead of modules referencing each other directly, they publish events that any module can subscribe to. This keeps modules independent while allowing cross-cutting behavior like audit logging, notifications, or cache invalidation. + +## Core Concepts + +### IEvent + +`IEvent` is a marker interface. Any record or class implementing it can be published through the event bus: + +```csharp +using SimpleModule.Core.Events; + +public sealed record OrderCreatedEvent(OrderId OrderId, UserId UserId, decimal Total) : IEvent; +``` + +Events are typically defined in a module's **Contracts** project so other modules can reference them without depending on the implementation. + +### IEventBus + +`IEventBus` exposes a single method for publishing events to all registered handlers: + +```csharp +public interface IEventBus +{ + Task PublishAsync(T @event, CancellationToken cancellationToken = default) + where T : IEvent; +} +``` + +Inject `IEventBus` into any service and call `PublishAsync`: + +```csharp +public sealed partial class OrderService( + OrdersDbContext db, + IEventBus eventBus, + ILogger logger +) : IOrderContracts +{ + public async Task CreateOrderAsync(CreateOrderRequest request) + { + // ... create the order ... + + db.Orders.Add(order); + await db.SaveChangesAsync(); + + await eventBus.PublishAsync( + new OrderCreatedEvent(order.Id, order.UserId, order.Total) + ); + + return order; + } +} +``` + +### IEventHandler\ + +Implement `IEventHandler` to react to a specific event type. Register handlers in DI and they are automatically discovered by the event bus: + +```csharp +public sealed class OrderCreatedNotificationHandler : IEventHandler +{ + public async Task HandleAsync( + OrderCreatedEvent @event, + CancellationToken cancellationToken + ) + { + // Send notification, update cache, etc. + } +} +``` + +Register in your module's `ConfigureServices`: + +```csharp +services.AddScoped, OrderCreatedNotificationHandler>(); +``` + +## Partial Success Semantics + +The event bus guarantees that **all handlers execute even if some fail**. This is the most important design decision in the event system. + +### How It Works + +1. Handlers execute **sequentially** in registration order +2. If a handler throws, the exception is **caught and logged** +3. Execution **continues** to the next handler +4. After all handlers complete, any collected exceptions are thrown as a single `AggregateException` + +``` +Handler A ──→ ✅ Success (side effects preserved) +Handler B ──→ ❌ Throws (exception caught and logged) +Handler C ──→ ✅ Success (still executes despite B's failure) + +Result: AggregateException containing B's exception +``` + +::: warning +Side effects from successful handlers are **preserved** even when the `AggregateException` is thrown. Design your error handling accordingly. +::: + +### Handling AggregateException + +The caller is responsible for handling the aggregate exception. Inspect `InnerExceptions` to see which handlers failed: + +```csharp +try +{ + await eventBus.PublishAsync(new OrderCreatedEvent(orderId, userId, total)); +} +catch (AggregateException ex) +{ + foreach (var inner in ex.InnerExceptions) + { + logger.LogError(inner, "Handler failed for OrderCreatedEvent"); + } +} +``` + +## Handler Best Practices + +### Be Stateless + +Handlers may be called concurrently in future versions. Avoid mutable state: + +```csharp +// Good: stateless, uses injected services +public sealed class AuditHandler(IAuditContext audit) : IEventHandler +{ + public async Task HandleAsync(OrderCreatedEvent @event, CancellationToken ct) + { + await audit.LogAsync("Order created", @event.OrderId.ToString()); + } +} +``` + +### Be Independent + +Do not rely on side effects from other handlers. They may execute in any order or be skipped in future versions. + +### Be Idempotent + +The same event may be reprocessed in retry scenarios. Design handlers to handle duplicate calls gracefully. + +### Don't Throw for Expected Failures + +For non-critical work like audit logging, catch exceptions inside the handler rather than letting them propagate: + +```csharp +public sealed class AuditLogEventHandler( + IAuditContext audit, + ILogger logger +) : IEventHandler +{ + public async Task HandleAsync( + OrderCreatedEvent @event, + CancellationToken cancellationToken + ) + { + try + { + await audit.LogAsync("Order created", @event.OrderId.ToString()); + } + catch (Exception ex) + { + // Don't throw: audit logging must never disrupt the primary operation + logger.LogError(ex, "Failed to log event"); + } + } +} +``` + +### Avoid Long-Running Work + +For expensive operations, queue work for a background service instead of blocking the event bus: + +```csharp +public sealed class EmailHandler(EmailChannel channel) : IEventHandler +{ + public Task HandleAsync(OrderCreatedEvent @event, CancellationToken ct) + { + // Queue for background processing instead of sending synchronously + return channel.EnqueueAsync(new OrderConfirmationEmail(@event.OrderId)); + } +} +``` + +## Testing Events + +### Basic Handler Test + +```csharp +[Fact] +public async Task Handler_processes_event() +{ + var handler = new OrderCreatedNotificationHandler(); + var @event = new OrderCreatedEvent(OrderId.From(1), UserId.From(1), 99.99m); + + await handler.HandleAsync(@event, CancellationToken.None); + + // Assert side effects +} +``` + +### Testing Partial Failure + +Verify that successful handlers complete their work even when other handlers fail: + +```csharp +[Fact] +public async Task Successful_handlers_complete_when_others_fail() +{ + var services = new ServiceCollection(); + services.AddScoped, SuccessfulHandler>(); + services.AddScoped, FailingHandler>(); + services.AddScoped, AnotherSuccessfulHandler>(); + + var provider = services.BuildServiceProvider(); + var bus = new EventBus(provider, NullLogger.Instance); + + var ex = await Assert.ThrowsAsync( + () => bus.PublishAsync(new TestEvent("value")) + ); + + ex.InnerExceptions.Should().HaveCount(1); + // Verify successful handlers completed their work +} +``` + +### Testing Handler Execution Order + +Handlers run in registration order. You can verify this: + +```csharp +[Fact] +public async Task Handlers_execute_in_registration_order() +{ + var order = new List(); + + var services = new ServiceCollection(); + services.AddScoped>(_ => new OrderTrackingHandler("A", order)); + services.AddScoped>(_ => new OrderTrackingHandler("B", order)); + + var provider = services.BuildServiceProvider(); + var bus = new EventBus(provider, NullLogger.Instance); + + await bus.PublishAsync(new TestEvent("value")); + + order.Should().ContainInOrder("A", "B"); +} +``` + +## EventBus Internals + +The `EventBus` implementation resolves all `IEventHandler` instances from `IServiceProvider` and iterates through them: + +```csharp +public async Task PublishAsync(T @event, CancellationToken cancellationToken = default) + where T : IEvent +{ + var handlers = serviceProvider.GetServices>(); + List? exceptions = null; + + foreach (var handler in handlers) + { + try + { + await handler.HandleAsync(@event, cancellationToken); + } + catch (Exception ex) + { + LogHandlerFailed(logger, handler.GetType().Name, typeof(T).Name, ex); + exceptions ??= []; + exceptions.Add(ex); + } + } + + if (exceptions is { Count: > 0 }) + { + throw new AggregateException( + $"One or more event handlers for {typeof(T).Name} failed.", + exceptions + ); + } +} +``` + +Key implementation details: + +- Handlers are resolved via `GetServices>()`, so registration order matters +- Failed handlers are logged with structured logging (`[LoggerMessage]` source generator) +- The `CancellationToken` is passed to every handler, allowing cooperative cancellation +- The `EventBus` is registered as scoped, so handlers share the same DI scope as the request diff --git a/docs/site/guide/inertia.md b/docs/site/guide/inertia.md new file mode 100644 index 00000000..36f950fa --- /dev/null +++ b/docs/site/guide/inertia.md @@ -0,0 +1,394 @@ +--- +outline: deep +--- + +# Inertia.js Integration + +SimpleModule uses [Inertia.js](https://inertiajs.com/) to bridge the server-side .NET backend with a React frontend. Instead of building a separate API + SPA, endpoints return Inertia responses that render React components with server-provided props -- giving you the DX of a SPA with the architecture of a server-rendered app. + +## How It Works + +The Inertia integration in SimpleModule has three layers: + +1. **ASP.NET endpoints** call `Inertia.Render()` to specify a component name and props +2. **Blazor SSR** renders the HTML shell with the serialized page data +3. **React ClientApp** hydrates the page by dynamically importing the correct module's page bundle + +## Request Flow + +### Initial Page Load + +On the first request (full page load), the flow is: + +``` +Browser GET /products/browse + ↓ +ASP.NET route handler + → Inertia.Render("Products/Browse", { products: [...] }) + ↓ +InertiaResult.ExecuteAsync() + → Serializes page data (component, props, url, version) as JSON + → Delegates to IInertiaPageRenderer + ↓ +InertiaPageRenderer (Blazor SSR) + → Renders InertiaShell component with page JSON + → Returns full HTML document + ↓ +Browser receives HTML + → React's createInertiaApp hydrates the page + → resolvePage() imports Products.pages.js + → "Products/Browse" component renders with props +``` + +### Subsequent Navigation + +On subsequent navigation (Inertia XHR requests), the flow is shorter: + +``` +Browser clicks Inertia link + → XHR GET /products/browse (with X-Inertia header) + ↓ +ASP.NET route handler + → Inertia.Render("Products/Browse", { products: [...] }) + ↓ +InertiaResult.ExecuteAsync() + → Detects X-Inertia header + → Returns JSON response (not HTML) + ↓ +Inertia.js client + → Swaps page component with new props + → No full page reload +``` + +## Server Side + +### Inertia.Render() + +The static `Inertia.Render()` method creates an `IResult` that handles both full page loads and XHR requests: + +```csharp +using SimpleModule.Core.Inertia; + +public class BrowseEndpoint : IViewEndpoint +{ + public void Map(IEndpointRouteBuilder app) + { + app.MapGet( + "/browse", + async (IProductContracts products) => + Inertia.Render( + "Products/Browse", + new { products = await products.GetAllProductsAsync() } + ) + ); + } +} +``` + +Parameters: +- **`component`** -- the page name (e.g., `"Products/Browse"`). Must match an entry in the module's `Pages/index.ts`. +- **`props`** -- an anonymous object or any serializable type. Serialized as camelCase JSON. + +### Props Serialization + +Props are serialized using `System.Text.Json` with `JsonNamingPolicy.CamelCase`: + +```csharp +// Server +Inertia.Render("Products/Edit", new { product }); + +// Client receives: +// { "component": "Products/Edit", "props": { "product": { "id": 1, "name": "..." } } } +``` + +::: warning +Property names are automatically converted to camelCase. A C# property `ProductName` becomes `productName` in JavaScript. +::: + +### Shared Data + +Use `InertiaSharedData` to share props across all Inertia responses in a single HTTP request. This is useful for data that every page needs (current user, flash messages, etc.): + +```csharp +public sealed class InertiaSharedData +{ + public void Set(string key, object? value); + public T? Get(string key, T? defaultValue = default); + public bool Remove(string key); + public bool Contains(string key); + public IReadOnlyDictionary All { get; } +} +``` + +`InertiaSharedData` is registered as a **scoped** service. Set values in middleware or endpoint filters, and they are automatically merged into every Inertia response for that request: + +```csharp +app.Use(async (context, next) => +{ + var sharedData = context.RequestServices.GetRequiredService(); + sharedData.Set("appName", "My Application"); + sharedData.Set("user", new { name = context.User.Identity?.Name }); + await next(); +}); +``` + +Shared data has **lower priority** than endpoint props. If an endpoint sets a prop with the same key as shared data, the endpoint's value wins. + +### Version Detection + +The Inertia middleware handles asset versioning to prevent stale JavaScript from running after deployments: + +```csharp +app.UseInertia(); // Add to middleware pipeline +``` + +The middleware: + +1. Sets `X-Inertia-Version` on every response +2. On XHR requests (`X-Inertia` header present), compares the client's version with the server's +3. If versions differ, returns `409 Conflict` with `X-Inertia-Location` header, triggering a full page reload +4. Converts `302` redirects to `303` for PUT/PATCH/DELETE requests (Inertia protocol requirement) + +The version is determined by: +1. `DEPLOYMENT_VERSION` environment variable (for rolling deployments) +2. Assembly version as fallback + +## Blazor SSR Shell + +The `InertiaShell` Blazor component renders the HTML document with embedded page data: + +```html + + + + + + + + + + + + + +``` + +The `InertiaPageRenderer` uses Blazor's `HtmlRenderer` to produce static HTML server-side: + +```csharp +public sealed class InertiaPageRenderer( + IServiceProvider services, + ILoggerFactory loggerFactory, + IOptions options +) : IInertiaPageRenderer +{ + public async Task RenderPageAsync(HttpContext httpContext, string pageJson) + { + await using var renderer = new HtmlRenderer(services, loggerFactory); + var html = await renderer.Dispatcher.InvokeAsync(async () => + { + var output = await renderer.RenderComponentAsync( + options.Value.ShellComponent, + ParameterView.FromDictionary(new Dictionary + { + ["PageJson"] = pageJson, + ["HttpContext"] = httpContext, + }) + ); + return output.ToHtmlString(); + }); + + httpContext.Response.ContentType = "text/html; charset=utf-8"; + await httpContext.Response.WriteAsync(html); + } +} +``` + +You can customize the shell component via `InertiaOptions`: + +```csharp +builder.Services.AddSimpleModuleBlazor(options => +{ + options.ShellComponent = typeof(MyCustomShell); +}); +``` + +## Client Side + +### App Bootstrap + +The React app is bootstrapped in `ClientApp/app.tsx`: + +```typescript +import { createInertiaApp } from '@inertiajs/react'; +import { resolvePage } from '@simplemodule/client/resolve-page'; +import { createRoot } from 'react-dom/client'; + +createInertiaApp({ + resolve: resolvePage, + setup({ el, App, props }) { + createRoot(el).render(); + }, +}); +``` + +### Page Resolution + +The `resolvePage` function dynamically imports module page bundles based on the component name: + +```typescript +export async function resolvePage(name: string) { + const moduleName = name.split('/')[0]; + const cacheBuster = ( + document.querySelector('meta[name="cache-buster"]') as HTMLMetaElement + )?.content; + const suffix = cacheBuster ? `?v=${cacheBuster}` : ''; + + const mod = await import( + `/_content/${moduleName}/${moduleName}.pages.js${suffix}` + ); + + const page = mod.pages[name]; + // Supports lazy entries: () => import('./SomePage') + if (typeof page === 'function') { + const resolved = await page(); + return resolved.default ? resolved : { default: resolved }; + } + + return page.default ? page : { default: page }; +} +``` + +For a component name like `"Products/Browse"`: +1. Extracts module name: `"Products"` +2. Imports `/_content/Products/Products.pages.js` +3. Looks up `"Products/Browse"` in the `pages` export +4. Supports lazy loading via function entries + +### Module Pages Registry + +Each module exports a `pages` record in `Pages/index.ts`: + +```typescript +// modules/Products/src/Products/Pages/index.ts +export const pages: Record = { + 'Products/Browse': () => import('../Views/Browse'), + 'Products/Manage': () => import('../Views/Manage'), + 'Products/Create': () => import('../Views/Create'), + 'Products/Edit': () => import('../Views/Edit'), +}; +``` + +::: danger Critical +Every `IViewEndpoint` that calls `Inertia.Render("Module/Page", ...)` **must** have a matching entry in the module's `Pages/index.ts`. Missing entries silently fail with no error in the console. Run `npm run validate-pages` to catch mismatches. +::: + +### Writing a Page Component + +Page components receive props from the server as React props: + +```tsx +import { PageHeader } from '@simplemodule/ui/components'; + +interface BrowseProps { + products: Product[]; +} + +export default function Browse({ products }: BrowseProps) { + return ( +
+ +
    + {products.map((p) => ( +
  • {p.name} - ${p.price}
  • + ))} +
+
+ ); +} +``` + +### Error Handling + +The ClientApp handles non-Inertia error responses (404, 500, etc.) by intercepting the `invalid` event on the Inertia router: + +```typescript +router.on('invalid', (event) => { + event.preventDefault(); + const response = event.detail.response; + const body = response.data as { detail?: string; title?: string } | undefined; + const message = body?.detail ?? body?.title ?? `Server error (${response.status})`; + showErrorToast(message); +}); +``` + +Instead of showing the default "must receive a valid Inertia response" error, a toast notification displays the server error message. + +## Full Example + +Here is the complete flow for a Products/Browse page: + +**1. Endpoint (C#):** + +```csharp +public class BrowseEndpoint : IViewEndpoint +{ + public void Map(IEndpointRouteBuilder app) + { + app.MapGet( + "/browse", + async (IProductContracts products) => + Inertia.Render( + "Products/Browse", + new { products = await products.GetAllProductsAsync() } + ) + ).AllowAnonymous(); + } +} +``` + +**2. Page registry (TypeScript):** + +```typescript +// Pages/index.ts +export const pages: Record = { + 'Products/Browse': () => import('../Views/Browse'), +}; +``` + +**3. Page component (React):** + +```tsx +// Views/Browse.tsx +export default function Browse({ products }: { products: Product[] }) { + return ( +
+

Products

+ {products.map((p) => ( +
{p.name}
+ ))} +
+ ); +} +``` + +**4. What happens at runtime:** + +1. User navigates to `/products/browse` +2. ASP.NET matches the route, calls the endpoint handler +3. `IProductContracts.GetAllProductsAsync()` fetches products from the database +4. `Inertia.Render("Products/Browse", { products })` serializes the page data +5. On initial load: Blazor SSR renders the full HTML shell with embedded JSON +6. React hydrates, `resolvePage("Products/Browse")` imports `Products.pages.js` +7. The Browse component renders with the server-provided products array +8. On subsequent navigation: only JSON is returned, React swaps the component diff --git a/docs/site/guide/menus.md b/docs/site/guide/menus.md new file mode 100644 index 00000000..4c66318c --- /dev/null +++ b/docs/site/guide/menus.md @@ -0,0 +1,230 @@ +--- +outline: deep +--- + +# Menus + +SimpleModule provides a menu system that allows each module to contribute navigation items. Menus are configured in module classes and organized into sections for different parts of the UI. + +## Overview + +The menu system has two layers: + +1. **Module menus** -- static navigation items defined by modules at startup via `IMenuBuilder` +2. **Public menus** -- dynamic, database-backed menu trees managed through the Settings module via `IPublicMenuProvider` + +## Module Menus + +### IMenuBuilder + +Each module contributes menu items by implementing `ConfigureMenu` on its module class: + +```csharp +public interface IMenuBuilder +{ + IMenuBuilder Add(MenuItem item); +} +``` + +The builder collects items from all modules, then sorts them by `Order` to produce the final list. + +### Configuring Menu Items + +Override `ConfigureMenu` in your module class: + +```csharp +[Module("Products", RoutePrefix = "/products", ViewPrefix = "/products")] +public class ProductsModule : IModule +{ + public void ConfigureMenu(IMenuBuilder menus) + { + menus.Add(new MenuItem + { + Label = "Products", + Url = "/products/browse", + Icon = """...""", + Order = 30, + Section = MenuSection.Navbar, + RequiresAuth = false, + }); + + menus.Add(new MenuItem + { + Label = "Manage Products", + Url = "/products/manage", + Icon = """...""", + Order = 31, + Section = MenuSection.Navbar, + }); + } +} +``` + +### MenuItem Properties + +| Property | Type | Default | Description | +|----------|------|---------|-------------| +| `Label` | `string` | *required* | Display text for the menu item | +| `Url` | `string` | *required* | Navigation URL | +| `Icon` | `string` | `""` | SVG icon markup | +| `Order` | `int` | `0` | Sort order (lower values appear first) | +| `Section` | `MenuSection` | `Navbar` | Which UI section this item belongs to | +| `RequiresAuth` | `bool` | `true` | Whether the user must be authenticated | +| `Group` | `string?` | `null` | Optional group label for visual grouping | + +### Menu Sections + +The `MenuSection` enum determines where a menu item is rendered: + +```csharp +public enum MenuSection +{ + Navbar, // Top navigation bar + UserDropdown, // User profile dropdown + AdminSidebar, // Admin panel sidebar + AppSidebar, // Main application sidebar +} +``` + +Items can appear in multiple sections by adding separate `MenuItem` entries: + +```csharp +public void ConfigureMenu(IMenuBuilder menus) +{ + // Show in the top navbar + menus.Add(new MenuItem + { + Label = "Products", + Url = "/products/browse", + Order = 30, + Section = MenuSection.Navbar, + RequiresAuth = false, + }); + + // Also show in the app sidebar + menus.Add(new MenuItem + { + Label = "Products", + Url = "/products/browse", + Order = 20, + Section = MenuSection.AppSidebar, + }); +} +``` + +### Grouping + +Use the `Group` property to visually group related items in sidebar sections: + +```csharp +menus.Add(new MenuItem +{ + Label = "Dashboard", + Url = "/audit-logs/dashboard", + Order = 94, + Section = MenuSection.AdminSidebar, + Group = "Audit Logs", +}); + +menus.Add(new MenuItem +{ + Label = "Browse Logs", + Url = "/audit-logs/browse", + Order = 95, + Section = MenuSection.AdminSidebar, + Group = "Audit Logs", +}); +``` + +Items with the same `Group` value are rendered together under a group header. + +### Ordering + +Menu items are sorted globally by `Order` within each section. Use consistent ranges per module to keep items together: + +| Range | Module | +|-------|--------| +| 10-19 | Dashboard | +| 20-29 | Core features | +| 30-39 | Products | +| 40-49 | Orders | +| 90-99 | Settings / Admin | + +::: tip +Leave gaps between order values so new items can be inserted without renumbering everything. +::: + +## IMenuRegistry + +At runtime, the `IMenuRegistry` provides read-only access to the collected menu items: + +```csharp +public interface IMenuRegistry +{ + IReadOnlyList GetItems(MenuSection section); +} +``` + +The `MenuRegistry` groups items by section and returns them sorted by `Order`: + +```csharp +app.MapGet("/api/menu", (IMenuRegistry registry) => +{ + return new + { + navbar = registry.GetItems(MenuSection.Navbar), + sidebar = registry.GetItems(MenuSection.AppSidebar), + }; +}); +``` + +## Public Menus + +The Settings module provides a database-backed public menu system through `IPublicMenuProvider`: + +```csharp +public interface IPublicMenuProvider +{ + Task> GetMenuTreeAsync(); + Task GetHomePageUrlAsync(); +} +``` + +### PublicMenuItem + +Public menu items support hierarchical nesting: + +```csharp +public sealed class PublicMenuItem +{ + public required string Label { get; init; } + public required string Url { get; init; } + public string Icon { get; init; } = ""; + public string? CssClass { get; init; } + public bool OpenInNewTab { get; init; } + public bool IsHomePage { get; init; } + public IReadOnlyList Children { get; init; } = []; +} +``` + +Public menus are managed through the Settings module's admin UI, which supports: + +- Creating, updating, and deleting menu items +- Hierarchical nesting (up to 3 levels deep) +- Drag-and-drop reordering +- Designating a home page +- Linking to internal page routes or external URLs + +### Home Page Resolution + +The framework uses `IPublicMenuProvider.GetHomePageUrlAsync()` to redirect the root URL (`/`) to the configured home page. If a menu item is marked as `IsHomePage`, requests to `/` are internally rewritten to that URL. + +## Frontend Rendering + +Menu data is typically shared with the frontend via Inertia shared data or API endpoints. The Blazor layout components (`ModuleNav`, `UserDropdown`, sidebar components) read from `IMenuRegistry` to render navigation: + +- **`ModuleNav`** renders `MenuSection.Navbar` items +- **Sidebar components** render `MenuSection.AppSidebar` and `MenuSection.AdminSidebar` items +- **`UserDropdown`** renders `MenuSection.UserDropdown` items + +The `RequiresAuth` property controls visibility -- items with `RequiresAuth = true` are only shown to authenticated users. diff --git a/docs/site/guide/modules.md b/docs/site/guide/modules.md new file mode 100644 index 00000000..2b930599 --- /dev/null +++ b/docs/site/guide/modules.md @@ -0,0 +1,329 @@ +--- +outline: deep +--- + +# Modules + +A **module** is the fundamental building block of a SimpleModule application. Each module is a self-contained feature unit that encapsulates its own services, endpoints, database context, menu items, permissions, and settings. Modules are discovered at compile time by a Roslyn source generator -- there is no reflection at runtime. + +## What Is a Module? + +A module is a class that implements the `IModule` interface and is decorated with the `[Module]` attribute. It groups related functionality into a single cohesive unit that can be developed, tested, and reasoned about independently. + +For example, a Products module owns everything related to products: the database table, the API endpoints, the React views, the menu entries, and the permission definitions. Other modules interact with Products only through its **contracts** interface. + +## The `IModule` Interface + +The `IModule` interface defines six lifecycle hooks, all with default (no-op) implementations. You only override the ones you need: + +```csharp +public interface IModule +{ + virtual void ConfigureServices(IServiceCollection services, IConfiguration configuration) { } + virtual void ConfigureEndpoints(IEndpointRouteBuilder endpoints) { } + virtual void ConfigureMiddleware(IApplicationBuilder app) { } + virtual void ConfigureMenu(IMenuBuilder menus) { } + virtual void ConfigurePermissions(PermissionRegistryBuilder builder) { } + virtual void ConfigureSettings(ISettingsBuilder settings) { } +} +``` + +| Method | Purpose | +|--------|---------| +| `ConfigureServices` | Register DI services, database contexts, and module-specific configuration | +| `ConfigureEndpoints` | **Escape hatch** for non-standard route registration. Most modules do not need this -- endpoints are auto-discovered | +| `ConfigureMiddleware` | Add module-specific middleware to the ASP.NET pipeline | +| `ConfigureMenu` | Register navigation items in the menu system | +| `ConfigurePermissions` | Define module-specific permissions for authorization | +| `ConfigureSettings` | Register configurable settings for the module | + +::: tip +All methods are `virtual` with default no-op implementations. You only need to override the hooks your module actually uses. +::: + +## The `[Module]` Attribute + +The `[Module]` attribute provides metadata that the source generator uses to wire up your module: + +```csharp +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public sealed class ModuleAttribute : Attribute +{ + public string Name { get; } + public string Version { get; } + public string RoutePrefix { get; set; } = ""; + public string ViewPrefix { get; set; } = ""; + + public ModuleAttribute(string name, string version = "1.0.0") + { + Name = name; + Version = version; + } +} +``` + +| Property | Description | +|----------|-------------| +| `Name` | The unique module name (e.g., `"Products"`). Used for database schema isolation and logging. | +| `Version` | Semantic version string. Defaults to `"1.0.0"`. | +| `RoutePrefix` | Base path for API endpoints (e.g., `"/api/products"`). All `IEndpoint` implementations are grouped under this prefix. | +| `ViewPrefix` | Base path for view (Inertia) endpoints (e.g., `"/products"`). All `IViewEndpoint` implementations are grouped under this prefix. | + +## Full Example: The Products Module + +Here is the Products module from the framework's reference implementation: + +```csharp +// ProductsConstants.cs +namespace SimpleModule.Products; + +public static class ProductsConstants +{ + public const string ModuleName = "Products"; + public const string RoutePrefix = "/api/products"; +} +``` + +```csharp +// ProductsModule.cs +[Module( + ProductsConstants.ModuleName, + RoutePrefix = ProductsConstants.RoutePrefix, + ViewPrefix = "/products" +)] +public class ProductsModule : IModule +{ + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + services.AddModuleDbContext( + configuration, ProductsConstants.ModuleName); + } + + public void ConfigureMenu(IMenuBuilder menus) + { + menus.Add(new MenuItem + { + Label = "Products", + Url = "/products/browse", + Icon = """...""", + Order = 30, + Section = MenuSection.Navbar, + RequiresAuth = false, + }); + + menus.Add(new MenuItem + { + Label = "Manage Products", + Url = "/products/manage", + Icon = """...""", + Order = 31, + Section = MenuSection.Navbar, + }); + } +} +``` + +This module: +1. Registers its database context in `ConfigureServices` +2. Adds two menu items in `ConfigureMenu` +3. Does **not** override `ConfigureEndpoints` -- its endpoints are auto-discovered + +## Compile-Time Discovery + +SimpleModule uses a Roslyn incremental source generator (`IIncrementalGenerator` targeting netstandard2.0) to discover modules at compile time. When the host project builds, the generator scans all referenced assemblies for: + +- Classes decorated with `[Module]` that implement `IModule` +- Classes implementing `IEndpoint` (API endpoints) +- Classes implementing `IViewEndpoint` (Inertia view endpoints) +- Types marked with `[Dto]` (data transfer objects) +- Contract interfaces following the `IContracts` pattern + +### Generated Code + +The source generator emits several extension methods as plain C# code: + +**`AddModules()`** -- Instantiates each discovered module and calls `ConfigureServices` in topologically sorted order (respecting inter-module dependencies). Also auto-registers contract implementations: + +```csharp +// Generated: ModuleExtensions.g.cs +public static class ModuleExtensions +{ + internal static readonly ProductsModule _productsModule = new(); + internal static readonly OrdersModule _ordersModule = new(); + + public static IServiceCollection AddModules( + this IServiceCollection services, IConfiguration configuration) + { + // Phase 1: No dependencies + ((IModule)_productsModule).ConfigureServices(services, configuration); + + // Phase 2: Depends on Products + ((IModule)_ordersModule).ConfigureServices(services, configuration); + + // Auto-discovered contract implementations + services.AddScoped(); + services.AddScoped(); + + return services; + } +} +``` + +**`MapModuleEndpoints()`** -- Creates route groups using each module's `RoutePrefix` and `ViewPrefix`, then maps all discovered endpoint classes: + +```csharp +// Generated: EndpointExtensions.g.cs +public static WebApplication MapModuleEndpoints(this WebApplication app) +{ + // Auto-registered endpoints for Products + { + var group = app.MapGroup("/api/products") + .WithTags("Products").RequireAuthorization(); + new GetAllEndpoint().Map(group); + new GetByIdEndpoint().Map(group); + new CreateEndpoint().Map(group); + new UpdateEndpoint().Map(group); + new DeleteEndpoint().Map(group); + } + + // Auto-registered view endpoints for Products + { + var viewGroup = app.MapGroup("/products") + .WithTags("Products").ExcludeFromDescription() + .RequireAuthorization(); + new BrowseEndpoint().Map(viewGroup); + new ManageEndpoint().Map(viewGroup); + // ... + } + + return app; +} +``` + +**`CollectModuleMenuItems()`** -- Calls `ConfigureMenu` on each module to build the menu registry: + +```csharp +// Generated: MenuExtensions.g.cs +public static IServiceCollection CollectModuleMenuItems( + this IServiceCollection services) +{ + var menus = new MenuBuilder(); + ((IModule)ModuleExtensions._productsModule).ConfigureMenu(menus); + ((IModule)ModuleExtensions._ordersModule).ConfigureMenu(menus); + services.AddSingleton(new MenuRegistry(menus.ToList())); + return services; +} +``` + +::: tip +You can inspect the generated code in your IDE by navigating to the `SimpleModule.Generator` analyzer output. It is plain C# with no hidden magic. +::: + +## Module Lifecycle and Registration Order + +The source generator performs a topological sort of modules based on their dependencies (determined by project references between contracts). This ensures that when module B depends on module A's contracts, module A is always registered first. + +The lifecycle during application startup: + +1. **`AddSimpleModule()`** is called on the `WebApplicationBuilder` +2. Infrastructure services are registered (event bus, Blazor, etc.) +3. **`AddModules()`** calls `ConfigureServices` on each module in dependency order +4. Contract implementations are auto-registered as scoped services +5. Permissions are collected from all modules +6. Menu items are collected from all modules +7. **`UseSimpleModule()`** configures the middleware pipeline +8. Module middleware is applied (for modules that override `ConfigureMiddleware`) +9. **`MapModuleEndpoints()`** maps all discovered API and view endpoints + +## Creating a New Module + +### Using the CLI + +The fastest way to create a module: + +```bash +sm new module Products +``` + +This scaffolds the full module structure with contracts, endpoints, tests, and frontend pages. + +### Manual Creation + +If you prefer to create a module by hand, follow this structure: + +``` +modules/Products/ + src/ + Products.Contracts/ + Products.Contracts.csproj # References Core only + IProductContracts.cs # Public API interface + Product.cs # Shared DTO types + ProductId.cs # Strongly-typed ID + Products/ + Products.csproj # References Core + Contracts + Database + ProductsModule.cs # Module class with [Module] attribute + ProductsConstants.cs # Module name and route prefix + ProductsDbContext.cs # EF Core DbContext + Endpoints/Products/ # IEndpoint implementations + Views/ # IViewEndpoint implementations + Pages/index.ts # React page registry + vite.config.ts # Vite library mode build + package.json # npm package with peer dependencies + tests/ + Products.Tests/ # xUnit test project +``` + +Key project file requirements: + +**Contracts project** (`Microsoft.NET.Sdk`): +```xml + + + + + +``` + +**Module project** (`Microsoft.NET.Sdk.Razor`): +```xml + + + + + + + + +``` + +::: warning +Modules **must** include `` in their project file. Without it, ASP.NET types like `IEndpointRouteBuilder` will not be available. +::: + +After creating the projects, remember to: + +1. Add a `ProjectReference` from the host project (`template/SimpleModule.Host/SimpleModule.Host.csproj`) to your module +2. Add all new projects to the solution file (`SimpleModule.slnx`) + +## The ConfigureEndpoints Escape Hatch + +Most modules should **not** override `ConfigureEndpoints`. The source generator automatically discovers all `IEndpoint` and `IViewEndpoint` classes and maps them under the module's route prefix. + +However, if you need non-standard routing (e.g., routes that don't fit the prefix pattern), you can override `ConfigureEndpoints`: + +```csharp +[Module("Webhooks", RoutePrefix = "/api/webhooks")] +public class WebhooksModule : IModule +{ + public void ConfigureEndpoints(IEndpointRouteBuilder endpoints) + { + // Custom routes that don't follow the standard prefix + endpoints.MapPost("/hooks/stripe", StripeWebhookHandler.Handle); + endpoints.MapPost("/hooks/github", GitHubWebhookHandler.Handle); + } +} +``` + +::: danger +When you override `ConfigureEndpoints`, the auto-discovery of `IEndpoint` classes is **skipped** for that module. You are responsible for mapping all endpoints manually. `IViewEndpoint` classes are still auto-discovered regardless. +::: diff --git a/docs/site/guide/permissions.md b/docs/site/guide/permissions.md new file mode 100644 index 00000000..17fff6fd --- /dev/null +++ b/docs/site/guide/permissions.md @@ -0,0 +1,252 @@ +--- +outline: deep +--- + +# Permissions + +SimpleModule includes a claims-based permission system that integrates with ASP.NET Core authorization. Each module defines its own permissions, registers them with the framework, and protects endpoints using attributes or extension methods. + +## Overview + +The permission system has three layers: + +1. **Permission definitions** -- constants in a class implementing `IModulePermissions` +2. **Permission registry** -- collects all permissions at startup, grouped by module +3. **Authorization handler** -- checks `permission` claims on the current user + +## Defining Permissions + +Create a sealed class implementing `IModulePermissions` with `public const string` fields. The convention is `ModuleName.Action`: + +```csharp +using SimpleModule.Core.Authorization; + +namespace SimpleModule.Products; + +public sealed class ProductsPermissions : IModulePermissions +{ + public const string View = "Products.View"; + public const string Create = "Products.Create"; + public const string Update = "Products.Update"; + public const string Delete = "Products.Delete"; +} +``` + +::: tip Auto-Discovery +Classes implementing `IModulePermissions` are **automatically discovered** by the source generator. You do not need to register them manually -- the generator scans for all `IModulePermissions` implementations and registers their constants with the `PermissionRegistry`. +::: + +The `PermissionRegistryBuilder` uses reflection to find all `public const string` fields in the class and groups them by the prefix before the first dot: + +```csharp +// "Products.View" → grouped under "Products" module +// "Orders.Create" → grouped under "Orders" module +// "GlobalAdmin" → grouped under "Global" (no dot prefix) +``` + +## Protecting Endpoints + +### Extension Method (Recommended) + +Use the `RequirePermission` extension method on endpoint builders: + +```csharp +public class CreateEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder app) => + app.MapPost( + "/", + (CreateProductRequest request, IProductContracts productContracts) => + { + // ... handle request + } + ) + .RequirePermission(ProductsPermissions.Create); +} +``` + +Multiple permissions can be required on a single endpoint: + +```csharp +app.MapDelete("/{id}", (int id, IProductContracts products) => + products.DeleteProductAsync(id) + ) + .RequirePermission(ProductsPermissions.View, ProductsPermissions.Delete); +``` + +Each permission becomes a separate `PermissionRequirement`. The user must satisfy **all** of them. + +### Attribute (for Source Generator Discovery) + +The `[RequirePermission]` attribute can be applied to endpoint classes: + +```csharp +[RequirePermission(ProductsPermissions.View)] +public class GetByIdEndpoint : IEndpoint +{ + public void Map(IEndpointRouteBuilder app) => + app.MapGet("/{id}", (int id, IProductContracts products) => + products.GetProductByIdAsync(id) + ); +} +``` + +## How Authorization Works + +### PermissionRequirement + +Each permission string is wrapped in a `PermissionRequirement` implementing `IAuthorizationRequirement`: + +```csharp +public sealed class PermissionRequirement : IAuthorizationRequirement +{ + public string Permission { get; } + + public PermissionRequirement(string permission) + { + Permission = permission; + } +} +``` + +### PermissionAuthorizationHandler + +The framework registers a `PermissionAuthorizationHandler` that checks for permission claims: + +```csharp +public sealed class PermissionAuthorizationHandler + : AuthorizationHandler +{ + protected override Task HandleRequirementAsync( + AuthorizationHandlerContext context, + PermissionRequirement requirement + ) + { + // Admin role bypasses all permission checks + if (context.User.IsInRole("Admin")) + { + context.Succeed(requirement); + return Task.CompletedTask; + } + + if (context.User.HasClaim("permission", requirement.Permission)) + { + context.Succeed(requirement); + } + + return Task.CompletedTask; + } +} +``` + +Key behaviors: + +- Users with the **Admin role** bypass all permission checks +- For other users, the handler looks for a `permission` claim matching the requirement string +- If no matching claim is found, the requirement fails (returns 403 Forbidden) + +### RequirePermission Extension + +The `RequirePermission` extension method on `IEndpointConventionBuilder` composes ASP.NET Core's `RequireAuthorization` with permission requirements: + +```csharp +public static TBuilder RequirePermission( + this TBuilder builder, + params string[] permissions +) + where TBuilder : IEndpointConventionBuilder +{ + return builder.RequireAuthorization(policy => + { + policy.RequireAuthenticatedUser(); + foreach (var permission in permissions) + { + policy.AddRequirements(new PermissionRequirement(permission)); + } + }); +} +``` + +This ensures the user is **authenticated** and has the required **permission claims**. + +## PermissionRegistry + +The `PermissionRegistry` provides read-only access to all registered permissions at runtime: + +```csharp +public sealed class PermissionRegistry +{ + public IReadOnlySet AllPermissions { get; } + public IReadOnlyDictionary> ByModule { get; } +} +``` + +This is useful for building admin UIs that display and assign permissions. For example, a role management page can enumerate all permissions grouped by module: + +```csharp +app.MapGet("/permissions", (PermissionRegistry registry) => +{ + return registry.ByModule; // { "Products": ["Products.View", ...], "Orders": [...] } +}); +``` + +## Assigning Permissions to Users + +The permission system checks for `permission` claims on the `ClaimsPrincipal`. How those claims get there depends on your authentication setup: + +- **Database-backed roles**: Store role-permission mappings, load as claims at login +- **OpenID Connect**: Include permissions in the ID token or userinfo response +- **Claims transformation**: Use an `IClaimsTransformation` to add permission claims dynamically + +Example claims transformation: + +```csharp +public class PermissionClaimsTransformation(IUserRoleService roleService) + : IClaimsTransformation +{ + public async Task TransformAsync(ClaimsPrincipal principal) + { + var userId = principal.FindFirstValue(ClaimTypes.NameIdentifier); + if (userId is null) return principal; + + var permissions = await roleService.GetPermissionsForUserAsync(userId); + var identity = new ClaimsIdentity(); + + foreach (var permission in permissions) + { + identity.AddClaim(new Claim("permission", permission)); + } + + principal.AddIdentity(identity); + return principal; + } +} +``` + +## Testing Permissions + +The test infrastructure provides `CreateAuthenticatedClient` which accepts claims. Add permission claims to test protected endpoints: + +```csharp +[Fact] +public async Task Create_product_requires_permission() +{ + var client = factory.CreateAuthenticatedClient( + new Claim("permission", ProductsPermissions.Create) + ); + + var response = await client.PostAsJsonAsync("/products", new { Name = "Test" }); + + response.StatusCode.Should().Be(HttpStatusCode.Created); +} + +[Fact] +public async Task Create_product_forbidden_without_permission() +{ + var client = factory.CreateAuthenticatedClient(); // no permission claims + + var response = await client.PostAsJsonAsync("/products", new { Name = "Test" }); + + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); +} +``` diff --git a/docs/site/guide/settings.md b/docs/site/guide/settings.md new file mode 100644 index 00000000..0583d76a --- /dev/null +++ b/docs/site/guide/settings.md @@ -0,0 +1,251 @@ +--- +outline: deep +--- + +# Settings + +SimpleModule provides a settings infrastructure that lets modules declare configurable values with metadata. Settings are stored in a database and scoped to different levels (system, application, user). + +## Overview + +The settings system has two parts: + +1. **Setting definitions** -- declared by modules at startup, describing what settings exist and their metadata +2. **Settings storage** -- the Settings module provides persistence and retrieval through `ISettingsContracts` + +## Defining Settings + +Override `ConfigureSettings` in your module class to declare settings: + +```csharp +[Module("Settings", RoutePrefix = "/settings", ViewPrefix = "/settings")] +public class SettingsModule : IModule +{ + public void ConfigureSettings(ISettingsBuilder settings) + { + settings + .Add(new SettingDefinition + { + Key = "app.title", + DisplayName = "Application Title", + Group = "General", + Scope = SettingScope.Application, + DefaultValue = "\"SimpleModule\"", + Type = SettingType.Text, + }) + .Add(new SettingDefinition + { + Key = "app.theme", + DisplayName = "Theme", + Description = "Default color theme for the application", + Group = "Appearance", + Scope = SettingScope.User, + DefaultValue = "\"light\"", + Type = SettingType.Text, + }); + } +} +``` + +### ISettingsBuilder + +The builder collects definitions from all modules: + +```csharp +public interface ISettingsBuilder +{ + ISettingsBuilder Add(SettingDefinition definition); +} +``` + +Calls can be chained since `Add` returns the builder. + +### SettingDefinition + +Each setting is described by a `SettingDefinition`: + +| Property | Type | Description | +|----------|------|-------------| +| `Key` | `string` | Unique identifier (convention: `module.name`) | +| `DisplayName` | `string` | Human-readable label for admin UI | +| `Description` | `string?` | Optional help text | +| `Group` | `string?` | Groups related settings in the UI | +| `Scope` | `SettingScope` | Where the setting applies | +| `DefaultValue` | `string?` | Default value (as a string) | +| `Type` | `SettingType` | Value type for UI rendering | + +### SettingScope + +Settings are scoped to control who can change them and where they apply: + +```csharp +public enum SettingScope +{ + System = 0, // Infrastructure settings (maintenance mode, feature flags) + Application = 1, // App-wide settings (timezone, title) + User = 2, // Per-user preferences (theme, language) +} +``` + +- **System** -- only administrators can modify; affects the entire system +- **Application** -- app-wide configuration visible to all users +- **User** -- per-user preferences that override application defaults + +### SettingType + +The type determines how the setting is rendered in the admin UI: + +```csharp +public enum SettingType +{ + Text = 0, // Single-line text input + Number = 1, // Numeric input + Bool = 2, // Toggle switch + Json = 3, // JSON editor +} +``` + +## Real-World Example + +The AuditLogs module demonstrates a comprehensive settings setup with multiple related settings: + +```csharp +public void ConfigureSettings(ISettingsBuilder settings) +{ + settings + .Add(new SettingDefinition + { + Key = "auditlogs.capture.http", + DisplayName = "HTTP Request Capture", + Description = "Capture all HTTP requests in audit log", + Group = "Audit Logs", + Scope = SettingScope.System, + DefaultValue = "true", + Type = SettingType.Bool, + }) + .Add(new SettingDefinition + { + Key = "auditlogs.retention.days", + DisplayName = "Retention Days", + Description = "Number of days to keep audit entries", + Group = "Audit Logs", + Scope = SettingScope.System, + DefaultValue = "90", + Type = SettingType.Number, + }) + .Add(new SettingDefinition + { + Key = "auditlogs.excluded.paths", + DisplayName = "Excluded Paths", + Description = "Comma-separated path prefixes to skip", + Group = "Audit Logs", + Scope = SettingScope.System, + DefaultValue = "/health,/metrics,/_content,/js/,/css/", + Type = SettingType.Text, + }); +} +``` + +## Reading and Writing Settings + +The Settings module exposes `ISettingsContracts` for other modules to read and write setting values: + +```csharp +public interface ISettingsContracts +{ + Task GetSettingAsync(string key, SettingScope scope, string? userId = null); + Task GetSettingAsync(string key, SettingScope scope, string? userId = null); + Task ResolveUserSettingAsync(string key, string userId); + Task SetSettingAsync(string key, string value, SettingScope scope, string? userId = null); + Task DeleteSettingAsync(string key, SettingScope scope, string? userId = null); + Task> GetSettingsAsync(SettingsFilter? filter = null); +} +``` + +### Reading Settings + +```csharp +public class MyService(ISettingsContracts settings) +{ + public async Task DoWorkAsync() + { + // Read a typed setting + var retentionDays = await settings.GetSettingAsync( + "auditlogs.retention.days", + SettingScope.System + ); + + // Read a string setting + var title = await settings.GetSettingAsync( + "app.title", + SettingScope.Application + ); + } +} +``` + +### User Setting Resolution + +`ResolveUserSettingAsync` implements a **fallback chain**: it first checks for a user-scoped value, then falls back to the application-scoped default: + +```csharp +// Returns user's theme if set, otherwise the app default +var theme = await settings.ResolveUserSettingAsync("app.theme", userId); +``` + +### Writing Settings + +```csharp +// Set an application setting +await settings.SetSettingAsync("app.title", "My App", SettingScope.Application); + +// Set a user preference +await settings.SetSettingAsync("app.theme", "dark", SettingScope.User, userId); + +// Delete a user override (reverts to application default) +await settings.DeleteSettingAsync("app.theme", SettingScope.User, userId); +``` + +## Settings Definition Registry + +The `ISettingsDefinitionRegistry` provides read-only access to all registered setting definitions at runtime: + +```csharp +public interface ISettingsDefinitionRegistry +{ + IReadOnlyList GetDefinitions(SettingScope? scope = null); + SettingDefinition? GetDefinition(string key); +} +``` + +This is used by the admin UI to dynamically render settings forms: + +```csharp +app.MapGet("/api/settings/definitions", (ISettingsDefinitionRegistry registry) => +{ + // All definitions + var all = registry.GetDefinitions(); + + // Only system-scoped definitions + var system = registry.GetDefinitions(SettingScope.System); + + // Look up a specific setting + var def = registry.GetDefinition("app.title"); +}); +``` + +## Key Naming Conventions + +Follow the `module.category.name` pattern for setting keys: + +| Key | Module | Category | Name | +|-----|--------|----------|------| +| `app.title` | app | -- | title | +| `app.theme` | app | -- | theme | +| `auditlogs.capture.http` | auditlogs | capture | http | +| `auditlogs.retention.days` | auditlogs | retention | days | +| `system.maintenance_mode` | system | -- | maintenance_mode | + +::: tip +Use dot-separated keys for consistency. The `Group` property on `SettingDefinition` controls visual grouping in the UI independently from the key structure. +::: diff --git a/docs/site/index.md b/docs/site/index.md new file mode 100644 index 00000000..e85b09af --- /dev/null +++ b/docs/site/index.md @@ -0,0 +1,35 @@ +--- +layout: home + +hero: + name: SimpleModule + text: Modular Monolith Framework for .NET + tagline: Compile-time module discovery via Roslyn source generators. React 19 + Inertia.js frontend served via Blazor SSR. + actions: + - theme: brand + text: Get Started + link: /getting-started/introduction + - theme: alt + text: View on GitHub + link: https://github.com/anthropics/SimpleModule + +features: + - icon: ⚡ + title: Compile-time Discovery + details: Roslyn source generators scan your modules at build time — no reflection, no runtime overhead. Endpoints, DTOs, and menus are auto-registered. + - icon: ⚛️ + title: React + Inertia.js + details: Build modern React 19 frontends with server-driven navigation. Each module ships its own pages bundle, dynamically loaded by the host app. + - icon: 🔒 + title: Module Isolation + details: Each module gets its own database schema, permissions, settings, and menu entries. Cross-module communication happens through contracts and events. + - icon: 🛠️ + title: CLI Tooling + details: Scaffold projects, modules, and features with the sm CLI. Built-in doctor command validates your project structure and auto-fixes issues. + - icon: 🧪 + title: Test Infrastructure + details: Pre-built WebApplicationFactory with in-memory SQLite, test auth scheme, and Bogus data generators. Run against PostgreSQL in CI. + - icon: 🗄️ + title: Multi-Provider Database + details: SQLite for development, PostgreSQL or SQL Server for production. Schema isolation per module with automatic table prefix or schema management. +--- diff --git a/docs/site/package.json b/docs/site/package.json new file mode 100644 index 00000000..d1d661ec --- /dev/null +++ b/docs/site/package.json @@ -0,0 +1,13 @@ +{ + "name": "docs", + "private": true, + "type": "module", + "scripts": { + "dev": "vitepress dev", + "build": "vitepress build", + "preview": "vitepress preview" + }, + "devDependencies": { + "vitepress": "^1.6.4" + } +} diff --git a/docs/site/public/favicon.svg b/docs/site/public/favicon.svg new file mode 100644 index 00000000..0eb27396 --- /dev/null +++ b/docs/site/public/favicon.svg @@ -0,0 +1,4 @@ + + + S + diff --git a/docs/site/reference/api.md b/docs/site/reference/api.md new file mode 100644 index 00000000..01d4f2da --- /dev/null +++ b/docs/site/reference/api.md @@ -0,0 +1,525 @@ +--- +outline: deep +--- + +# API Reference + +Complete reference for SimpleModule's public interfaces, attributes, types, and generated extension methods. + +## Interfaces + +### IModule + +The core module interface. All modules must implement this interface (typically via a class decorated with `[Module]`). All methods have default (empty) implementations -- override only what you need. + +```csharp +namespace SimpleModule.Core; + +public interface IModule +{ + virtual void ConfigureServices(IServiceCollection services, IConfiguration configuration) { } + virtual void ConfigureEndpoints(IEndpointRouteBuilder endpoints) { } + virtual void ConfigureMiddleware(IApplicationBuilder app) { } + virtual void ConfigureMenu(IMenuBuilder menus) { } + virtual void ConfigurePermissions(PermissionRegistryBuilder builder) { } + virtual void ConfigureSettings(ISettingsBuilder settings) { } +} +``` + +| Method | Called When | Purpose | +|--------|-----------|---------| +| `ConfigureServices` | Application startup | Register DI services and configuration | +| `ConfigureEndpoints` | Application startup | Manual endpoint registration (escape hatch) | +| `ConfigureMiddleware` | Application startup | Register ASP.NET middleware | +| `ConfigureMenu` | Application startup | Add navigation menu items | +| `ConfigurePermissions` | Application startup | Register permission definitions | +| `ConfigureSettings` | Application startup | Register settings definitions | + +::: info +`ConfigureEndpoints` is an escape hatch. If your module has `IEndpoint`/`IViewEndpoint` implementations, the source generator registers them automatically. Only override `ConfigureEndpoints` for non-standard routing needs. +::: + +--- + +### IEndpoint + +Defines an API endpoint. Implementations are auto-discovered by the source generator and mapped to route groups based on the module's `RoutePrefix`. + +```csharp +namespace SimpleModule.Core; + +public interface IEndpoint +{ + void Map(IEndpointRouteBuilder app); +} +``` + +**Usage:** + +```csharp +public sealed class GetProducts : IEndpoint +{ + public void Map(IEndpointRouteBuilder app) + { + app.MapGet("/", async (IProductContracts products) => + { + var result = await products.GetAllAsync(); + return TypedResults.Ok(result); + }); + } +} +``` + +--- + +### IViewEndpoint + +Defines a view (page) endpoint that renders an Inertia.js page. Implementations are auto-discovered and mapped to route groups based on the module's `ViewPrefix`. + +```csharp +namespace SimpleModule.Core; + +public interface IViewEndpoint +{ + void Map(IEndpointRouteBuilder app); +} +``` + +**Usage:** + +```csharp +public sealed class Browse : IViewEndpoint +{ + public void Map(IEndpointRouteBuilder app) + { + app.MapGet("/", async (IProductContracts products, HttpContext context) => + { + var result = await products.GetAllAsync(); + return Inertia.Render("Products/Browse", new { products = result }); + }); + } +} +``` + +::: warning +Every `IViewEndpoint` must have a corresponding entry in the module's `Pages/index.ts`. See the [Pages Registry Pattern](/guide/modules#pages-registry) for details. +::: + +--- + +### IEvent + +Marker interface for event types. All events published through the event bus must implement this interface. + +```csharp +namespace SimpleModule.Core.Events; + +public interface IEvent; +``` + +**Usage:** + +```csharp +public sealed record OrderCreatedEvent(int OrderId, string CustomerName) : IEvent; +``` + +--- + +### IEventBus + +Publishes events to all registered handlers with exception isolation semantics. + +```csharp +namespace SimpleModule.Core.Events; + +public interface IEventBus +{ + Task PublishAsync(T @event, CancellationToken cancellationToken = default) + where T : IEvent; +} +``` + +**Behavior:** +- Handlers execute **sequentially** in registration order +- Handler failures are **isolated** -- if one handler throws, the others still run +- After all handlers execute, any exceptions are rethrown as `AggregateException` + +**Usage:** + +```csharp +public sealed class CreateOrder : IEndpoint +{ + public void Map(IEndpointRouteBuilder app) + { + app.MapPost("/", async (CreateOrderRequest request, + IOrderContracts orders, IEventBus eventBus) => + { + var order = await orders.CreateAsync(request); + await eventBus.PublishAsync(new OrderCreatedEvent(order.Id, request.CustomerName)); + return TypedResults.Created($"/{order.Id}", order); + }); + } +} +``` + +--- + +### IEventHandler\ + +Handles a specific event type. Implementations are registered in DI and invoked by the event bus. + +```csharp +namespace SimpleModule.Core.Events; + +public interface IEventHandler where T : IEvent +{ + Task HandleAsync(T @event, CancellationToken cancellationToken); +} +``` + +**Usage:** + +```csharp +public sealed class OrderCreatedAuditHandler : IEventHandler +{ + public async Task HandleAsync(OrderCreatedEvent @event, CancellationToken cancellationToken) + { + // Log the order creation for audit purposes + } +} +``` + +--- + +### IMenuBuilder + +Fluent interface for adding navigation menu items during module initialization. + +```csharp +namespace SimpleModule.Core.Menu; + +public interface IMenuBuilder +{ + IMenuBuilder Add(MenuItem item); +} +``` + +--- + +### IMenuRegistry + +Read-only access to registered menu items at runtime, grouped by section. + +```csharp +namespace SimpleModule.Core.Menu; + +public interface IMenuRegistry +{ + IReadOnlyList GetItems(MenuSection section); +} +``` + +--- + +### ISettingsBuilder + +Interface for registering module settings definitions during module initialization. + +```csharp +namespace SimpleModule.Core.Settings; + +public interface ISettingsBuilder +{ + ISettingsBuilder Add(SettingDefinition definition); +} +``` + +--- + +### IModulePermissions + +Marker interface for permission classes. Implementations are auto-discovered by the source generator. Permission classes must be `sealed` and contain only `public const string` fields. + +```csharp +namespace SimpleModule.Core.Authorization; + +public interface IModulePermissions; +``` + +**Usage:** + +```csharp +public sealed class ProductPermissions : IModulePermissions +{ + public const string View = "Products.View"; + public const string Create = "Products.Create"; + public const string Edit = "Products.Edit"; + public const string Delete = "Products.Delete"; +} +``` + +## Attributes + +### [Module] + +Marks a class as a module. Required for source generator discovery. + +```csharp +namespace SimpleModule.Core; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public sealed class ModuleAttribute : Attribute +{ + public string Name { get; } + public string Version { get; } + public string RoutePrefix { get; set; } = ""; + public string ViewPrefix { get; set; } = ""; + + public ModuleAttribute(string name, string version = "1.0.0") { ... } +} +``` + +| Property | Description | Default | +|----------|-------------|---------| +| `Name` | Module display name (required) | -- | +| `Version` | Module version | `"1.0.0"` | +| `RoutePrefix` | URL prefix for API endpoints | `""` | +| `ViewPrefix` | URL prefix for view endpoints | `""` | + +**Usage:** + +```csharp +[Module("Products", RoutePrefix = "/api/products", ViewPrefix = "/products")] +public sealed class ProductsModule : IModule +{ + public void ConfigureServices(IServiceCollection services, IConfiguration configuration) + { + // Register module services + } +} +``` + +--- + +### [Dto] + +Marks a type for TypeScript generation. Not required for types in `*.Contracts` assemblies (those are included by convention). + +```csharp +[AttributeUsage( + AttributeTargets.Class | AttributeTargets.Struct, + AllowMultiple = false, Inherited = false +)] +public sealed class DtoAttribute : Attribute { } +``` + +--- + +### [NoDtoGeneration] + +Excludes a public type in a Contracts assembly from automatic DTO/TypeScript generation. + +```csharp +[AttributeUsage( + AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Interface, + AllowMultiple = false, Inherited = false +)] +public sealed class NoDtoGenerationAttribute : Attribute { } +``` + +--- + +### [RequirePermission] + +Declares that an endpoint requires specific permissions. Used by the source generator to apply authorization metadata. + +```csharp +namespace SimpleModule.Core.Authorization; + +[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)] +public sealed class RequirePermissionAttribute : Attribute +{ + public string[] Permissions { get; } + + public RequirePermissionAttribute(params string[] permissions) { ... } +} +``` + +**Usage:** + +```csharp +[RequirePermission(ProductPermissions.Create)] +public sealed class CreateProduct : IEndpoint +{ + public void Map(IEndpointRouteBuilder app) + { + app.MapPost("/", async (CreateProductRequest request, IProductContracts products) => + { + var product = await products.CreateAsync(request); + return TypedResults.Created($"/{product.Id}", product); + }); + } +} +``` + +## Types + +### PagedResult\ + +Standard wrapper for paginated query results. + +```csharp +namespace SimpleModule.Core; + +public class PagedResult +{ + public IReadOnlyList Items { get; set; } = []; + public int TotalCount { get; set; } + public int Page { get; set; } + public int PageSize { get; set; } +} +``` + +--- + +### MenuItem + +Represents a navigation menu entry. + +```csharp +namespace SimpleModule.Core.Menu; + +public sealed class MenuItem +{ + public required string Label { get; init; } + public required string Url { get; init; } + public string Icon { get; init; } = ""; + public int Order { get; init; } + public MenuSection Section { get; init; } = MenuSection.Navbar; + public bool RequiresAuth { get; init; } = true; + public string? Group { get; init; } +} +``` + +--- + +### MenuSection + +Enum defining where a menu item appears in the UI. + +```csharp +namespace SimpleModule.Core.Menu; + +public enum MenuSection +{ + Navbar, + UserDropdown, + AdminSidebar, + AppSidebar, +} +``` + +--- + +### ModuleDbContextInfo + +Associates a module name with its DbContext type for schema isolation. + +```csharp +namespace SimpleModule.Database; + +public sealed record ModuleDbContextInfo(string ModuleName, Type DbContextType); +``` + +--- + +### PermissionRegistry + +Read-only registry of all permissions in the application. Registered as a singleton. + +```csharp +namespace SimpleModule.Core.Authorization; + +public sealed class PermissionRegistry +{ + public IReadOnlySet AllPermissions { get; } + public IReadOnlyDictionary> ByModule { get; } +} +``` + +--- + +### PermissionRegistryBuilder + +Builder for constructing the `PermissionRegistry` during startup. Passed to `IModule.ConfigurePermissions`. + +```csharp +namespace SimpleModule.Core.Authorization; + +public sealed class PermissionRegistryBuilder +{ + public void AddPermissions() where T : class { ... } + public void AddPermission(string permission) { ... } + public PermissionRegistry Build() { ... } +} +``` + +Permissions follow the convention `ModuleName.Action` (e.g., `Products.Create`). The module prefix is extracted from the first segment before the `.`. + +--- + +### SettingDefinition + +Defines a configurable setting for a module. + +```csharp +namespace SimpleModule.Core.Settings; + +public class SettingDefinition +{ + public string Key { get; set; } = string.Empty; + public string DisplayName { get; set; } = string.Empty; + public string? Description { get; set; } + public string? Group { get; set; } + public SettingScope Scope { get; set; } + public string? DefaultValue { get; set; } + public SettingType Type { get; set; } +} +``` + +## Generated Extension Methods + +These extension methods are generated by the source generator and called in `Program.cs`: + +### AddModules + +```csharp +public static IServiceCollection AddModules( + this IServiceCollection services, + IConfiguration configuration) +``` + +Registers all discovered modules' services in dependency order. This includes: +- Calling `ConfigureServices` on each module (topologically sorted) +- Registering auto-discovered contract implementations as scoped services +- Building and registering the `PermissionRegistry` +- Configuring JSON serializer options with the generated type info resolver + +### MapModuleEndpoints + +```csharp +public static WebApplication MapModuleEndpoints(this WebApplication app) +``` + +Maps all discovered `IEndpoint` and `IViewEndpoint` implementations. API endpoints are grouped by `RoutePrefix` with authorization required. View endpoints are grouped by `ViewPrefix` and excluded from API descriptions. + +Modules that override `ConfigureEndpoints` are called separately (the escape hatch). + +### CollectModuleMenuItems + +```csharp +public static IServiceCollection CollectModuleMenuItems( + this IServiceCollection services) +``` + +Invokes `ConfigureMenu` on all modules that implement it and registers the resulting `IMenuRegistry` as a singleton. diff --git a/docs/site/reference/configuration.md b/docs/site/reference/configuration.md new file mode 100644 index 00000000..ed5c930d --- /dev/null +++ b/docs/site/reference/configuration.md @@ -0,0 +1,201 @@ +--- +outline: deep +--- + +# Configuration + +SimpleModule uses the standard ASP.NET configuration system. Settings are loaded from `appsettings.json`, environment-specific files, and environment variables. + +## appsettings.json Structure + +The default `appsettings.json` for the host application: + +```json +{ + "Database": { + "DefaultConnection": "Data Source=app.db", + "Provider": "Sqlite" + }, + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} +``` + +## Database Configuration + +### Provider + +The `Database:Provider` setting determines which database engine to use: + +| Value | Database | Connection String Format | +|-------|----------|-------------------------| +| `Sqlite` | SQLite | `Data Source=app.db` | +| `PostgreSQL` | PostgreSQL | `Host=server;Database=db;Username=user;Password=pass` | + +### Connection String + +The `Database:DefaultConnection` setting holds the connection string for the selected provider. + +**SQLite (default for development):** + +```json +{ + "Database": { + "DefaultConnection": "Data Source=app.db", + "Provider": "Sqlite" + } +} +``` + +**PostgreSQL (recommended for production):** + +```json +{ + "Database": { + "DefaultConnection": "Host=localhost;Database=simplemodule;Username=app;Password=secret", + "Provider": "PostgreSQL" + } +} +``` + +### Schema Isolation + +Each module gets its own isolated storage space: + +| Provider | Isolation Strategy | Example | +|----------|-------------------|---------| +| SQLite | Table name prefixes | `Products_Products`, `Orders_Orders` | +| PostgreSQL | Schemas | `products.Products`, `orders.Orders` | + +This is configured automatically via `ModuleDbContextInfo` -- you do not need to set this manually. + +## Environment Variables + +Environment variables override `appsettings.json` values. Use double underscores (`__`) as separators for nested keys: + +```bash +# Database configuration +export Database__DefaultConnection="Host=server;Database=simplemodule;Username=app;Password=secret" +export Database__Provider="PostgreSQL" + +# ASP.NET environment +export ASPNETCORE_ENVIRONMENT="Production" + +# Logging +export Logging__LogLevel__Default="Warning" +``` + +::: tip +Environment variables take precedence over all JSON configuration files. This is the recommended way to configure secrets in production. +::: + +### Common Environment Variables + +| Variable | Description | Values | +|----------|-------------|--------| +| `ASPNETCORE_ENVIRONMENT` | Runtime environment name | `Development`, `Production` | +| `Database__DefaultConnection` | Database connection string | Provider-specific | +| `Database__Provider` | Database provider | `Sqlite`, `PostgreSQL` | +| `Logging__LogLevel__Default` | Default log level | `Trace`, `Debug`, `Information`, `Warning`, `Error` | + +## Development Configuration + +The `appsettings.Development.json` file adds development-specific overrides: + +```json +{ + "Database": { + "DefaultConnection": "Data Source=app.db" + }, + "Logging": { + "LogLevel": { + "Microsoft.EntityFrameworkCore.Database.Command": "Information" + }, + "Console": { + "FormatterName": "simple", + "FormatterOptions": { + "TimestampFormat": "HH:mm:ss.fff ", + "UseUtcTimestamp": false + } + } + } +} +``` + +Key differences from production: +- **SQLite** is used by default (no external database needed) +- **EF Core SQL logging** is enabled (`Microsoft.EntityFrameworkCore.Database.Command: Information`) so you can see the generated SQL queries +- **Console formatter** uses a simple format with local timestamps for readability + +## Production Configuration + +For production deployments, configure via environment variables or a secrets manager: + +### Docker Compose + +```yaml +services: + api: + environment: + - ASPNETCORE_ENVIRONMENT=Production + - Database__DefaultConnection=Host=postgres;Database=simplemodule;Username=app;Password=secret +``` + +### Docker Run + +```bash +docker run -d \ + -e ASPNETCORE_ENVIRONMENT=Production \ + -e Database__DefaultConnection="Host=db;Database=simplemodule;Username=app;Password=secret" \ + simplemodule +``` + +### CI/CD + +In the CI pipeline, the PostgreSQL test job sets the connection string as an environment variable: + +```yaml +env: + Database__DefaultConnection: "Host=localhost;Database=simplemodule_test;Username=test;Password=test" +``` + +::: warning +Never commit production credentials to `appsettings.json` or any file in source control. Use environment variables, Docker secrets, or a vault service. +::: + +## Module-Specific Settings + +Modules can define their own settings through the `ISettingsBuilder` interface in their `ConfigureSettings` method. These settings are registered with a `SettingDefinition`: + +```csharp +public void ConfigureSettings(ISettingsBuilder settings) +{ + settings.Add(new SettingDefinition + { + Key = "Products.MaxItemsPerPage", + DisplayName = "Max Items Per Page", + Description = "Maximum number of products displayed per page", + Group = "Products", + DefaultValue = "25", + Type = SettingType.Integer, + Scope = SettingScope.Application, + }); +} +``` + +Module settings are stored in the database and can be managed through the admin UI at runtime without restarting the application. + +## Configuration Precedence + +ASP.NET loads configuration from multiple sources. Later sources override earlier ones: + +1. `appsettings.json` (lowest priority) +2. `appsettings.{Environment}.json` +3. Environment variables +4. Command-line arguments (highest priority) + +For the `Development` environment, the effective configuration merges `appsettings.json` with `appsettings.Development.json`, with the Development file winning on conflicts. diff --git a/docs/site/testing/e2e-tests.md b/docs/site/testing/e2e-tests.md new file mode 100644 index 00000000..6bc7abf9 --- /dev/null +++ b/docs/site/testing/e2e-tests.md @@ -0,0 +1,292 @@ +--- +outline: deep +--- + +# E2E Tests + +End-to-end tests use [Playwright](https://playwright.dev/) to drive a real browser against a running SimpleModule application. They verify complete user flows including authentication, navigation, and CRUD operations. + +## Setup + +E2E tests live in `tests/e2e/` and are a separate npm workspace with their own `package.json`: + +```json +{ + "name": "@simplemodule/e2e", + "private": true, + "scripts": { + "test": "playwright test", + "test:ui": "playwright test --ui", + "test:headed": "playwright test --headed", + "test:smoke": "playwright test tests/smoke/", + "test:flows": "playwright test tests/flows/", + "report": "playwright show-report" + }, + "devDependencies": { + "@faker-js/faker": "^10.4.0", + "@playwright/test": "^1.52.0" + } +} +``` + +Install Playwright browsers before first use: + +```bash +npx playwright install +``` + +## Running Tests + +From the repository root: + +```bash +# Run all E2E tests +npm run test:e2e + +# Run with Playwright UI (interactive mode) +npm run test:e2e:ui + +# Run only smoke tests +npm run test:e2e -- -- tests/smoke/ + +# Run only flow tests +npm run test:e2e -- -- tests/flows/ +``` + +Or from within the `tests/e2e/` directory: + +```bash +npm test # all tests +npm run test:ui # interactive UI mode +npm run test:headed # headed browser +npm run test:smoke # smoke tests only +npm run test:flows # flow tests only +npm run report # view last test report +``` + +## Configuration + +Playwright is configured in `tests/e2e/playwright.config.ts`: + +```typescript +export default defineConfig({ + testDir: './tests', + fullyParallel: true, + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + workers: process.env.CI ? 1 : undefined, + reporter: [['html'], ...(process.env.CI ? [['github']] : [])], + use: { + baseURL: 'https://localhost:5001', + trace: 'on-first-retry', + ignoreHTTPSErrors: true, + screenshot: 'only-on-failure', + }, + projects: [ + { name: 'setup', testMatch: /.*\.setup\.ts/ }, + { + name: 'chromium', + use: { ...devices['Desktop Chrome'] }, + dependencies: ['setup'], + }, + // Firefox and WebKit are added in CI only + ], + webServer: { + command: 'dotnet run --project ../../template/SimpleModule.Host', + url: 'https://localhost:5001/health/live', + reuseExistingServer: true, + ignoreHTTPSErrors: true, + timeout: 60_000, + env: { + ASPNETCORE_URLS: 'https://localhost:5001', + Database__DefaultConnection: 'Data Source=e2e-test.db', + }, + }, +}); +``` + +Key points: + +- **`webServer`** -- Playwright automatically starts the .NET host if it is not already running, using the health endpoint to detect readiness +- **`reuseExistingServer: true`** -- if you already have the app running (e.g., via `npm run dev`), Playwright uses it directly +- **Browser matrix** -- locally tests run on Chromium only; CI adds Firefox and WebKit +- **Retries** -- CI retries failed tests twice; local runs have zero retries +- **Traces and screenshots** -- captured on first retry and on failure for debugging + +## Authentication + +E2E tests authenticate once in a setup project and reuse the auth state across all test files. + +### Auth Setup + +The `tests/auth/auth.setup.ts` file logs in as the admin user and saves the browser storage state: + +```typescript +setup('authenticate as admin', async ({ page }) => { + await page.goto('/'); + await page.getByRole('link', { name: 'Log in' }).click(); + await page.waitForURL('**/Identity/Account/Login**'); + + await page.getByPlaceholder('you@example.com') + .fill('admin@simplemodule.dev'); + await page.locator('input[type="password"]') + .fill('Admin123!'); + await page.getByRole('button', { name: 'Log in' }).click(); + + await page.waitForURL('/'); + await page.context().storageState({ path: authFile }); +}); +``` + +### Using Auth in Tests + +Tests import a custom `test` fixture from `fixtures/base.ts` that automatically loads the saved auth state: + +```typescript +import { test as base } from '@playwright/test'; + +const authFile = path.resolve( + __dirname, '../auth/.auth/user.json'); + +export const test = base.extend({ + storageState: async (_, use) => { + await use(authFile); + }, +}); + +export { expect } from '@playwright/test'; +``` + +All test files import from this fixture instead of directly from `@playwright/test`: + +```typescript +import { expect, test } from '../../fixtures/base'; +``` + +## Test Organization + +Tests are organized into two categories: + +### Smoke Tests (`tests/smoke/`) + +Quick page-load checks that verify pages render without errors: + +```typescript +test.describe('Products pages', () => { + test('browse page loads', async ({ page }) => { + const browse = new ProductsBrowsePage(page); + await browse.goto(); + await expect(browse.heading).toBeVisible(); + }); + + test('create page loads', async ({ page }) => { + const create = new ProductsCreatePage(page); + await create.goto(); + await expect(create.heading).toBeVisible(); + }); +}); +``` + +### Flow Tests (`tests/flows/`) + +Full CRUD and business workflows that create, read, update, and delete data: + +```typescript +test.describe('Orders CRUD', () => { + test.describe.configure({ mode: 'serial' }); + + test('create an order and verify it appears in the list', + async ({ page, request }) => { + const productsPage = new ProductsCreatePage(page); + await productsPage.goto(); + await productsPage.createProduct(productName, price); + + const createPage = new OrdersCreatePage(page); + await createPage.goto(); + await createPage.createOrder(adminUserId, 0, quantity); + + const listPage = new OrdersListPage(page); + await listPage.goto(); + await expect( + listPage.orderRowByUser(adminUserId).first() + ).toBeVisible(); + }); +}); +``` + +::: info Serial Mode +Flow tests use `test.describe.configure({ mode: 'serial' })` because they depend on data created by earlier tests in the same describe block (e.g., create then delete). +::: + +## Page Object Model + +E2E tests use the Page Object Model pattern. Each page has a corresponding class in `tests/e2e/pages/`: + +```typescript +import type { Page } from '@playwright/test'; + +export class ProductsBrowsePage { + constructor(private page: Page) {} + + async goto() { + await this.page.goto('/products/browse'); + } + + get heading() { + return this.page.getByRole('heading', { name: /products/i }); + } + + get productCards() { + return this.page.locator('[data-testid="product-card"]'); + } + + productByName(name: string) { + return this.page.getByText(name); + } +} +``` + +Page objects are available for all module pages under `tests/e2e/pages/`: + +``` +pages/ + dashboard.page.ts + products/ + browse.page.ts + create.page.ts + edit.page.ts + manage.page.ts + orders/ + create.page.ts + edit.page.ts + list.page.ts + settings/ + admin.page.ts + user.page.ts + menu-manager.page.ts + pagebuilder/ + editor.page.ts + manage.page.ts + pages-list.page.ts + viewer.page.ts + openiddict/ + clients.page.ts +``` + +## CI Integration + +In CI, the Playwright configuration automatically: + +- Expands the browser matrix to include Firefox and WebKit +- Sets retries to 2 +- Limits to 1 worker for stability +- Adds the `github` reporter alongside HTML + +```yaml +- name: Run E2E tests + run: npm run test:e2e + env: + CI: true +``` + +Test results are available as an HTML report via `npm run report` (or `npx playwright show-report` from `tests/e2e/`). diff --git a/docs/site/testing/integration-tests.md b/docs/site/testing/integration-tests.md new file mode 100644 index 00000000..8cdfb8bb --- /dev/null +++ b/docs/site/testing/integration-tests.md @@ -0,0 +1,233 @@ +--- +outline: deep +--- + +# Integration Tests + +Integration tests verify HTTP endpoints through the full ASP.NET pipeline using `WebApplicationFactory`. They exercise routing, authentication, authorization, model binding, and database access in a single test. + +## SimpleModuleWebApplicationFactory + +The shared test infrastructure provides `SimpleModuleWebApplicationFactory`, which configures an in-process test server with: + +- **In-memory SQLite** -- a shared `SqliteConnection` kept open for the factory lifetime, with all module `DbContext` instances pointing to it +- **Test authentication scheme** -- bypasses OpenIddict validation; claims are passed via the `X-Test-Claims` header +- **Removed hosted services** -- seed services and background workers are stripped out to avoid side effects +- **Environment set to `"Testing"`** -- allows conditional behavior in the application + +### Database Setup + +Each module's `DbContext` is replaced with one backed by the shared in-memory SQLite connection. The factory calls `EnsureCreated()` on all module databases when the first authenticated client is created: + +```csharp +public class SimpleModuleWebApplicationFactory : WebApplicationFactory +{ + private readonly SqliteConnection _connection = new("Data Source=:memory:"); + + protected override void ConfigureWebHost(IWebHostBuilder builder) + { + _connection.Open(); + builder.UseEnvironment("Testing"); + + builder.ConfigureServices(services => + { + services.Configure(opts => + { + opts.DefaultConnection = "Data Source=:memory:"; + opts.Provider = "Sqlite"; + }); + + ReplaceDbContext(services); + ReplaceDbContext(services); + // ... all module DbContexts + }); + } +} +``` + +## Creating Authenticated Clients + +The factory provides two overloads for creating test HTTP clients with authentication: + +### With Specific Claims + +```csharp +var client = factory.CreateAuthenticatedClient( + new Claim(ClaimTypes.NameIdentifier, "user-123"), + new Claim(ClaimTypes.Email, "user@example.com") +); +``` + +If no `NameIdentifier` claim is provided, a default `"test-user-id"` is added automatically. + +### With Permissions + +```csharp +var client = factory.CreateAuthenticatedClient( + permissions: [ProductsPermissions.View, ProductsPermissions.Create] +); +``` + +This overload converts each permission string into a `permission` claim. You can also pass additional claims: + +```csharp +var client = factory.CreateAuthenticatedClient( + permissions: [ProductsPermissions.View], + new Claim(ClaimTypes.NameIdentifier, "custom-user-id") +); +``` + +### Unauthenticated Client + +For testing 401 responses, use the standard `CreateClient()` method without claims: + +```csharp +var client = factory.CreateClient(); +``` + +## How Test Auth Works + +Claims are serialized into the `X-Test-Claims` header as semicolon-separated `type=value` pairs. The `TestAuthHandler` reads this header and builds a `ClaimsPrincipal`: + +``` +X-Test-Claims: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier=test-user-id;permission=Products.View +``` + +Requests without the `X-Test-Claims` header are treated as unauthenticated. + +## Writing Integration Tests + +Use `IClassFixture` to share the factory across tests in a class: + +```csharp +public class ProductsEndpointTests : IClassFixture +{ + private readonly SimpleModuleWebApplicationFactory _factory; + + public ProductsEndpointTests(SimpleModuleWebApplicationFactory factory) + { + _factory = factory; + } + + [Fact] + public async Task GetAllProducts_WithViewPermission_Returns200WithProductList() + { + var client = _factory.CreateAuthenticatedClient( + [ProductsPermissions.View]); + + var response = await client.GetAsync("/api/products"); + + response.StatusCode.Should().Be(HttpStatusCode.OK); + var products = await response.Content + .ReadFromJsonAsync>(); + products.Should().NotBeEmpty(); + } + + [Fact] + public async Task GetAllProducts_Unauthenticated_Returns401() + { + var client = _factory.CreateClient(); + + var response = await client.GetAsync("/api/products"); + + response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); + } + + [Fact] + public async Task GetAllProducts_WithoutPermission_Returns403() + { + var client = _factory.CreateAuthenticatedClient( + [ProductsPermissions.Create]); // wrong permission + + var response = await client.GetAsync("/api/products"); + + response.StatusCode.Should().Be(HttpStatusCode.Forbidden); + } +} +``` + +## Common Patterns + +### Testing CRUD Operations + +```csharp +[Fact] +public async Task CreateProduct_WithCreatePermission_Returns201() +{ + var client = _factory.CreateAuthenticatedClient( + [ProductsPermissions.Create]); + var request = new CreateProductRequest + { + Name = "New Product", + Price = 29.99m, + }; + + var response = await client.PostAsJsonAsync("/api/products", request); + + response.StatusCode.Should().Be(HttpStatusCode.Created); + var product = await response.Content.ReadFromJsonAsync(); + product.Should().NotBeNull(); + product!.Name.Should().Be("New Product"); +} +``` + +### Testing Not Found + +```csharp +[Fact] +public async Task UpdateProduct_WithNonExistentId_Returns404() +{ + var client = _factory.CreateAuthenticatedClient( + [ProductsPermissions.Update]); + var request = new UpdateProductRequest + { + Name = "Updated", + Price = 10.00m, + }; + + var response = await client.PutAsJsonAsync( + "/api/products/99999", request); + + response.StatusCode.Should().Be(HttpStatusCode.NotFound); +} +``` + +### Testing Delete with Setup + +```csharp +[Fact] +public async Task DeleteProduct_WithExistingId_Returns204() +{ + var client = _factory.CreateAuthenticatedClient([ + ProductsPermissions.Create, + ProductsPermissions.Delete, + ]); + + // Create a product first + var createRequest = new CreateProductRequest + { + Name = "ToDelete", + Price = 5.00m, + }; + var createResponse = await client.PostAsJsonAsync( + "/api/products", createRequest); + var created = await createResponse.Content + .ReadFromJsonAsync(); + + var response = await client.DeleteAsync( + $"/api/products/{created!.Id}"); + + response.StatusCode.Should().Be(HttpStatusCode.NoContent); +} +``` + +## PostgreSQL in CI + +By default, the factory uses in-memory SQLite. In CI, you can switch to PostgreSQL by setting the `Database__DefaultConnection` environment variable: + +```yaml +env: + Database__DefaultConnection: "Host=localhost;Database=test;Username=postgres;Password=..." +``` + +The `DatabaseOptions` configuration picks this up automatically, and module `DbContext` instances use the configured provider. diff --git a/docs/site/testing/overview.md b/docs/site/testing/overview.md new file mode 100644 index 00000000..e51d69f0 --- /dev/null +++ b/docs/site/testing/overview.md @@ -0,0 +1,88 @@ +--- +outline: deep +--- + +# Testing Overview + +SimpleModule uses a layered testing strategy: unit tests for isolated logic, integration tests for HTTP endpoints with a real application pipeline, and end-to-end tests for browser-based flows. + +## Test Stack + +| Library | Purpose | +|---------|---------| +| [xUnit.v3](https://xunit.net/) | Test framework | +| [FluentAssertions](https://fluentassertions.com/) | Expressive assertions | +| [Bogus](https://github.com/bchavez/Bogus) | Fake data generation | +| [NSubstitute](https://nsubstitute.github.io/) | Mocking / test doubles | +| [Microsoft.AspNetCore.Mvc.Testing](https://learn.microsoft.com/en-us/aspnet/core/test/integration-tests) | In-process integration testing | +| [Playwright](https://playwright.dev/) | Browser-based E2E testing | + +## Project Conventions + +Test projects follow a consistent naming and location pattern: + +| Type | Location | Naming | +|------|----------|--------| +| Module tests | `modules//tests/.Tests/` | `.Tests.csproj` | +| Shared infrastructure | `tests/SimpleModule.Tests.Shared/` | Fixtures, fakes, generators | +| E2E tests | `tests/e2e/` | Playwright specs | + +Each module test project is organized into subdirectories: + +``` +modules/Products/tests/Products.Tests/ + Unit/ + ProductServiceTests.cs + CreateRequestValidatorTests.cs + Integration/ + ProductsEndpointTests.cs +``` + +## Running Tests + +```bash +# Run all .NET tests +dotnet test + +# Run a single test class +dotnet test --filter "FullyQualifiedName~ProductServiceTests" + +# Run a single test method +dotnet test --filter "FullyQualifiedName~CreateProductAsync_CreatesAndReturnsProduct" + +# Run E2E tests +npm run test:e2e + +# Run E2E tests with UI +npm run test:e2e:ui +``` + +## Test Naming + +Test methods use underscore-separated names following the pattern: + +``` +Method_Scenario_Expected +``` + +For example: +- `GetProductByIdAsync_WithExistingId_ReturnsProduct` +- `CreateProduct_WithCreatePermission_Returns201` +- `Validate_WithEmptyName_ReturnsError` + +This convention is enforced by `.editorconfig` which suppresses the `CA1707` naming rule in test projects. + +## CI Strategy + +CI runs tests against two database providers: + +- **SQLite in-memory** -- fast, used for all local development and the primary CI pass +- **PostgreSQL** -- used for integration verification in CI to catch provider-specific issues + +The `SimpleModuleWebApplicationFactory` automatically uses SQLite in-memory with a shared connection kept open for the lifetime of the test run. + +## Next Steps + +- [Unit tests](./unit-tests) -- testing services, validators, and event handlers in isolation +- [Integration tests](./integration-tests) -- testing HTTP endpoints end-to-end with `WebApplicationFactory` +- [E2E tests](./e2e-tests) -- browser-based testing with Playwright diff --git a/docs/site/testing/unit-tests.md b/docs/site/testing/unit-tests.md new file mode 100644 index 00000000..2609dbe4 --- /dev/null +++ b/docs/site/testing/unit-tests.md @@ -0,0 +1,195 @@ +--- +outline: deep +--- + +# Unit Tests + +Unit tests verify individual services, validators, and event handlers in isolation. They use in-memory SQLite for database-backed tests and fake implementations for cross-module dependencies. + +## Service Tests + +Service tests create a real `DbContext` with an in-memory SQLite connection and test the service directly. Here is the pattern from `ProductServiceTests`: + +```csharp +public sealed class ProductServiceTests : IDisposable +{ + private readonly ProductsDbContext _db; + private readonly ProductService _sut; + + public ProductServiceTests() + { + var options = new DbContextOptionsBuilder() + .UseSqlite("Data Source=:memory:") + .Options; + var dbOptions = Options.Create( + new DatabaseOptions + { + ModuleConnections = new Dictionary + { + ["Products"] = "Data Source=:memory:", + }, + } + ); + _db = new ProductsDbContext(options, dbOptions); + _db.Database.OpenConnection(); + _db.Database.EnsureCreated(); + _sut = new ProductService(_db, NullLogger.Instance); + } + + public void Dispose() => _db.Dispose(); + + [Fact] + public async Task CreateProductAsync_CreatesAndReturnsProduct() + { + var request = new CreateProductRequest { Name = "Test Widget", Price = 19.99m }; + + var product = await _sut.CreateProductAsync(request); + + product.Should().NotBeNull(); + product.Name.Should().Be("Test Widget"); + product.Price.Should().Be(19.99m); + product.Id.Value.Should().BeGreaterThan(0); + } + + [Fact] + public async Task UpdateProductAsync_WithNonExistentId_ThrowsNotFoundException() + { + var request = new UpdateProductRequest { Name = "Test", Price = 10.00m }; + + var act = () => _sut.UpdateProductAsync(ProductId.From(99999), request); + + await act.Should().ThrowAsync() + .WithMessage("*Product*99999*not found*"); + } +} +``` + +::: tip Key Pattern +Each test class creates its own in-memory SQLite connection and `DbContext`. The connection is opened in the constructor and the database schema is created with `EnsureCreated()`. The `IDisposable` pattern ensures cleanup. +::: + +## Validator Tests + +Validators are pure functions that return a validation result. They are straightforward to test: + +```csharp +public class CreateRequestValidatorTests +{ + [Fact] + public void Validate_WithValidRequest_ReturnsSuccess() + { + var request = new CreateProductRequest { Name = "Widget", Price = 9.99m }; + + var result = CreateRequestValidator.Validate(request); + + result.IsValid.Should().BeTrue(); + } + + [Fact] + public void Validate_WithEmptyName_ReturnsError() + { + var request = new CreateProductRequest { Name = "", Price = 9.99m }; + + var result = CreateRequestValidator.Validate(request); + + result.IsValid.Should().BeFalse(); + result.Errors.Should().ContainKey("Name"); + } +} +``` + +## Fake Data Generators + +The `SimpleModule.Tests.Shared` project provides pre-built Bogus fakers for all module DTOs and request types in `FakeDataGenerators`: + +```csharp +public static class FakeDataGenerators +{ + public static Faker ProductFaker { get; } = + new Faker() + .RuleFor(p => p.Id, f => ProductId.From(f.IndexFaker + 1)) + .RuleFor(p => p.Name, f => f.Commerce.ProductName()) + .RuleFor(p => p.Price, f => f.Finance.Amount(1, 1000)); + + public static Faker CreateProductRequestFaker { get; } = + new Faker() + .RuleFor(r => r.Name, f => f.Commerce.ProductName()) + .RuleFor(r => r.Price, f => f.Finance.Amount(1, 1000)); + + public static Faker OrderFaker { get; } = + new Faker() + .RuleFor(o => o.Id, f => OrderId.From(f.IndexFaker + 1)) + .RuleFor(o => o.UserId, f => UserId.From( + f.Random.Int(1, 100).ToString(CultureInfo.InvariantCulture))) + .RuleFor(o => o.Items, f => OrderItemFaker.Generate(f.Random.Int(1, 3))) + .RuleFor(o => o.Total, f => f.Finance.Amount(10, 500)) + .RuleFor(o => o.CreatedAt, f => f.Date.Recent()); + + // ... fakers for all module DTOs and request types +} +``` + +Use them in tests to generate realistic test data: + +```csharp +var products = FakeDataGenerators.ProductFaker.Generate(5); +var request = FakeDataGenerators.CreateProductRequestFaker.Generate(); +``` + +## Fake Contract Implementations + +For testing code that depends on other modules, the shared project provides fake implementations of contract interfaces. For example, `FakeProductContracts` implements `IProductContracts` with an in-memory list: + +```csharp +public class FakeProductContracts : IProductContracts +{ + public List Products { get; set; } = + FakeDataGenerators.ProductFaker.Generate(3); + + public Task> GetAllProductsAsync() => + Task.FromResult>(Products); + + public Task GetProductByIdAsync(ProductId id) => + Task.FromResult(Products.FirstOrDefault(p => p.Id == id)); + + public Task CreateProductAsync(CreateProductRequest request) + { + var product = new Product + { + Id = ProductId.From(_nextId++), + Name = request.Name, + Price = request.Price, + }; + Products.Add(product); + return Task.FromResult(product); + } + + // ... other CRUD methods +} +``` + +These fakes are useful when a module under test depends on another module's contracts. Rather than spinning up the full dependency, inject the fake: + +```csharp +var fakeProducts = new FakeProductContracts(); +var service = new OrderService(fakeProducts, db, logger); +``` + +## Testing Event Handlers + +Event handlers are tested by invoking `HandleAsync` directly and asserting on side effects: + +```csharp +[Fact] +public async Task HandleAsync_LogsEvent() +{ + var handler = new AuditLogEventHandler(logger); + var @event = new OrderCreatedEvent { OrderId = OrderId.From(1) }; + + await handler.HandleAsync(@event, CancellationToken.None); + + // Assert on side effects (e.g., logger calls, database writes) +} +``` + +For testing the `EventBus` itself, including partial failure semantics, see the `EventBusPartialFailureTests` in the core test project. diff --git a/package-lock.json b/package-lock.json index 33fc3045..7aacc13a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "packages/SimpleModule.Theme.Default", "packages/SimpleModule.UI", "template/SimpleModule.Host/ClientApp", - "tests/e2e" + "tests/e2e", + "docs/site" ], "dependencies": { "tailwindcss": "^4.2.1" @@ -29,16 +30,13 @@ "vite": "^6.2.0" } }, - "modules/Admin/src/Admin": { - "name": "@simplemodule/admin", - "version": "0.0.0", - "extraneous": true, - "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" + "docs/site": { + "name": "docs", + "devDependencies": { + "vitepress": "^1.6.4" } }, - "modules/Admin/src/SimpleModule.Admin": { + "modules/Admin/src/Admin": { "name": "@simplemodule/admin", "version": "0.0.0", "peerDependencies": { @@ -47,15 +45,6 @@ } }, "modules/AuditLogs/src/AuditLogs": { - "name": "@simplemodule/auditlogs", - "version": "0.0.0", - "extraneous": true, - "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" - } - }, - "modules/AuditLogs/src/SimpleModule.AuditLogs": { "name": "@simplemodule/auditlogs", "version": "0.0.0", "peerDependencies": { @@ -66,155 +55,324 @@ "modules/Dashboard/src/Dashboard": { "name": "@simplemodule/dashboard", "version": "0.0.0", - "extraneous": true, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "modules/Dashboard/src/SimpleModule.Dashboard": { - "name": "@simplemodule/dashboard", + "modules/OpenIddict/src/OpenIddict": { + "name": "@simplemodule/openiddict", "version": "0.0.0", "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "modules/FileStorage/src/FileStorage": { - "name": "@simplemodule/filestorage", + "modules/Orders/src/Orders": { + "name": "@simplemodule/orders", "version": "0.0.0", - "extraneous": true, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "modules/FileStorage/src/SimpleModule.FileStorage": { - "name": "@simplemodule/filestorage", + "modules/PageBuilder/src/PageBuilder": { + "name": "@simplemodule/pagebuilder", "version": "0.0.0", + "dependencies": { + "@measured/puck": "^0.18.0" + }, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "modules/OpenIddict/src/OpenIddict": { - "name": "@simplemodule/openiddict", + "modules/Products/src/Products": { + "name": "@simplemodule/products", "version": "0.0.0", - "extraneous": true, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "modules/OpenIddict/src/SimpleModule.OpenIddict": { - "name": "@simplemodule/openiddict", + "modules/Settings/src/Settings": { + "name": "@simplemodule/settings", "version": "0.0.0", "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "modules/Orders/src/Orders": { - "name": "@simplemodule/orders", + "modules/Users/src/Users": { + "name": "@simplemodule/users", "version": "0.0.0", - "extraneous": true, + "dependencies": { + "qrcode": "^1.5.4" + }, + "devDependencies": { + "@types/qrcode": "^1.5.6" + }, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "modules/Orders/src/SimpleModule.Orders": { - "name": "@simplemodule/orders", - "version": "0.0.0", - "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" + "node_modules/@algolia/abtesting": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.16.0.tgz", + "integrity": "sha512-alHFZ68/i9qLC/muEB07VQ9r7cB8AvCcGX6dVQi2PNHhc/ZQRmmFAv8KK1ay4UiseGSFr7f0nXBKsZ/jRg7e4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "modules/PageBuilder/src/PageBuilder": { - "name": "@simplemodule/pagebuilder", - "version": "0.0.0", - "extraneous": true, + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", + "dev": true, + "license": "MIT", "dependencies": { - "@measured/puck": "^0.18.0" + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" + "search-insights": ">= 1 < 3" } }, - "modules/PageBuilder/src/SimpleModule.PageBuilder": { - "name": "@simplemodule/pagebuilder", - "version": "0.0.0", + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, + "license": "MIT", "dependencies": { - "@puckeditor/core": "^0.21.1" + "@algolia/autocomplete-shared": "1.17.7" }, "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" } }, - "modules/Products/src/Products": { - "name": "@simplemodule/products", - "version": "0.0.0", - "extraneous": true, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "license": "MIT", "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" } }, - "modules/Products/src/SimpleModule.Products": { - "name": "@simplemodule/products", - "version": "0.0.0", - "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" + "node_modules/@algolia/client-abtesting": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.50.0.tgz", + "integrity": "sha512-mfgUdLQNxOAvCZUGzPQxjahEWEPuQkKlV0ZtGmePOa9ZxIQZlk31vRBNbM6ScU8jTH41SCYE77G/lCifDr1SVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "modules/Settings/src/Settings": { - "name": "@simplemodule/settings", - "version": "0.0.0", - "extraneous": true, - "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" + "node_modules/@algolia/client-analytics": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.50.0.tgz", + "integrity": "sha512-5mjokeKYyPaP3Q8IYJEnutI+O4dW/Ixxx5IgsSxT04pCfGqPXxTOH311hTQxyNpcGGEOGrMv8n8Z+UMTPamioQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" } }, - "modules/Settings/src/SimpleModule.Settings": { - "name": "@simplemodule/settings", - "version": "0.0.0", - "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" + "node_modules/@algolia/client-common": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.50.0.tgz", + "integrity": "sha512-emtOvR6dl3rX3sBJXXbofMNHU1qMQqQSWu319RMrNL5BWoBqyiq7y0Zn6cjJm7aGHV/Qbf+KCCYeWNKEMPI3BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" } }, - "modules/Users/src/SimpleModule.Users": { - "name": "@simplemodule/users", - "version": "0.0.0", + "node_modules/@algolia/client-insights": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.50.0.tgz", + "integrity": "sha512-IerGH2/hcj/6bwkpQg/HHRqmlGN1XwygQWythAk0gZFBrghs9danJaYuSS3ShzLSVoIVth4jY5GDPX9Lbw5cgg==", + "dev": true, + "license": "MIT", "dependencies": { - "qrcode": "^1.5.4" + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" }, - "devDependencies": { - "@types/qrcode": "^1.5.6" + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.50.0.tgz", + "integrity": "sha512-3idPJeXn5L0MmgP9jk9JJqblrQ/SguN93dNK9z9gfgyupBhHnJMOEjrRYcVgTIfvG13Y04wO+Q0FxE2Ut8PVbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" }, - "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" + "engines": { + "node": ">= 14.0.0" } }, - "modules/Users/src/Users": { - "name": "@simplemodule/users", - "version": "0.0.0", - "extraneous": true, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.50.0.tgz", + "integrity": "sha512-q7qRoWrQK1a8m5EFQEmPlo7+pg9mVQ8X5jsChtChERre0uS2pdYEDixBBl0ydBSGkdGbLUDufcACIhH/077E4g==", + "dev": true, + "license": "MIT", "dependencies": { - "qrcode": "^1.5.4" + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" }, - "devDependencies": { - "@types/qrcode": "^1.5.6" + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.50.0.tgz", + "integrity": "sha512-Jc360x4yqb3eEg4OY4KEIdGePBxZogivKI+OGIU8aLXgAYPTECvzeOBc90312yHA1hr3AeRlAFl0rIc8lQaIrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" }, - "peerDependencies": { - "react": "^19.0.0", - "react-dom": "^19.0.0" + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.50.0.tgz", + "integrity": "sha512-OS3/Viao+NPpyBbEY3tf6hLewppG+UclD+9i0ju56mq2DrdMJFCkEky6Sk9S5VPcbLzxzg3BqBX6u9Q35w19aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.50.0.tgz", + "integrity": "sha512-/znwgSiGufpbJVIoDmeQaHtTq+OMdDawFRbMSJVv+12n79hW+qdQXS8/Uu3BD3yn0BzgVFJEvrsHrCsInZKdhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.50.0.tgz", + "integrity": "sha512-dHjUfu4jfjdQiKDpCpAnM7LP5yfG0oNShtfpF5rMCel6/4HIoqJ4DC4h5GKDzgrvJYtgAhblo0AYBmOM00T+lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.50.0.tgz", + "integrity": "sha512-bffIbUljAWnh/Ctu5uScORajuUavqmZ0ACYd1fQQeSSYA9NNN83ynO26pSc2dZRXpSK0fkc1//qSSFXMKGu+aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.50.0.tgz", + "integrity": "sha512-y0EwNvPGvkM+yTAqqO6Gpt9wVGm3CLDtpLvNEiB3VGvN3WzfkjZGtLUsG/ru2kVJIIU7QcV0puuYgEpBeFxcJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.50.0.tgz", + "integrity": "sha512-xpwefe4fCOWnZgXCbkGpqQY6jgBSCf2hmgnySbyzZIccrv3SoashHKGPE4x6vVG+gdHrGciMTAcDo9HOZwH22Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" } }, "node_modules/@babel/code-frame": { @@ -378,7 +536,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.0", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", "dependencies": { @@ -624,82 +784,270 @@ "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==", "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", - "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", - "cpu": [ - "ppc64" - ], + "node_modules/@dnd-kit/abstract": { + "version": "0.0.7-beta-20250130032138", + "resolved": "https://registry.npmjs.org/@dnd-kit/abstract/-/abstract-0.0.7-beta-20250130032138.tgz", + "integrity": "sha512-CFprqfJTtvSqq6ST7AEu41M1CIMk3lko5JNggTsx9seuqodJ0kCqBDul92BBWoAxPiRuQPm+TrK5vijj1tO7rg==", "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@dnd-kit/geometry": "0.0.7-beta-20250130032138", + "@dnd-kit/state": "0.0.7-beta-20250130032138", + "tslib": "^2.6.2" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", - "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", - "cpu": [ - "arm" - ], + "node_modules/@dnd-kit/collision": { + "version": "0.0.7-beta-20250130032138", + "resolved": "https://registry.npmjs.org/@dnd-kit/collision/-/collision-0.0.7-beta-20250130032138.tgz", + "integrity": "sha512-IlR3Qi1Fwc+DFDIdxHCcuolv7+l+M/0KAXdW5gd8m4y5sfW1do1pogAcRA6qNHcUvlUtbsdINf/CzFoztMJ5Bw==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@dnd-kit/abstract": "0.0.7-beta-20250130032138", + "@dnd-kit/geometry": "0.0.7-beta-20250130032138", + "tslib": "^2.6.2" } }, - "node_modules/@esbuild/android-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", - "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", - "cpu": [ - "arm64" - ], + "node_modules/@dnd-kit/dom": { + "version": "0.0.7-beta-20250130032138", + "resolved": "https://registry.npmjs.org/@dnd-kit/dom/-/dom-0.0.7-beta-20250130032138.tgz", + "integrity": "sha512-LuySH+UkuvlNgMGDfRPe00cSbRMxyE9PbVNfUEmsnXUmltSW9ht5jVZrkQYAI8kZB3y7ry8h1fZtK/dOI3QFBg==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@dnd-kit/abstract": "0.0.7-beta-20250130032138", + "@dnd-kit/collision": "0.0.7-beta-20250130032138", + "@dnd-kit/geometry": "0.0.7-beta-20250130032138", + "@dnd-kit/state": "0.0.7-beta-20250130032138", + "tslib": "^2.6.2" } }, - "node_modules/@esbuild/android-x64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", - "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", - "cpu": [ - "x64" - ], + "node_modules/@dnd-kit/geometry": { + "version": "0.0.7-beta-20250130032138", + "resolved": "https://registry.npmjs.org/@dnd-kit/geometry/-/geometry-0.0.7-beta-20250130032138.tgz", + "integrity": "sha512-XfXqVq+LdMt5VKTjiyhvCcc68xRvD9xZilA52mdODN1qM3wh5ezpe17MyjlqefqH+sratpjmZwifHk2CONRWeg==", "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" + "dependencies": { + "@dnd-kit/state": "0.0.7-beta-20250130032138", + "tslib": "^2.6.2" } }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", - "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", - "cpu": [ - "arm64" - ], + "node_modules/@dnd-kit/helpers": { + "version": "0.0.7-beta-20250130032138", + "resolved": "https://registry.npmjs.org/@dnd-kit/helpers/-/helpers-0.0.7-beta-20250130032138.tgz", + "integrity": "sha512-KzZhgi3zoSJMSEwSL+6d/8GS2FfvY1s5wpIUPxEAgJM9ati+iU8uFL4T5qgxeTt8LVxobyFRzI5rVisUGgSICA==", "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], + "dependencies": { + "@dnd-kit/abstract": "0.0.7-beta-20250130032138", + "tslib": "^2.6.2" + } + }, + "node_modules/@dnd-kit/react": { + "version": "0.0.7-beta-20250130032138", + "resolved": "https://registry.npmjs.org/@dnd-kit/react/-/react-0.0.7-beta-20250130032138.tgz", + "integrity": "sha512-JsBHxVGoJlmFMt0mo6/7mwasrvq09Iz/uFUXcX/M7ob8UjdthNjL5y9vk8VGnJlka7yMHQxEGHJvQs6yh+FWow==", + "license": "MIT", + "dependencies": { + "@dnd-kit/abstract": "0.0.7-beta-20250130032138", + "@dnd-kit/dom": "0.0.7-beta-20250130032138", + "@dnd-kit/state": "0.0.7-beta-20250130032138", + "tslib": "^2.6.2" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@dnd-kit/state": { + "version": "0.0.7-beta-20250130032138", + "resolved": "https://registry.npmjs.org/@dnd-kit/state/-/state-0.0.7-beta-20250130032138.tgz", + "integrity": "sha512-xBW0LTG+jzXbdeha35uAlvZhO4M6F81fq7vpA54nodIu/RkBIB38NhnReMv/UrVRHxaZlwILDpW/3oHGu5h2jg==", + "license": "MIT", + "dependencies": { + "@preact/signals-core": "^1.6.0", + "tslib": "^2.6.2" + } + }, + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/js/node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, + "node_modules/@docsearch/js/node_modules/@types/react": { + "version": "18.3.28", + "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", + "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@types/prop-types": "*", + "csstype": "^3.2.2" + } + }, + "node_modules/@docsearch/js/node_modules/react": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", + "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@docsearch/js/node_modules/react-dom": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", + "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0", + "scheduler": "^0.23.2" + }, + "peerDependencies": { + "react": "^18.3.1" + } + }, + "node_modules/@docsearch/js/node_modules/scheduler": { + "version": "0.23.2", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", + "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "loose-envify": "^1.1.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], "engines": { "node": ">=18" } @@ -1085,6 +1433,23 @@ "version": "0.2.11", "license": "MIT" }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.75", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.75.tgz", + "integrity": "sha512-KvcCUbvcBWb0sbqLIxHoY8z5/piXY08wcY9gfMhF+ph3AfzGMaSmZFkUY71HSXAljQngXkgs4bdKdekO0HQWvg==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, "node_modules/@inertiajs/core": { "version": "2.3.18", "license": "MIT", @@ -1150,6 +1515,26 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@measured/puck": { + "version": "0.18.3", + "resolved": "https://registry.npmjs.org/@measured/puck/-/puck-0.18.3.tgz", + "integrity": "sha512-mw78Z7Ykk4LbyM+8n3CPsjI41SyMg1dOfYoV3VSWJiZWkMj47x6DJS6SdwfaNSyseyPpcSx6sEbL6BaOBAVoHQ==", + "deprecated": "Puck has moved. Please use @puckeditor/core instead: https://www.npmjs.com/package/@puckeditor/core", + "license": "MIT", + "dependencies": { + "@dnd-kit/helpers": "0.0.7-beta-20250130032138", + "@dnd-kit/react": "0.0.7-beta-20250130032138", + "deep-diff": "^1.0.2", + "object-hash": "^3.0.0", + "react-hotkeys-hook": "^4.6.1", + "use-debounce": "^9.0.4", + "uuid": "^9.0.1", + "zustand": "^5.0.2" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } + }, "node_modules/@playwright/test": { "version": "1.58.2", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", @@ -1176,130 +1561,6 @@ "url": "https://opencollective.com/preact" } }, - "node_modules/@puckeditor/core": { - "version": "0.21.1", - "resolved": "https://registry.npmjs.org/@puckeditor/core/-/core-0.21.1.tgz", - "integrity": "sha512-Mk25WAHR8QLzHk+BtOO4bOu+y3pjzo0GowMHAJKNhrbxGVh3ETJx/bAZHaxA9dv8lKyuqK9a9AeRr7HYgg/sVw==", - "license": "MIT", - "dependencies": { - "@dnd-kit/helpers": "0.1.18", - "@dnd-kit/react": "0.1.18", - "@radix-ui/react-popover": "^1.1.15", - "@tiptap/core": "^3.11.1", - "@tiptap/extension-blockquote": "^3.11.1", - "@tiptap/extension-bold": "^3.11.1", - "@tiptap/extension-code": "^3.11.1", - "@tiptap/extension-code-block": "^3.11.1", - "@tiptap/extension-document": "^3.11.1", - "@tiptap/extension-hard-break": "^3.11.1", - "@tiptap/extension-heading": "^3.11.1", - "@tiptap/extension-horizontal-rule": "^3.11.1", - "@tiptap/extension-italic": "^3.11.1", - "@tiptap/extension-link": "^3.11.1", - "@tiptap/extension-list": "^3.11.1", - "@tiptap/extension-paragraph": "^3.11.1", - "@tiptap/extension-strike": "^3.11.1", - "@tiptap/extension-text": "^3.11.1", - "@tiptap/extension-text-align": "^3.11.1", - "@tiptap/extension-underline": "^3.11.1", - "@tiptap/html": "^3.11.1", - "@tiptap/pm": "^3.11.1", - "@tiptap/react": "^3.11.1", - "deep-diff": "^1.0.2", - "fast-equals": "5.2.2", - "flat": "^5.0.2", - "happy-dom": "^20.0.10", - "object-hash": "^3.0.0", - "react-hotkeys-hook": "^4.6.1", - "use-debounce": "^9.0.4", - "uuid": "^9.0.1", - "zustand": "^5.0.3" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@puckeditor/core/node_modules/@dnd-kit/abstract": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@dnd-kit/abstract/-/abstract-0.1.21.tgz", - "integrity": "sha512-6sJut6/D21xPIK8EFMu+JJeF+fBCOmQKN1BRpeUYFi5m9P1CJpTYbBwfI107h7PHObI6a5bsckiKkRpF2orHpw==", - "license": "MIT", - "dependencies": { - "@dnd-kit/geometry": "^0.1.21", - "@dnd-kit/state": "^0.1.21", - "tslib": "^2.6.2" - } - }, - "node_modules/@puckeditor/core/node_modules/@dnd-kit/collision": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@dnd-kit/collision/-/collision-0.1.21.tgz", - "integrity": "sha512-9AJ4NbuwGDexxMCZXZyKdNQhbAe93p6C6IezQaDaWmdCqZHMHmC3+ul7pGefBQfOooSarGwIf8Bn182o9SMa1A==", - "license": "MIT", - "dependencies": { - "@dnd-kit/abstract": "^0.1.21", - "@dnd-kit/geometry": "^0.1.21", - "tslib": "^2.6.2" - } - }, - "node_modules/@puckeditor/core/node_modules/@dnd-kit/dom": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@dnd-kit/dom/-/dom-0.1.21.tgz", - "integrity": "sha512-6UDc1y2Y3oLQKArGlgCrZxz5pdEjRSiQujXOn5JdbuWvKqTdUR5RTYDeicr+y2sVm3liXjTqs3WlUoV+eqhqUQ==", - "license": "MIT", - "dependencies": { - "@dnd-kit/abstract": "^0.1.21", - "@dnd-kit/collision": "^0.1.21", - "@dnd-kit/geometry": "^0.1.21", - "@dnd-kit/state": "^0.1.21", - "tslib": "^2.6.2" - } - }, - "node_modules/@puckeditor/core/node_modules/@dnd-kit/geometry": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@dnd-kit/geometry/-/geometry-0.1.21.tgz", - "integrity": "sha512-Tir97wNJbopN2HgkD7AjAcoB3vvrVuUHvwdPALmNDUH0fWR637c4MKQ66YjjZAbUEAR8KL6mlDiHH4MzTLd7CQ==", - "license": "MIT", - "dependencies": { - "@dnd-kit/state": "^0.1.21", - "tslib": "^2.6.2" - } - }, - "node_modules/@puckeditor/core/node_modules/@dnd-kit/helpers": { - "version": "0.1.18", - "resolved": "https://registry.npmjs.org/@dnd-kit/helpers/-/helpers-0.1.18.tgz", - "integrity": "sha512-k4hVXIb8ysPt+J0KOxbBTc6rG0JSlsrNevI/fCHLbyXvEyj1imxl7yOaAQX13cAZnte88db6JvbgsSWlVjtxbw==", - "license": "MIT", - "dependencies": { - "@dnd-kit/abstract": "^0.1.18", - "tslib": "^2.6.2" - } - }, - "node_modules/@puckeditor/core/node_modules/@dnd-kit/react": { - "version": "0.1.18", - "resolved": "https://registry.npmjs.org/@dnd-kit/react/-/react-0.1.18.tgz", - "integrity": "sha512-OCeCO9WbKnN4rVlEOEe9QWxSIFzP0m/fBFmVYfu2pDSb4pemRkfrvCsI/FH3jonuESYS8qYnN9vc8Vp3EiCWCA==", - "license": "MIT", - "dependencies": { - "@dnd-kit/abstract": "^0.1.18", - "@dnd-kit/dom": "^0.1.18", - "@dnd-kit/state": "^0.1.18", - "tslib": "^2.6.2" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@puckeditor/core/node_modules/@dnd-kit/state": { - "version": "0.1.21", - "resolved": "https://registry.npmjs.org/@dnd-kit/state/-/state-0.1.21.tgz", - "integrity": "sha512-pdhntEPvn/QttcF295bOJpWiLsRqA/Iczh1ODOJUxGiR+E4GkYVz9VapNNm9gDq6ST0tr/e1Q2xBztUHlJqQgA==", - "license": "MIT", - "dependencies": { - "@preact/signals-core": "^1.10.0", - "tslib": "^2.6.2" - } - }, "node_modules/@radix-ui/number": { "version": "1.1.1", "license": "MIT" @@ -2707,12 +2968,6 @@ "url": "https://opencollective.com/immer" } }, - "node_modules/@remirror/core-constants": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", - "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", - "license": "MIT" - }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "dev": true, @@ -3037,52 +3292,135 @@ "win32" ] }, - "node_modules/@simplemodule/admin": { - "resolved": "modules/Admin/src/SimpleModule.Admin", - "link": true - }, - "node_modules/@simplemodule/app": { - "resolved": "template/SimpleModule.Host/ClientApp", - "link": true + "node_modules/@shikijs/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", + "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } }, - "node_modules/@simplemodule/auditlogs": { - "resolved": "modules/AuditLogs/src/SimpleModule.AuditLogs", - "link": true + "node_modules/@shikijs/engine-javascript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", + "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^3.1.0" + } }, - "node_modules/@simplemodule/client": { - "resolved": "packages/SimpleModule.Client", - "link": true + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", + "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + } }, - "node_modules/@simplemodule/dashboard": { - "resolved": "modules/Dashboard/src/SimpleModule.Dashboard", - "link": true + "node_modules/@shikijs/langs": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", + "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } }, - "node_modules/@simplemodule/e2e": { - "resolved": "tests/e2e", + "node_modules/@shikijs/themes": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", + "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", + "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", + "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@simplemodule/admin": { + "resolved": "modules/Admin/src/Admin", + "link": true + }, + "node_modules/@simplemodule/app": { + "resolved": "template/SimpleModule.Host/ClientApp", + "link": true + }, + "node_modules/@simplemodule/auditlogs": { + "resolved": "modules/AuditLogs/src/AuditLogs", + "link": true + }, + "node_modules/@simplemodule/client": { + "resolved": "packages/SimpleModule.Client", + "link": true + }, + "node_modules/@simplemodule/dashboard": { + "resolved": "modules/Dashboard/src/Dashboard", "link": true }, - "node_modules/@simplemodule/filestorage": { - "resolved": "modules/FileStorage/src/SimpleModule.FileStorage", + "node_modules/@simplemodule/e2e": { + "resolved": "tests/e2e", "link": true }, "node_modules/@simplemodule/openiddict": { - "resolved": "modules/OpenIddict/src/SimpleModule.OpenIddict", + "resolved": "modules/OpenIddict/src/OpenIddict", "link": true }, "node_modules/@simplemodule/orders": { - "resolved": "modules/Orders/src/SimpleModule.Orders", + "resolved": "modules/Orders/src/Orders", "link": true }, "node_modules/@simplemodule/pagebuilder": { - "resolved": "modules/PageBuilder/src/SimpleModule.PageBuilder", + "resolved": "modules/PageBuilder/src/PageBuilder", "link": true }, "node_modules/@simplemodule/products": { - "resolved": "modules/Products/src/SimpleModule.Products", + "resolved": "modules/Products/src/Products", "link": true }, "node_modules/@simplemodule/settings": { - "resolved": "modules/Settings/src/SimpleModule.Settings", + "resolved": "modules/Settings/src/Settings", "link": true }, "node_modules/@simplemodule/theme-default": { @@ -3094,7 +3432,7 @@ "link": true }, "node_modules/@simplemodule/users": { - "resolved": "modules/Users/src/SimpleModule.Users", + "resolved": "modules/Users/src/Users", "link": true }, "node_modules/@standard-schema/spec": { @@ -3380,349 +3718,6 @@ "vite": "^5.2.0 || ^6 || ^7" } }, - "node_modules/@tiptap/core": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.5.tgz", - "integrity": "sha512-Pkjd41UJ4F6Z8cPV+gEvqnt1VhY2g66xMjbpxREs0ECA5jRezCNKSZcc2pueQRTMtmn1SaSzGM9U/ifhVlVYOA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/pm": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-blockquote": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.20.5.tgz", - "integrity": "sha512-0wU6H/MWWes0rGzgSW6MMU6YDs/3ofUDkqmqCqmb+Siu1ZD0bpzOYpBtujgOYDY8moB9+zCE3G9HSYGcmZxHew==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-bold": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.20.5.tgz", - "integrity": "sha512-hraiiWkF58n8Jy0Wl3OGwjCTrGWwZZxez/IlexrzKQ/nMFdjDpensZucWwu59zhAM9fqZwGSLDtCFuak03WKnA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-bubble-menu": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.20.5.tgz", - "integrity": "sha512-6FsASu4o32bp3FzBVb5N2ERjrBy83DtJQAGv9/ycYqsgv2kq9DNlhvtNI7GPiTW7a73ZcImjIX+jEWrARbzOlQ==", - "license": "MIT", - "optional": true, - "dependencies": { - "@floating-ui/dom": "^1.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5", - "@tiptap/pm": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-code": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.20.5.tgz", - "integrity": "sha512-jBZK/CfdMvg1gkNK/zNAk02IExpBPwUfNLRPiJvGhReL2Q73naKxZGQGp+5Lej9VaeFB70UKuRma/iIzuZbgsA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-code-block": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.20.5.tgz", - "integrity": "sha512-0YZnqfqZ1IjzKBM4aezw8j3LZWJFEfs4+mbizHNlnZSYpKzpESYLeaLWGO5SpqF9Z8tmYmSoCaf0fqi5LwgdIA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5", - "@tiptap/pm": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-document": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.20.5.tgz", - "integrity": "sha512-BpNGHtOTAjjs/6QbkrafMTlaJqb0gsPngFzd5rB0csxx7rYRE9nIEY+oZ44qMw161+2YB4u20L17SX2mUJANBw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-floating-menu": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.20.5.tgz", - "integrity": "sha512-mTzBNUeAocinrxa5xV+5hGnnNCQB0pVI1GSBwUTHwdB7jNwBqfKAILmtLZONgmhxKWLmGa6WCA59sk+yDI+N0A==", - "license": "MIT", - "optional": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@floating-ui/dom": "^1.0.0", - "@tiptap/core": "^3.20.5", - "@tiptap/pm": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-hard-break": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.20.5.tgz", - "integrity": "sha512-+aILNDO7BsXf0IJ4/0BYh570usFK3Q1t/ZQd8zhHuO2ATeWeDVu1x2F+ouFS4X8fmoCcioMzw15aoz93GET6kQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-heading": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.20.5.tgz", - "integrity": "sha512-zXxuIrCSpzgXzRxgCbRE8DZ/NFuinVaniE3pp/9LYAWgRlsAyko8pI2XrVvzzXmDQqRGi2HrNVkNy1yutUWSWQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-horizontal-rule": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.20.5.tgz", - "integrity": "sha512-4UtpUHg8cRzxWjJUGtni5VnXYbhsO7ygf1H1pr4Rv63XMBg9lfYDeSwByIuVy9biEFP7eGEFnezzb5Zlh1btmQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5", - "@tiptap/pm": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-italic": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.20.5.tgz", - "integrity": "sha512-7bZCgdJVTvhR5vSmNgFQbGvgRoC6m26KcUpHqWiKA95kLL5Wk4YlMCIqdiDpvJ1eakeFEvDcGZvFLg5+1NiQ+w==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-link": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.20.5.tgz", - "integrity": "sha512-0PukrSYnHX2CrGSThlKfQWxpPWmL7QAvdpDUraKknGvVNSH7tUjchTshy5JdLrn/SQAU92REowRCB6zzCNEFjA==", - "license": "MIT", - "dependencies": { - "linkifyjs": "^4.3.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5", - "@tiptap/pm": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-list": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.20.5.tgz", - "integrity": "sha512-s+Y8Q7Orq+WQiwgFB/VPMYZe+6EAR2F69xCpvOynlzTInLO4cF6QpXomuGEYAZxLHe8ZBmeIaR7y8MH/OgjrDw==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5", - "@tiptap/pm": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-paragraph": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.20.5.tgz", - "integrity": "sha512-mwuhwmff67IpGfOViyRvUC14IlkpsOnB+hSExVnq5+hCntjt/Cr2Z8GGOgzHeIM2FIS0UqX9Lv/b6ttUg4+Now==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-strike": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.20.5.tgz", - "integrity": "sha512-uwhvmfS4ciGYJRLUg0AHbWsprMCwyWVWd2RXOLRm0ZQeWkvzonPXZhJvzIhIgsFkPLj/dsN5t0+LdiK4UQMnyA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-text": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.20.5.tgz", - "integrity": "sha512-DMa9g5cH2d/Gx1KXtV7txTxaa6FBqgG8glmfug+N93VMb8sEZR1Yu1az++yAep4SGGq9GWIGZCUS3H6W66et6Q==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-text-align": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.20.5.tgz", - "integrity": "sha512-r7xIt6AE1Zf6epMg2GLAD55YIqJewunQnLUeApLkcWdTNuCJqZUrmVIXUvTtj2Ivw0VIaPoCtnB+VBvXEuC3Kg==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5" - } - }, - "node_modules/@tiptap/extension-underline": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.20.5.tgz", - "integrity": "sha512-HMhr5KIAqZsEhlN8RxKHr/ql1a8OvBa9fLf69IwUVFolBcDExHWUtaEV/axYVRQJvvIy2oKGJxlJWDZ4hkotHQ==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5" - } - }, - "node_modules/@tiptap/html": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/html/-/html-3.20.5.tgz", - "integrity": "sha512-IyUqfIVlYsMrbdI22AJDlzTWc/R8jMInjzav2Qrj+OPxarIW3f2b/knlI9FFgYauru3asplQmoRgx7gKnG7ruA==", - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5", - "@tiptap/pm": "^3.20.5", - "happy-dom": "^20.0.2" - } - }, - "node_modules/@tiptap/pm": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.20.5.tgz", - "integrity": "sha512-yJhDa7Chx2EqJMX/jlewBv0za7slf1dKHWYve1XaApuVHEkxl0Ul3EDbwnx316vIITkuFW/pWSwkSsAplyBeCw==", - "license": "MIT", - "dependencies": { - "prosemirror-changeset": "^2.3.0", - "prosemirror-collab": "^1.3.1", - "prosemirror-commands": "^1.6.2", - "prosemirror-dropcursor": "^1.8.1", - "prosemirror-gapcursor": "^1.3.2", - "prosemirror-history": "^1.4.1", - "prosemirror-inputrules": "^1.4.0", - "prosemirror-keymap": "^1.2.2", - "prosemirror-markdown": "^1.13.1", - "prosemirror-menu": "^1.2.4", - "prosemirror-model": "^1.24.1", - "prosemirror-schema-basic": "^1.2.3", - "prosemirror-schema-list": "^1.5.0", - "prosemirror-state": "^1.4.3", - "prosemirror-tables": "^1.6.4", - "prosemirror-trailing-node": "^3.0.0", - "prosemirror-transform": "^1.10.2", - "prosemirror-view": "^1.38.1" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - } - }, - "node_modules/@tiptap/react": { - "version": "3.20.5", - "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.20.5.tgz", - "integrity": "sha512-in37o1Eo7JCflcHyK/SDfgkJBgX0LRN3LMk+NdLPTerRnC0zhGLQlpfBL4591TLTOUQde7QIrLv98smYO2mj+w==", - "license": "MIT", - "dependencies": { - "@types/use-sync-external-store": "^0.0.6", - "fast-equals": "^5.3.3", - "use-sync-external-store": "^1.4.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/ueberdosis" - }, - "optionalDependencies": { - "@tiptap/extension-bubble-menu": "^3.20.5", - "@tiptap/extension-floating-menu": "^3.20.5" - }, - "peerDependencies": { - "@tiptap/core": "^3.20.5", - "@tiptap/pm": "^3.20.5", - "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react": "^17.0.0 || ^18.0.0 || ^19.0.0", - "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/@tiptap/react/node_modules/fast-equals": { - "version": "5.4.0", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", - "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/@types/babel__core": { "version": "7.20.5", "dev": true, @@ -3827,10 +3822,21 @@ "version": "1.0.8", "license": "MIT" }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", + "dev": true, "license": "MIT" }, "node_modules/@types/lodash": { @@ -3848,25 +3854,47 @@ "version": "14.1.2", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", + "dev": true, "license": "MIT", "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", + "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "25.5.0", + "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.18.0" } }, + "node_modules/@types/prop-types": { + "version": "15.7.15", + "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", + "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true + }, "node_modules/@types/qrcode": { "version": "1.5.6", "dev": true, @@ -3877,6 +3905,7 @@ }, "node_modules/@types/react": { "version": "19.2.14", + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -3884,31 +3913,38 @@ }, "node_modules/@types/react-dom": { "version": "19.2.3", + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", "license": "MIT" }, - "node_modules/@types/whatwg-mimetype": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", - "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true, "license": "MIT" }, - "node_modules/@types/ws": { - "version": "8.18.1", - "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", - "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", - "license": "MIT", - "dependencies": { - "@types/node": "*" - } + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", @@ -3929,31 +3965,316 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, "license": "MIT", "engines": { - "node": ">=8" + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" } }, - "node_modules/ansi-styles": { - "version": "4.3.0", + "node_modules/@vue/compiler-core": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.31.tgz", + "integrity": "sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ==", + "dev": true, "license": "MIT", "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" + "@babel/parser": "^7.29.2", + "@vue/shared": "3.5.31", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" } }, - "node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "license": "Python-2.0" + "node_modules/@vue/compiler-dom": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.31.tgz", + "integrity": "sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.31", + "@vue/shared": "3.5.31" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.31.tgz", + "integrity": "sha512-M8wpPgR9UJ8MiRGjppvx9uWJfLV7A/T+/rL8s/y3QG3u0c2/YZgff3d6SuimKRIhcYnWg5fTfDMlz2E6seUW8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/compiler-core": "3.5.31", + "@vue/compiler-dom": "3.5.31", + "@vue/compiler-ssr": "3.5.31", + "@vue/shared": "3.5.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.8", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.31.tgz", + "integrity": "sha512-h0xIMxrt/LHOvJKMri+vdYT92BrK3HFLtDqq9Pr/lVVfE4IyKZKvWf0vJFW10Yr6nX02OR4MkJwI0c1HDa1hog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.31", + "@vue/shared": "3.5.31" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.31.tgz", + "integrity": "sha512-DtKXxk9E/KuVvt8VxWu+6Luc9I9ETNcqR1T1oW1gf02nXaZ1kuAx58oVu7uX9XxJR0iJCro6fqBLw9oSBELo5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.31" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.31.tgz", + "integrity": "sha512-AZPmIHXEAyhpkmN7aWlqjSfYynmkWlluDNPHMCZKFHH+lLtxP/30UJmoVhXmbDoP1Ng0jG0fyY2zCj1PnSSA6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.31", + "@vue/shared": "3.5.31" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.31.tgz", + "integrity": "sha512-xQJsNRmGPeDCJq/u813tyonNgWBFjzfVkBwDREdEWndBnGdHLHgkwNBQxLtg4zDrzKTEcnikUy1UUNecb3lJ6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.31", + "@vue/runtime-core": "3.5.31", + "@vue/shared": "3.5.31", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.31.tgz", + "integrity": "sha512-GJuwRvMcdZX/CriUnyIIOGkx3rMV3H6sOu0JhdKbduaeCji6zb60iOGMY7tFoN24NfsUYoFBhshZtGxGpxO4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.31", + "@vue/shared": "3.5.31" + }, + "peerDependencies": { + "vue": "3.5.31" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.31.tgz", + "integrity": "sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", + "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vueuse/core": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/algoliasearch": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.50.0.tgz", + "integrity": "sha512-yE5I83Q2s8euVou8Y3feXK08wyZInJWLYXgWO6Xti9jBUEZAGUahyeQ7wSZWkifLWVnQVKEz5RAmBlXG5nqxog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.16.0", + "@algolia/client-abtesting": "5.50.0", + "@algolia/client-analytics": "5.50.0", + "@algolia/client-common": "5.50.0", + "@algolia/client-insights": "5.50.0", + "@algolia/client-personalization": "5.50.0", + "@algolia/client-query-suggestions": "5.50.0", + "@algolia/client-search": "5.50.0", + "@algolia/ingestion": "1.50.0", + "@algolia/monitoring": "1.50.0", + "@algolia/recommend": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, "node_modules/aria-hidden": { "version": "1.2.6", @@ -3989,6 +4310,16 @@ "node": ">=6.0.0" } }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/browserslist": { "version": "4.28.1", "dev": true, @@ -4072,6 +4403,39 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "license": "Apache-2.0", @@ -4136,16 +4500,37 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "dev": true, "license": "MIT" }, - "node_modules/crelt": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", - "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", - "license": "MIT" + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } }, "node_modules/cross-env": { "version": "7.0.3", @@ -4183,6 +4568,7 @@ }, "node_modules/csstype": { "version": "3.2.3", + "devOptional": true, "license": "MIT" }, "node_modules/d3-array": { @@ -4365,6 +4751,16 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "devOptional": true, @@ -4377,10 +4773,28 @@ "version": "1.1.0", "license": "MIT" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dijkstrajs": { "version": "1.0.3", "license": "MIT" }, + "node_modules/docs": { + "resolved": "docs/site", + "link": true + }, "node_modules/dunder-proto": { "version": "1.0.1", "license": "MIT", @@ -4402,6 +4816,13 @@ "version": "8.0.0", "license": "MIT" }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true, + "license": "MIT" + }, "node_modules/enhanced-resolve": { "version": "5.20.0", "dev": true, @@ -4418,6 +4839,7 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -4520,17 +4942,12 @@ "node": ">=6" } }, - "node_modules/escape-string-regexp": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", - "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" }, "node_modules/eventemitter3": { "version": "5.0.4", @@ -4538,15 +4955,6 @@ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, - "node_modules/fast-equals": { - "version": "5.2.2", - "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", - "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/fdir": { "version": "6.5.0", "license": "MIT", @@ -4573,13 +4981,14 @@ "node": ">=8" } }, - "node_modules/flat": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", - "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", - "license": "BSD-3-Clause", - "bin": { - "flat": "cli.js" + "node_modules/focus-trap": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", + "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tabbable": "^6.4.0" } }, "node_modules/follow-redirects": { @@ -4705,23 +5114,6 @@ "dev": true, "license": "ISC" }, - "node_modules/happy-dom": { - "version": "20.8.8", - "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.8.8.tgz", - "integrity": "sha512-5/F8wxkNxYtsN0bXfMwIyNLZ9WYsoOYPbmoluqVJqv8KBUbcyKZawJ7uYK4WTX8IHBLYv+VXIwfeNDPy1oKMwQ==", - "license": "MIT", - "dependencies": { - "@types/node": ">=20.0.0", - "@types/whatwg-mimetype": "^3.0.2", - "@types/ws": "^8.18.1", - "entities": "^7.0.1", - "whatwg-mimetype": "^3.0.0", - "ws": "^8.18.3" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "license": "MIT", @@ -4755,30 +5147,99 @@ "node": ">= 0.4" } }, - "node_modules/immer": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", - "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "engines": { - "node": ">=12" + "url": "https://opencollective.com/unified" } }, - "node_modules/is-fullwidth-code-point": { + "node_modules/hast-util-whitespace": { "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, "license": "MIT", - "engines": { - "node": ">=8" + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" } }, "node_modules/isexe": { @@ -5075,21 +5536,6 @@ "url": "https://opencollective.com/parcel" } }, - "node_modules/linkify-it": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", - "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", - "license": "MIT", - "dependencies": { - "uc.micro": "^2.0.0" - } - }, - "node_modules/linkifyjs": { - "version": "4.3.2", - "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz", - "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", - "license": "MIT" - }, "node_modules/locate-path": { "version": "5.0.0", "license": "MIT", @@ -5104,6 +5550,21 @@ "version": "4.17.23", "license": "MIT" }, + "node_modules/loose-envify": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", + "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "js-tokens": "^3.0.0 || ^4.0.0" + }, + "bin": { + "loose-envify": "cli.js" + } + }, "node_modules/lru-cache": { "version": "5.1.1", "dev": true, @@ -5120,46 +5581,134 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/markdown-it": { - "version": "14.1.1", - "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", - "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dev": true, "license": "MIT", "dependencies": { - "argparse": "^2.0.1", - "entities": "^4.4.0", - "linkify-it": "^5.0.0", - "mdurl": "^2.0.0", - "punycode.js": "^2.3.1", - "uc.micro": "^2.1.0" + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" }, - "bin": { - "markdown-it": "bin/markdown-it.mjs" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" } }, - "node_modules/markdown-it/node_modules/entities": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", - "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=0.12" - }, - "funding": { - "url": "https://github.com/fb55/entities?sponsor=1" + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" } }, - "node_modules/math-intrinsics": { - "version": "1.1.0", + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT", - "engines": { - "node": ">= 0.4" + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" } }, - "node_modules/mdurl": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", - "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], "license": "MIT" }, "node_modules/mime-db": { @@ -5179,6 +5728,20 @@ "node": ">= 0.6" } }, + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", + "dev": true, + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "dev": true, @@ -5224,11 +5787,17 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/orderedmap": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", - "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", - "license": "MIT" + "node_modules/oniguruma-to-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", + "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } }, "node_modules/p-limit": { "version": "2.3.0", @@ -5277,6 +5846,13 @@ "node": ">=8" } }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "license": "ISC" @@ -5371,214 +5947,32 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/prosemirror-changeset": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz", - "integrity": "sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==", - "license": "MIT", - "dependencies": { - "prosemirror-transform": "^1.0.0" - } - }, - "node_modules/prosemirror-collab": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", - "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0" - } - }, - "node_modules/prosemirror-commands": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", - "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.10.2" - } - }, - "node_modules/prosemirror-dropcursor": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", - "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.1.0", - "prosemirror-view": "^1.1.0" - } - }, - "node_modules/prosemirror-gapcursor": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz", - "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==", - "license": "MIT", - "dependencies": { - "prosemirror-keymap": "^1.0.0", - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-view": "^1.0.0" - } - }, - "node_modules/prosemirror-history": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", - "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.2.2", - "prosemirror-transform": "^1.0.0", - "prosemirror-view": "^1.31.0", - "rope-sequence": "^1.3.0" - } - }, - "node_modules/prosemirror-inputrules": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", - "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.0.0" - } - }, - "node_modules/prosemirror-keymap": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", - "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", - "license": "MIT", - "dependencies": { - "prosemirror-state": "^1.0.0", - "w3c-keyname": "^2.2.0" - } - }, - "node_modules/prosemirror-markdown": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz", - "integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==", - "license": "MIT", - "dependencies": { - "@types/markdown-it": "^14.0.0", - "markdown-it": "^14.0.0", - "prosemirror-model": "^1.25.0" - } - }, - "node_modules/prosemirror-menu": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz", - "integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==", - "license": "MIT", - "dependencies": { - "crelt": "^1.0.0", - "prosemirror-commands": "^1.0.0", - "prosemirror-history": "^1.0.0", - "prosemirror-state": "^1.0.0" - } - }, - "node_modules/prosemirror-model": { - "version": "1.25.4", - "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", - "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", - "license": "MIT", - "dependencies": { - "orderedmap": "^2.0.0" - } - }, - "node_modules/prosemirror-schema-basic": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", - "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.25.0" - } - }, - "node_modules/prosemirror-schema-list": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", - "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.7.3" - } - }, - "node_modules/prosemirror-state": { - "version": "1.4.4", - "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", - "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", - "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.0.0", - "prosemirror-transform": "^1.0.0", - "prosemirror-view": "^1.27.0" - } - }, - "node_modules/prosemirror-tables": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", - "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", - "license": "MIT", - "dependencies": { - "prosemirror-keymap": "^1.2.3", - "prosemirror-model": "^1.25.4", - "prosemirror-state": "^1.4.4", - "prosemirror-transform": "^1.10.5", - "prosemirror-view": "^1.41.4" - } - }, - "node_modules/prosemirror-trailing-node": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", - "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", - "license": "MIT", - "dependencies": { - "@remirror/core-constants": "3.0.0", - "escape-string-regexp": "^4.0.0" - }, - "peerDependencies": { - "prosemirror-model": "^1.22.1", - "prosemirror-state": "^1.4.2", - "prosemirror-view": "^1.33.8" - } - }, - "node_modules/prosemirror-transform": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.11.0.tgz", - "integrity": "sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==", + "node_modules/preact": { + "version": "10.29.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.0.tgz", + "integrity": "sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg==", + "dev": true, "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.21.0" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" } }, - "node_modules/prosemirror-view": { - "version": "1.41.7", - "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.7.tgz", - "integrity": "sha512-jUwKNCEIGiqdvhlS91/2QAg21e4dfU5bH2iwmSDQeosXJgKF7smG0YSplOWK0cjSNgIqXe7VXqo7EIfUFJdt3w==", + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "dev": true, "license": "MIT", - "dependencies": { - "prosemirror-model": "^1.20.0", - "prosemirror-state": "^1.0.0", - "prosemirror-transform": "^1.1.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" } }, "node_modules/proxy-from-env": { "version": "1.1.0", "license": "MIT" }, - "node_modules/punycode.js": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", - "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/qrcode": { "version": "1.5.4", "license": "MIT", @@ -5802,6 +6196,33 @@ "redux": "^5.0.0" } }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "license": "MIT", @@ -5819,6 +6240,13 @@ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", "license": "MIT" }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { "version": "4.59.0", "license": "MIT", @@ -5861,17 +6289,19 @@ "fsevents": "~2.3.2" } }, - "node_modules/rope-sequence": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", - "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", - "license": "MIT" - }, "node_modules/scheduler": { "version": "0.27.0", "license": "MIT" }, - "node_modules/semver": { + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, + "node_modules/semver": { "version": "6.3.1", "dev": true, "license": "ISC", @@ -5906,6 +6336,23 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", + "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/langs": "2.5.0", + "@shikijs/themes": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/side-channel": { "version": "1.1.0", "license": "MIT", @@ -5977,6 +6424,27 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "license": "MIT", @@ -5989,6 +6457,21 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "license": "MIT", @@ -5999,6 +6482,26 @@ "node": ">=8" } }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "dev": true, + "license": "MIT" + }, "node_modules/tailwind-merge": { "version": "3.5.0", "license": "MIT", @@ -6043,6 +6546,17 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/tslib": { "version": "2.8.1", "license": "0BSD" @@ -6059,16 +6573,84 @@ "node": ">=14.17" } }, - "node_modules/uc.micro": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", - "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", - "license": "MIT" - }, "node_modules/undici-types": { "version": "7.18.2", + "devOptional": true, "license": "MIT" }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "dev": true, @@ -6169,6 +6751,36 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/victory-vendor": { "version": "37.3.6", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", @@ -6263,19 +6875,558 @@ } } }, - "node_modules/w3c-keyname": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "license": "MIT" + "node_modules/vitepress": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", + "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" + }, + "bin": { + "vitepress": "bin/vitepress.js" + }, + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } + } }, - "node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "node_modules/vitepress/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, "engines": { "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitepress/node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.31.tgz", + "integrity": "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.31", + "@vue/compiler-sfc": "3.5.31", + "@vue/runtime-dom": "3.5.31", + "@vue/server-renderer": "3.5.31", + "@vue/shared": "3.5.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/which": { @@ -6310,27 +7461,6 @@ "node": ">=8" } }, - "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - }, - "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" - }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } - } - }, "node_modules/y18n": { "version": "4.0.3", "license": "ISC" @@ -6400,6 +7530,17 @@ } } }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "packages/SimpleModule.Client": { "name": "@simplemodule/client", "version": "0.0.0", diff --git a/package.json b/package.json index afb3635c..6d4e6232 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "packages/SimpleModule.Theme.Default", "packages/SimpleModule.UI", "template/SimpleModule.Host/ClientApp", - "tests/e2e" + "tests/e2e", + "docs/site" ], "scripts": { "dev": "node tools/dev-orchestrator.mjs", From 831488a6d85d7d8a7f400eff579c3c8b840113ec Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Thu, 26 Mar 2026 15:20:43 +0100 Subject: [PATCH 2/7] fix: correct indentation and formatting in index.md --- docs/site/index.md | 59 +++++++++++++++++++++++----------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/docs/site/index.md b/docs/site/index.md index e85b09af..0e09b76b 100644 --- a/docs/site/index.md +++ b/docs/site/index.md @@ -1,35 +1,36 @@ ---- +--- layout: home hero: - name: SimpleModule - text: Modular Monolith Framework for .NET - tagline: Compile-time module discovery via Roslyn source generators. React 19 + Inertia.js frontend served via Blazor SSR. - actions: - - theme: brand - text: Get Started - link: /getting-started/introduction - - theme: alt - text: View on GitHub - link: https://github.com/anthropics/SimpleModule + name: SimpleModule + text: Modular Monolith Framework for .NET + tagline: Compile-time module discovery via Roslyn source generators. React 19 + Inertia.js frontend served via Blazor SSR. + actions: + - theme: brand + text: Get Started + link: /getting-started/introduction + - theme: alt + text: View on GitHub + link: https://github.com/antosubash/SimpleModule features: - - icon: ⚡ - title: Compile-time Discovery - details: Roslyn source generators scan your modules at build time — no reflection, no runtime overhead. Endpoints, DTOs, and menus are auto-registered. - - icon: ⚛️ - title: React + Inertia.js - details: Build modern React 19 frontends with server-driven navigation. Each module ships its own pages bundle, dynamically loaded by the host app. - - icon: 🔒 - title: Module Isolation - details: Each module gets its own database schema, permissions, settings, and menu entries. Cross-module communication happens through contracts and events. - - icon: 🛠️ - title: CLI Tooling - details: Scaffold projects, modules, and features with the sm CLI. Built-in doctor command validates your project structure and auto-fixes issues. - - icon: 🧪 - title: Test Infrastructure - details: Pre-built WebApplicationFactory with in-memory SQLite, test auth scheme, and Bogus data generators. Run against PostgreSQL in CI. - - icon: 🗄️ - title: Multi-Provider Database - details: SQLite for development, PostgreSQL or SQL Server for production. Schema isolation per module with automatic table prefix or schema management. + - icon: ⚡ + title: Compile-time Discovery + details: Roslyn source generators scan your modules at build time — no reflection, no runtime overhead. Endpoints, DTOs, and menus are auto-registered. + - icon: ⚛️ + title: React + Inertia.js + details: Build modern React 19 frontends with server-driven navigation. Each module ships its own pages bundle, dynamically loaded by the host app. + - icon: 🔒 + title: Module Isolation + details: Each module gets its own database schema, permissions, settings, and menu entries. Cross-module communication happens through contracts and events. + - icon: 🛠️ + title: CLI Tooling + details: Scaffold projects, modules, and features with the sm CLI. Built-in doctor command validates your project structure and auto-fixes issues. + - icon: 🧪 + title: Test Infrastructure + details: Pre-built WebApplicationFactory with in-memory SQLite, test auth scheme, and Bogus data generators. Run against PostgreSQL in CI. + - icon: 🗄️ + title: Multi-Provider Database + details: SQLite for development, PostgreSQL or SQL Server for production. Schema isolation per module with automatic table prefix or schema management. --- + From c161926c7975f681253cfa4ffd5a0ca55173d140 Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Fri, 27 Mar 2026 23:29:55 +0100 Subject: [PATCH 3/7] docs: improve navigation and add Next Steps links across all pages Fix top nav: split "Guide" into separate "Getting Started" and "Guide" entries, add "Advanced" to nav bar. Add curated "Next Steps" sections to all 27 doc pages with cross-section links for better discoverability. Disable VitePress auto prev/next footer in favor of the manual links. --- docs/site/.vitepress/config.ts | 9 ++- docs/site/advanced/deployment.md | 6 ++ docs/site/advanced/interceptors.md | 6 ++ docs/site/advanced/source-generator.md | 6 ++ docs/site/advanced/type-generation.md | 6 ++ docs/site/cli/doctor.md | 6 ++ docs/site/cli/new-feature.md | 6 ++ docs/site/cli/new-module.md | 6 ++ docs/site/cli/new-project.md | 6 ++ docs/site/frontend/components.md | 6 ++ docs/site/frontend/overview.md | 6 ++ docs/site/frontend/pages.md | 6 ++ docs/site/frontend/styling.md | 6 ++ docs/site/frontend/vite.md | 6 ++ docs/site/getting-started/introduction.md | 1 + .../site/getting-started/project-structure.md | 6 ++ docs/site/getting-started/quick-start.md | 2 + docs/site/guide/contracts.md | 6 ++ docs/site/guide/database.md | 6 ++ docs/site/guide/endpoints.md | 6 ++ docs/site/guide/events.md | 6 ++ docs/site/guide/inertia.md | 6 ++ docs/site/guide/menus.md | 6 ++ docs/site/guide/modules.md | 6 ++ docs/site/guide/permissions.md | 6 ++ docs/site/guide/settings.md | 6 ++ docs/site/reference/api.md | 6 ++ docs/site/reference/configuration.md | 6 ++ docs/site/testing/e2e-tests.md | 6 ++ docs/site/testing/integration-tests.md | 6 ++ docs/site/testing/unit-tests.md | 6 ++ package-lock.json | 80 ------------------- 32 files changed, 179 insertions(+), 81 deletions(-) diff --git a/docs/site/.vitepress/config.ts b/docs/site/.vitepress/config.ts index 5b858200..1f6e1fdc 100644 --- a/docs/site/.vitepress/config.ts +++ b/docs/site/.vitepress/config.ts @@ -16,10 +16,12 @@ export default defineConfig({ logo: '/favicon.svg', nav: [ - { text: 'Guide', link: '/getting-started/introduction' }, + { text: 'Getting Started', link: '/getting-started/introduction' }, + { text: 'Guide', link: '/guide/modules' }, { text: 'Frontend', link: '/frontend/overview' }, { text: 'CLI', link: '/cli/overview' }, { text: 'Testing', link: '/testing/overview' }, + { text: 'Advanced', link: '/advanced/source-generator' }, { text: 'Reference', link: '/reference/api' }, ], @@ -131,6 +133,11 @@ export default defineConfig({ provider: 'local', }, + docFooter: { + prev: false, + next: false, + }, + footer: { message: 'Released under the MIT License.', copyright: 'Copyright 2026 SimpleModule Contributors', diff --git a/docs/site/advanced/deployment.md b/docs/site/advanced/deployment.md index 89d83021..4b09162c 100644 --- a/docs/site/advanced/deployment.md +++ b/docs/site/advanced/deployment.md @@ -223,3 +223,9 @@ dotnet ef database update \ ::: tip Each module has its own DbContext and schema. In PostgreSQL, modules use separate schemas for isolation. In SQLite, modules use table prefixes. ::: + +## Next Steps + +- [Configuration Reference](/reference/configuration) -- all framework settings in one place +- [API Reference](/reference/api) -- complete interface and type documentation +- [Database](/guide/database) -- module database contexts and migration patterns diff --git a/docs/site/advanced/interceptors.md b/docs/site/advanced/interceptors.md index 3b7a5390..83d4fef6 100644 --- a/docs/site/advanced/interceptors.md +++ b/docs/site/advanced/interceptors.md @@ -153,3 +153,9 @@ internal readonly record struct InterceptorInfoRecord( | Resolve services inside interception methods | Resolve services in the constructor | | Use `?.GetService()` for null safety | Assume services are always available | | Keep constructors minimal | Add complex initialization logic to constructors | + +## Next Steps + +- [Deployment](/advanced/deployment) -- production configuration and CI/CD pipeline +- [Database](/guide/database) -- module database contexts and schema isolation +- [Configuration Reference](/reference/configuration) -- all framework settings diff --git a/docs/site/advanced/source-generator.md b/docs/site/advanced/source-generator.md index 5024cb1f..d781b55e 100644 --- a/docs/site/advanced/source-generator.md +++ b/docs/site/advanced/source-generator.md @@ -242,3 +242,9 @@ dotnet build ``` The incremental cache occasionally needs a full rebuild to reset. ::: + +## Next Steps + +- [Type Generation](/advanced/type-generation) -- how DTOs become TypeScript interfaces +- [Modules](/guide/modules) -- how modules are defined and registered +- [API Reference](/reference/api) -- all generated extension methods diff --git a/docs/site/advanced/type-generation.md b/docs/site/advanced/type-generation.md index 6cb4577e..8bf102ae 100644 --- a/docs/site/advanced/type-generation.md +++ b/docs/site/advanced/type-generation.md @@ -267,3 +267,9 @@ The `extract-ts-types.mjs` tool then: 4. Writes a `types.ts` file to the module's source directory Property names are automatically converted from `PascalCase` (C#) to `camelCase` (TypeScript) during generation, matching the default `System.Text.Json` serialization behavior. + +## Next Steps + +- [EF Core Interceptors](/advanced/interceptors) -- safe DI patterns for database interceptors +- [Contracts & DTOs](/guide/contracts) -- where DTO types are defined +- [Frontend Overview](/frontend/overview) -- how generated types are used in React diff --git a/docs/site/cli/doctor.md b/docs/site/cli/doctor.md index ca6bccbc..30c8b8a4 100644 --- a/docs/site/cli/doctor.md +++ b/docs/site/cli/doctor.md @@ -115,3 +115,9 @@ Add `sm doctor` to your CI pipeline to catch structural issues early: ``` The non-zero exit code on failure makes it suitable for CI gates. Pair it with `--fix` in a pre-commit hook for local development if desired. + +## Next Steps + +- [Testing Overview](/testing/overview) -- test strategies for your modules +- [Deployment](/advanced/deployment) -- CI/CD pipeline and production configuration +- [Project Structure](/getting-started/project-structure) -- understand the expected layout diff --git a/docs/site/cli/new-feature.md b/docs/site/cli/new-feature.md index 0ba01912..4de45754 100644 --- a/docs/site/cli/new-feature.md +++ b/docs/site/cli/new-feature.md @@ -73,3 +73,9 @@ sm new feature CreateInvoice --module Invoices --method POST --route / --validat # Interactive sm new feature ``` + +## Next Steps + +- [Endpoints](/guide/endpoints) -- API and view endpoint patterns in detail +- [Pages Registry](/frontend/pages) -- register your new page component +- [sm doctor](/cli/doctor) -- validate everything is wired correctly diff --git a/docs/site/cli/new-module.md b/docs/site/cli/new-module.md index ccaeb075..d3c0f7de 100644 --- a/docs/site/cli/new-module.md +++ b/docs/site/cli/new-module.md @@ -103,3 +103,9 @@ sm new module # Direct mode sm new module Invoices ``` + +## Next Steps + +- [sm new feature](/cli/new-feature) -- add endpoints and pages to your module +- [Modules](/guide/modules) -- deep dive into the module system +- [sm doctor](/cli/doctor) -- validate your project structure after changes diff --git a/docs/site/cli/new-project.md b/docs/site/cli/new-project.md index faf03072..149ff489 100644 --- a/docs/site/cli/new-project.md +++ b/docs/site/cli/new-project.md @@ -76,3 +76,9 @@ sm new project MyApp # Create in a specific directory sm new project MyApp --output ~/projects ``` + +## Next Steps + +- [sm new module](/cli/new-module) -- add a module to your project +- [Quick Start](/getting-started/quick-start) -- build and run your new project +- [Project Structure](/getting-started/project-structure) -- understand the generated layout diff --git a/docs/site/frontend/components.md b/docs/site/frontend/components.md index 042f87a4..390f61a3 100644 --- a/docs/site/frontend/components.md +++ b/docs/site/frontend/components.md @@ -181,3 +181,9 @@ The UI package relies on these key dependencies: - **recharts** -- Charting library React and React-DOM are peer dependencies -- they are provided by the host application, not bundled. + +## Next Steps + +- [Styling & Theming](/frontend/styling) -- Tailwind CSS configuration and theme customization +- [Vite Build System](/frontend/vite) -- how module bundles are built and served +- [Pages Registry](/frontend/pages) -- how components are registered and resolved diff --git a/docs/site/frontend/overview.md b/docs/site/frontend/overview.md index dedea30e..e1a3738e 100644 --- a/docs/site/frontend/overview.md +++ b/docs/site/frontend/overview.md @@ -127,3 +127,9 @@ export default function Browse({ products }: { products: Product[] }) { ); } ``` + +## Next Steps + +- [Pages Registry](/frontend/pages) -- how page components are resolved and loaded +- [UI Components](/frontend/components) -- the shared Radix UI component library +- [Styling & Theming](/frontend/styling) -- Tailwind CSS configuration and theme customization diff --git a/docs/site/frontend/pages.md b/docs/site/frontend/pages.md index 95c9baf9..ceec9429 100644 --- a/docs/site/frontend/pages.md +++ b/docs/site/frontend/pages.md @@ -176,3 +176,9 @@ The pages registry supports both lazy and eager component imports: ``` Lazy imports are recommended because they allow Vite to code-split within the module bundle. The `resolvePage` function handles both patterns transparently. + +## Next Steps + +- [UI Components](/frontend/components) -- the shared Radix UI component library +- [Styling & Theming](/frontend/styling) -- Tailwind CSS configuration and theming +- [Vite Build System](/frontend/vite) -- how module bundles are built diff --git a/docs/site/frontend/styling.md b/docs/site/frontend/styling.md index 6361bcdf..d995cfda 100644 --- a/docs/site/frontend/styling.md +++ b/docs/site/frontend/styling.md @@ -197,3 +197,9 @@ Biome is configured to understand Tailwind CSS directives like `@theme`, `@layer ``` This prevents false-positive lint/format errors on Tailwind-specific CSS syntax. + +## Next Steps + +- [Vite Build System](/frontend/vite) -- how styles are bundled per module +- [UI Components](/frontend/components) -- pre-built components using these styles +- [Inertia.js Integration](/guide/inertia) -- how server data flows to styled React components diff --git a/docs/site/frontend/vite.md b/docs/site/frontend/vite.md index 708ef631..91316961 100644 --- a/docs/site/frontend/vite.md +++ b/docs/site/frontend/vite.md @@ -224,3 +224,9 @@ modules/Products/src/Products/wwwroot/ ``` These files are served as static content via ASP.NET's `_content/{ModuleName}/` path, which is how `resolvePage` finds them at `/_content/Products/Products.pages.js`. + +## Next Steps + +- [Testing Overview](/testing/overview) -- test strategies for your modules +- [CLI Overview](/cli/overview) -- scaffold modules and features with the `sm` command +- [Deployment](/advanced/deployment) -- production builds and Docker configuration diff --git a/docs/site/getting-started/introduction.md b/docs/site/getting-started/introduction.md index 1db47118..ac291a10 100644 --- a/docs/site/getting-started/introduction.md +++ b/docs/site/getting-started/introduction.md @@ -177,3 +177,4 @@ You don't write registration code, startup configuration, or reflection-based di - [Quick Start](./quick-start) -- install the CLI and create your first project in under five minutes - [Project Structure](./project-structure) -- understand how a SimpleModule solution is organized +- [Modules](/guide/modules) -- deep dive into the module system diff --git a/docs/site/getting-started/project-structure.md b/docs/site/getting-started/project-structure.md index b9086bec..e3028395 100644 --- a/docs/site/getting-started/project-structure.md +++ b/docs/site/getting-started/project-structure.md @@ -404,3 +404,9 @@ If you prefer not to use the CLI, here are the steps: ::: tip Use the CLI `sm new module ` does all of this in seconds and ensures nothing is missed. Use `sm doctor --fix` afterward to verify everything is wired correctly. ::: + +## Next Steps + +- [Modules](/guide/modules) -- how modules are defined and discovered +- [Endpoints](/guide/endpoints) -- API and view endpoint patterns +- [Contracts & DTOs](/guide/contracts) -- cross-module communication boundaries diff --git a/docs/site/getting-started/quick-start.md b/docs/site/getting-started/quick-start.md index 3d4bd379..653fd0b5 100644 --- a/docs/site/getting-started/quick-start.md +++ b/docs/site/getting-started/quick-start.md @@ -296,3 +296,5 @@ During local development with `npm run dev`, the app uses SQLite by default -- n ## Next Steps - [Project Structure](./project-structure) -- understand how the solution is organized +- [Modules](/guide/modules) -- deep dive into the module system +- [Endpoints](/guide/endpoints) -- learn about API and view endpoint patterns diff --git a/docs/site/guide/contracts.md b/docs/site/guide/contracts.md index e6ee3760..6377ac7e 100644 --- a/docs/site/guide/contracts.md +++ b/docs/site/guide/contracts.md @@ -320,3 +320,9 @@ Any module can implement `IEventHandler` to react when an ord | Events (`IEvent`) | Contracts project | Cross-module event definitions | | `[Dto]` | On types outside contracts | Opt-in to TypeScript generation | | `[NoDtoGeneration]` | On types inside contracts | Opt-out of TypeScript generation | + +## Next Steps + +- [Database](/guide/database) -- configure per-module database contexts and schema isolation +- [Event Bus](/guide/events) -- publish and handle cross-module events +- [Type Generation](/advanced/type-generation) -- how DTOs become TypeScript interfaces diff --git a/docs/site/guide/database.md b/docs/site/guide/database.md index bac2b913..c9bdf02f 100644 --- a/docs/site/guide/database.md +++ b/docs/site/guide/database.md @@ -360,3 +360,9 @@ Full `Database` configuration section: | `DefaultConnection` | `string` | Connection string shared by all modules (required) | | `Provider` | `string?` | Explicit provider override. Auto-detected from connection string if omitted | | `ModuleConnections` | `Dictionary` | Per-module connection strings for database isolation | + +## Next Steps + +- [Event Bus](/guide/events) -- cross-module communication via events +- [EF Core Interceptors](/advanced/interceptors) -- safe dependency injection patterns for interceptors +- [Deployment](/advanced/deployment) -- production database configuration and migrations diff --git a/docs/site/guide/endpoints.md b/docs/site/guide/endpoints.md index fbe521d1..e8573ec6 100644 --- a/docs/site/guide/endpoints.md +++ b/docs/site/guide/endpoints.md @@ -415,3 +415,9 @@ modules/Products/src/Products/ - `Endpoints/` contains `IEndpoint` classes (API), organized by resource - `Views/` contains `IViewEndpoint` classes (Inertia pages) - Validators sit alongside their corresponding endpoint + +## Next Steps + +- [Contracts & DTOs](/guide/contracts) -- define shared types and cross-module interfaces +- [Inertia.js Integration](/guide/inertia) -- how view endpoints render React pages +- [Permissions](/guide/permissions) -- protect endpoints with claims-based authorization diff --git a/docs/site/guide/events.md b/docs/site/guide/events.md index f06847d3..34a03301 100644 --- a/docs/site/guide/events.md +++ b/docs/site/guide/events.md @@ -295,3 +295,9 @@ Key implementation details: - Failed handlers are logged with structured logging (`[LoggerMessage]` source generator) - The `CancellationToken` is passed to every handler, allowing cooperative cancellation - The `EventBus` is registered as scoped, so handlers share the same DI scope as the request + +## Next Steps + +- [Permissions](/guide/permissions) -- claims-based authorization for endpoints +- [Database](/guide/database) -- persistence patterns commonly paired with events +- [Unit Tests](/testing/unit-tests) -- how to test event handlers and partial failure scenarios diff --git a/docs/site/guide/inertia.md b/docs/site/guide/inertia.md index 36f950fa..c2d91aa9 100644 --- a/docs/site/guide/inertia.md +++ b/docs/site/guide/inertia.md @@ -392,3 +392,9 @@ export default function Browse({ products }: { products: Product[] }) { 6. React hydrates, `resolvePage("Products/Browse")` imports `Products.pages.js` 7. The Browse component renders with the server-provided products array 8. On subsequent navigation: only JSON is returned, React swaps the component + +## Next Steps + +- [Frontend Overview](/frontend/overview) -- the complete React + Inertia.js architecture +- [Pages Registry](/frontend/pages) -- how page components are resolved at runtime +- [Vite Build System](/frontend/vite) -- module-scoped library mode builds diff --git a/docs/site/guide/menus.md b/docs/site/guide/menus.md index 4c66318c..a2259ae9 100644 --- a/docs/site/guide/menus.md +++ b/docs/site/guide/menus.md @@ -228,3 +228,9 @@ Menu data is typically shared with the frontend via Inertia shared data or API e - **`UserDropdown`** renders `MenuSection.UserDropdown` items The `RequiresAuth` property controls visibility -- items with `RequiresAuth = true` are only shown to authenticated users. + +## Next Steps + +- [Settings](/guide/settings) -- module-scoped configurable settings +- [Inertia.js Integration](/guide/inertia) -- how server data flows to React pages +- [Frontend Overview](/frontend/overview) -- the React + Inertia.js architecture diff --git a/docs/site/guide/modules.md b/docs/site/guide/modules.md index 2b930599..d9ff0ecb 100644 --- a/docs/site/guide/modules.md +++ b/docs/site/guide/modules.md @@ -327,3 +327,9 @@ public class WebhooksModule : IModule ::: danger When you override `ConfigureEndpoints`, the auto-discovery of `IEndpoint` classes is **skipped** for that module. You are responsible for mapping all endpoints manually. `IViewEndpoint` classes are still auto-discovered regardless. ::: + +## Next Steps + +- [Endpoints](/guide/endpoints) -- API and view endpoint patterns +- [Contracts & DTOs](/guide/contracts) -- define the public interface between modules +- [Source Generator](/advanced/source-generator) -- how modules are discovered at compile time diff --git a/docs/site/guide/permissions.md b/docs/site/guide/permissions.md index 17fff6fd..51fb9c9f 100644 --- a/docs/site/guide/permissions.md +++ b/docs/site/guide/permissions.md @@ -250,3 +250,9 @@ public async Task Create_product_forbidden_without_permission() response.StatusCode.Should().Be(HttpStatusCode.Forbidden); } ``` + +## Next Steps + +- [Menus](/guide/menus) -- navigation menu system with auth-aware visibility +- [Settings](/guide/settings) -- module-scoped configurable settings +- [Integration Tests](/testing/integration-tests) -- test permission-protected endpoints end-to-end diff --git a/docs/site/guide/settings.md b/docs/site/guide/settings.md index 0583d76a..1cc03319 100644 --- a/docs/site/guide/settings.md +++ b/docs/site/guide/settings.md @@ -249,3 +249,9 @@ Follow the `module.category.name` pattern for setting keys: ::: tip Use dot-separated keys for consistency. The `Group` property on `SettingDefinition` controls visual grouping in the UI independently from the key structure. ::: + +## Next Steps + +- [Inertia.js Integration](/guide/inertia) -- how server data reaches React components +- [Frontend Overview](/frontend/overview) -- the complete frontend architecture +- [Configuration Reference](/reference/configuration) -- all framework configuration options diff --git a/docs/site/reference/api.md b/docs/site/reference/api.md index 01d4f2da..4949701c 100644 --- a/docs/site/reference/api.md +++ b/docs/site/reference/api.md @@ -523,3 +523,9 @@ public static IServiceCollection CollectModuleMenuItems( ``` Invokes `ConfigureMenu` on all modules that implement it and registers the resulting `IMenuRegistry` as a singleton. + +## Next Steps + +- [Configuration Reference](/reference/configuration) -- all framework configuration options +- [Source Generator](/advanced/source-generator) -- how these extension methods are generated +- [Modules](/guide/modules) -- practical guide to using these interfaces diff --git a/docs/site/reference/configuration.md b/docs/site/reference/configuration.md index ed5c930d..30a0c963 100644 --- a/docs/site/reference/configuration.md +++ b/docs/site/reference/configuration.md @@ -199,3 +199,9 @@ ASP.NET loads configuration from multiple sources. Later sources override earlie 4. Command-line arguments (highest priority) For the `Development` environment, the effective configuration merges `appsettings.json` with `appsettings.Development.json`, with the Development file winning on conflicts. + +## Next Steps + +- [API Reference](/reference/api) -- complete interface and type documentation +- [Deployment](/advanced/deployment) -- production configuration and Docker setup +- [Database](/guide/database) -- database-specific configuration details diff --git a/docs/site/testing/e2e-tests.md b/docs/site/testing/e2e-tests.md index 6bc7abf9..38e25853 100644 --- a/docs/site/testing/e2e-tests.md +++ b/docs/site/testing/e2e-tests.md @@ -290,3 +290,9 @@ In CI, the Playwright configuration automatically: ``` Test results are available as an HTML report via `npm run report` (or `npx playwright show-report` from `tests/e2e/`). + +## Next Steps + +- [Deployment](/advanced/deployment) -- CI/CD pipeline and Docker configuration +- [CLI Overview](/cli/overview) -- project scaffolding and validation tools +- [Configuration Reference](/reference/configuration) -- all framework settings diff --git a/docs/site/testing/integration-tests.md b/docs/site/testing/integration-tests.md index 8cdfb8bb..469eb1e5 100644 --- a/docs/site/testing/integration-tests.md +++ b/docs/site/testing/integration-tests.md @@ -231,3 +231,9 @@ env: ``` The `DatabaseOptions` configuration picks this up automatically, and module `DbContext` instances use the configured provider. + +## Next Steps + +- [E2E Tests](/testing/e2e-tests) -- browser-based testing with Playwright +- [Permissions](/guide/permissions) -- how to test permission-protected endpoints +- [Deployment](/advanced/deployment) -- CI/CD pipeline configuration diff --git a/docs/site/testing/unit-tests.md b/docs/site/testing/unit-tests.md index 2609dbe4..aeced50e 100644 --- a/docs/site/testing/unit-tests.md +++ b/docs/site/testing/unit-tests.md @@ -193,3 +193,9 @@ public async Task HandleAsync_LogsEvent() ``` For testing the `EventBus` itself, including partial failure semantics, see the `EventBusPartialFailureTests` in the core test project. + +## Next Steps + +- [Integration Tests](/testing/integration-tests) -- test HTTP endpoints through the full pipeline +- [E2E Tests](/testing/e2e-tests) -- browser-based testing with Playwright +- [Event Bus](/guide/events) -- understand event handler patterns and partial failure diff --git a/package-lock.json b/package-lock.json index 7aacc13a..8ef21bec 100644 --- a/package-lock.json +++ b/package-lock.json @@ -916,62 +916,6 @@ } } }, - "node_modules/@docsearch/js/node_modules/@types/react": { - "version": "18.3.28", - "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.28.tgz", - "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "@types/prop-types": "*", - "csstype": "^3.2.2" - } - }, - "node_modules/@docsearch/js/node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/@docsearch/js/node_modules/react-dom": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", - "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" - }, - "peerDependencies": { - "react": "^18.3.1" - } - }, - "node_modules/@docsearch/js/node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -3886,15 +3830,6 @@ "undici-types": "~7.18.0" } }, - "node_modules/@types/prop-types": { - "version": "15.7.15", - "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.15.tgz", - "integrity": "sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true - }, "node_modules/@types/qrcode": { "version": "1.5.6", "dev": true, @@ -5550,21 +5485,6 @@ "version": "4.17.23", "license": "MIT" }, - "node_modules/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, "node_modules/lru-cache": { "version": "5.1.1", "dev": true, From 57c7653fb6e3515e192ac5cc06e268d35c9a371e Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Fri, 27 Mar 2026 23:42:56 +0100 Subject: [PATCH 4/7] revert: undo unintended changes to CI workflow --- .github/workflows/ci.yml | 107 ++++++++++++--------------------------- 1 file changed, 31 insertions(+), 76 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e79d0766..be469bab 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -6,104 +6,59 @@ on: pull_request: branches: [main] -env: - FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true jobs: - build: + lint: runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - name: Setup .NET - uses: actions/setup-dotnet@v4 + - name: Setup Node.js + uses: actions/setup-node@v6 with: - dotnet-version: '10.0.x' + node-version: '22' + cache: 'npm' - - name: Download Tailwind CSS - run: bash tools/download-tailwind.sh - - - name: Restore - run: dotnet restore + - name: Install npm dependencies + run: npm ci - - name: Build - run: dotnet build --no-restore + - name: Lint and format check + run: npm run check - test-sqlite: + build: runs-on: ubuntu-latest - needs: build - + needs: lint steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Setup .NET - uses: actions/setup-dotnet@v4 + uses: actions/setup-dotnet@v5 with: dotnet-version: '10.0.x' - - name: Download Tailwind CSS - run: bash tools/download-tailwind.sh - - - name: Test - run: dotnet test --verbosity normal - - test-postgresql: - runs-on: ubuntu-latest - needs: build - - services: - postgres: - image: postgres:16 - env: - POSTGRES_USER: test - POSTGRES_PASSWORD: test - POSTGRES_DB: simplemodule_test - ports: - - 5432:5432 - options: >- - --health-cmd pg_isready - --health-interval 10s - --health-timeout 5s - --health-retries 5 - - steps: - - uses: actions/checkout@v4 - - - name: Setup .NET - uses: actions/setup-dotnet@v4 + - name: Setup Node.js + uses: actions/setup-node@v6 with: - dotnet-version: '10.0.x' + node-version: '22' + cache: 'npm' - name: Download Tailwind CSS run: bash tools/download-tailwind.sh - - name: Test with PostgreSQL - run: dotnet test --verbosity normal - env: - Database__DefaultConnection: "Host=localhost;Database=simplemodule_test;Username=test;Password=test" - - - name: Verify migrations are up-to-date - working-directory: template/SimpleModule.Host - env: - Database__DefaultConnection: "Host=localhost;Database=simplemodule_test;Username=test;Password=test" - run: dotnet ef migrations has-pending-model-changes - - publish: - runs-on: ubuntu-latest - needs: [test-sqlite, test-postgresql] - if: github.ref == 'refs/heads/main' + - name: Restore .NET + run: dotnet restore - steps: - - uses: actions/checkout@v4 + - name: Build .NET + run: dotnet build --no-restore - - name: Setup .NET - uses: actions/setup-dotnet@v4 - with: - dotnet-version: '10.0.x' + - name: Install npm dependencies + run: npm ci - - name: Download Tailwind CSS - run: bash tools/download-tailwind.sh + - name: Build frontend + run: npm run build - - name: Publish - run: dotnet publish template/SimpleModule.Host/SimpleModule.Host.csproj -c Release + - name: Test + run: dotnet test --no-build From 7b2ab8a8a6be8973ac95d72693c3ac6e301c5ad2 Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Fri, 27 Mar 2026 23:44:37 +0100 Subject: [PATCH 5/7] fix: restore package-lock.json from main to fix npm ci --- package-lock.json | 3459 ++++++++++++++++----------------------------- 1 file changed, 1199 insertions(+), 2260 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8ef21bec..33fc3045 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,8 +13,7 @@ "packages/SimpleModule.Theme.Default", "packages/SimpleModule.UI", "template/SimpleModule.Host/ClientApp", - "tests/e2e", - "docs/site" + "tests/e2e" ], "dependencies": { "tailwindcss": "^4.2.1" @@ -30,13 +29,16 @@ "vite": "^6.2.0" } }, - "docs/site": { - "name": "docs", - "devDependencies": { - "vitepress": "^1.6.4" + "modules/Admin/src/Admin": { + "name": "@simplemodule/admin", + "version": "0.0.0", + "extraneous": true, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" } }, - "modules/Admin/src/Admin": { + "modules/Admin/src/SimpleModule.Admin": { "name": "@simplemodule/admin", "version": "0.0.0", "peerDependencies": { @@ -47,332 +49,172 @@ "modules/AuditLogs/src/AuditLogs": { "name": "@simplemodule/auditlogs", "version": "0.0.0", + "extraneous": true, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "modules/Dashboard/src/Dashboard": { - "name": "@simplemodule/dashboard", + "modules/AuditLogs/src/SimpleModule.AuditLogs": { + "name": "@simplemodule/auditlogs", "version": "0.0.0", "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "modules/OpenIddict/src/OpenIddict": { - "name": "@simplemodule/openiddict", + "modules/Dashboard/src/Dashboard": { + "name": "@simplemodule/dashboard", "version": "0.0.0", + "extraneous": true, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "modules/Orders/src/Orders": { - "name": "@simplemodule/orders", + "modules/Dashboard/src/SimpleModule.Dashboard": { + "name": "@simplemodule/dashboard", "version": "0.0.0", "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "modules/PageBuilder/src/PageBuilder": { - "name": "@simplemodule/pagebuilder", + "modules/FileStorage/src/FileStorage": { + "name": "@simplemodule/filestorage", "version": "0.0.0", - "dependencies": { - "@measured/puck": "^0.18.0" - }, + "extraneous": true, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "modules/Products/src/Products": { - "name": "@simplemodule/products", + "modules/FileStorage/src/SimpleModule.FileStorage": { + "name": "@simplemodule/filestorage", "version": "0.0.0", "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "modules/Settings/src/Settings": { - "name": "@simplemodule/settings", + "modules/OpenIddict/src/OpenIddict": { + "name": "@simplemodule/openiddict", "version": "0.0.0", + "extraneous": true, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "modules/Users/src/Users": { - "name": "@simplemodule/users", + "modules/OpenIddict/src/SimpleModule.OpenIddict": { + "name": "@simplemodule/openiddict", "version": "0.0.0", - "dependencies": { - "qrcode": "^1.5.4" - }, - "devDependencies": { - "@types/qrcode": "^1.5.6" - }, "peerDependencies": { "react": "^19.0.0", "react-dom": "^19.0.0" } }, - "node_modules/@algolia/abtesting": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.16.0.tgz", - "integrity": "sha512-alHFZ68/i9qLC/muEB07VQ9r7cB8AvCcGX6dVQi2PNHhc/ZQRmmFAv8KK1ay4UiseGSFr7f0nXBKsZ/jRg7e4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.50.0", - "@algolia/requester-browser-xhr": "5.50.0", - "@algolia/requester-fetch": "5.50.0", - "@algolia/requester-node-http": "5.50.0" - }, - "engines": { - "node": ">= 14.0.0" + "modules/Orders/src/Orders": { + "name": "@simplemodule/orders", + "version": "0.0.0", + "extraneous": true, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" } }, - "node_modules/@algolia/autocomplete-core": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", - "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", - "@algolia/autocomplete-shared": "1.17.7" + "modules/Orders/src/SimpleModule.Orders": { + "name": "@simplemodule/orders", + "version": "0.0.0", + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" } }, - "node_modules/@algolia/autocomplete-plugin-algolia-insights": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", - "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", - "dev": true, - "license": "MIT", + "modules/PageBuilder/src/PageBuilder": { + "name": "@simplemodule/pagebuilder", + "version": "0.0.0", + "extraneous": true, "dependencies": { - "@algolia/autocomplete-shared": "1.17.7" + "@measured/puck": "^0.18.0" }, "peerDependencies": { - "search-insights": ">= 1 < 3" + "react": "^19.0.0", + "react-dom": "^19.0.0" } }, - "node_modules/@algolia/autocomplete-preset-algolia": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", - "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", - "dev": true, - "license": "MIT", + "modules/PageBuilder/src/SimpleModule.PageBuilder": { + "name": "@simplemodule/pagebuilder", + "version": "0.0.0", "dependencies": { - "@algolia/autocomplete-shared": "1.17.7" + "@puckeditor/core": "^0.21.1" }, "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" + "react": "^19.0.0", + "react-dom": "^19.0.0" } }, - "node_modules/@algolia/autocomplete-shared": { - "version": "1.17.7", - "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", - "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", - "dev": true, - "license": "MIT", + "modules/Products/src/Products": { + "name": "@simplemodule/products", + "version": "0.0.0", + "extraneous": true, "peerDependencies": { - "@algolia/client-search": ">= 4.9.1 < 6", - "algoliasearch": ">= 4.9.1 < 6" - } - }, - "node_modules/@algolia/client-abtesting": { - "version": "5.50.0", - "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.50.0.tgz", - "integrity": "sha512-mfgUdLQNxOAvCZUGzPQxjahEWEPuQkKlV0ZtGmePOa9ZxIQZlk31vRBNbM6ScU8jTH41SCYE77G/lCifDr1SVw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.50.0", - "@algolia/requester-browser-xhr": "5.50.0", - "@algolia/requester-fetch": "5.50.0", - "@algolia/requester-node-http": "5.50.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-analytics": { - "version": "5.50.0", - "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.50.0.tgz", - "integrity": "sha512-5mjokeKYyPaP3Q8IYJEnutI+O4dW/Ixxx5IgsSxT04pCfGqPXxTOH311hTQxyNpcGGEOGrMv8n8Z+UMTPamioQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.50.0", - "@algolia/requester-browser-xhr": "5.50.0", - "@algolia/requester-fetch": "5.50.0", - "@algolia/requester-node-http": "5.50.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-common": { - "version": "5.50.0", - "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.50.0.tgz", - "integrity": "sha512-emtOvR6dl3rX3sBJXXbofMNHU1qMQqQSWu319RMrNL5BWoBqyiq7y0Zn6cjJm7aGHV/Qbf+KCCYeWNKEMPI3BQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-insights": { - "version": "5.50.0", - "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.50.0.tgz", - "integrity": "sha512-IerGH2/hcj/6bwkpQg/HHRqmlGN1XwygQWythAk0gZFBrghs9danJaYuSS3ShzLSVoIVth4jY5GDPX9Lbw5cgg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.50.0", - "@algolia/requester-browser-xhr": "5.50.0", - "@algolia/requester-fetch": "5.50.0", - "@algolia/requester-node-http": "5.50.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-personalization": { - "version": "5.50.0", - "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.50.0.tgz", - "integrity": "sha512-3idPJeXn5L0MmgP9jk9JJqblrQ/SguN93dNK9z9gfgyupBhHnJMOEjrRYcVgTIfvG13Y04wO+Q0FxE2Ut8PVbA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.50.0", - "@algolia/requester-browser-xhr": "5.50.0", - "@algolia/requester-fetch": "5.50.0", - "@algolia/requester-node-http": "5.50.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/client-query-suggestions": { - "version": "5.50.0", - "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.50.0.tgz", - "integrity": "sha512-q7qRoWrQK1a8m5EFQEmPlo7+pg9mVQ8X5jsChtChERre0uS2pdYEDixBBl0ydBSGkdGbLUDufcACIhH/077E4g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.50.0", - "@algolia/requester-browser-xhr": "5.50.0", - "@algolia/requester-fetch": "5.50.0", - "@algolia/requester-node-http": "5.50.0" - }, - "engines": { - "node": ">= 14.0.0" + "react": "^19.0.0", + "react-dom": "^19.0.0" } }, - "node_modules/@algolia/client-search": { - "version": "5.50.0", - "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.50.0.tgz", - "integrity": "sha512-Jc360x4yqb3eEg4OY4KEIdGePBxZogivKI+OGIU8aLXgAYPTECvzeOBc90312yHA1hr3AeRlAFl0rIc8lQaIrQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.50.0", - "@algolia/requester-browser-xhr": "5.50.0", - "@algolia/requester-fetch": "5.50.0", - "@algolia/requester-node-http": "5.50.0" - }, - "engines": { - "node": ">= 14.0.0" + "modules/Products/src/SimpleModule.Products": { + "name": "@simplemodule/products", + "version": "0.0.0", + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" } }, - "node_modules/@algolia/ingestion": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.50.0.tgz", - "integrity": "sha512-OS3/Viao+NPpyBbEY3tf6hLewppG+UclD+9i0ju56mq2DrdMJFCkEky6Sk9S5VPcbLzxzg3BqBX6u9Q35w19aQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.50.0", - "@algolia/requester-browser-xhr": "5.50.0", - "@algolia/requester-fetch": "5.50.0", - "@algolia/requester-node-http": "5.50.0" - }, - "engines": { - "node": ">= 14.0.0" + "modules/Settings/src/Settings": { + "name": "@simplemodule/settings", + "version": "0.0.0", + "extraneous": true, + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" } }, - "node_modules/@algolia/monitoring": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.50.0.tgz", - "integrity": "sha512-/znwgSiGufpbJVIoDmeQaHtTq+OMdDawFRbMSJVv+12n79hW+qdQXS8/Uu3BD3yn0BzgVFJEvrsHrCsInZKdhw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.50.0", - "@algolia/requester-browser-xhr": "5.50.0", - "@algolia/requester-fetch": "5.50.0", - "@algolia/requester-node-http": "5.50.0" - }, - "engines": { - "node": ">= 14.0.0" + "modules/Settings/src/SimpleModule.Settings": { + "name": "@simplemodule/settings", + "version": "0.0.0", + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" } }, - "node_modules/@algolia/recommend": { - "version": "5.50.0", - "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.50.0.tgz", - "integrity": "sha512-dHjUfu4jfjdQiKDpCpAnM7LP5yfG0oNShtfpF5rMCel6/4HIoqJ4DC4h5GKDzgrvJYtgAhblo0AYBmOM00T+lQ==", - "dev": true, - "license": "MIT", + "modules/Users/src/SimpleModule.Users": { + "name": "@simplemodule/users", + "version": "0.0.0", "dependencies": { - "@algolia/client-common": "5.50.0", - "@algolia/requester-browser-xhr": "5.50.0", - "@algolia/requester-fetch": "5.50.0", - "@algolia/requester-node-http": "5.50.0" + "qrcode": "^1.5.4" }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-browser-xhr": { - "version": "5.50.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.50.0.tgz", - "integrity": "sha512-bffIbUljAWnh/Ctu5uScORajuUavqmZ0ACYd1fQQeSSYA9NNN83ynO26pSc2dZRXpSK0fkc1//qSSFXMKGu+aw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.50.0" + "devDependencies": { + "@types/qrcode": "^1.5.6" }, - "engines": { - "node": ">= 14.0.0" + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" } }, - "node_modules/@algolia/requester-fetch": { - "version": "5.50.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.50.0.tgz", - "integrity": "sha512-y0EwNvPGvkM+yTAqqO6Gpt9wVGm3CLDtpLvNEiB3VGvN3WzfkjZGtLUsG/ru2kVJIIU7QcV0puuYgEpBeFxcJg==", - "dev": true, - "license": "MIT", + "modules/Users/src/Users": { + "name": "@simplemodule/users", + "version": "0.0.0", + "extraneous": true, "dependencies": { - "@algolia/client-common": "5.50.0" + "qrcode": "^1.5.4" }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/@algolia/requester-node-http": { - "version": "5.50.0", - "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.50.0.tgz", - "integrity": "sha512-xpwefe4fCOWnZgXCbkGpqQY6jgBSCf2hmgnySbyzZIccrv3SoashHKGPE4x6vVG+gdHrGciMTAcDo9HOZwH22Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/client-common": "5.50.0" + "devDependencies": { + "@types/qrcode": "^1.5.6" }, - "engines": { - "node": ">= 14.0.0" + "peerDependencies": { + "react": "^19.0.0", + "react-dom": "^19.0.0" } }, "node_modules/@babel/code-frame": { @@ -536,9 +378,7 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "version": "7.29.0", "dev": true, "license": "MIT", "dependencies": { @@ -784,138 +624,6 @@ "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==", "license": "MIT" }, - "node_modules/@dnd-kit/abstract": { - "version": "0.0.7-beta-20250130032138", - "resolved": "https://registry.npmjs.org/@dnd-kit/abstract/-/abstract-0.0.7-beta-20250130032138.tgz", - "integrity": "sha512-CFprqfJTtvSqq6ST7AEu41M1CIMk3lko5JNggTsx9seuqodJ0kCqBDul92BBWoAxPiRuQPm+TrK5vijj1tO7rg==", - "license": "MIT", - "dependencies": { - "@dnd-kit/geometry": "0.0.7-beta-20250130032138", - "@dnd-kit/state": "0.0.7-beta-20250130032138", - "tslib": "^2.6.2" - } - }, - "node_modules/@dnd-kit/collision": { - "version": "0.0.7-beta-20250130032138", - "resolved": "https://registry.npmjs.org/@dnd-kit/collision/-/collision-0.0.7-beta-20250130032138.tgz", - "integrity": "sha512-IlR3Qi1Fwc+DFDIdxHCcuolv7+l+M/0KAXdW5gd8m4y5sfW1do1pogAcRA6qNHcUvlUtbsdINf/CzFoztMJ5Bw==", - "license": "MIT", - "dependencies": { - "@dnd-kit/abstract": "0.0.7-beta-20250130032138", - "@dnd-kit/geometry": "0.0.7-beta-20250130032138", - "tslib": "^2.6.2" - } - }, - "node_modules/@dnd-kit/dom": { - "version": "0.0.7-beta-20250130032138", - "resolved": "https://registry.npmjs.org/@dnd-kit/dom/-/dom-0.0.7-beta-20250130032138.tgz", - "integrity": "sha512-LuySH+UkuvlNgMGDfRPe00cSbRMxyE9PbVNfUEmsnXUmltSW9ht5jVZrkQYAI8kZB3y7ry8h1fZtK/dOI3QFBg==", - "license": "MIT", - "dependencies": { - "@dnd-kit/abstract": "0.0.7-beta-20250130032138", - "@dnd-kit/collision": "0.0.7-beta-20250130032138", - "@dnd-kit/geometry": "0.0.7-beta-20250130032138", - "@dnd-kit/state": "0.0.7-beta-20250130032138", - "tslib": "^2.6.2" - } - }, - "node_modules/@dnd-kit/geometry": { - "version": "0.0.7-beta-20250130032138", - "resolved": "https://registry.npmjs.org/@dnd-kit/geometry/-/geometry-0.0.7-beta-20250130032138.tgz", - "integrity": "sha512-XfXqVq+LdMt5VKTjiyhvCcc68xRvD9xZilA52mdODN1qM3wh5ezpe17MyjlqefqH+sratpjmZwifHk2CONRWeg==", - "license": "MIT", - "dependencies": { - "@dnd-kit/state": "0.0.7-beta-20250130032138", - "tslib": "^2.6.2" - } - }, - "node_modules/@dnd-kit/helpers": { - "version": "0.0.7-beta-20250130032138", - "resolved": "https://registry.npmjs.org/@dnd-kit/helpers/-/helpers-0.0.7-beta-20250130032138.tgz", - "integrity": "sha512-KzZhgi3zoSJMSEwSL+6d/8GS2FfvY1s5wpIUPxEAgJM9ati+iU8uFL4T5qgxeTt8LVxobyFRzI5rVisUGgSICA==", - "license": "MIT", - "dependencies": { - "@dnd-kit/abstract": "0.0.7-beta-20250130032138", - "tslib": "^2.6.2" - } - }, - "node_modules/@dnd-kit/react": { - "version": "0.0.7-beta-20250130032138", - "resolved": "https://registry.npmjs.org/@dnd-kit/react/-/react-0.0.7-beta-20250130032138.tgz", - "integrity": "sha512-JsBHxVGoJlmFMt0mo6/7mwasrvq09Iz/uFUXcX/M7ob8UjdthNjL5y9vk8VGnJlka7yMHQxEGHJvQs6yh+FWow==", - "license": "MIT", - "dependencies": { - "@dnd-kit/abstract": "0.0.7-beta-20250130032138", - "@dnd-kit/dom": "0.0.7-beta-20250130032138", - "@dnd-kit/state": "0.0.7-beta-20250130032138", - "tslib": "^2.6.2" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0", - "react-dom": "^18.0.0 || ^19.0.0" - } - }, - "node_modules/@dnd-kit/state": { - "version": "0.0.7-beta-20250130032138", - "resolved": "https://registry.npmjs.org/@dnd-kit/state/-/state-0.0.7-beta-20250130032138.tgz", - "integrity": "sha512-xBW0LTG+jzXbdeha35uAlvZhO4M6F81fq7vpA54nodIu/RkBIB38NhnReMv/UrVRHxaZlwILDpW/3oHGu5h2jg==", - "license": "MIT", - "dependencies": { - "@preact/signals-core": "^1.6.0", - "tslib": "^2.6.2" - } - }, - "node_modules/@docsearch/css": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", - "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/@docsearch/js": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", - "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@docsearch/react": "3.8.2", - "preact": "^10.0.0" - } - }, - "node_modules/@docsearch/js/node_modules/@docsearch/react": { - "version": "3.8.2", - "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", - "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/autocomplete-core": "1.17.7", - "@algolia/autocomplete-preset-algolia": "1.17.7", - "@docsearch/css": "3.8.2", - "algoliasearch": "^5.14.2" - }, - "peerDependencies": { - "@types/react": ">= 16.8.0 < 19.0.0", - "react": ">= 16.8.0 < 19.0.0", - "react-dom": ">= 16.8.0 < 19.0.0", - "search-insights": ">= 1 < 3" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "search-insights": { - "optional": true - } - } - }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -1377,26 +1085,9 @@ "version": "0.2.11", "license": "MIT" }, - "node_modules/@iconify-json/simple-icons": { - "version": "1.2.75", - "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.75.tgz", - "integrity": "sha512-KvcCUbvcBWb0sbqLIxHoY8z5/piXY08wcY9gfMhF+ph3AfzGMaSmZFkUY71HSXAljQngXkgs4bdKdekO0HQWvg==", - "dev": true, - "license": "CC0-1.0", - "dependencies": { - "@iconify/types": "*" - } - }, - "node_modules/@iconify/types": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", - "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@inertiajs/core": { - "version": "2.3.18", - "license": "MIT", + "node_modules/@inertiajs/core": { + "version": "2.3.18", + "license": "MIT", "dependencies": { "@types/lodash-es": "^4.17.12", "axios": "^1.13.5", @@ -1459,26 +1150,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@measured/puck": { - "version": "0.18.3", - "resolved": "https://registry.npmjs.org/@measured/puck/-/puck-0.18.3.tgz", - "integrity": "sha512-mw78Z7Ykk4LbyM+8n3CPsjI41SyMg1dOfYoV3VSWJiZWkMj47x6DJS6SdwfaNSyseyPpcSx6sEbL6BaOBAVoHQ==", - "deprecated": "Puck has moved. Please use @puckeditor/core instead: https://www.npmjs.com/package/@puckeditor/core", - "license": "MIT", - "dependencies": { - "@dnd-kit/helpers": "0.0.7-beta-20250130032138", - "@dnd-kit/react": "0.0.7-beta-20250130032138", - "deep-diff": "^1.0.2", - "object-hash": "^3.0.0", - "react-hotkeys-hook": "^4.6.1", - "use-debounce": "^9.0.4", - "uuid": "^9.0.1", - "zustand": "^5.0.2" - }, - "peerDependencies": { - "react": "^18.0.0 || ^19.0.0" - } - }, "node_modules/@playwright/test": { "version": "1.58.2", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.58.2.tgz", @@ -1505,6 +1176,130 @@ "url": "https://opencollective.com/preact" } }, + "node_modules/@puckeditor/core": { + "version": "0.21.1", + "resolved": "https://registry.npmjs.org/@puckeditor/core/-/core-0.21.1.tgz", + "integrity": "sha512-Mk25WAHR8QLzHk+BtOO4bOu+y3pjzo0GowMHAJKNhrbxGVh3ETJx/bAZHaxA9dv8lKyuqK9a9AeRr7HYgg/sVw==", + "license": "MIT", + "dependencies": { + "@dnd-kit/helpers": "0.1.18", + "@dnd-kit/react": "0.1.18", + "@radix-ui/react-popover": "^1.1.15", + "@tiptap/core": "^3.11.1", + "@tiptap/extension-blockquote": "^3.11.1", + "@tiptap/extension-bold": "^3.11.1", + "@tiptap/extension-code": "^3.11.1", + "@tiptap/extension-code-block": "^3.11.1", + "@tiptap/extension-document": "^3.11.1", + "@tiptap/extension-hard-break": "^3.11.1", + "@tiptap/extension-heading": "^3.11.1", + "@tiptap/extension-horizontal-rule": "^3.11.1", + "@tiptap/extension-italic": "^3.11.1", + "@tiptap/extension-link": "^3.11.1", + "@tiptap/extension-list": "^3.11.1", + "@tiptap/extension-paragraph": "^3.11.1", + "@tiptap/extension-strike": "^3.11.1", + "@tiptap/extension-text": "^3.11.1", + "@tiptap/extension-text-align": "^3.11.1", + "@tiptap/extension-underline": "^3.11.1", + "@tiptap/html": "^3.11.1", + "@tiptap/pm": "^3.11.1", + "@tiptap/react": "^3.11.1", + "deep-diff": "^1.0.2", + "fast-equals": "5.2.2", + "flat": "^5.0.2", + "happy-dom": "^20.0.10", + "object-hash": "^3.0.0", + "react-hotkeys-hook": "^4.6.1", + "use-debounce": "^9.0.4", + "uuid": "^9.0.1", + "zustand": "^5.0.3" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@puckeditor/core/node_modules/@dnd-kit/abstract": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@dnd-kit/abstract/-/abstract-0.1.21.tgz", + "integrity": "sha512-6sJut6/D21xPIK8EFMu+JJeF+fBCOmQKN1BRpeUYFi5m9P1CJpTYbBwfI107h7PHObI6a5bsckiKkRpF2orHpw==", + "license": "MIT", + "dependencies": { + "@dnd-kit/geometry": "^0.1.21", + "@dnd-kit/state": "^0.1.21", + "tslib": "^2.6.2" + } + }, + "node_modules/@puckeditor/core/node_modules/@dnd-kit/collision": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@dnd-kit/collision/-/collision-0.1.21.tgz", + "integrity": "sha512-9AJ4NbuwGDexxMCZXZyKdNQhbAe93p6C6IezQaDaWmdCqZHMHmC3+ul7pGefBQfOooSarGwIf8Bn182o9SMa1A==", + "license": "MIT", + "dependencies": { + "@dnd-kit/abstract": "^0.1.21", + "@dnd-kit/geometry": "^0.1.21", + "tslib": "^2.6.2" + } + }, + "node_modules/@puckeditor/core/node_modules/@dnd-kit/dom": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@dnd-kit/dom/-/dom-0.1.21.tgz", + "integrity": "sha512-6UDc1y2Y3oLQKArGlgCrZxz5pdEjRSiQujXOn5JdbuWvKqTdUR5RTYDeicr+y2sVm3liXjTqs3WlUoV+eqhqUQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/abstract": "^0.1.21", + "@dnd-kit/collision": "^0.1.21", + "@dnd-kit/geometry": "^0.1.21", + "@dnd-kit/state": "^0.1.21", + "tslib": "^2.6.2" + } + }, + "node_modules/@puckeditor/core/node_modules/@dnd-kit/geometry": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@dnd-kit/geometry/-/geometry-0.1.21.tgz", + "integrity": "sha512-Tir97wNJbopN2HgkD7AjAcoB3vvrVuUHvwdPALmNDUH0fWR637c4MKQ66YjjZAbUEAR8KL6mlDiHH4MzTLd7CQ==", + "license": "MIT", + "dependencies": { + "@dnd-kit/state": "^0.1.21", + "tslib": "^2.6.2" + } + }, + "node_modules/@puckeditor/core/node_modules/@dnd-kit/helpers": { + "version": "0.1.18", + "resolved": "https://registry.npmjs.org/@dnd-kit/helpers/-/helpers-0.1.18.tgz", + "integrity": "sha512-k4hVXIb8ysPt+J0KOxbBTc6rG0JSlsrNevI/fCHLbyXvEyj1imxl7yOaAQX13cAZnte88db6JvbgsSWlVjtxbw==", + "license": "MIT", + "dependencies": { + "@dnd-kit/abstract": "^0.1.18", + "tslib": "^2.6.2" + } + }, + "node_modules/@puckeditor/core/node_modules/@dnd-kit/react": { + "version": "0.1.18", + "resolved": "https://registry.npmjs.org/@dnd-kit/react/-/react-0.1.18.tgz", + "integrity": "sha512-OCeCO9WbKnN4rVlEOEe9QWxSIFzP0m/fBFmVYfu2pDSb4pemRkfrvCsI/FH3jonuESYS8qYnN9vc8Vp3EiCWCA==", + "license": "MIT", + "dependencies": { + "@dnd-kit/abstract": "^0.1.18", + "@dnd-kit/dom": "^0.1.18", + "@dnd-kit/state": "^0.1.18", + "tslib": "^2.6.2" + }, + "peerDependencies": { + "react": "^18.0.0 || ^19.0.0", + "react-dom": "^18.0.0 || ^19.0.0" + } + }, + "node_modules/@puckeditor/core/node_modules/@dnd-kit/state": { + "version": "0.1.21", + "resolved": "https://registry.npmjs.org/@dnd-kit/state/-/state-0.1.21.tgz", + "integrity": "sha512-pdhntEPvn/QttcF295bOJpWiLsRqA/Iczh1ODOJUxGiR+E4GkYVz9VapNNm9gDq6ST0tr/e1Q2xBztUHlJqQgA==", + "license": "MIT", + "dependencies": { + "@preact/signals-core": "^1.10.0", + "tslib": "^2.6.2" + } + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "license": "MIT" @@ -2912,6 +2707,12 @@ "url": "https://opencollective.com/immer" } }, + "node_modules/@remirror/core-constants": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@remirror/core-constants/-/core-constants-3.0.0.tgz", + "integrity": "sha512-42aWfPrimMfDKDi4YegyS7x+/0tlzaqwPQCULLanv3DMIlu96KTJR0fM5isWX2UViOqlGnX6YFgqWepcX+XMNg==", + "license": "MIT" + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "dev": true, @@ -3236,95 +3037,8 @@ "win32" ] }, - "node_modules/@shikijs/core": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", - "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/engine-javascript": "2.5.0", - "@shikijs/engine-oniguruma": "2.5.0", - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4", - "hast-util-to-html": "^9.0.4" - } - }, - "node_modules/@shikijs/engine-javascript": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", - "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2", - "oniguruma-to-es": "^3.1.0" - } - }, - "node_modules/@shikijs/engine-oniguruma": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", - "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2" - } - }, - "node_modules/@shikijs/langs": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", - "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "2.5.0" - } - }, - "node_modules/@shikijs/themes": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", - "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/types": "2.5.0" - } - }, - "node_modules/@shikijs/transformers": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", - "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/core": "2.5.0", - "@shikijs/types": "2.5.0" - } - }, - "node_modules/@shikijs/types": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", - "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, - "node_modules/@shikijs/vscode-textmate": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", - "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", - "dev": true, - "license": "MIT" - }, "node_modules/@simplemodule/admin": { - "resolved": "modules/Admin/src/Admin", + "resolved": "modules/Admin/src/SimpleModule.Admin", "link": true }, "node_modules/@simplemodule/app": { @@ -3332,7 +3046,7 @@ "link": true }, "node_modules/@simplemodule/auditlogs": { - "resolved": "modules/AuditLogs/src/AuditLogs", + "resolved": "modules/AuditLogs/src/SimpleModule.AuditLogs", "link": true }, "node_modules/@simplemodule/client": { @@ -3340,31 +3054,35 @@ "link": true }, "node_modules/@simplemodule/dashboard": { - "resolved": "modules/Dashboard/src/Dashboard", + "resolved": "modules/Dashboard/src/SimpleModule.Dashboard", "link": true }, "node_modules/@simplemodule/e2e": { "resolved": "tests/e2e", "link": true }, + "node_modules/@simplemodule/filestorage": { + "resolved": "modules/FileStorage/src/SimpleModule.FileStorage", + "link": true + }, "node_modules/@simplemodule/openiddict": { - "resolved": "modules/OpenIddict/src/OpenIddict", + "resolved": "modules/OpenIddict/src/SimpleModule.OpenIddict", "link": true }, "node_modules/@simplemodule/orders": { - "resolved": "modules/Orders/src/Orders", + "resolved": "modules/Orders/src/SimpleModule.Orders", "link": true }, "node_modules/@simplemodule/pagebuilder": { - "resolved": "modules/PageBuilder/src/PageBuilder", + "resolved": "modules/PageBuilder/src/SimpleModule.PageBuilder", "link": true }, "node_modules/@simplemodule/products": { - "resolved": "modules/Products/src/Products", + "resolved": "modules/Products/src/SimpleModule.Products", "link": true }, "node_modules/@simplemodule/settings": { - "resolved": "modules/Settings/src/Settings", + "resolved": "modules/Settings/src/SimpleModule.Settings", "link": true }, "node_modules/@simplemodule/theme-default": { @@ -3376,7 +3094,7 @@ "link": true }, "node_modules/@simplemodule/users": { - "resolved": "modules/Users/src/Users", + "resolved": "modules/Users/src/SimpleModule.Users", "link": true }, "node_modules/@standard-schema/spec": { @@ -3662,125 +3380,457 @@ "vite": "^5.2.0 || ^6 || ^7" } }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "dev": true, + "node_modules/@tiptap/core": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/core/-/core-3.20.5.tgz", + "integrity": "sha512-Pkjd41UJ4F6Z8cPV+gEvqnt1VhY2g66xMjbpxREs0ECA5jRezCNKSZcc2pueQRTMtmn1SaSzGM9U/ifhVlVYOA==", "license": "MIT", - "dependencies": { - "@babel/parser": "^7.20.7", - "@babel/types": "^7.20.7", - "@types/babel__generator": "*", - "@types/babel__template": "*", - "@types/babel__traverse": "*" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/pm": "^3.20.5" } }, - "node_modules/@types/babel__generator": { - "version": "7.27.0", - "dev": true, + "node_modules/@tiptap/extension-blockquote": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-blockquote/-/extension-blockquote-3.20.5.tgz", + "integrity": "sha512-0wU6H/MWWes0rGzgSW6MMU6YDs/3ofUDkqmqCqmb+Siu1ZD0bpzOYpBtujgOYDY8moB9+zCE3G9HSYGcmZxHew==", "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5" } }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "dev": true, + "node_modules/@tiptap/extension-bold": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bold/-/extension-bold-3.20.5.tgz", + "integrity": "sha512-hraiiWkF58n8Jy0Wl3OGwjCTrGWwZZxez/IlexrzKQ/nMFdjDpensZucWwu59zhAM9fqZwGSLDtCFuak03WKnA==", "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5" } }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "dev": true, + "node_modules/@tiptap/extension-bubble-menu": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-bubble-menu/-/extension-bubble-menu-3.20.5.tgz", + "integrity": "sha512-6FsASu4o32bp3FzBVb5N2ERjrBy83DtJQAGv9/ycYqsgv2kq9DNlhvtNI7GPiTW7a73ZcImjIX+jEWrARbzOlQ==", "license": "MIT", + "optional": true, "dependencies": { - "@babel/types": "^7.28.2" + "@floating-ui/dom": "^1.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5", + "@tiptap/pm": "^3.20.5" } }, - "node_modules/@types/d3-array": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", - "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", - "license": "MIT" - }, - "node_modules/@types/d3-color": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", - "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", - "license": "MIT" - }, - "node_modules/@types/d3-ease": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", - "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", - "license": "MIT" - }, - "node_modules/@types/d3-interpolate": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", - "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "node_modules/@tiptap/extension-code": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code/-/extension-code-3.20.5.tgz", + "integrity": "sha512-jBZK/CfdMvg1gkNK/zNAk02IExpBPwUfNLRPiJvGhReL2Q73naKxZGQGp+5Lej9VaeFB70UKuRma/iIzuZbgsA==", "license": "MIT", - "dependencies": { - "@types/d3-color": "*" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5" } }, - "node_modules/@types/d3-path": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", - "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", - "license": "MIT" + "node_modules/@tiptap/extension-code-block": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-code-block/-/extension-code-block-3.20.5.tgz", + "integrity": "sha512-0YZnqfqZ1IjzKBM4aezw8j3LZWJFEfs4+mbizHNlnZSYpKzpESYLeaLWGO5SpqF9Z8tmYmSoCaf0fqi5LwgdIA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5", + "@tiptap/pm": "^3.20.5" + } }, - "node_modules/@types/d3-scale": { - "version": "4.0.9", - "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", - "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "node_modules/@tiptap/extension-document": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-document/-/extension-document-3.20.5.tgz", + "integrity": "sha512-BpNGHtOTAjjs/6QbkrafMTlaJqb0gsPngFzd5rB0csxx7rYRE9nIEY+oZ44qMw161+2YB4u20L17SX2mUJANBw==", "license": "MIT", - "dependencies": { - "@types/d3-time": "*" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5" } }, - "node_modules/@types/d3-shape": { - "version": "3.1.8", - "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", - "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "node_modules/@tiptap/extension-floating-menu": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-floating-menu/-/extension-floating-menu-3.20.5.tgz", + "integrity": "sha512-mTzBNUeAocinrxa5xV+5hGnnNCQB0pVI1GSBwUTHwdB7jNwBqfKAILmtLZONgmhxKWLmGa6WCA59sk+yDI+N0A==", "license": "MIT", - "dependencies": { - "@types/d3-path": "*" + "optional": true, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@floating-ui/dom": "^1.0.0", + "@tiptap/core": "^3.20.5", + "@tiptap/pm": "^3.20.5" } }, - "node_modules/@types/d3-time": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", - "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", - "license": "MIT" + "node_modules/@tiptap/extension-hard-break": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-hard-break/-/extension-hard-break-3.20.5.tgz", + "integrity": "sha512-+aILNDO7BsXf0IJ4/0BYh570usFK3Q1t/ZQd8zhHuO2ATeWeDVu1x2F+ouFS4X8fmoCcioMzw15aoz93GET6kQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5" + } }, - "node_modules/@types/d3-timer": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", - "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", - "license": "MIT" + "node_modules/@tiptap/extension-heading": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-heading/-/extension-heading-3.20.5.tgz", + "integrity": "sha512-zXxuIrCSpzgXzRxgCbRE8DZ/NFuinVaniE3pp/9LYAWgRlsAyko8pI2XrVvzzXmDQqRGi2HrNVkNy1yutUWSWQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5" + } }, - "node_modules/@types/estree": { - "version": "1.0.8", - "license": "MIT" + "node_modules/@tiptap/extension-horizontal-rule": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-horizontal-rule/-/extension-horizontal-rule-3.20.5.tgz", + "integrity": "sha512-4UtpUHg8cRzxWjJUGtni5VnXYbhsO7ygf1H1pr4Rv63XMBg9lfYDeSwByIuVy9biEFP7eGEFnezzb5Zlh1btmQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5", + "@tiptap/pm": "^3.20.5" + } }, - "node_modules/@types/hast": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", - "dev": true, + "node_modules/@tiptap/extension-italic": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-italic/-/extension-italic-3.20.5.tgz", + "integrity": "sha512-7bZCgdJVTvhR5vSmNgFQbGvgRoC6m26KcUpHqWiKA95kLL5Wk4YlMCIqdiDpvJ1eakeFEvDcGZvFLg5+1NiQ+w==", "license": "MIT", - "dependencies": { - "@types/unist": "*" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5" } }, + "node_modules/@tiptap/extension-link": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-link/-/extension-link-3.20.5.tgz", + "integrity": "sha512-0PukrSYnHX2CrGSThlKfQWxpPWmL7QAvdpDUraKknGvVNSH7tUjchTshy5JdLrn/SQAU92REowRCB6zzCNEFjA==", + "license": "MIT", + "dependencies": { + "linkifyjs": "^4.3.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5", + "@tiptap/pm": "^3.20.5" + } + }, + "node_modules/@tiptap/extension-list": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-list/-/extension-list-3.20.5.tgz", + "integrity": "sha512-s+Y8Q7Orq+WQiwgFB/VPMYZe+6EAR2F69xCpvOynlzTInLO4cF6QpXomuGEYAZxLHe8ZBmeIaR7y8MH/OgjrDw==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5", + "@tiptap/pm": "^3.20.5" + } + }, + "node_modules/@tiptap/extension-paragraph": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-paragraph/-/extension-paragraph-3.20.5.tgz", + "integrity": "sha512-mwuhwmff67IpGfOViyRvUC14IlkpsOnB+hSExVnq5+hCntjt/Cr2Z8GGOgzHeIM2FIS0UqX9Lv/b6ttUg4+Now==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5" + } + }, + "node_modules/@tiptap/extension-strike": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-strike/-/extension-strike-3.20.5.tgz", + "integrity": "sha512-uwhvmfS4ciGYJRLUg0AHbWsprMCwyWVWd2RXOLRm0ZQeWkvzonPXZhJvzIhIgsFkPLj/dsN5t0+LdiK4UQMnyA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5" + } + }, + "node_modules/@tiptap/extension-text": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text/-/extension-text-3.20.5.tgz", + "integrity": "sha512-DMa9g5cH2d/Gx1KXtV7txTxaa6FBqgG8glmfug+N93VMb8sEZR1Yu1az++yAep4SGGq9GWIGZCUS3H6W66et6Q==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5" + } + }, + "node_modules/@tiptap/extension-text-align": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-text-align/-/extension-text-align-3.20.5.tgz", + "integrity": "sha512-r7xIt6AE1Zf6epMg2GLAD55YIqJewunQnLUeApLkcWdTNuCJqZUrmVIXUvTtj2Ivw0VIaPoCtnB+VBvXEuC3Kg==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5" + } + }, + "node_modules/@tiptap/extension-underline": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/extension-underline/-/extension-underline-3.20.5.tgz", + "integrity": "sha512-HMhr5KIAqZsEhlN8RxKHr/ql1a8OvBa9fLf69IwUVFolBcDExHWUtaEV/axYVRQJvvIy2oKGJxlJWDZ4hkotHQ==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5" + } + }, + "node_modules/@tiptap/html": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/html/-/html-3.20.5.tgz", + "integrity": "sha512-IyUqfIVlYsMrbdI22AJDlzTWc/R8jMInjzav2Qrj+OPxarIW3f2b/knlI9FFgYauru3asplQmoRgx7gKnG7ruA==", + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5", + "@tiptap/pm": "^3.20.5", + "happy-dom": "^20.0.2" + } + }, + "node_modules/@tiptap/pm": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/pm/-/pm-3.20.5.tgz", + "integrity": "sha512-yJhDa7Chx2EqJMX/jlewBv0za7slf1dKHWYve1XaApuVHEkxl0Ul3EDbwnx316vIITkuFW/pWSwkSsAplyBeCw==", + "license": "MIT", + "dependencies": { + "prosemirror-changeset": "^2.3.0", + "prosemirror-collab": "^1.3.1", + "prosemirror-commands": "^1.6.2", + "prosemirror-dropcursor": "^1.8.1", + "prosemirror-gapcursor": "^1.3.2", + "prosemirror-history": "^1.4.1", + "prosemirror-inputrules": "^1.4.0", + "prosemirror-keymap": "^1.2.2", + "prosemirror-markdown": "^1.13.1", + "prosemirror-menu": "^1.2.4", + "prosemirror-model": "^1.24.1", + "prosemirror-schema-basic": "^1.2.3", + "prosemirror-schema-list": "^1.5.0", + "prosemirror-state": "^1.4.3", + "prosemirror-tables": "^1.6.4", + "prosemirror-trailing-node": "^3.0.0", + "prosemirror-transform": "^1.10.2", + "prosemirror-view": "^1.38.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + } + }, + "node_modules/@tiptap/react": { + "version": "3.20.5", + "resolved": "https://registry.npmjs.org/@tiptap/react/-/react-3.20.5.tgz", + "integrity": "sha512-in37o1Eo7JCflcHyK/SDfgkJBgX0LRN3LMk+NdLPTerRnC0zhGLQlpfBL4591TLTOUQde7QIrLv98smYO2mj+w==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "fast-equals": "^5.3.3", + "use-sync-external-store": "^1.4.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/ueberdosis" + }, + "optionalDependencies": { + "@tiptap/extension-bubble-menu": "^3.20.5", + "@tiptap/extension-floating-menu": "^3.20.5" + }, + "peerDependencies": { + "@tiptap/core": "^3.20.5", + "@tiptap/pm": "^3.20.5", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "@types/react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@tiptap/react/node_modules/fast-equals": { + "version": "5.4.0", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.4.0.tgz", + "integrity": "sha512-jt2DW/aNFNwke7AUd+Z+e6pz39KO5rzdbbFCg2sGafS4mk13MI7Z8O5z9cADNn5lhGODIgLwug6TZO2ctf7kcw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.27.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.28.0", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.28.2" + } + }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "license": "MIT" + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", "integrity": "sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q==", - "dev": true, "license": "MIT" }, "node_modules/@types/lodash": { @@ -3798,33 +3848,20 @@ "version": "14.1.2", "resolved": "https://registry.npmjs.org/@types/markdown-it/-/markdown-it-14.1.2.tgz", "integrity": "sha512-promo4eFwuiW+TfGxhi+0x3czqTYJkG8qB17ZUJiVF10Xm7NLVRSLUsfRTU/6h1e24VvRnXCx+hG7li58lkzog==", - "dev": true, "license": "MIT", "dependencies": { "@types/linkify-it": "^5", "@types/mdurl": "^2" } }, - "node_modules/@types/mdast": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", - "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "*" - } - }, "node_modules/@types/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg==", - "dev": true, "license": "MIT" }, "node_modules/@types/node": { "version": "25.5.0", - "devOptional": true, "license": "MIT", "dependencies": { "undici-types": "~7.18.0" @@ -3840,7 +3877,6 @@ }, "node_modules/@types/react": { "version": "19.2.14", - "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -3848,38 +3884,31 @@ }, "node_modules/@types/react-dom": { "version": "19.2.3", - "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" } }, - "node_modules/@types/unist": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", "license": "MIT" }, - "node_modules/@types/web-bluetooth": { - "version": "0.0.21", - "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", - "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", - "dev": true, + "node_modules/@types/whatwg-mimetype": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", + "integrity": "sha512-c2AKvDT8ToxLIOUlN51gTiHXflsfIFisS4pO7pDPoKouJCESkhZnEy623gwP9laCy5lnLDAw1vAzu2vM2YLOrA==", "license": "MIT" }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", - "dev": true, - "license": "ISC" + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", @@ -3900,342 +3929,57 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, - "node_modules/@vitejs/plugin-vue": { - "version": "5.2.4", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", - "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", - "dev": true, + "node_modules/ansi-regex": { + "version": "5.0.1", "license": "MIT", "engines": { - "node": "^18.0.0 || >=20.0.0" - }, - "peerDependencies": { - "vite": "^5.0.0 || ^6.0.0", - "vue": "^3.2.25" + "node": ">=8" } }, - "node_modules/@vue/compiler-core": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.31.tgz", - "integrity": "sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ==", - "dev": true, + "node_modules/ansi-styles": { + "version": "4.3.0", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.2", - "@vue/shared": "3.5.31", - "entities": "^7.0.1", - "estree-walker": "^2.0.2", - "source-map-js": "^1.2.1" + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "node_modules/@vue/compiler-dom": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.31.tgz", - "integrity": "sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/compiler-core": "3.5.31", - "@vue/shared": "3.5.31" - } + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.0" }, - "node_modules/@vue/compiler-sfc": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.31.tgz", - "integrity": "sha512-M8wpPgR9UJ8MiRGjppvx9uWJfLV7A/T+/rL8s/y3QG3u0c2/YZgff3d6SuimKRIhcYnWg5fTfDMlz2E6seUW8Q==", - "dev": true, + "node_modules/aria-hidden": { + "version": "1.2.6", "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.2", - "@vue/compiler-core": "3.5.31", - "@vue/compiler-dom": "3.5.31", - "@vue/compiler-ssr": "3.5.31", - "@vue/shared": "3.5.31", - "estree-walker": "^2.0.2", - "magic-string": "^0.30.21", - "postcss": "^8.5.8", - "source-map-js": "^1.2.1" + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" } }, - "node_modules/@vue/compiler-ssr": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.31.tgz", - "integrity": "sha512-h0xIMxrt/LHOvJKMri+vdYT92BrK3HFLtDqq9Pr/lVVfE4IyKZKvWf0vJFW10Yr6nX02OR4MkJwI0c1HDa1hog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.31", - "@vue/shared": "3.5.31" - } + "node_modules/asynckit": { + "version": "0.4.0", + "license": "MIT" }, - "node_modules/@vue/devtools-api": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", - "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", - "dev": true, + "node_modules/axios": { + "version": "1.13.6", "license": "MIT", "dependencies": { - "@vue/devtools-kit": "^7.7.9" + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^1.1.0" } }, - "node_modules/@vue/devtools-kit": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", - "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/devtools-shared": "^7.7.9", - "birpc": "^2.3.0", - "hookable": "^5.5.3", - "mitt": "^3.0.1", - "perfect-debounce": "^1.0.0", - "speakingurl": "^14.0.1", - "superjson": "^2.2.2" - } - }, - "node_modules/@vue/devtools-shared": { - "version": "7.7.9", - "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", - "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "rfdc": "^1.4.1" - } - }, - "node_modules/@vue/reactivity": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.31.tgz", - "integrity": "sha512-DtKXxk9E/KuVvt8VxWu+6Luc9I9ETNcqR1T1oW1gf02nXaZ1kuAx58oVu7uX9XxJR0iJCro6fqBLw9oSBELo5g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/shared": "3.5.31" - } - }, - "node_modules/@vue/runtime-core": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.31.tgz", - "integrity": "sha512-AZPmIHXEAyhpkmN7aWlqjSfYynmkWlluDNPHMCZKFHH+lLtxP/30UJmoVhXmbDoP1Ng0jG0fyY2zCj1PnSSA6Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.31", - "@vue/shared": "3.5.31" - } - }, - "node_modules/@vue/runtime-dom": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.31.tgz", - "integrity": "sha512-xQJsNRmGPeDCJq/u813tyonNgWBFjzfVkBwDREdEWndBnGdHLHgkwNBQxLtg4zDrzKTEcnikUy1UUNecb3lJ6g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/reactivity": "3.5.31", - "@vue/runtime-core": "3.5.31", - "@vue/shared": "3.5.31", - "csstype": "^3.2.3" - } - }, - "node_modules/@vue/server-renderer": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.31.tgz", - "integrity": "sha512-GJuwRvMcdZX/CriUnyIIOGkx3rMV3H6sOu0JhdKbduaeCji6zb60iOGMY7tFoN24NfsUYoFBhshZtGxGpxO4iA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vue/compiler-ssr": "3.5.31", - "@vue/shared": "3.5.31" - }, - "peerDependencies": { - "vue": "3.5.31" - } - }, - "node_modules/@vue/shared": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.31.tgz", - "integrity": "sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@vueuse/core": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", - "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/web-bluetooth": "^0.0.21", - "@vueuse/metadata": "12.8.2", - "@vueuse/shared": "12.8.2", - "vue": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/integrations": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", - "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@vueuse/core": "12.8.2", - "@vueuse/shared": "12.8.2", - "vue": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - }, - "peerDependencies": { - "async-validator": "^4", - "axios": "^1", - "change-case": "^5", - "drauu": "^0.4", - "focus-trap": "^7", - "fuse.js": "^7", - "idb-keyval": "^6", - "jwt-decode": "^4", - "nprogress": "^0.2", - "qrcode": "^1.5", - "sortablejs": "^1", - "universal-cookie": "^7" - }, - "peerDependenciesMeta": { - "async-validator": { - "optional": true - }, - "axios": { - "optional": true - }, - "change-case": { - "optional": true - }, - "drauu": { - "optional": true - }, - "focus-trap": { - "optional": true - }, - "fuse.js": { - "optional": true - }, - "idb-keyval": { - "optional": true - }, - "jwt-decode": { - "optional": true - }, - "nprogress": { - "optional": true - }, - "qrcode": { - "optional": true - }, - "sortablejs": { - "optional": true - }, - "universal-cookie": { - "optional": true - } - } - }, - "node_modules/@vueuse/metadata": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", - "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/@vueuse/shared": { - "version": "12.8.2", - "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", - "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", - "dev": true, - "license": "MIT", - "dependencies": { - "vue": "^3.5.13" - }, - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, - "node_modules/algoliasearch": { - "version": "5.50.0", - "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.50.0.tgz", - "integrity": "sha512-yE5I83Q2s8euVou8Y3feXK08wyZInJWLYXgWO6Xti9jBUEZAGUahyeQ7wSZWkifLWVnQVKEz5RAmBlXG5nqxog==", - "dev": true, - "license": "MIT", - "dependencies": { - "@algolia/abtesting": "1.16.0", - "@algolia/client-abtesting": "5.50.0", - "@algolia/client-analytics": "5.50.0", - "@algolia/client-common": "5.50.0", - "@algolia/client-insights": "5.50.0", - "@algolia/client-personalization": "5.50.0", - "@algolia/client-query-suggestions": "5.50.0", - "@algolia/client-search": "5.50.0", - "@algolia/ingestion": "1.50.0", - "@algolia/monitoring": "1.50.0", - "@algolia/recommend": "5.50.0", - "@algolia/requester-browser-xhr": "5.50.0", - "@algolia/requester-fetch": "5.50.0", - "@algolia/requester-node-http": "5.50.0" - }, - "engines": { - "node": ">= 14.0.0" - } - }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/aria-hidden": { - "version": "1.2.6", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/asynckit": { - "version": "0.4.0", - "license": "MIT" - }, - "node_modules/axios": { - "version": "1.13.6", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.15.11", - "form-data": "^4.0.5", - "proxy-from-env": "^1.1.0" - } - }, - "node_modules/baseline-browser-mapping": { - "version": "2.10.8", + "node_modules/baseline-browser-mapping": { + "version": "2.10.8", "dev": true, "license": "Apache-2.0", "bin": { @@ -4245,16 +3989,6 @@ "node": ">=6.0.0" } }, - "node_modules/birpc": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", - "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", - "dev": true, - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/antfu" - } - }, "node_modules/browserslist": { "version": "4.28.1", "dev": true, @@ -4338,39 +4072,6 @@ ], "license": "CC-BY-4.0" }, - "node_modules/ccount": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", - "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-html4": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", - "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/character-entities-legacy": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", - "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/class-variance-authority": { "version": "0.7.1", "license": "Apache-2.0", @@ -4435,37 +4136,16 @@ "node": ">= 0.8" } }, - "node_modules/comma-separated-tokens": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", - "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/convert-source-map": { "version": "2.0.0", "dev": true, "license": "MIT" }, - "node_modules/copy-anything": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", - "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-what": "^5.2.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } + "node_modules/crelt": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", + "integrity": "sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==", + "license": "MIT" }, "node_modules/cross-env": { "version": "7.0.3", @@ -4503,7 +4183,6 @@ }, "node_modules/csstype": { "version": "3.2.3", - "devOptional": true, "license": "MIT" }, "node_modules/d3-array": { @@ -4686,16 +4365,6 @@ "node": ">=0.4.0" } }, - "node_modules/dequal": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", - "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/detect-libc": { "version": "2.1.2", "devOptional": true, @@ -4708,28 +4377,10 @@ "version": "1.1.0", "license": "MIT" }, - "node_modules/devlop": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", - "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", - "dev": true, - "license": "MIT", - "dependencies": { - "dequal": "^2.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/dijkstrajs": { "version": "1.0.3", "license": "MIT" }, - "node_modules/docs": { - "resolved": "docs/site", - "link": true - }, "node_modules/dunder-proto": { "version": "1.0.1", "license": "MIT", @@ -4751,13 +4402,6 @@ "version": "8.0.0", "license": "MIT" }, - "node_modules/emoji-regex-xs": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", - "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", - "dev": true, - "license": "MIT" - }, "node_modules/enhanced-resolve": { "version": "5.20.0", "dev": true, @@ -4774,7 +4418,6 @@ "version": "7.0.1", "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", - "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=0.12" @@ -4877,12 +4520,17 @@ "node": ">=6" } }, - "node_modules/estree-walker": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", - "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", - "dev": true, - "license": "MIT" + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, "node_modules/eventemitter3": { "version": "5.0.4", @@ -4890,6 +4538,15 @@ "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", "license": "MIT" }, + "node_modules/fast-equals": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/fast-equals/-/fast-equals-5.2.2.tgz", + "integrity": "sha512-V7/RktU11J3I36Nwq2JnZEM7tNm17eBJz+u25qdxBZeCKiX6BkVSZQjwWIr+IobgnZy+ag73tTZgZi7tr0LrBw==", + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/fdir": { "version": "6.5.0", "license": "MIT", @@ -4916,14 +4573,13 @@ "node": ">=8" } }, - "node_modules/focus-trap": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", - "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", - "dev": true, - "license": "MIT", - "dependencies": { - "tabbable": "^6.4.0" + "node_modules/flat": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz", + "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==", + "license": "BSD-3-Clause", + "bin": { + "flat": "cli.js" } }, "node_modules/follow-redirects": { @@ -5049,6 +4705,23 @@ "dev": true, "license": "ISC" }, + "node_modules/happy-dom": { + "version": "20.8.8", + "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-20.8.8.tgz", + "integrity": "sha512-5/F8wxkNxYtsN0bXfMwIyNLZ9WYsoOYPbmoluqVJqv8KBUbcyKZawJ7uYK4WTX8IHBLYv+VXIwfeNDPy1oKMwQ==", + "license": "MIT", + "dependencies": { + "@types/node": ">=20.0.0", + "@types/whatwg-mimetype": "^3.0.2", + "@types/ws": "^8.18.1", + "entities": "^7.0.1", + "whatwg-mimetype": "^3.0.0", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/has-symbols": { "version": "1.1.0", "license": "MIT", @@ -5082,79 +4755,23 @@ "node": ">= 0.4" } }, - "node_modules/hast-util-to-html": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", - "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", - "dev": true, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0", - "@types/unist": "^3.0.0", - "ccount": "^2.0.0", - "comma-separated-tokens": "^2.0.0", - "hast-util-whitespace": "^3.0.0", - "html-void-elements": "^3.0.0", - "mdast-util-to-hast": "^13.0.0", - "property-information": "^7.0.0", - "space-separated-tokens": "^2.0.0", - "stringify-entities": "^4.0.0", - "zwitch": "^2.0.4" - }, "funding": { "type": "opencollective", - "url": "https://opencollective.com/unified" + "url": "https://opencollective.com/immer" } }, - "node_modules/hast-util-whitespace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", - "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/hast": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/hookable": { - "version": "5.5.3", - "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", - "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/html-void-elements": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", - "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/immer": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", - "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/immer" - } - }, - "node_modules/internmap": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", - "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", - "license": "ISC", - "engines": { - "node": ">=12" + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" } }, "node_modules/is-fullwidth-code-point": { @@ -5164,19 +4781,6 @@ "node": ">=8" } }, - "node_modules/is-what": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", - "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/mesqueeb" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5471,6 +5075,21 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "license": "MIT", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, + "node_modules/linkifyjs": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/linkifyjs/-/linkifyjs-4.3.2.tgz", + "integrity": "sha512-NT1CJtq3hHIreOianA8aSXn6Cw0JzYOuDQbOrSPe7gqFnCpKP++MQe3ODgO3oh2GJFORkAAdqredOa60z63GbA==", + "license": "MIT" + }, "node_modules/locate-path": { "version": "5.0.0", "license": "MIT", @@ -5501,134 +5120,46 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, - "node_modules/mark.js": { - "version": "8.11.1", - "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", - "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", - "dev": true, - "license": "MIT" - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/mdast-util-to-hast": { - "version": "13.2.1", - "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", - "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", - "dev": true, + "node_modules/markdown-it": { + "version": "14.1.1", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", + "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==", "license": "MIT", "dependencies": { - "@types/hast": "^3.0.0", - "@types/mdast": "^4.0.0", - "@ungap/structured-clone": "^1.0.0", - "devlop": "^1.0.0", - "micromark-util-sanitize-uri": "^2.0.0", - "trim-lines": "^3.0.0", - "unist-util-position": "^5.0.0", - "unist-util-visit": "^5.0.0", - "vfile": "^6.0.0" + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" + "bin": { + "markdown-it": "bin/markdown-it.mjs" } }, - "node_modules/micromark-util-character": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", - "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT", - "dependencies": { - "micromark-util-symbol": "^2.0.0", - "micromark-util-types": "^2.0.0" + "node_modules/markdown-it/node_modules/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/micromark-util-encode": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", - "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-sanitize-uri": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", - "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/math-intrinsics": { + "version": "1.1.0", "license": "MIT", - "dependencies": { - "micromark-util-character": "^2.0.0", - "micromark-util-encode": "^2.0.0", - "micromark-util-symbol": "^2.0.0" + "engines": { + "node": ">= 0.4" } }, - "node_modules/micromark-util-symbol": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", - "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], - "license": "MIT" - }, - "node_modules/micromark-util-types": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", - "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", - "dev": true, - "funding": [ - { - "type": "GitHub Sponsors", - "url": "https://github.com/sponsors/unifiedjs" - }, - { - "type": "OpenCollective", - "url": "https://opencollective.com/unified" - } - ], + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "license": "MIT" }, "node_modules/mime-db": { @@ -5648,20 +5179,6 @@ "node": ">= 0.6" } }, - "node_modules/minisearch": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", - "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", - "dev": true, - "license": "MIT" - }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "dev": true, - "license": "MIT" - }, "node_modules/ms": { "version": "2.1.3", "dev": true, @@ -5707,17 +5224,11 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/oniguruma-to-es": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", - "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "emoji-regex-xs": "^1.0.0", - "regex": "^6.0.1", - "regex-recursion": "^6.0.2" - } + "node_modules/orderedmap": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", + "integrity": "sha512-TvAWxi0nDe1j/rtMcWcIj94+Ffe6n7zhow33h40SKxmsmozs6dz/e+EajymfoFcHd7sxNn8yHM8839uixMOV6g==", + "license": "MIT" }, "node_modules/p-limit": { "version": "2.3.0", @@ -5766,13 +5277,6 @@ "node": ">=8" } }, - "node_modules/perfect-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", - "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", - "dev": true, - "license": "MIT" - }, "node_modules/picocolors": { "version": "1.1.1", "license": "ISC" @@ -5867,32 +5371,214 @@ "node": "^10 || ^12 || >=14" } }, - "node_modules/preact": { - "version": "10.29.0", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.0.tgz", - "integrity": "sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg==", - "dev": true, + "node_modules/prosemirror-changeset": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz", + "integrity": "sha512-LvqH2v7Q2SF6yxatuPP2e8vSUKS/L+xAU7dPDC4RMyHMhZoGDfBC74mYuyYF4gLqOEG758wajtyhNnsTkuhvng==", "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" + "dependencies": { + "prosemirror-transform": "^1.0.0" } }, - "node_modules/property-information": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", - "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", - "dev": true, + "node_modules/prosemirror-collab": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/prosemirror-collab/-/prosemirror-collab-1.3.1.tgz", + "integrity": "sha512-4SnynYR9TTYaQVXd/ieUvsVV4PDMBzrq2xPUWutHivDuOshZXqQ5rGbZM84HEaXKbLdItse7weMGOUdDVcLKEQ==", "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" + "dependencies": { + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-commands": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/prosemirror-commands/-/prosemirror-commands-1.7.1.tgz", + "integrity": "sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.10.2" + } + }, + "node_modules/prosemirror-dropcursor": { + "version": "1.8.2", + "resolved": "https://registry.npmjs.org/prosemirror-dropcursor/-/prosemirror-dropcursor-1.8.2.tgz", + "integrity": "sha512-CCk6Gyx9+Tt2sbYk5NK0nB1ukHi2ryaRgadV/LvyNuO3ena1payM2z6Cg0vO1ebK8cxbzo41ku2DE5Axj1Zuiw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0", + "prosemirror-view": "^1.1.0" + } + }, + "node_modules/prosemirror-gapcursor": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/prosemirror-gapcursor/-/prosemirror-gapcursor-1.4.1.tgz", + "integrity": "sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.0.0", + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-view": "^1.0.0" + } + }, + "node_modules/prosemirror-history": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/prosemirror-history/-/prosemirror-history-1.5.0.tgz", + "integrity": "sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.2.2", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.31.0", + "rope-sequence": "^1.3.0" + } + }, + "node_modules/prosemirror-inputrules": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-inputrules/-/prosemirror-inputrules-1.5.1.tgz", + "integrity": "sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.0.0" + } + }, + "node_modules/prosemirror-keymap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/prosemirror-keymap/-/prosemirror-keymap-1.2.3.tgz", + "integrity": "sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==", + "license": "MIT", + "dependencies": { + "prosemirror-state": "^1.0.0", + "w3c-keyname": "^2.2.0" + } + }, + "node_modules/prosemirror-markdown": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/prosemirror-markdown/-/prosemirror-markdown-1.13.4.tgz", + "integrity": "sha512-D98dm4cQ3Hs6EmjK500TdAOew4Z03EV71ajEFiWra3Upr7diytJsjF4mPV2dW+eK5uNectiRj0xFxYI9NLXDbw==", + "license": "MIT", + "dependencies": { + "@types/markdown-it": "^14.0.0", + "markdown-it": "^14.0.0", + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-menu": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/prosemirror-menu/-/prosemirror-menu-1.3.0.tgz", + "integrity": "sha512-TImyPXCHPcDsSka2/lwJ6WjTASr4re/qWq1yoTTuLOqfXucwF6VcRa2LWCkM/EyTD1UO3CUwiH8qURJoWJRxwg==", + "license": "MIT", + "dependencies": { + "crelt": "^1.0.0", + "prosemirror-commands": "^1.0.0", + "prosemirror-history": "^1.0.0", + "prosemirror-state": "^1.0.0" + } + }, + "node_modules/prosemirror-model": { + "version": "1.25.4", + "resolved": "https://registry.npmjs.org/prosemirror-model/-/prosemirror-model-1.25.4.tgz", + "integrity": "sha512-PIM7E43PBxKce8OQeezAs9j4TP+5yDpZVbuurd1h5phUxEKIu+G2a+EUZzIC5nS1mJktDJWzbqS23n1tsAf5QA==", + "license": "MIT", + "dependencies": { + "orderedmap": "^2.0.0" + } + }, + "node_modules/prosemirror-schema-basic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/prosemirror-schema-basic/-/prosemirror-schema-basic-1.2.4.tgz", + "integrity": "sha512-ELxP4TlX3yr2v5rM7Sb70SqStq5NvI15c0j9j/gjsrO5vaw+fnnpovCLEGIcpeGfifkuqJwl4fon6b+KdrODYQ==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.25.0" + } + }, + "node_modules/prosemirror-schema-list": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/prosemirror-schema-list/-/prosemirror-schema-list-1.5.1.tgz", + "integrity": "sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.7.3" + } + }, + "node_modules/prosemirror-state": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/prosemirror-state/-/prosemirror-state-1.4.4.tgz", + "integrity": "sha512-6jiYHH2CIGbCfnxdHbXZ12gySFY/fz/ulZE333G6bPqIZ4F+TXo9ifiR86nAHpWnfoNjOb3o5ESi7J8Uz1jXHw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.0.0", + "prosemirror-transform": "^1.0.0", + "prosemirror-view": "^1.27.0" + } + }, + "node_modules/prosemirror-tables": { + "version": "1.8.5", + "resolved": "https://registry.npmjs.org/prosemirror-tables/-/prosemirror-tables-1.8.5.tgz", + "integrity": "sha512-V/0cDCsHKHe/tfWkeCmthNUcEp1IVO3p6vwN8XtwE9PZQLAZJigbw3QoraAdfJPir4NKJtNvOB8oYGKRl+t0Dw==", + "license": "MIT", + "dependencies": { + "prosemirror-keymap": "^1.2.3", + "prosemirror-model": "^1.25.4", + "prosemirror-state": "^1.4.4", + "prosemirror-transform": "^1.10.5", + "prosemirror-view": "^1.41.4" + } + }, + "node_modules/prosemirror-trailing-node": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/prosemirror-trailing-node/-/prosemirror-trailing-node-3.0.0.tgz", + "integrity": "sha512-xiun5/3q0w5eRnGYfNlW1uU9W6x5MoFKWwq/0TIRgt09lv7Hcser2QYV8t4muXbEr+Fwo0geYn79Xs4GKywrRQ==", + "license": "MIT", + "dependencies": { + "@remirror/core-constants": "3.0.0", + "escape-string-regexp": "^4.0.0" + }, + "peerDependencies": { + "prosemirror-model": "^1.22.1", + "prosemirror-state": "^1.4.2", + "prosemirror-view": "^1.33.8" + } + }, + "node_modules/prosemirror-transform": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/prosemirror-transform/-/prosemirror-transform-1.11.0.tgz", + "integrity": "sha512-4I7Ce4KpygXb9bkiPS3hTEk4dSHorfRw8uI0pE8IhxlK2GXsqv5tIA7JUSxtSu7u8APVOTtbUBxTmnHIxVkIJw==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.21.0" + } + }, + "node_modules/prosemirror-view": { + "version": "1.41.7", + "resolved": "https://registry.npmjs.org/prosemirror-view/-/prosemirror-view-1.41.7.tgz", + "integrity": "sha512-jUwKNCEIGiqdvhlS91/2QAg21e4dfU5bH2iwmSDQeosXJgKF7smG0YSplOWK0cjSNgIqXe7VXqo7EIfUFJdt3w==", + "license": "MIT", + "dependencies": { + "prosemirror-model": "^1.20.0", + "prosemirror-state": "^1.0.0", + "prosemirror-transform": "^1.1.0" } }, "node_modules/proxy-from-env": { "version": "1.1.0", "license": "MIT" }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qrcode": { "version": "1.5.4", "license": "MIT", @@ -6116,33 +5802,6 @@ "redux": "^5.0.0" } }, - "node_modules/regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", - "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", - "dev": true, - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-recursion": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", - "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", - "dev": true, - "license": "MIT", - "dependencies": { - "regex-utilities": "^2.3.0" - } - }, - "node_modules/regex-utilities": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", - "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", - "dev": true, - "license": "MIT" - }, "node_modules/require-directory": { "version": "2.1.1", "license": "MIT", @@ -6160,13 +5819,6 @@ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", "license": "MIT" }, - "node_modules/rfdc": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", - "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", - "dev": true, - "license": "MIT" - }, "node_modules/rollup": { "version": "4.59.0", "license": "MIT", @@ -6209,18 +5861,16 @@ "fsevents": "~2.3.2" } }, + "node_modules/rope-sequence": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/rope-sequence/-/rope-sequence-1.3.4.tgz", + "integrity": "sha512-UT5EDe2cu2E/6O4igUr5PSFs23nvvukicWHx6GnOPlHAiiYbzNuCRQCuiUdHJQcqKalLKlrYJnjY0ySGsXNQXQ==", + "license": "MIT" + }, "node_modules/scheduler": { "version": "0.27.0", "license": "MIT" }, - "node_modules/search-insights": { - "version": "2.17.3", - "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", - "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", - "dev": true, - "license": "MIT", - "peer": true - }, "node_modules/semver": { "version": "6.3.1", "dev": true, @@ -6256,23 +5906,6 @@ "node": ">=8" } }, - "node_modules/shiki": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", - "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@shikijs/core": "2.5.0", - "@shikijs/engine-javascript": "2.5.0", - "@shikijs/engine-oniguruma": "2.5.0", - "@shikijs/langs": "2.5.0", - "@shikijs/themes": "2.5.0", - "@shikijs/types": "2.5.0", - "@shikijs/vscode-textmate": "^10.0.2", - "@types/hast": "^3.0.4" - } - }, "node_modules/side-channel": { "version": "1.1.0", "license": "MIT", @@ -6344,29 +5977,8 @@ "node": ">=0.10.0" } }, - "node_modules/space-separated-tokens": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", - "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", + "node_modules/string-width": { + "version": "4.2.3", "license": "MIT", "dependencies": { "emoji-regex": "^8.0.0", @@ -6377,21 +5989,6 @@ "node": ">=8" } }, - "node_modules/stringify-entities": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", - "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", - "dev": true, - "license": "MIT", - "dependencies": { - "character-entities-html4": "^2.0.0", - "character-entities-legacy": "^3.0.0" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "node_modules/strip-ansi": { "version": "6.0.1", "license": "MIT", @@ -6402,26 +5999,6 @@ "node": ">=8" } }, - "node_modules/superjson": { - "version": "2.2.6", - "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", - "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", - "dev": true, - "license": "MIT", - "dependencies": { - "copy-anything": "^4" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/tabbable": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", - "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", - "dev": true, - "license": "MIT" - }, "node_modules/tailwind-merge": { "version": "3.5.0", "license": "MIT", @@ -6466,823 +6043,170 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, - "node_modules/trim-lines": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", - "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, - "node_modules/tslib": { - "version": "2.8.1", - "license": "0BSD" - }, - "node_modules/typescript": { - "version": "5.9.3", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/undici-types": { - "version": "7.18.2", - "devOptional": true, - "license": "MIT" - }, - "node_modules/unist-util-is": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", - "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-position": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", - "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-stringify-position": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", - "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", - "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0", - "unist-util-visit-parents": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/unist-util-visit-parents": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", - "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-is": "^6.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/update-browserslist-db": { - "version": "1.2.3", - "dev": true, - "funding": [ - { - "type": "opencollective", - "url": "https://opencollective.com/browserslist" - }, - { - "type": "tidelift", - "url": "https://tidelift.com/funding/github/npm/browserslist" - }, - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "dependencies": { - "escalade": "^3.2.0", - "picocolors": "^1.1.1" - }, - "bin": { - "update-browserslist-db": "cli.js" - }, - "peerDependencies": { - "browserslist": ">= 4.21.0" - } - }, - "node_modules/use-callback-ref": { - "version": "1.3.3", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - }, - "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 - } - } - }, - "node_modules/use-debounce": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-9.0.4.tgz", - "integrity": "sha512-6X8H/mikbrt0XE8e+JXRtZ8yYVvKkdYRfmIhWZYsP8rcNs9hk3APV8Ua2mFkKRLcJKVdnX2/Vwrmg2GWKUQEaQ==", - "license": "MIT", - "engines": { - "node": ">= 10.0.0" - }, - "peerDependencies": { - "react": ">=16.8.0" - } - }, - "node_modules/use-sidecar": { - "version": "1.1.3", - "license": "MIT", - "dependencies": { - "detect-node-es": "^1.1.0", - "tslib": "^2.0.0" - }, - "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 - } - } - }, - "node_modules/use-sync-external-store": { - "version": "1.6.0", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/uuid": { - "version": "9.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", - "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/bin/uuid" - } - }, - "node_modules/vfile": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", - "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "vfile-message": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/vfile-message": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", - "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/unist": "^3.0.0", - "unist-util-stringify-position": "^4.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/unified" - } - }, - "node_modules/victory-vendor": { - "version": "37.3.6", - "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", - "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", - "license": "MIT AND ISC", - "dependencies": { - "@types/d3-array": "^3.0.3", - "@types/d3-ease": "^3.0.0", - "@types/d3-interpolate": "^3.0.1", - "@types/d3-scale": "^4.0.2", - "@types/d3-shape": "^3.1.0", - "@types/d3-time": "^3.0.0", - "@types/d3-timer": "^3.0.0", - "d3-array": "^3.1.6", - "d3-ease": "^3.0.1", - "d3-interpolate": "^3.0.1", - "d3-scale": "^4.0.2", - "d3-shape": "^3.1.0", - "d3-time": "^3.0.0", - "d3-timer": "^3.0.1" - } - }, - "node_modules/vite": { - "version": "6.4.1", - "license": "MIT", - "dependencies": { - "esbuild": "^0.25.0", - "fdir": "^6.4.4", - "picomatch": "^4.0.2", - "postcss": "^8.5.3", - "rollup": "^4.34.9", - "tinyglobby": "^0.2.13" - }, - "bin": { - "vite": "bin/vite.js" - }, - "engines": { - "node": "^18.0.0 || ^20.0.0 || >=22.0.0" - }, - "funding": { - "url": "https://github.com/vitejs/vite?sponsor=1" - }, - "optionalDependencies": { - "fsevents": "~2.3.3" - }, - "peerDependencies": { - "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", - "jiti": ">=1.21.0", - "less": "*", - "lightningcss": "^1.21.0", - "sass": "*", - "sass-embedded": "*", - "stylus": "*", - "sugarss": "*", - "terser": "^5.16.0", - "tsx": "^4.8.1", - "yaml": "^2.4.2" - }, - "peerDependenciesMeta": { - "@types/node": { - "optional": true - }, - "jiti": { - "optional": true - }, - "less": { - "optional": true - }, - "lightningcss": { - "optional": true - }, - "sass": { - "optional": true - }, - "sass-embedded": { - "optional": true - }, - "stylus": { - "optional": true - }, - "sugarss": { - "optional": true - }, - "terser": { - "optional": true - }, - "tsx": { - "optional": true - }, - "yaml": { - "optional": true - } - } - }, - "node_modules/vitepress": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", - "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@docsearch/css": "3.8.2", - "@docsearch/js": "3.8.2", - "@iconify-json/simple-icons": "^1.2.21", - "@shikijs/core": "^2.1.0", - "@shikijs/transformers": "^2.1.0", - "@shikijs/types": "^2.1.0", - "@types/markdown-it": "^14.1.2", - "@vitejs/plugin-vue": "^5.2.1", - "@vue/devtools-api": "^7.7.0", - "@vue/shared": "^3.5.13", - "@vueuse/core": "^12.4.0", - "@vueuse/integrations": "^12.4.0", - "focus-trap": "^7.6.4", - "mark.js": "8.11.1", - "minisearch": "^7.1.1", - "shiki": "^2.1.0", - "vite": "^5.4.14", - "vue": "^3.5.13" - }, - "bin": { - "vitepress": "bin/vitepress.js" - }, - "peerDependencies": { - "markdown-it-mathjax3": "^4", - "postcss": "^8" - }, - "peerDependenciesMeta": { - "markdown-it-mathjax3": { - "optional": true - }, - "postcss": { - "optional": true - } - } - }, - "node_modules/vitepress/node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/vitepress/node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], + "node_modules/tslib": { + "version": "2.8.1", + "license": "0BSD" + }, + "node_modules/typescript": { + "version": "5.9.3", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, "engines": { - "node": ">=12" + "node": ">=14.17" } }, - "node_modules/vitepress/node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==", + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "7.18.2", + "license": "MIT" + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } ], - "engines": { - "node": ">=12" + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" } }, - "node_modules/vitepress/node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/use-callback-ref": { + "version": "1.3.3", "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], + "dependencies": { + "tslib": "^2.0.0" + }, "engines": { - "node": ">=12" + "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 + } } }, - "node_modules/vitepress/node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/use-debounce": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-9.0.4.tgz", + "integrity": "sha512-6X8H/mikbrt0XE8e+JXRtZ8yYVvKkdYRfmIhWZYsP8rcNs9hk3APV8Ua2mFkKRLcJKVdnX2/Vwrmg2GWKUQEaQ==", "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], "engines": { - "node": ">=12" + "node": ">= 10.0.0" + }, + "peerDependencies": { + "react": ">=16.8.0" } }, - "node_modules/vitepress/node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, + "node_modules/use-sidecar": { + "version": "1.1.3", "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, "engines": { - "node": ">=12" + "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 + } } }, - "node_modules/vitepress/node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, + "node_modules/use-sync-external-store": { + "version": "1.6.0", "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, - "node_modules/vitepress/node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" + "node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" ], - "dev": true, "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "bin": { + "uuid": "dist/bin/uuid" } }, - "node_modules/vitepress/node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" } }, - "node_modules/vitepress/node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=12" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" - } - }, - "node_modules/vitepress/node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", - "dev": true, + "node_modules/vite": { + "version": "6.4.1", "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -7291,19 +6215,25 @@ "fsevents": "~2.3.3" }, "peerDependencies": { - "@types/node": "^18.0.0 || >=20.0.0", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", - "terser": "^5.4.0" + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "jiti": { + "optional": true + }, "less": { "optional": true }, @@ -7324,29 +6254,28 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, - "node_modules/vue": { - "version": "3.5.31", - "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.31.tgz", - "integrity": "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==", - "dev": true, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", "license": "MIT", - "dependencies": { - "@vue/compiler-dom": "3.5.31", - "@vue/compiler-sfc": "3.5.31", - "@vue/runtime-dom": "3.5.31", - "@vue/server-renderer": "3.5.31", - "@vue/shared": "3.5.31" - }, - "peerDependencies": { - "typescript": "*" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": ">=12" } }, "node_modules/which": { @@ -7381,6 +6310,27 @@ "node": ">=8" } }, + "node_modules/ws": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", + "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/y18n": { "version": "4.0.3", "license": "ISC" @@ -7450,17 +6400,6 @@ } } }, - "node_modules/zwitch": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", - "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", - "dev": true, - "license": "MIT", - "funding": { - "type": "github", - "url": "https://github.com/sponsors/wooorm" - } - }, "packages/SimpleModule.Client": { "name": "@simplemodule/client", "version": "0.0.0", From 4afd3870462cd97062f1c39afba9c6efe4d295c4 Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Fri, 27 Mar 2026 23:47:26 +0100 Subject: [PATCH 6/7] fix: regenerate package-lock.json to include docs/site workspace --- package-lock.json | 2061 ++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 2021 insertions(+), 40 deletions(-) diff --git a/package-lock.json b/package-lock.json index 33fc3045..4e248d50 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,8 @@ "packages/SimpleModule.Theme.Default", "packages/SimpleModule.UI", "template/SimpleModule.Host/ClientApp", - "tests/e2e" + "tests/e2e", + "docs/site" ], "dependencies": { "tailwindcss": "^4.2.1" @@ -29,6 +30,12 @@ "vite": "^6.2.0" } }, + "docs/site": { + "name": "docs", + "devDependencies": { + "vitepress": "^1.6.4" + } + }, "modules/Admin/src/Admin": { "name": "@simplemodule/admin", "version": "0.0.0", @@ -217,6 +224,264 @@ "react-dom": "^19.0.0" } }, + "node_modules/@algolia/abtesting": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/@algolia/abtesting/-/abtesting-1.16.0.tgz", + "integrity": "sha512-alHFZ68/i9qLC/muEB07VQ9r7cB8AvCcGX6dVQi2PNHhc/ZQRmmFAv8KK1ay4UiseGSFr7f0nXBKsZ/jRg7e4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/autocomplete-core": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-core/-/autocomplete-core-1.17.7.tgz", + "integrity": "sha512-BjiPOW6ks90UKl7TwMv7oNQMnzU+t/wk9mgIDi6b1tXpUek7MW0lbNOUHpvam9pe3lVCf4xPFT+lK7s+e+fs7Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-plugin-algolia-insights": "1.17.7", + "@algolia/autocomplete-shared": "1.17.7" + } + }, + "node_modules/@algolia/autocomplete-plugin-algolia-insights": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.7.tgz", + "integrity": "sha512-Jca5Ude6yUOuyzjnz57og7Et3aXjbwCSDf/8onLHSQgw1qW3ALl9mrMWaXb5FmPVkV3EtkD2F/+NkT6VHyPu9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "search-insights": ">= 1 < 3" + } + }, + "node_modules/@algolia/autocomplete-preset-algolia": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.7.tgz", + "integrity": "sha512-ggOQ950+nwbWROq2MOCIL71RE0DdQZsceqrg32UqnhDz8FlO9rL8ONHNsI2R1MH0tkgVIDKI/D0sMiUchsFdWA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-shared": "1.17.7" + }, + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/autocomplete-shared": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.7.tgz", + "integrity": "sha512-o/1Vurr42U/qskRSuhBH+VKxMvkkUVTLU6WZQr+L5lGZZLYWyhdzWjW0iGXY7EkwRTjBqvN2EsR81yCTGV/kmg==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "@algolia/client-search": ">= 4.9.1 < 6", + "algoliasearch": ">= 4.9.1 < 6" + } + }, + "node_modules/@algolia/client-abtesting": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/client-abtesting/-/client-abtesting-5.50.0.tgz", + "integrity": "sha512-mfgUdLQNxOAvCZUGzPQxjahEWEPuQkKlV0ZtGmePOa9ZxIQZlk31vRBNbM6ScU8jTH41SCYE77G/lCifDr1SVw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-analytics": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/client-analytics/-/client-analytics-5.50.0.tgz", + "integrity": "sha512-5mjokeKYyPaP3Q8IYJEnutI+O4dW/Ixxx5IgsSxT04pCfGqPXxTOH311hTQxyNpcGGEOGrMv8n8Z+UMTPamioQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-common": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/client-common/-/client-common-5.50.0.tgz", + "integrity": "sha512-emtOvR6dl3rX3sBJXXbofMNHU1qMQqQSWu319RMrNL5BWoBqyiq7y0Zn6cjJm7aGHV/Qbf+KCCYeWNKEMPI3BQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-insights": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/client-insights/-/client-insights-5.50.0.tgz", + "integrity": "sha512-IerGH2/hcj/6bwkpQg/HHRqmlGN1XwygQWythAk0gZFBrghs9danJaYuSS3ShzLSVoIVth4jY5GDPX9Lbw5cgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-personalization": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/client-personalization/-/client-personalization-5.50.0.tgz", + "integrity": "sha512-3idPJeXn5L0MmgP9jk9JJqblrQ/SguN93dNK9z9gfgyupBhHnJMOEjrRYcVgTIfvG13Y04wO+Q0FxE2Ut8PVbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-query-suggestions": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/client-query-suggestions/-/client-query-suggestions-5.50.0.tgz", + "integrity": "sha512-q7qRoWrQK1a8m5EFQEmPlo7+pg9mVQ8X5jsChtChERre0uS2pdYEDixBBl0ydBSGkdGbLUDufcACIhH/077E4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/client-search": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/client-search/-/client-search-5.50.0.tgz", + "integrity": "sha512-Jc360x4yqb3eEg4OY4KEIdGePBxZogivKI+OGIU8aLXgAYPTECvzeOBc90312yHA1hr3AeRlAFl0rIc8lQaIrQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/ingestion": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/@algolia/ingestion/-/ingestion-1.50.0.tgz", + "integrity": "sha512-OS3/Viao+NPpyBbEY3tf6hLewppG+UclD+9i0ju56mq2DrdMJFCkEky6Sk9S5VPcbLzxzg3BqBX6u9Q35w19aQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/monitoring": { + "version": "1.50.0", + "resolved": "https://registry.npmjs.org/@algolia/monitoring/-/monitoring-1.50.0.tgz", + "integrity": "sha512-/znwgSiGufpbJVIoDmeQaHtTq+OMdDawFRbMSJVv+12n79hW+qdQXS8/Uu3BD3yn0BzgVFJEvrsHrCsInZKdhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/recommend": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/recommend/-/recommend-5.50.0.tgz", + "integrity": "sha512-dHjUfu4jfjdQiKDpCpAnM7LP5yfG0oNShtfpF5rMCel6/4HIoqJ4DC4h5GKDzgrvJYtgAhblo0AYBmOM00T+lQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-browser-xhr": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.50.0.tgz", + "integrity": "sha512-bffIbUljAWnh/Ctu5uScORajuUavqmZ0ACYd1fQQeSSYA9NNN83ynO26pSc2dZRXpSK0fkc1//qSSFXMKGu+aw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-fetch": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-fetch/-/requester-fetch-5.50.0.tgz", + "integrity": "sha512-y0EwNvPGvkM+yTAqqO6Gpt9wVGm3CLDtpLvNEiB3VGvN3WzfkjZGtLUsG/ru2kVJIIU7QcV0puuYgEpBeFxcJg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, + "node_modules/@algolia/requester-node-http": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/@algolia/requester-node-http/-/requester-node-http-5.50.0.tgz", + "integrity": "sha512-xpwefe4fCOWnZgXCbkGpqQY6jgBSCf2hmgnySbyzZIccrv3SoashHKGPE4x6vVG+gdHrGciMTAcDo9HOZwH22Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/client-common": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/@babel/code-frame": { "version": "7.29.0", "dev": true, @@ -378,7 +643,9 @@ } }, "node_modules/@babel/parser": { - "version": "7.29.0", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "dev": true, "license": "MIT", "dependencies": { @@ -624,6 +891,57 @@ "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==", "license": "MIT" }, + "node_modules/@docsearch/css": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/css/-/css-3.8.2.tgz", + "integrity": "sha512-y05ayQFyUmCXze79+56v/4HpycYF3uFqB78pLPrSV5ZKAlDuIAAJNhaRi8tTdRNXh05yxX/TyNnzD6LwSM89vQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/@docsearch/js": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/js/-/js-3.8.2.tgz", + "integrity": "sha512-Q5wY66qHn0SwA7Taa0aDbHiJvaFJLOJyHmooQ7y8hlwwQLQ/5WwCcoX0g7ii04Qi2DJlHsd0XXzJ8Ypw9+9YmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@docsearch/react": "3.8.2", + "preact": "^10.0.0" + } + }, + "node_modules/@docsearch/js/node_modules/@docsearch/react": { + "version": "3.8.2", + "resolved": "https://registry.npmjs.org/@docsearch/react/-/react-3.8.2.tgz", + "integrity": "sha512-xCRrJQlTt8N9GU0DG4ptwHRkfnSnD/YpdeaXe02iKfqs97TkZJv60yE+1eq/tjPcVnTW8dP5qLP7itifFVV5eg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/autocomplete-core": "1.17.7", + "@algolia/autocomplete-preset-algolia": "1.17.7", + "@docsearch/css": "3.8.2", + "algoliasearch": "^5.14.2" + }, + "peerDependencies": { + "@types/react": ">= 16.8.0 < 19.0.0", + "react": ">= 16.8.0 < 19.0.0", + "react-dom": ">= 16.8.0 < 19.0.0", + "search-insights": ">= 1 < 3" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "search-insights": { + "optional": true + } + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -1085,6 +1403,23 @@ "version": "0.2.11", "license": "MIT" }, + "node_modules/@iconify-json/simple-icons": { + "version": "1.2.75", + "resolved": "https://registry.npmjs.org/@iconify-json/simple-icons/-/simple-icons-1.2.75.tgz", + "integrity": "sha512-KvcCUbvcBWb0sbqLIxHoY8z5/piXY08wcY9gfMhF+ph3AfzGMaSmZFkUY71HSXAljQngXkgs4bdKdekO0HQWvg==", + "dev": true, + "license": "CC0-1.0", + "dependencies": { + "@iconify/types": "*" + } + }, + "node_modules/@iconify/types": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@iconify/types/-/types-2.0.0.tgz", + "integrity": "sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==", + "dev": true, + "license": "MIT" + }, "node_modules/@inertiajs/core": { "version": "2.3.18", "license": "MIT", @@ -3037,6 +3372,93 @@ "win32" ] }, + "node_modules/@shikijs/core": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/core/-/core-2.5.0.tgz", + "integrity": "sha512-uu/8RExTKtavlpH7XqnVYBrfBkUc20ngXiX9NSrBhOVZYv/7XQRKUyhtkeflY5QsxC0GbJThCerruZfsUaSldg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4", + "hast-util-to-html": "^9.0.4" + } + }, + "node_modules/@shikijs/engine-javascript": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-2.5.0.tgz", + "integrity": "sha512-VjnOpnQf8WuCEZtNUdjjwGUbtAVKuZkVQ/5cHy/tojVVRIRtlWMYVjyWhxOmIq05AlSOv72z7hRNRGVBgQOl0w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "oniguruma-to-es": "^3.1.0" + } + }, + "node_modules/@shikijs/engine-oniguruma": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-2.5.0.tgz", + "integrity": "sha512-pGd1wRATzbo/uatrCIILlAdFVKdxImWJGQ5rFiB5VZi2ve5xj3Ax9jny8QvkaV93btQEwR/rSz5ERFpC5mKNIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2" + } + }, + "node_modules/@shikijs/langs": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-2.5.0.tgz", + "integrity": "sha512-Qfrrt5OsNH5R+5tJ/3uYBBZv3SuGmnRPejV9IlIbFH3HTGLDlkqgHymAlzklVmKBjAaVmkPkyikAV/sQ1wSL+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/themes": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-2.5.0.tgz", + "integrity": "sha512-wGrk+R8tJnO0VMzmUExHR+QdSaPUl/NKs+a4cQQRWyoc3YFbUzuLEi/KWK1hj+8BfHRKm2jNhhJck1dfstJpiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/transformers": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/transformers/-/transformers-2.5.0.tgz", + "integrity": "sha512-SI494W5X60CaUwgi8u4q4m4s3YAFSxln3tzNjOSYqq54wlVgz0/NbbXEb3mdLbqMBztcmS7bVTaEd2w0qMmfeg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/types": "2.5.0" + } + }, + "node_modules/@shikijs/types": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@shikijs/types/-/types-2.5.0.tgz", + "integrity": "sha512-ygl5yhxki9ZLNuNpPitBWvcy9fsSKKaRuO4BAlMyagszQidxcpLAr0qiW/q43DtSIDxO6hEbtYLiFZNXO/hdGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, + "node_modules/@shikijs/vscode-textmate": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz", + "integrity": "sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg==", + "dev": true, + "license": "MIT" + }, "node_modules/@simplemodule/admin": { "resolved": "modules/Admin/src/SimpleModule.Admin", "link": true @@ -3333,6 +3755,70 @@ "node": ">=14.0.0" } }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": { + "version": "1.8.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.1.0", + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": { + "version": "1.8.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": { + "version": "1.1.0", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@tybys/wasm-util": "^0.10.1" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "dev": true, + "inBundle": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": { + "version": "2.8.1", + "dev": true, + "inBundle": true, + "license": "0BSD", + "optional": true + }, "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.1.tgz", @@ -3827,6 +4313,16 @@ "version": "1.0.8", "license": "MIT" }, + "node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/linkify-it": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/@types/linkify-it/-/linkify-it-5.0.0.tgz", @@ -3854,6 +4350,16 @@ "@types/mdurl": "^2" } }, + "node_modules/@types/mdast": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", + "integrity": "sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/@types/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/mdurl/-/mdurl-2.0.0.tgz", @@ -3889,12 +4395,26 @@ "@types/react": "^19.2.0" } }, + "node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/use-sync-external-store": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", "license": "MIT" }, + "node_modules/@types/web-bluetooth": { + "version": "0.0.21", + "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", + "integrity": "sha512-oIQLCGWtcFZy2JW77j9k8nHzAOpqMHLQejDA48XXMWH6tjCQHz5RCFz1bzsmROyL6PUm+LLnUiI4BCn221inxA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/whatwg-mimetype": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@types/whatwg-mimetype/-/whatwg-mimetype-3.0.2.tgz", @@ -3910,6 +4430,13 @@ "@types/node": "*" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", + "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "dev": true, + "license": "ISC" + }, "node_modules/@vitejs/plugin-react": { "version": "4.7.0", "dev": true, @@ -3929,6 +4456,297 @@ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.31.tgz", + "integrity": "sha512-k/ueL14aNIEy5Onf0OVzR8kiqF/WThgLdFhxwa4e/KF/0qe38IwIdofoSWBTvvxQOesaz6riAFAUaYjoF9fLLQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/shared": "3.5.31", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.31.tgz", + "integrity": "sha512-BMY/ozS/xxjYqRFL+tKdRpATJYDTTgWSo0+AJvJNg4ig+Hgb0dOsHPXvloHQ5hmlivUqw1Yt2pPIqp4e0v1GUw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.31", + "@vue/shared": "3.5.31" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.31.tgz", + "integrity": "sha512-M8wpPgR9UJ8MiRGjppvx9uWJfLV7A/T+/rL8s/y3QG3u0c2/YZgff3d6SuimKRIhcYnWg5fTfDMlz2E6seUW8Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.2", + "@vue/compiler-core": "3.5.31", + "@vue/compiler-dom": "3.5.31", + "@vue/compiler-ssr": "3.5.31", + "@vue/shared": "3.5.31", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.8", + "source-map-js": "^1.2.1" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.31.tgz", + "integrity": "sha512-h0xIMxrt/LHOvJKMri+vdYT92BrK3HFLtDqq9Pr/lVVfE4IyKZKvWf0vJFW10Yr6nX02OR4MkJwI0c1HDa1hog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.31", + "@vue/shared": "3.5.31" + } + }, + "node_modules/@vue/devtools-api": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-7.7.9.tgz", + "integrity": "sha512-kIE8wvwlcZ6TJTbNeU2HQNtaxLx3a84aotTITUuL/4bzfPxzajGBOoqjMhwZJ8L9qFYDU/lAYMEEm11dnZOD6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-kit": "^7.7.9" + } + }, + "node_modules/@vue/devtools-kit": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-kit/-/devtools-kit-7.7.9.tgz", + "integrity": "sha512-PyQ6odHSgiDVd4hnTP+aDk2X4gl2HmLDfiyEnn3/oV+ckFDuswRs4IbBT7vacMuGdwY/XemxBoh302ctbsptuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/devtools-shared": "^7.7.9", + "birpc": "^2.3.0", + "hookable": "^5.5.3", + "mitt": "^3.0.1", + "perfect-debounce": "^1.0.0", + "speakingurl": "^14.0.1", + "superjson": "^2.2.2" + } + }, + "node_modules/@vue/devtools-shared": { + "version": "7.7.9", + "resolved": "https://registry.npmjs.org/@vue/devtools-shared/-/devtools-shared-7.7.9.tgz", + "integrity": "sha512-iWAb0v2WYf0QWmxCGy0seZNDPdO3Sp5+u78ORnyeonS6MT4PC7VPrryX2BpMJrwlDeaZ6BD4vP4XKjK0SZqaeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "rfdc": "^1.4.1" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.31.tgz", + "integrity": "sha512-DtKXxk9E/KuVvt8VxWu+6Luc9I9ETNcqR1T1oW1gf02nXaZ1kuAx58oVu7uX9XxJR0iJCro6fqBLw9oSBELo5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.31" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.31.tgz", + "integrity": "sha512-AZPmIHXEAyhpkmN7aWlqjSfYynmkWlluDNPHMCZKFHH+lLtxP/30UJmoVhXmbDoP1Ng0jG0fyY2zCj1PnSSA6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.31", + "@vue/shared": "3.5.31" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.31.tgz", + "integrity": "sha512-xQJsNRmGPeDCJq/u813tyonNgWBFjzfVkBwDREdEWndBnGdHLHgkwNBQxLtg4zDrzKTEcnikUy1UUNecb3lJ6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.31", + "@vue/runtime-core": "3.5.31", + "@vue/shared": "3.5.31", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.31.tgz", + "integrity": "sha512-GJuwRvMcdZX/CriUnyIIOGkx3rMV3H6sOu0JhdKbduaeCji6zb60iOGMY7tFoN24NfsUYoFBhshZtGxGpxO4iA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.31", + "@vue/shared": "3.5.31" + }, + "peerDependencies": { + "vue": "3.5.31" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.31.tgz", + "integrity": "sha512-nBxuiuS9Lj5bPkPbWogPUnjxxWpkRniX7e5UBQDWl6Fsf4roq9wwV+cR7ezQ4zXswNvPIlsdj1slcLB7XCsRAw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vueuse/core": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-12.8.2.tgz", + "integrity": "sha512-HbvCmZdzAu3VGi/pWYm5Ut+Kd9mn1ZHnn4L5G8kOQTPs/IwIAmJoBrmYk2ckLArgMXZj0AW3n5CAejLUO+PhdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/web-bluetooth": "^0.0.21", + "@vueuse/metadata": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/integrations": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/integrations/-/integrations-12.8.2.tgz", + "integrity": "sha512-fbGYivgK5uBTRt7p5F3zy6VrETlV9RtZjBqd1/HxGdjdckBgBM4ugP8LHpjolqTj14TXTxSK1ZfgPbHYyGuH7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vueuse/core": "12.8.2", + "@vueuse/shared": "12.8.2", + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "async-validator": "^4", + "axios": "^1", + "change-case": "^5", + "drauu": "^0.4", + "focus-trap": "^7", + "fuse.js": "^7", + "idb-keyval": "^6", + "jwt-decode": "^4", + "nprogress": "^0.2", + "qrcode": "^1.5", + "sortablejs": "^1", + "universal-cookie": "^7" + }, + "peerDependenciesMeta": { + "async-validator": { + "optional": true + }, + "axios": { + "optional": true + }, + "change-case": { + "optional": true + }, + "drauu": { + "optional": true + }, + "focus-trap": { + "optional": true + }, + "fuse.js": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "jwt-decode": { + "optional": true + }, + "nprogress": { + "optional": true + }, + "qrcode": { + "optional": true + }, + "sortablejs": { + "optional": true + }, + "universal-cookie": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-12.8.2.tgz", + "integrity": "sha512-rAyLGEuoBJ/Il5AmFHiziCPdQzRt88VxR+Y/A/QhJ1EWtWqPBBAxTAFaSkviwEuOEZNtW8pvkPgoCZQ+HxqW1A==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared": { + "version": "12.8.2", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-12.8.2.tgz", + "integrity": "sha512-dznP38YzxZoNloI0qpEfpkms8knDtaoQ6Y/sfS0L7Yki4zh40LFHEhur0odJC6xTHG5dxWVPiUWBXn+wCG2s5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "vue": "^3.5.13" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/algoliasearch": { + "version": "5.50.0", + "resolved": "https://registry.npmjs.org/algoliasearch/-/algoliasearch-5.50.0.tgz", + "integrity": "sha512-yE5I83Q2s8euVou8Y3feXK08wyZInJWLYXgWO6Xti9jBUEZAGUahyeQ7wSZWkifLWVnQVKEz5RAmBlXG5nqxog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@algolia/abtesting": "1.16.0", + "@algolia/client-abtesting": "5.50.0", + "@algolia/client-analytics": "5.50.0", + "@algolia/client-common": "5.50.0", + "@algolia/client-insights": "5.50.0", + "@algolia/client-personalization": "5.50.0", + "@algolia/client-query-suggestions": "5.50.0", + "@algolia/client-search": "5.50.0", + "@algolia/ingestion": "1.50.0", + "@algolia/monitoring": "1.50.0", + "@algolia/recommend": "5.50.0", + "@algolia/requester-browser-xhr": "5.50.0", + "@algolia/requester-fetch": "5.50.0", + "@algolia/requester-node-http": "5.50.0" + }, + "engines": { + "node": ">= 14.0.0" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "license": "MIT", @@ -3989,6 +4807,16 @@ "node": ">=6.0.0" } }, + "node_modules/birpc": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/birpc/-/birpc-2.9.0.tgz", + "integrity": "sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/browserslist": { "version": "4.28.1", "dev": true, @@ -4072,6 +4900,39 @@ ], "license": "CC-BY-4.0" }, + "node_modules/ccount": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/ccount/-/ccount-2.0.1.tgz", + "integrity": "sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-html4": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/character-entities-html4/-/character-entities-html4-2.1.0.tgz", + "integrity": "sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/character-entities-legacy": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz", + "integrity": "sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/class-variance-authority": { "version": "0.7.1", "license": "Apache-2.0", @@ -4136,11 +4997,38 @@ "node": ">= 0.8" } }, + "node_modules/comma-separated-tokens": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", + "integrity": "sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/convert-source-map": { "version": "2.0.0", "dev": true, "license": "MIT" }, + "node_modules/copy-anything": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/copy-anything/-/copy-anything-4.0.5.tgz", + "integrity": "sha512-7Vv6asjS4gMOuILabD3l739tsaxFQmC+a7pLZm02zyvs8p977bL3zEgq3yDk5rn9B0PbYgIv++jmHcuUab4RhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-what": "^5.2.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/crelt": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/crelt/-/crelt-1.0.6.tgz", @@ -4365,6 +5253,16 @@ "node": ">=0.4.0" } }, + "node_modules/dequal": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/dequal/-/dequal-2.0.3.tgz", + "integrity": "sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "devOptional": true, @@ -4377,10 +5275,28 @@ "version": "1.1.0", "license": "MIT" }, + "node_modules/devlop": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/devlop/-/devlop-1.1.0.tgz", + "integrity": "sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA==", + "dev": true, + "license": "MIT", + "dependencies": { + "dequal": "^2.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/dijkstrajs": { "version": "1.0.3", "license": "MIT" }, + "node_modules/docs": { + "resolved": "docs/site", + "link": true + }, "node_modules/dunder-proto": { "version": "1.0.1", "license": "MIT", @@ -4402,6 +5318,13 @@ "version": "8.0.0", "license": "MIT" }, + "node_modules/emoji-regex-xs": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz", + "integrity": "sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg==", + "dev": true, + "license": "MIT" + }, "node_modules/enhanced-resolve": { "version": "5.20.0", "dev": true, @@ -4532,6 +5455,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "dev": true, + "license": "MIT" + }, "node_modules/eventemitter3": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", @@ -4582,6 +5512,16 @@ "flat": "cli.js" } }, + "node_modules/focus-trap": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/focus-trap/-/focus-trap-7.8.0.tgz", + "integrity": "sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tabbable": "^6.4.0" + } + }, "node_modules/follow-redirects": { "version": "1.15.11", "funding": [ @@ -4755,6 +5695,62 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-to-html": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz", + "integrity": "sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "ccount": "^2.0.0", + "comma-separated-tokens": "^2.0.0", + "hast-util-whitespace": "^3.0.0", + "html-void-elements": "^3.0.0", + "mdast-util-to-hast": "^13.0.0", + "property-information": "^7.0.0", + "space-separated-tokens": "^2.0.0", + "stringify-entities": "^4.0.0", + "zwitch": "^2.0.4" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-whitespace": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", + "integrity": "sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hookable": { + "version": "5.5.3", + "resolved": "https://registry.npmjs.org/hookable/-/hookable-5.5.3.tgz", + "integrity": "sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/html-void-elements": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-3.0.0.tgz", + "integrity": "sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/immer": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", @@ -4781,6 +5777,19 @@ "node": ">=8" } }, + "node_modules/is-what": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/is-what/-/is-what-5.5.0.tgz", + "integrity": "sha512-oG7cgbmg5kLYae2N5IVd3jm2s+vldjxJzK1pcu9LfpGuQ93MQSzo0okvRna+7y5ifrD+20FE8FvjusyGaz14fw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/mesqueeb" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -5120,6 +6129,13 @@ "@jridgewell/sourcemap-codec": "^1.5.5" } }, + "node_modules/mark.js": { + "version": "8.11.1", + "resolved": "https://registry.npmjs.org/mark.js/-/mark.js-8.11.1.tgz", + "integrity": "sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ==", + "dev": true, + "license": "MIT" + }, "node_modules/markdown-it": { "version": "14.1.1", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz", @@ -5156,17 +6172,133 @@ "node": ">= 0.4" } }, + "node_modules/mdast-util-to-hast": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz", + "integrity": "sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "@ungap/structured-clone": "^1.0.0", + "devlop": "^1.0.0", + "micromark-util-sanitize-uri": "^2.0.0", + "trim-lines": "^3.0.0", + "unist-util-position": "^5.0.0", + "unist-util-visit": "^5.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==", "license": "MIT" }, - "node_modules/mime-db": { - "version": "1.52.0", - "license": "MIT", - "engines": { - "node": ">= 0.6" + "node_modules/micromark-util-character": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/micromark-util-character/-/micromark-util-character-2.1.1.tgz", + "integrity": "sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + } + }, + "node_modules/micromark-util-encode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz", + "integrity": "sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-sanitize-uri": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz", + "integrity": "sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT", + "dependencies": { + "micromark-util-character": "^2.0.0", + "micromark-util-encode": "^2.0.0", + "micromark-util-symbol": "^2.0.0" + } + }, + "node_modules/micromark-util-symbol": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz", + "integrity": "sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/micromark-util-types": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/micromark-util-types/-/micromark-util-types-2.0.2.tgz", + "integrity": "sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA==", + "dev": true, + "funding": [ + { + "type": "GitHub Sponsors", + "url": "https://github.com/sponsors/unifiedjs" + }, + { + "type": "OpenCollective", + "url": "https://opencollective.com/unified" + } + ], + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.52.0", + "license": "MIT", + "engines": { + "node": ">= 0.6" } }, "node_modules/mime-types": { @@ -5179,6 +6311,20 @@ "node": ">= 0.6" } }, + "node_modules/minisearch": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/minisearch/-/minisearch-7.2.0.tgz", + "integrity": "sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg==", + "dev": true, + "license": "MIT" + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "dev": true, + "license": "MIT" + }, "node_modules/ms": { "version": "2.1.3", "dev": true, @@ -5224,6 +6370,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/oniguruma-to-es": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/oniguruma-to-es/-/oniguruma-to-es-3.1.1.tgz", + "integrity": "sha512-bUH8SDvPkH3ho3dvwJwfonjlQ4R80vjyvrU8YpxuROddv55vAEJrTuCuCVUhhsHbtlD9tGGbaNApGQckXhS8iQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex-xs": "^1.0.0", + "regex": "^6.0.1", + "regex-recursion": "^6.0.2" + } + }, "node_modules/orderedmap": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/orderedmap/-/orderedmap-2.1.1.tgz", @@ -5277,6 +6435,13 @@ "node": ">=8" } }, + "node_modules/perfect-debounce": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", + "integrity": "sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==", + "dev": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "license": "ISC" @@ -5371,6 +6536,28 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/preact": { + "version": "10.29.0", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.29.0.tgz", + "integrity": "sha512-wSAGyk2bYR1c7t3SZ3jHcM6xy0lcBcDel6lODcs9ME6Th++Dx2KU+6D3HD8wMMKGA8Wpw7OMd3/4RGzYRpzwRg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/property-information": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/property-information/-/property-information-7.1.0.tgz", + "integrity": "sha512-TwEZ+X+yCJmYfL7TPUOcvBZ4QfoT5YenQiJuX//0th53DE6w0xxLEtfK3iyryQFddXuvkIk51EEgrJQ0WJkOmQ==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/prosemirror-changeset": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/prosemirror-changeset/-/prosemirror-changeset-2.4.0.tgz", @@ -5802,6 +6989,33 @@ "redux": "^5.0.0" } }, + "node_modules/regex": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/regex/-/regex-6.1.0.tgz", + "integrity": "sha512-6VwtthbV4o/7+OaAF9I5L5V3llLEsoPyq9P1JVXkedTP33c7MfCG0/5NOPcSJn0TzXcG9YUrR0gQSWioew3LDg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-recursion": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/regex-recursion/-/regex-recursion-6.0.2.tgz", + "integrity": "sha512-0YCaSCq2VRIebiaUviZNs0cBz1kg5kVS2UKUfNIx8YVs1cN3AV7NTctO5FOKBA+UT2BPJIWZauYHPqJODG50cg==", + "dev": true, + "license": "MIT", + "dependencies": { + "regex-utilities": "^2.3.0" + } + }, + "node_modules/regex-utilities": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/regex-utilities/-/regex-utilities-2.3.0.tgz", + "integrity": "sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng==", + "dev": true, + "license": "MIT" + }, "node_modules/require-directory": { "version": "2.1.1", "license": "MIT", @@ -5819,6 +7033,13 @@ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", "license": "MIT" }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rollup": { "version": "4.59.0", "license": "MIT", @@ -5871,6 +7092,14 @@ "version": "0.27.0", "license": "MIT" }, + "node_modules/search-insights": { + "version": "2.17.3", + "resolved": "https://registry.npmjs.org/search-insights/-/search-insights-2.17.3.tgz", + "integrity": "sha512-RQPdCYTa8A68uM2jwxoY842xDhvx3E5LFL1LxvxCNMev4o5mLuokczhzjAgGwUZBAmOKZknArSxLKmXtIi2AxQ==", + "dev": true, + "license": "MIT", + "peer": true + }, "node_modules/semver": { "version": "6.3.1", "dev": true, @@ -5906,6 +7135,23 @@ "node": ">=8" } }, + "node_modules/shiki": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/shiki/-/shiki-2.5.0.tgz", + "integrity": "sha512-mI//trrsaiCIPsja5CNfsyNOqgAZUb6VpJA+340toL42UpzQlXpwRV9nch69X6gaUxrr9kaOOa6e3y3uAkGFxQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@shikijs/core": "2.5.0", + "@shikijs/engine-javascript": "2.5.0", + "@shikijs/engine-oniguruma": "2.5.0", + "@shikijs/langs": "2.5.0", + "@shikijs/themes": "2.5.0", + "@shikijs/types": "2.5.0", + "@shikijs/vscode-textmate": "^10.0.2", + "@types/hast": "^3.0.4" + } + }, "node_modules/side-channel": { "version": "1.1.0", "license": "MIT", @@ -5977,6 +7223,27 @@ "node": ">=0.10.0" } }, + "node_modules/space-separated-tokens": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz", + "integrity": "sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, + "node_modules/speakingurl": { + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", + "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/string-width": { "version": "4.2.3", "license": "MIT", @@ -5989,6 +7256,21 @@ "node": ">=8" } }, + "node_modules/stringify-entities": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/stringify-entities/-/stringify-entities-4.0.4.tgz", + "integrity": "sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==", + "dev": true, + "license": "MIT", + "dependencies": { + "character-entities-html4": "^2.0.0", + "character-entities-legacy": "^3.0.0" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/strip-ansi": { "version": "6.0.1", "license": "MIT", @@ -5999,6 +7281,26 @@ "node": ">=8" } }, + "node_modules/superjson": { + "version": "2.2.6", + "resolved": "https://registry.npmjs.org/superjson/-/superjson-2.2.6.tgz", + "integrity": "sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==", + "dev": true, + "license": "MIT", + "dependencies": { + "copy-anything": "^4" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tabbable": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/tabbable/-/tabbable-6.4.0.tgz", + "integrity": "sha512-05PUHKSNE8ou2dwIxTngl4EzcnsCDZGJ/iCLtDflR/SHB/ny14rXc+qU5P4mG9JkusiV7EivzY9Mhm55AzAvCg==", + "dev": true, + "license": "MIT" + }, "node_modules/tailwind-merge": { "version": "3.5.0", "license": "MIT", @@ -6043,6 +7345,17 @@ "url": "https://github.com/sponsors/SuperchupuDev" } }, + "node_modules/trim-lines": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/trim-lines/-/trim-lines-3.0.1.tgz", + "integrity": "sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "node_modules/tslib": { "version": "2.8.1", "license": "0BSD" @@ -6069,6 +7382,79 @@ "version": "7.18.2", "license": "MIT" }, + "node_modules/unist-util-is": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.1.tgz", + "integrity": "sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-position": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-position/-/unist-util-position-5.0.0.tgz", + "integrity": "sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-stringify-position": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz", + "integrity": "sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.1.0.tgz", + "integrity": "sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/unist-util-visit-parents": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz", + "integrity": "sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "dev": true, @@ -6169,6 +7555,36 @@ "uuid": "dist/bin/uuid" } }, + "node_modules/vfile": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/vfile/-/vfile-6.0.3.tgz", + "integrity": "sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/vfile-message": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/vfile-message/-/vfile-message-4.0.3.tgz", + "integrity": "sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-stringify-position": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/victory-vendor": { "version": "37.3.6", "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", @@ -6263,46 +7679,600 @@ } } }, - "node_modules/w3c-keyname": { - "version": "2.2.8", - "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", - "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", - "license": "MIT" - }, - "node_modules/whatwg-mimetype": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", - "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", - "license": "MIT", - "engines": { - "node": ">=12" - } - }, - "node_modules/which": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", - "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "node_modules/vitepress": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/vitepress/-/vitepress-1.6.4.tgz", + "integrity": "sha512-+2ym1/+0VVrbhNyRoFFesVvBvHAVMZMK0rw60E3X/5349M1GuVdKeazuksqopEdvkKwKGs21Q729jX81/bkBJg==", "dev": true, - "license": "ISC", + "license": "MIT", "dependencies": { - "isexe": "^2.0.0" + "@docsearch/css": "3.8.2", + "@docsearch/js": "3.8.2", + "@iconify-json/simple-icons": "^1.2.21", + "@shikijs/core": "^2.1.0", + "@shikijs/transformers": "^2.1.0", + "@shikijs/types": "^2.1.0", + "@types/markdown-it": "^14.1.2", + "@vitejs/plugin-vue": "^5.2.1", + "@vue/devtools-api": "^7.7.0", + "@vue/shared": "^3.5.13", + "@vueuse/core": "^12.4.0", + "@vueuse/integrations": "^12.4.0", + "focus-trap": "^7.6.4", + "mark.js": "8.11.1", + "minisearch": "^7.1.1", + "shiki": "^2.1.0", + "vite": "^5.4.14", + "vue": "^3.5.13" }, "bin": { - "node-which": "bin/node-which" + "vitepress": "bin/vitepress.js" }, - "engines": { - "node": ">= 8" + "peerDependencies": { + "markdown-it-mathjax3": "^4", + "postcss": "^8" + }, + "peerDependenciesMeta": { + "markdown-it-mathjax3": { + "optional": true + }, + "postcss": { + "optional": true + } } }, - "node_modules/which-module": { - "version": "2.0.1", - "license": "ISC" - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", + "node_modules/vitepress/node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/vitepress/node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, + "node_modules/vitepress/node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.31", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.31.tgz", + "integrity": "sha512-iV/sU9SzOlmA/0tygSmjkEN6Jbs3nPoIPFhCMLD2STrjgOU8DX7ZtzMhg4ahVwf5Rp9KoFzcXeB1ZrVbLBp5/Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.31", + "@vue/compiler-sfc": "3.5.31", + "@vue/runtime-dom": "3.5.31", + "@vue/server-renderer": "3.5.31", + "@vue/shared": "3.5.31" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/w3c-keyname": { + "version": "2.2.8", + "resolved": "https://registry.npmjs.org/w3c-keyname/-/w3c-keyname-2.2.8.tgz", + "integrity": "sha512-dpojBhNsCNN7T82Tm7k26A6G9ML3NkhDsnw9n/eoxSRlVBB4CEtIQ/KTCLI2Fwf3ataSXRhYFkQi3SlnFwPvPQ==", + "license": "MIT" + }, + "node_modules/whatwg-mimetype": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-3.0.0.tgz", + "integrity": "sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==", + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "license": "ISC" + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" }, @@ -6400,6 +8370,17 @@ } } }, + "node_modules/zwitch": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/zwitch/-/zwitch-2.0.4.tgz", + "integrity": "sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/wooorm" + } + }, "packages/SimpleModule.Client": { "name": "@simplemodule/client", "version": "0.0.0", From af8c2d4e7c60b606b01561ba88d50f5a08837057 Mon Sep 17 00:00:00 2001 From: Anto Subash Date: Fri, 27 Mar 2026 23:51:56 +0100 Subject: [PATCH 7/7] fix: resolve biome formatting errors in types.ts and config.ts --- docs/site/.vitepress/config.ts | 8 ++------ modules/Admin/src/SimpleModule.Admin/types.ts | 4 +--- modules/AuditLogs/src/SimpleModule.AuditLogs/types.ts | 1 - modules/FileStorage/src/SimpleModule.FileStorage/types.ts | 1 - modules/OpenIddict/src/SimpleModule.OpenIddict/types.ts | 4 +--- modules/Orders/src/SimpleModule.Orders/types.ts | 1 - modules/PageBuilder/src/SimpleModule.PageBuilder/types.ts | 1 - modules/Products/src/SimpleModule.Products/types.ts | 1 - modules/Settings/src/SimpleModule.Settings/types.ts | 1 - modules/Users/src/SimpleModule.Users/types.ts | 1 - 10 files changed, 4 insertions(+), 19 deletions(-) diff --git a/docs/site/.vitepress/config.ts b/docs/site/.vitepress/config.ts index 1f6e1fdc..0d005b31 100644 --- a/docs/site/.vitepress/config.ts +++ b/docs/site/.vitepress/config.ts @@ -8,9 +8,7 @@ export default defineConfig({ cleanUrls: true, - ignoreDeadLinks: [ - /localhost/, - ], + ignoreDeadLinks: [/localhost/], themeConfig: { logo: '/favicon.svg', @@ -120,9 +118,7 @@ export default defineConfig({ ], }, - socialLinks: [ - { icon: 'github', link: 'https://github.com/anthropics/SimpleModule' }, - ], + socialLinks: [{ icon: 'github', link: 'https://github.com/anthropics/SimpleModule' }], editLink: { pattern: 'https://github.com/anthropics/SimpleModule/edit/main/docs/site/:path', diff --git a/modules/Admin/src/SimpleModule.Admin/types.ts b/modules/Admin/src/SimpleModule.Admin/types.ts index 140b48c4..a7ed2736 100644 --- a/modules/Admin/src/SimpleModule.Admin/types.ts +++ b/modules/Admin/src/SimpleModule.Admin/types.ts @@ -1,6 +1,5 @@ // Auto-generated from [Dto] types — do not edit -export interface AdminPermissions { -} +export type AdminPermissions = {}; export interface AuditLogEntryDto { id: number; @@ -11,4 +10,3 @@ export interface AuditLogEntryDto { details: string; timestamp: string; } - diff --git a/modules/AuditLogs/src/SimpleModule.AuditLogs/types.ts b/modules/AuditLogs/src/SimpleModule.AuditLogs/types.ts index 11ee1f8f..f4cc2b2e 100644 --- a/modules/AuditLogs/src/SimpleModule.AuditLogs/types.ts +++ b/modules/AuditLogs/src/SimpleModule.AuditLogs/types.ts @@ -83,4 +83,3 @@ export interface AuditStats { byAction: any; byStatusCode: any; } - diff --git a/modules/FileStorage/src/SimpleModule.FileStorage/types.ts b/modules/FileStorage/src/SimpleModule.FileStorage/types.ts index 97621843..89ade8f2 100644 --- a/modules/FileStorage/src/SimpleModule.FileStorage/types.ts +++ b/modules/FileStorage/src/SimpleModule.FileStorage/types.ts @@ -8,4 +8,3 @@ export interface StoredFile { folder: string; createdAt: string; } - diff --git a/modules/OpenIddict/src/SimpleModule.OpenIddict/types.ts b/modules/OpenIddict/src/SimpleModule.OpenIddict/types.ts index 294e1680..7d5dadaa 100644 --- a/modules/OpenIddict/src/SimpleModule.OpenIddict/types.ts +++ b/modules/OpenIddict/src/SimpleModule.OpenIddict/types.ts @@ -1,4 +1,2 @@ // Auto-generated from [Dto] types — do not edit -export interface OpenIddictPermissions { -} - +export type OpenIddictPermissions = {}; diff --git a/modules/Orders/src/SimpleModule.Orders/types.ts b/modules/Orders/src/SimpleModule.Orders/types.ts index 1e9249d7..c2131e36 100644 --- a/modules/Orders/src/SimpleModule.Orders/types.ts +++ b/modules/Orders/src/SimpleModule.Orders/types.ts @@ -21,4 +21,3 @@ export interface UpdateOrderRequest { userId: string; items: OrderItem[]; } - diff --git a/modules/PageBuilder/src/SimpleModule.PageBuilder/types.ts b/modules/PageBuilder/src/SimpleModule.PageBuilder/types.ts index 7af0e9e9..797e3f2c 100644 --- a/modules/PageBuilder/src/SimpleModule.PageBuilder/types.ts +++ b/modules/PageBuilder/src/SimpleModule.PageBuilder/types.ts @@ -68,4 +68,3 @@ export interface UpdatePageRequest { metaKeywords: string; ogImage: string; } - diff --git a/modules/Products/src/SimpleModule.Products/types.ts b/modules/Products/src/SimpleModule.Products/types.ts index bd271339..8b0b9646 100644 --- a/modules/Products/src/SimpleModule.Products/types.ts +++ b/modules/Products/src/SimpleModule.Products/types.ts @@ -14,4 +14,3 @@ export interface UpdateProductRequest { name: string; price: number; } - diff --git a/modules/Settings/src/SimpleModule.Settings/types.ts b/modules/Settings/src/SimpleModule.Settings/types.ts index ba807c56..579d3b3b 100644 --- a/modules/Settings/src/SimpleModule.Settings/types.ts +++ b/modules/Settings/src/SimpleModule.Settings/types.ts @@ -65,4 +65,3 @@ export interface UpdateSettingRequest { value: string; scope: any; } - diff --git a/modules/Users/src/SimpleModule.Users/types.ts b/modules/Users/src/SimpleModule.Users/types.ts index 133573ae..c224866b 100644 --- a/modules/Users/src/SimpleModule.Users/types.ts +++ b/modules/Users/src/SimpleModule.Users/types.ts @@ -17,4 +17,3 @@ export interface UserDto { emailConfirmed: boolean; twoFactorEnabled: boolean; } -