From c42a88dff619d30433b5d989958b627db912794e Mon Sep 17 00:00:00 2001 From: MaKarin Date: Fri, 3 Apr 2026 14:17:21 +0300 Subject: [PATCH] (llm-first): context budget, validation, and eval harness, orchestration general-prompt --- .codex/AGENTS.md | 108 + .codex/agents/docs-researcher.toml | 10 + .codex/agents/explorer.toml | 23 + .codex/agents/generator.toml | 51 + .codex/agents/reviewer.toml | 32 + .codex/config.toml | 125 + .gitignore | 2 + AGENTS.md | 216 + README.md | 40 +- api-summary.json | 2185 +++++++ client/.dockerignore | 7 - client/.env.example | 1 - client/.eslintrc.cjs | 18 - client/.gitignore | 28 +- client/Dockerfile | 27 - client/README.md | 75 +- client/eslint.config.js | 23 + client/index.html | 4 +- client/nginx/default.conf | 27 - client/package-lock.json | 2911 ++++----- client/package.json | 34 +- client/public/favicon.svg | 1 + client/public/icons.svg | 24 + client/public/vite.svg | 1 - client/src/App.css | 184 + client/src/App.tsx | 117 +- client/src/assets/hero.png | Bin 0 -> 44919 bytes client/src/assets/vite.svg | 1 + client/src/auth/authProvider.ts | 55 +- client/src/auth/keycloak.ts | 115 +- client/src/config/env.ts | 28 +- client/src/dataProvider.ts | 262 +- client/src/index.css | 110 + client/src/main.tsx | 24 +- .../CategoryResourceCreate.tsx | 25 + .../CategoryResourceEdit.tsx | 25 + .../CategoryResourceList.tsx | 53 + .../CategoryResourceShow.tsx | 14 + .../src/resources/employee/EmployeeCreate.tsx | 28 + .../src/resources/employee/EmployeeEdit.tsx | 27 + .../src/resources/employee/EmployeeList.tsx | 38 + .../src/resources/employee/EmployeeShow.tsx | 22 + .../equipment-type/EquipmentTypeCreate.tsx | 14 - .../equipment-type/EquipmentTypeEdit.tsx | 14 - .../equipment-type/EquipmentTypeList.tsx | 38 - .../equipment-type/EquipmentTypeShow.tsx | 13 - .../resources/equipment/EquipmentCreate.tsx | 58 +- .../src/resources/equipment/EquipmentEdit.tsx | 51 +- .../src/resources/equipment/EquipmentList.tsx | 116 +- .../src/resources/equipment/EquipmentShow.tsx | 51 +- client/src/resources/part/PartCreate.tsx | 15 + client/src/resources/part/PartEdit.tsx | 15 + client/src/resources/part/PartList.tsx | 34 + client/src/resources/part/PartShow.tsx | 19 + .../resources/price-list/PriceListCreate.tsx | 9 + .../resources/price-list/PriceListEdit.tsx | 9 + .../resources/price-list/PriceListList.tsx | 20 + .../resources/price-list/PriceListShow.tsx | 10 + .../repair-order/RepairOrderCreate.tsx | 38 - .../repair-order/RepairOrderEdit.tsx | 39 - .../repair-order/RepairOrderList.tsx | 77 - .../repair-order/RepairOrderShow.tsx | 38 - client/src/resources/shared/ListActions.tsx | 17 + client/src/resources/shared/enums.ts | 66 + client/src/resources/shared/inputs.tsx | 3 + client/src/vite-env.d.ts | 11 - client/tsconfig.app.json | 28 + client/tsconfig.json | 28 +- client/tsconfig.node.json | 23 +- client/vite.config.ts | 2 +- docker-compose.yml | 82 +- docs/AID_EXPORT_README.md | 74 +- docs/api-dsl-conventions.md | 162 + docs/future-work.md | 154 + docs/generation-playbook.md | 237 + docs/repository-structure.md | 2 +- docs/source-of-truth.md | 96 + domain-summary.json | 324 - domain/TOiR.domain.dsl | 236 - domain/dsl-spec.md | 102 +- domain/toir.api.dsl | 1291 ++++ generation/generate.mjs | 751 --- openapi.json | 931 +++ overrides/api-overrides.dsl | 2 - overrides/ui-overrides.dsl | 2 - package.json | 7 +- prompts/auth-rules.md | 112 +- prompts/backend-rules.md | 179 +- prompts/frontend-rules.md | 139 +- prompts/general-prompt.md | 362 +- prompts/prisma-rules.md | 119 + prompts/runtime-rules.md | 132 +- prompts/validation-rules.md | 123 +- server/.dockerignore | 8 - server/.env.example | 3 +- server/.eslintrc.js | 25 - server/.gitignore | 33 +- server/.prettierrc | 2 +- server/Dockerfile | 37 - server/README.md | 3 +- server/eslint.config.mjs | 35 + server/package-lock.json | 5733 +++++++++-------- server/package.json | 62 +- server/prisma.config.ts | 16 + .../20260322114233_baseline/migration.sql | 68 - .../20260403103310_init/migration.sql | 85 + server/prisma/migrations/migration_lock.toml | 3 + server/prisma/schema.prisma | 119 +- server/prisma/seed.ts | 172 - server/src/aid-export/README.md | 87 - .../src/aid-export/aid-export.controller.ts | 112 - server/src/aid-export/aid-export.module.ts | 9 - server/src/aid-export/aid-export.service.ts | 154 - server/src/app.controller.spec.ts | 22 + server/src/app.controller.ts | 12 + server/src/app.module.ts | 29 +- server/src/app.service.ts | 8 + server/src/auth/auth.constants.ts | 3 - server/src/auth/auth.module.ts | 19 +- server/src/auth/auth.service.ts | 187 +- .../src/auth/decorators/public.decorator.ts | 3 +- server/src/auth/decorators/roles.decorator.ts | 6 +- server/src/auth/guards/jwt-auth.guard.ts | 36 +- server/src/auth/guards/roles.guard.ts | 53 +- .../authenticated-user.interface.ts | 20 - .../interfaces/express-request.interface.ts | 12 - server/src/auth/roles/realm-role.enum.ts | 6 - server/src/config/env.validation.ts | 58 - server/src/health/health.module.ts | 7 - server/src/main.ts | 31 +- .../category-resource.controller.ts | 44 + .../category-resource.module.ts | 9 + .../category-resource.service.ts | 142 + .../dto/create-category-resource.dto.ts | 15 + .../dto/update-category-resource.dto.ts | 15 + .../employee/dto/create-employee.dto.ts | 28 + .../employee/dto/update-employee.dto.ts | 28 + .../modules/employee/employee.controller.ts | 44 + .../src/modules/employee/employee.module.ts | 11 + .../src/modules/employee/employee.service.ts | 145 + .../dto/create-equipment-type.dto.ts | 7 - .../dto/update-equipment-type.dto.ts | 8 - .../equipment-type.controller.ts | 45 - .../equipment-type/equipment-type.module.ts | 9 - .../equipment-type/equipment-type.service.ts | 90 - .../equipment/dto/create-equipment.dto.ts | 53 +- .../equipment/dto/update-equipment.dto.ts | 60 +- .../modules/equipment/equipment.controller.ts | 39 +- .../src/modules/equipment/equipment.module.ts | 2 + .../modules/equipment/equipment.service.ts | 191 +- .../src/modules/part/dto/create-part.dto.ts | 28 + .../src/modules/part/dto/update-part.dto.ts | 29 + server/src/modules/part/part.controller.ts | 44 + server/src/modules/part/part.module.ts | 9 + server/src/modules/part/part.service.ts | 107 + .../price-list/dto/create-price-list.dto.ts | 1 + .../price-list/dto/update-price-list.dto.ts | 1 + .../price-list/price-list.controller.ts | 17 + .../modules/price-list/price-list.module.ts | 9 + .../modules/price-list/price-list.service.ts | 35 + .../dto/create-repair-order.dto.ts | 13 - .../dto/update-repair-order.dto.ts | 14 - .../repair-order/repair-order.controller.ts | 45 - .../repair-order/repair-order.module.ts | 9 - .../repair-order/repair-order.service.ts | 100 - server/src/modules/shared/dsl-enums.ts | 40 + server/src/modules/shared/query-utils.ts | 103 + server/src/modules/shared/record-mappers.ts | 85 + server/src/prisma/prisma.module.ts | 5 +- server/src/prisma/prisma.service.ts | 2 +- server/test/app.e2e-spec.ts | 82 +- server/test/jest-e2e.json | 3 - server/test/mocks/jose.ts | 8 - server/tsconfig.json | 18 +- tools/api-summary-to-openapi.mjs | 301 + tools/api-summary.mjs | 389 ++ tools/dsl-summary.mjs | 357 - tools/eval/README.md | 106 + .../equipment/backend.assertions.json | 79 + .../equipment/frontend.assertions.json | 57 + tools/eval/fixtures/equipment/meta.json | 15 + .../repair-order/backend.assertions.json | 62 + .../repair-order/frontend.assertions.json | 53 + tools/eval/fixtures/repair-order/meta.json | 13 + tools/eval/run-evals.mjs | 184 + ...n-summary.mjs => generate-api-summary.mjs} | 6 +- tools/hooks/pre-commit | 5 + tools/install-hooks.mjs | 19 + tools/validate-generation.mjs | 355 +- 189 files changed, 15538 insertions(+), 9109 deletions(-) create mode 100644 .codex/AGENTS.md create mode 100644 .codex/agents/docs-researcher.toml create mode 100644 .codex/agents/explorer.toml create mode 100644 .codex/agents/generator.toml create mode 100644 .codex/agents/reviewer.toml create mode 100644 .codex/config.toml create mode 100644 AGENTS.md create mode 100644 api-summary.json delete mode 100644 client/.dockerignore delete mode 100644 client/.eslintrc.cjs delete mode 100644 client/Dockerfile create mode 100644 client/eslint.config.js delete mode 100644 client/nginx/default.conf create mode 100644 client/public/favicon.svg create mode 100644 client/public/icons.svg delete mode 100644 client/public/vite.svg create mode 100644 client/src/App.css create mode 100644 client/src/assets/hero.png create mode 100644 client/src/assets/vite.svg create mode 100644 client/src/index.css create mode 100644 client/src/resources/category-resource/CategoryResourceCreate.tsx create mode 100644 client/src/resources/category-resource/CategoryResourceEdit.tsx create mode 100644 client/src/resources/category-resource/CategoryResourceList.tsx create mode 100644 client/src/resources/category-resource/CategoryResourceShow.tsx create mode 100644 client/src/resources/employee/EmployeeCreate.tsx create mode 100644 client/src/resources/employee/EmployeeEdit.tsx create mode 100644 client/src/resources/employee/EmployeeList.tsx create mode 100644 client/src/resources/employee/EmployeeShow.tsx delete mode 100644 client/src/resources/equipment-type/EquipmentTypeCreate.tsx delete mode 100644 client/src/resources/equipment-type/EquipmentTypeEdit.tsx delete mode 100644 client/src/resources/equipment-type/EquipmentTypeList.tsx delete mode 100644 client/src/resources/equipment-type/EquipmentTypeShow.tsx create mode 100644 client/src/resources/part/PartCreate.tsx create mode 100644 client/src/resources/part/PartEdit.tsx create mode 100644 client/src/resources/part/PartList.tsx create mode 100644 client/src/resources/part/PartShow.tsx create mode 100644 client/src/resources/price-list/PriceListCreate.tsx create mode 100644 client/src/resources/price-list/PriceListEdit.tsx create mode 100644 client/src/resources/price-list/PriceListList.tsx create mode 100644 client/src/resources/price-list/PriceListShow.tsx delete mode 100644 client/src/resources/repair-order/RepairOrderCreate.tsx delete mode 100644 client/src/resources/repair-order/RepairOrderEdit.tsx delete mode 100644 client/src/resources/repair-order/RepairOrderList.tsx delete mode 100644 client/src/resources/repair-order/RepairOrderShow.tsx create mode 100644 client/src/resources/shared/ListActions.tsx create mode 100644 client/src/resources/shared/enums.ts create mode 100644 client/src/resources/shared/inputs.tsx create mode 100644 client/tsconfig.app.json create mode 100644 docs/api-dsl-conventions.md create mode 100644 docs/future-work.md create mode 100644 docs/generation-playbook.md create mode 100644 docs/source-of-truth.md delete mode 100644 domain-summary.json delete mode 100644 domain/TOiR.domain.dsl create mode 100644 domain/toir.api.dsl delete mode 100644 generation/generate.mjs create mode 100644 openapi.json delete mode 100644 overrides/api-overrides.dsl delete mode 100644 overrides/ui-overrides.dsl create mode 100644 prompts/prisma-rules.md delete mode 100644 server/.dockerignore delete mode 100644 server/.eslintrc.js delete mode 100644 server/Dockerfile create mode 100644 server/eslint.config.mjs create mode 100644 server/prisma.config.ts delete mode 100644 server/prisma/migrations/20260322114233_baseline/migration.sql create mode 100644 server/prisma/migrations/20260403103310_init/migration.sql create mode 100644 server/prisma/migrations/migration_lock.toml delete mode 100644 server/prisma/seed.ts delete mode 100644 server/src/aid-export/README.md delete mode 100644 server/src/aid-export/aid-export.controller.ts delete mode 100644 server/src/aid-export/aid-export.module.ts delete mode 100644 server/src/aid-export/aid-export.service.ts create mode 100644 server/src/app.controller.spec.ts create mode 100644 server/src/app.controller.ts create mode 100644 server/src/app.service.ts delete mode 100644 server/src/auth/auth.constants.ts delete mode 100644 server/src/auth/interfaces/authenticated-user.interface.ts delete mode 100644 server/src/auth/interfaces/express-request.interface.ts delete mode 100644 server/src/auth/roles/realm-role.enum.ts delete mode 100644 server/src/config/env.validation.ts delete mode 100644 server/src/health/health.module.ts create mode 100644 server/src/modules/category-resource/category-resource.controller.ts create mode 100644 server/src/modules/category-resource/category-resource.module.ts create mode 100644 server/src/modules/category-resource/category-resource.service.ts create mode 100644 server/src/modules/category-resource/dto/create-category-resource.dto.ts create mode 100644 server/src/modules/category-resource/dto/update-category-resource.dto.ts create mode 100644 server/src/modules/employee/dto/create-employee.dto.ts create mode 100644 server/src/modules/employee/dto/update-employee.dto.ts create mode 100644 server/src/modules/employee/employee.controller.ts create mode 100644 server/src/modules/employee/employee.module.ts create mode 100644 server/src/modules/employee/employee.service.ts delete mode 100644 server/src/modules/equipment-type/dto/create-equipment-type.dto.ts delete mode 100644 server/src/modules/equipment-type/dto/update-equipment-type.dto.ts delete mode 100644 server/src/modules/equipment-type/equipment-type.controller.ts delete mode 100644 server/src/modules/equipment-type/equipment-type.module.ts delete mode 100644 server/src/modules/equipment-type/equipment-type.service.ts create mode 100644 server/src/modules/part/dto/create-part.dto.ts create mode 100644 server/src/modules/part/dto/update-part.dto.ts create mode 100644 server/src/modules/part/part.controller.ts create mode 100644 server/src/modules/part/part.module.ts create mode 100644 server/src/modules/part/part.service.ts create mode 100644 server/src/modules/price-list/dto/create-price-list.dto.ts create mode 100644 server/src/modules/price-list/dto/update-price-list.dto.ts create mode 100644 server/src/modules/price-list/price-list.controller.ts create mode 100644 server/src/modules/price-list/price-list.module.ts create mode 100644 server/src/modules/price-list/price-list.service.ts delete mode 100644 server/src/modules/repair-order/dto/create-repair-order.dto.ts delete mode 100644 server/src/modules/repair-order/dto/update-repair-order.dto.ts delete mode 100644 server/src/modules/repair-order/repair-order.controller.ts delete mode 100644 server/src/modules/repair-order/repair-order.module.ts delete mode 100644 server/src/modules/repair-order/repair-order.service.ts create mode 100644 server/src/modules/shared/dsl-enums.ts create mode 100644 server/src/modules/shared/query-utils.ts create mode 100644 server/src/modules/shared/record-mappers.ts delete mode 100644 server/test/mocks/jose.ts create mode 100644 tools/api-summary-to-openapi.mjs create mode 100644 tools/api-summary.mjs delete mode 100644 tools/dsl-summary.mjs create mode 100644 tools/eval/README.md create mode 100644 tools/eval/fixtures/equipment/backend.assertions.json create mode 100644 tools/eval/fixtures/equipment/frontend.assertions.json create mode 100644 tools/eval/fixtures/equipment/meta.json create mode 100644 tools/eval/fixtures/repair-order/backend.assertions.json create mode 100644 tools/eval/fixtures/repair-order/frontend.assertions.json create mode 100644 tools/eval/fixtures/repair-order/meta.json create mode 100644 tools/eval/run-evals.mjs rename tools/{generate-domain-summary.mjs => generate-api-summary.mjs} (65%) create mode 100644 tools/hooks/pre-commit create mode 100644 tools/install-hooks.mjs diff --git a/.codex/AGENTS.md b/.codex/AGENTS.md new file mode 100644 index 0000000..1b41c84 --- /dev/null +++ b/.codex/AGENTS.md @@ -0,0 +1,108 @@ +# Codex CLI — KIS-TOiR workspace supplement + +This file supplements the repository root `AGENTS.md` with Codex-specific +operational notes. The root `AGENTS.md` is the authoritative contract — +if anything here contradicts root, root wins. + +--- + +## Agent role summary + +| Role | Config file | Sandbox | Write boundary | +|------|-------------|---------|----------------| +| `generator` | `agents/generator.toml` | workspace-write | Tier 3 generation zones | +| `explorer` | `agents/explorer.toml` | read-only | None | +| `reviewer` | `agents/reviewer.toml` | read-only | Proposes patches only | +| `docs_researcher` | `agents/docs-researcher.toml` | read-only | None | + +Use `/agent generator` for implementation work. Use `/agent explorer` first for +discovery, `/agent docs_researcher` when framework or prompt patterns need +verification, and `/agent reviewer` before claiming a generation run is complete. + +--- + +## Mutation boundary map + +``` +Tier 1 — Source of truth (NEVER written by any agent) + domain/*.api.dsl — single source of truth for all generation + prompts/*.md — generation spec / rules + AGENTS.md — agent operating rules + .codex/AGENTS.md (this file) — Codex-specific supplement + +Tier 2 — Deterministic derivatives (written only by npm scripts, not by agents) + api-summary.json ← npm run generate:api-summary + openapi.json ← npm run generate:openapi (auxiliary) + +Tier 3 — LLM-generated artifacts (written ONLY by generator agent) + server/src/modules// + client/src/resources// + server/src/app.module.ts + client/src/App.tsx + server/prisma/schema.prisma ← LLM-generated per prompts/prisma-rules.md + server/src/auth/ + client/src/auth/ + client/src/dataProvider.ts + toir-realm.json + docker-compose.yml + server/.env.example + client/.env.example + +Tier 4 — Handwritten / framework-managed support files + framework scaffold and other manual support files outside prompt-governed outputs +``` + +--- + +## Standard generation invocation + +```bash +# 1. Read AGENTS.md + prompts/general-prompt.md +# 2. Read the entity-scoped DSL block from domain/toir.api.dsl +# 3. Load only the stage-specific companion rules you need +# 4. Run generation or repair with the appropriate agent +# 5. Refresh api-summary.json only if validator/tooling expects the auxiliary freshness artifact +# 6. Verify (both stages must pass) +node tools/validate-generation.mjs --artifacts-only +npm run eval:generation +``` + +--- + +## MCP servers (project-local) + +Defined in `.codex/config.toml`: + +- **github** — repository access +- **context7** — library documentation lookup (use for framework questions) +- **exa** — web search +- **memory** — persistent cross-session context +- **playwright** — browser automation for smoke tests +- **sequential-thinking** — structured multi-step reasoning + +Add heavier or credential-backed servers in `~/.codex/config.toml`. + +--- + +## Validation gate + +Run before every commit and after every generation: + +```bash +# Stage 1 — structural gate +node tools/validate-generation.mjs --artifacts-only + +# Stage 2 — eval harness +npm run eval:generation +``` + +The pre-commit hook (`tools/hooks/pre-commit`) runs both stages automatically +after `npm run install-hooks`. + +--- + +## Security notes + +- Never commit secrets. Use environment variables from `.env.example` templates. +- Run `npm audit` when adding new dependencies to `server/` or `client/`. +- Auth contracts live in `prompts/auth-rules.md`. Do not deviate from them. diff --git a/.codex/agents/docs-researcher.toml b/.codex/agents/docs-researcher.toml new file mode 100644 index 0000000..0de61ce --- /dev/null +++ b/.codex/agents/docs-researcher.toml @@ -0,0 +1,10 @@ +model = "gpt-5.4-mini" +model_reasoning_effort = "low" +sandbox_mode = "read-only" + +developer_instructions = """ +Verify APIs, framework behavior, and release-note claims against primary documentation before changes land. +Cite the exact docs or file paths that support each claim. +Do not invent undocumented behavior. +Use Context7 and official docs first when the current environment exposes them. +""" diff --git a/.codex/agents/explorer.toml b/.codex/agents/explorer.toml new file mode 100644 index 0000000..a0c160b --- /dev/null +++ b/.codex/agents/explorer.toml @@ -0,0 +1,23 @@ +model = "gpt-5.4-mini" +model_reasoning_effort = "medium" +sandbox_mode = "read-only" + +developer_instructions = """ +Stay in exploration mode. Read files freely; write nothing. + +Trace the real execution path, cite files and symbols, and avoid proposing +fixes unless the parent agent asks for them. +Prefer targeted search and file reads over broad scans. + +KIS-TOiR source-of-truth tier reference (read-only for this agent): + Tier 1: domain/*.api.dsl, prompts/*.md, AGENTS.md + Tier 2: api-summary.json (deterministic auxiliary derivative; never authoritative) + Tier 3: server/src/modules/, client/src/resources/, server/src/app.module.ts, + client/src/App.tsx, server/prisma/schema.prisma, server/src/auth/, + client/src/auth/, client/src/dataProvider.ts, toir-realm.json, + docker-compose.yml, server/.env.example, client/.env.example + Tier 4: framework scaffold and other handwritten support files + +When asked about generation output, always trace it back to its Tier 1 DSL source +and do not recommend api-summary.json as the primary input when the DSL is available. +""" diff --git a/.codex/agents/generator.toml b/.codex/agents/generator.toml new file mode 100644 index 0000000..9b344a8 --- /dev/null +++ b/.codex/agents/generator.toml @@ -0,0 +1,51 @@ +model = "gpt-5.4" +model_reasoning_effort = "high" +sandbox_mode = "workspace-write" +approval_policy = "on-request" + +developer_instructions = """ +You are the LLM generation agent for KIS-TOiR. + +PERMITTED write zones (Tier 3 — LLM-generated artifacts): + server/src/modules// — NestJS modules, controllers, services, DTOs + client/src/resources// — React Admin List/Create/Edit/Show + server/src/app.module.ts — module registration section only + client/src/App.tsx — resource registration section only + server/prisma/schema.prisma — LLM-generated per prompts/prisma-rules.md + server/src/auth/ — auth artifacts per prompts/auth-rules.md + client/src/auth/ — auth artifacts per prompts/auth-rules.md + client/src/dataProvider.ts — authenticated data provider seam per prompts/auth-rules.md + toir-realm.json — realm artifact per prompts/auth-rules.md + docker-compose.yml — runtime artifact per prompts/runtime-rules.md + server/.env.example — runtime defaults per prompts/runtime-rules.md + client/.env.example — runtime defaults per prompts/runtime-rules.md + +FORBIDDEN write zones — never modify these files: + domain/*.api.dsl — source of truth (Tier 1) + prompts/*.md — generation spec (Tier 1) + AGENTS.md — workflow contract (Tier 1) + api-summary.json — deterministic derivative (Tier 2) + tools/ — deterministic tooling, not generated artifacts + +CONTEXT BUDGET (mandatory): +1. Read prompts/general-prompt.md first. +2. Read ONLY the entity-scoped api.dsl block (api API. + its DTOs + enums) + from domain/toir.api.dsl. Do NOT inject the full api.dsl as a blob. +3. Read ONLY the relevant companion rule file for the active stage. +4. Before generating any DTO or component, quote the relevant DSL field definitions + verbatim, then generate from those quotes. This prevents training-data contamination. +5. Use api-summary.json only as an auxiliary inventory or validator-related artifact, + never as the source of truth or default starting point. + +GENERATION WORKFLOW: +1. Read prompts/general-prompt.md. +2. Read the entity-scoped block from domain/toir.api.dsl. +3. Read the relevant stage rule docs. +4. Generate or update Tier 3 artifacts. +5. Refresh api-summary.json only if the validator/tooling requires it. +6. Run: node tools/validate-generation.mjs --artifacts-only +7. Run: npm run eval:generation +8. Fix all failures before reporting complete. + +NEVER report generation complete if either validation gate fails. +""" diff --git a/.codex/agents/reviewer.toml b/.codex/agents/reviewer.toml new file mode 100644 index 0000000..28bbc7e --- /dev/null +++ b/.codex/agents/reviewer.toml @@ -0,0 +1,32 @@ +model = "gpt-5.4" +model_reasoning_effort = "medium" +sandbox_mode = "read-only" + +developer_instructions = """ +Review mode. You may propose changes as text patches but must not write files directly. + +Focus on: +- Correctness: does generated code match the api.dsl and prompt contracts? +- Security: auth guard placement, CORS, env variable handling. +- Regression: do both verification gates pass? + node tools/validate-generation.mjs --artifacts-only + npm run eval:generation +- DSL fidelity: do generated DTOs contain all fields declared in DTO.Create/Update? +- Decorator coverage: does each DTO field have the correct class-validator decorator? +- Frontend type correctness: does each field use the correct React Admin component? +- Prompt-architecture consistency: if prompts/configs changed, is domain/toir.api.dsl still clearly authoritative and api-summary.json still clearly auxiliary? + +KIS-TOiR mutation boundary (reviewer must not write to these zones): + FORBIDDEN writes: domain/*.api.dsl, prompts/*.md, AGENTS.md, + api-summary.json, tools/, server/prisma/schema.prisma + + ALLOWED proposal targets (propose patches, not direct writes): + server/src/modules// — backend artifacts + client/src/resources// — frontend artifacts + server/src/app.module.ts, client/src/App.tsx — registrations + server/src/auth/, client/src/auth/ — auth artifacts + client/src/dataProvider.ts — authenticated data provider seam + toir-realm.json, docker-compose.yml — runtime/realm artifacts + server/.env.example, client/.env.example — runtime defaults + docs/ — documentation updates +""" diff --git a/.codex/config.toml b/.codex/config.toml new file mode 100644 index 0000000..d607b3e --- /dev/null +++ b/.codex/config.toml @@ -0,0 +1,125 @@ +#:schema https://developers.openai.com/codex/config-schema.json + +# Everything Claude Code (ECC) — Codex Reference Configuration +# +# Copy this file to ~/.codex/config.toml for global defaults, or keep it in +# the project root as .codex/config.toml for project-local settings. +# +# Official docs: +# - https://developers.openai.com/codex/config-reference +# - https://developers.openai.com/codex/multi-agent + +# Model selection +# Leave `model` and `model_provider` unset so Codex CLI uses its current +# built-in defaults. Uncomment and pin them only if you intentionally want +# repo-local or global model overrides. + +# Top-level runtime settings (current Codex schema) +approval_policy = "on-request" +sandbox_mode = "workspace-write" +web_search = "live" + +# External notifications receive a JSON payload on stdin. +# macOS example (uncomment on Mac if `terminal-notifier` is installed): +# notify = [ +# "terminal-notifier", +# "-title", "Codex KIS", +# "-message", "Task completed!", +# "-sound", "default", +# ] + +# Persistent instructions are appended to every prompt (additive, unlike +# model_instructions_file which replaces AGENTS.md). +persistent_instructions = "Follow project AGENTS.md guidelines. Use available MCP servers when they can help." + +# model_instructions_file replaces built-in instructions instead of AGENTS.md, +# so leave it unset unless you intentionally want a single override file. +# model_instructions_file = "/absolute/path/to/instructions.md" + +# MCP servers +# Keep the default project set lean. API-backed servers inherit credentials from +# the launching environment or can be supplied by a user-level ~/.codex/config.toml. +[mcp_servers.github] +command = "npx" +args = ["-y", "@modelcontextprotocol/server-github"] +startup_timeout_sec = 30 + +[mcp_servers.context7] +command = "npx" +# Canonical Codex section name is `context7`; the package itself remains +# `@upstash/context7-mcp`. +args = ["-y", "@upstash/context7-mcp@latest"] +startup_timeout_sec = 30 + +[mcp_servers.exa] +url = "https://mcp.exa.ai/mcp" + +[mcp_servers.memory] +command = "npx" +args = ["-y", "@modelcontextprotocol/server-memory"] +startup_timeout_sec = 30 + +[mcp_servers.playwright] +command = "npx" +args = ["-y", "@playwright/mcp@latest", "--extension"] +startup_timeout_sec = 30 + +[mcp_servers.sequential-thinking] +command = "npx" +args = ["-y", "@modelcontextprotocol/server-sequential-thinking"] +startup_timeout_sec = 30 + +# Additional MCP servers (uncomment as needed): +# [mcp_servers.supabase] +# command = "npx" +# args = ["-y", "supabase-mcp-server@latest", "--read-only"] +# +# [mcp_servers.firecrawl] +# command = "npx" +# args = ["-y", "firecrawl-mcp"] +# +# [mcp_servers.fal-ai] +# command = "npx" +# args = ["-y", "fal-ai-mcp-server"] +# +# [mcp_servers.cloudflare] +# command = "npx" +# args = ["-y", "@cloudflare/mcp-server-cloudflare"] + +[features] +# Codex multi-agent collaboration is stable and on by default in current builds. +# Keep the explicit toggle here so the repo documents its expectation clearly. +multi_agent = true + +# Profiles — switch with `codex -p ` +[profiles.strict] +approval_policy = "on-request" +sandbox_mode = "read-only" +web_search = "cached" + +[profiles.yolo] +approval_policy = "never" +sandbox_mode = "workspace-write" +web_search = "live" + +[agents] +# Multi-agent role limits and local role definitions. +# These map to `.codex/agents/*.toml` and mirror the repo's explorer/reviewer/docs workflow. +max_threads = 6 +max_depth = 1 + +[agents.explorer] +description = "Read-only codebase explorer for gathering evidence before changes are proposed." +config_file = "agents/explorer.toml" + +[agents.reviewer] +description = "PR reviewer focused on correctness, security, and DSL fidelity. Proposes patches; writes nothing directly." +config_file = "agents/reviewer.toml" + +[agents.docs_researcher] +description = "Documentation specialist that verifies APIs, framework behavior, and release notes." +config_file = "agents/docs-researcher.toml" + +[agents.generator] +description = "LLM generation agent: writes server/src/modules/, client/src/resources/, app.module.ts, App.tsx only. Never touches DSL, prompts, or deterministic tooling." +config_file = "agents/generator.toml" \ No newline at end of file diff --git a/.gitignore b/.gitignore index e769025..58f091b 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,5 @@ Thumbs.db openapi.generated.json openapi.llm.json tools/api-format-to-openapi/demo-output/ + +.cursor/ \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..7ac05a8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,216 @@ +# KIS-TOiR — Agent Operating Rules + +Read this file at the start of every session before reading any other file. + +--- + +## What this repository is + +KIS-TOiR is a fullstack CRUD application (NestJS backend + React Admin frontend) +for equipment maintenance management (Техническое обслуживание и ремонт). + +Generation is driven by a single authoritative source file: + +- `domain/toir.api.dsl` — the complete API contract: enums, DTOs, endpoints, HTTP methods, pagination + +--- + +## Source-of-truth hierarchy + +### Tier 1 — authoritative (hand-authored; never overwritten by generation) + +| File | Authoritative for | +| ----------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| `domain/*.api.dsl` | Enums, DTO shapes per operation, nullability, HTTP verb+path per endpoint, endpoint names, pagination contracts. Single source of truth. Drives: NestJS modules + React Admin resources + Prisma schema. | +| `prompts/*.md` | Auth rules, runtime rules, framework scaffold rules, Prisma rules, validation rules, generation policy, naming conventions. | +| `AGENTS.md` (this file) | Agent workflow, mutation boundaries, verification contract. | + +### Tier 2 — deterministic derivative (generated by script; never edited manually) + +| File | Generated from | Command | +| ------------------ | ------------------ | ------------------------------ | +| `api-summary.json` | `domain/*.api.dsl` | `npm run generate:api-summary` | + +### Tier 3 — LLM-generated artifacts (never edit manually after generation) + +| Zone | Generated from | +| -------------------------------- | ------------------------------------------------------ | +| `server/src/modules//` | `domain/*.api.dsl` + `prompts/backend-rules.md` | +| `client/src/resources//` | `domain/*.api.dsl` + `prompts/frontend-rules.md` | +| `server/src/app.module.ts` | Module registrations derived from api.dsl api blocks | +| `client/src/App.tsx` | Resource registrations derived from api.dsl api blocks | +| `server/prisma/schema.prisma` | `domain/*.api.dsl` + `prompts/prisma-rules.md` | +| `server/src/auth/` | `prompts/auth-rules.md` | +| `client/src/auth/` | `prompts/auth-rules.md` | +| `client/src/dataProvider.ts` | `prompts/auth-rules.md` | +| `toir-realm.json` | `prompts/auth-rules.md` | +| `docker-compose.yml` | `prompts/runtime-rules.md` | +| `server/.env.example` | `prompts/runtime-rules.md` | +| `client/.env.example` | `prompts/runtime-rules.md` | + +### Tier 4 — handwritten / framework-managed support files + +- Framework scaffold: `server/nest-cli.json`, `server/tsconfig*.json`, `client/vite.config.*`, etc. + +--- + +## Forbidden mutations during any generation run + +**NEVER write to `*.dsl` files.** +They are read-only inputs. To change the API contract or domain model, edit `domain/*.api.dsl` +as a separate explicit task. + +**NEVER manually edit files under `server/src/modules/` or `client/src/resources/`.** +To change generated code: update `domain/*.api.dsl` and regenerate. + +**NEVER edit `server/prisma/schema.prisma` directly.** +It is LLM-generated from `domain/*.api.dsl` following `prompts/prisma-rules.md`. + +--- + +## Generation workflow (required sequence) + +1. Read `AGENTS.md` and `prompts/general-prompt.md`. +2. Read the active `api API.` block + its DTOs + its enums from `domain/toir.api.dsl` (entity-scoped — do not inject the full DSL as a blob). +3. Load the companion rule files required for the active stage: + - `prompts/prisma-rules.md` + - `prompts/backend-rules.md` + - `prompts/frontend-rules.md` + - `prompts/auth-rules.md` + - `prompts/runtime-rules.md` + - `prompts/validation-rules.md` +4. Verify or repair framework scaffolds before domain generation: + - official Nest CLI for backend workspace creation/repair + - official Vite React TypeScript CLI for frontend workspace creation/repair + - official Prisma CLI when Prisma initialization or repair is required +5. Generate `server/prisma/schema.prisma` per `prompts/prisma-rules.md`. +6. Generate backend modules and registrations per `prompts/backend-rules.md`. +7. Generate frontend resources and registrations per `prompts/frontend-rules.md`. +8. Generate auth/runtime/realm artifacts per `prompts/auth-rules.md` and `prompts/runtime-rules.md`. +9. Refresh `api-summary.json` only when validation/tooling requires the auxiliary freshness artifact: `npm run generate:api-summary`. +10. Run: `node tools/validate-generation.mjs --artifacts-only` +11. Run: `npm run eval:generation` +12. Fix all failures before considering the task complete. + +**Context budget rule:** Before generating any DTO or component, quote the field +definitions from the DSL api block verbatim, then generate from those quotes. This +prevents training-data contamination. See `prompts/general-prompt.md`. + +--- + +## Type mappings + +| DSL type | Prisma type | TS DTO type | React Admin component | +| --------- | ----------------------------- | ----------- | ----------------------------- | +| `uuid` | `String @id @default(uuid())` | `string` | `TextInput` / `TextField` | +| `string` | `String` | `string` | `TextInput` / `TextField` | +| `text` | `String` | `string` | `TextInput` / `TextField` | +| `integer` | `Int` | `number` | `NumberInput` / `NumberField` | +| `number` | `Float` | `number` | `NumberInput` / `NumberField` | +| `decimal` | `Decimal` | `string` | `NumberInput` / `NumberField` | +| `date` | `DateTime` | `string` | `DateInput` / `DateField` | +| `boolean` | `Boolean` | `boolean` | (appropriate boolean input) | +| enum name | enum name | `string` | `SelectInput` / `SelectField` | + +--- + +## Naming conventions + +Resource name (plural, kebab-case): + +- `Equipment` → `equipment` (irregular — stays as-is) +- `EquipmentType` → `equipment-types` +- `RepairOrder` → `repair-orders` +- General: PascalCase → kebab-case → append `s` (or `es` if ends in `s`; irregular cases explicit) + +Default sort field (when not declared in api.dsl): +Priority: `inventoryNumber` > `number` > `code` > `name` > primary key + +--- + +## Verification gate (two-stage) + +### Stage 1 — Structural gate + +``` +node tools/validate-generation.mjs --artifacts-only +``` + +Checks: file existence, api-summary freshness, auth seam contracts, natural-key handling, realm structure, runtime contract, api.dsl coverage (backend + frontend files per entity), DTO field name coverage, **DTO class-validator decorator coverage**, **@UseGuards presence on controllers**, **frontend component type correctness**. + +Full structural verification (requires installed `node_modules`): + +``` +node tools/validate-generation.mjs +``` + +### Stage 2 — Eval harness (Rule 6) + +``` +npm run eval:generation +``` + +Fixture-based semantic checks from `tools/eval/fixtures/`. Checks: correct HTTP method decorators, Content-Range header, enum filter patterns, FK reference wiring, component type correctness, class-validator decorator patterns. + +See `tools/eval/README.md` for fixture authoring and eval-driven development workflow. + +**Generation is incomplete unless both stages pass.** + +## Pre-commit hook + +Install with `npm run install-hooks`. The hook runs **both** the structural gate and +the eval harness before every commit. Commits are blocked when either fails. + +--- + +## Auth and runtime defaults + +Full auth contract: `prompts/auth-rules.md` + +Working defaults (do not regress to localhost): + +- Keycloak base: `https://sso.greact.ru` +- Realm/client: `toir` / `toir-frontend` / `toir-backend` +- Production frontend: `https://toir-frontend.greact.ru` +- CORS: `http://localhost:5173,https://toir-frontend.greact.ru` + +--- + +## OpenRouter configuration + +``` +OPENAI_API_KEY= +OPENAI_BASE_URL=https://openrouter.ai/api/v1 +OPENAI_MODEL= +``` + +These variables are used by `tools/api-format-to-openapi/convert.mjs --mode llm`. + +--- + +## Reading order for generation tasks + +**Critical zone (load first, never drop):** + +1. `AGENTS.md` (this file) — project governance, mutation boundaries, tier hierarchy +2. `prompts/general-prompt.md` — master orchestration prompt: mission, stage ownership, delegation model, completion criteria +3. `domain/toir.api.dsl §API.` — active api block only, plus its referenced DTOs and enums + +**Companion zone (load when the matching stage is active):** + +4. `prompts/prisma-rules.md` — Prisma schema generation details +5. `prompts/backend-rules.md` — NestJS generation details +6. `prompts/frontend-rules.md` — React Admin generation details +7. `prompts/auth-rules.md` — auth seam and realm requirements +8. `prompts/runtime-rules.md` — scaffold, env, and bootstrap requirements +9. `prompts/validation-rules.md` — success gate requirements + +**Auxiliary zone (never authoritative):** + +10. `api-summary.json` — optional inventory/freshness artifact for validators and supporting tooling; do not use it instead of the DSL + +**Reference only (do not load proactively):** + +- `domain/dsl-spec.md` — DSL syntax reference; load only if DSL is ambiguous +- `docs/generation-playbook.md` — step-by-step workflow reference +- `docs/future-work.md` — deferred items (Rules 7 and 8) diff --git a/README.md b/README.md index a035d58..19a24b4 100644 --- a/README.md +++ b/README.md @@ -8,25 +8,29 @@ It is not a new generator engine and it is not a compiler platform. The reposito - `client/` as the active frontend target output path - an LLM-first orchestration baseline with CLI-first framework bootstrap - a compact rule set that strengthens the existing pipeline with: - - `domain-summary.json` + - `api-summary.json` (deterministic intermediate context) - a physical root-level realm artifact - a lightweight automated validation gate ## Active knowledge blocks -The active prompt corpus is intentionally normalized to six stable blocks: +The master generation prompt is `prompts/general-prompt.md`. It contains the complete +generation workflow, type mappings, naming conventions, and all core rules. -1. [prompts/general-prompt.md](prompts/general-prompt.md) -2. [prompts/auth-rules.md](prompts/auth-rules.md) -3. [prompts/backend-rules.md](prompts/backend-rules.md) -4. [prompts/frontend-rules.md](prompts/frontend-rules.md) -5. [prompts/runtime-rules.md](prompts/runtime-rules.md) -6. [prompts/validation-rules.md](prompts/validation-rules.md) +Companion rule files for artifact-specific details: + +1. [prompts/general-prompt.md](prompts/general-prompt.md) — master generation prompt +2. [prompts/auth-rules.md](prompts/auth-rules.md) — auth seam / realm spec +3. [prompts/backend-rules.md](prompts/backend-rules.md) — backend reference +4. [prompts/frontend-rules.md](prompts/frontend-rules.md) — frontend reference +5. [prompts/prisma-rules.md](prompts/prisma-rules.md) — Prisma schema rules +6. [prompts/runtime-rules.md](prompts/runtime-rules.md) — runtime / bootstrap +7. [prompts/validation-rules.md](prompts/validation-rules.md) — validation gate ## Baseline contracts -- `domain/*.dsl` is the source of truth for the domain model. -- [domain-summary.json](domain-summary.json) is a derived artifact for LLM stabilization and validation. +- `domain/*.api.dsl` is the single source of truth for the domain model and API contract. +- [api-summary.json](api-summary.json) is a derived artifact for LLM stabilization and validation. - [toir-realm.json](toir-realm.json) is the physical Keycloak bootstrap artifact baseline. - `server/` and `client/` are the active target output paths for this repository. - `server/` must remain a valid NestJS workspace baseline. @@ -45,7 +49,7 @@ The active prompt corpus is intentionally normalized to six stable blocks: - The active prompts define forbidden generation patterns, required invariants, and recovery rules for future agents. - Buildability is part of the baseline contract, not an optional follow-up. -- Validation targets `domain/*.dsl` as reusable source inputs, while TOiR names remain project defaults/examples. +- Validation targets `domain/*.api.dsl` as reusable source inputs, while TOiR names remain project defaults/examples. ## Repository layout @@ -56,13 +60,19 @@ The active prompt corpus is intentionally normalized to six stable blocks: ## Commands ```bash -npm run generate:domain-summary +npm run generate:api-summary npm run validate:generation npm run validate:generation:runtime +npm run eval:generation ``` -`npm run validate:generation` now checks both contract shape and workspace validity. When dependencies are installed, it also verifies `npm run build` in `server/` and `client/`. If dependencies are missing, it reports build verification as skipped instead of pretending the baseline is fully green. +`npm run validate:generation` checks both contract shape and workspace validity. When dependencies are installed, it also verifies `npm run build` in `server/` and `client/`. If dependencies are missing, it reports build verification as skipped instead of pretending the baseline is fully green. -## AID export (OpenAPI + app generator) +## AID export (OpenAPI) -HTTP-экспортёры для интеграции с AID: **`POST /aid/export/openapi`** (api-format → OpenAPI 3.0) и **`POST /aid/export/app`** (DSL → бандл файлов или `--apply`). Подробно: **[docs/AID_EXPORT_README.md](docs/AID_EXPORT_README.md)**. +HTTP-экспортёр для интеграции с AID: **`POST /aid/export/openapi`** (api-format → OpenAPI 3.0). Подробно: **[docs/AID_EXPORT_README.md](docs/AID_EXPORT_README.md)**. + +> **Note:** The `POST /aid/export/app` endpoint (DSL → generated app bundle) is currently +> non-operative because its backing script (`generation/generate.mjs`) was removed during +> the architecture migration to api.dsl-first generation. See `docs/AID_EXPORT_README.md` +> for details. diff --git a/api-summary.json b/api-summary.json new file mode 100644 index 0000000..96ac7f4 --- /dev/null +++ b/api-summary.json @@ -0,0 +1,2185 @@ +{ + "sourceFiles": [ + "domain/toir.api.dsl" + ], + "enums": [ + { + "name": "EquipmentStatus", + "description": null, + "values": [ + { + "name": "Active", + "label": "В эксплуатации" + }, + { + "name": "Repair", + "label": "В ремонте" + }, + { + "name": "Reserve", + "label": "В резерве" + }, + { + "name": "WriteOff", + "label": "Списано" + } + ] + }, + { + "name": "laborOperation", + "description": "Трудовые операции", + "values": [ + { + "name": "Manual", + "label": "Ручные" + }, + { + "name": "MachineManual", + "label": "Машинно-ручные" + }, + { + "name": "Machine", + "label": "Машинные" + } + ] + }, + { + "name": "EnumPeriodicityTO", + "description": null, + "values": [ + { + "name": "Ежедневное", + "label": "Ежедневное" + }, + { + "name": "Еженедельное", + "label": "Еженедельное" + }, + { + "name": "Ежемесячное", + "label": "Ежемесячное" + }, + { + "name": "Полугодовое", + "label": "Полугодовое" + }, + { + "name": "Годовое", + "label": "Годовое" + } + ] + }, + { + "name": "Role", + "description": null, + "values": [ + { + "name": "Исполнитель", + "label": "Исполнитель" + }, + { + "name": "Подписант", + "label": "Подписант" + }, + { + "name": "Пользователь", + "label": "Пользователь" + } + ] + }, + { + "name": "CategoryPart", + "description": null, + "values": [ + { + "name": "Расходник", + "label": "Расходник" + }, + { + "name": "Запчасть", + "label": "Запчасть" + }, + { + "name": "Инструмент", + "label": "Инструмент" + }, + { + "name": "Спецодежда", + "label": "Спецодежда" + } + ] + }, + { + "name": "EquipmentType", + "description": "Типы оборудования", + "values": [ + { + "name": "Производственное", + "label": "производственное" + }, + { + "name": "Энергетическое", + "label": "энергетическое" + }, + { + "name": "Насосное", + "label": "насосное" + }, + { + "name": "Компрессорное", + "label": "компрессорное" + } + ] + } + ], + "dtos": [ + { + "name": "DTO.Equipment", + "description": "Оборудование — полный response-объект", + "fields": [ + { + "name": "id", + "type": "uuid", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Идентификатор оборудования", + "map": "Equipment.id", + "sync": false, + "label": null + }, + { + "name": "name", + "type": "string", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Название оборудования", + "map": "Equipment.name", + "sync": false, + "label": null + }, + { + "name": "serialNumber", + "type": "string", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Заводской (серийный) номер", + "map": "Equipment.serialNumber", + "sync": false, + "label": null + }, + { + "name": "inventoryNumber", + "type": "string", + "required": false, + "nullable": false, + "unique": true, + "primary": false, + "description": "Инвентарный номер", + "map": "Equipment.inventoryNumber", + "sync": false, + "label": null + }, + { + "name": "equipmentType", + "type": "EquipmentType", + "required": true, + "nullable": false, + "unique": false, + "primary": false, + "description": "Тип оборудования", + "map": "Equipment.equipmentType", + "sync": false, + "label": null + }, + { + "name": "dateOfInspection", + "type": "date", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Дата проверки", + "map": "Equipment.dateOfInspection", + "sync": false, + "label": null + }, + { + "name": "periodicityTO", + "type": "EnumPeriodicityTO", + "required": true, + "nullable": false, + "unique": false, + "primary": false, + "description": "Периодичность ТО", + "map": "Equipment.periodicityTO", + "sync": false, + "label": null + }, + { + "name": "location", + "type": "string", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Место эксплуатации / скважина / куст", + "map": "Equipment.location", + "sync": false, + "label": null + }, + { + "name": "status", + "type": "EquipmentStatus", + "required": true, + "nullable": false, + "unique": false, + "primary": false, + "description": "Текущий статус", + "map": "Equipment.status", + "sync": false, + "label": null + }, + { + "name": "commissionedAt", + "type": "date", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Год изготовления", + "map": "Equipment.commissionedAt", + "sync": false, + "label": null + }, + { + "name": "totalEngineHours", + "type": "decimal", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Общая наработка, моточасов", + "map": "Equipment.totalEngineHours", + "sync": false, + "label": null + }, + { + "name": "engineHoursSinceLastRepair", + "type": "decimal", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Наработка с последнего ремонта, моточасов", + "map": "Equipment.engineHoursSinceLastRepair", + "sync": false, + "label": null + }, + { + "name": "lastRepairAt", + "type": "date", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Дата последнего ремонта", + "map": "Equipment.lastRepairAt", + "sync": false, + "label": null + }, + { + "name": "notes", + "type": "text", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Примечания", + "map": "Equipment.notes", + "sync": false, + "label": null + }, + { + "name": "workAsPartOf", + "type": "laborOperation", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Работы в составе", + "map": "Equipment.workAsPartOf", + "sync": false, + "label": null + }, + { + "name": "fuelConsumed", + "type": "number", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Расход топлива", + "map": "Equipment.fuelConsumed", + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.EquipmentCreate", + "description": "Тело запроса на создание оборудования", + "fields": [ + { + "name": "name", + "type": "string", + "required": true, + "nullable": false, + "unique": false, + "primary": false, + "description": "Название оборудования", + "map": "Equipment.name", + "sync": false, + "label": null + }, + { + "name": "serialNumber", + "type": "string", + "required": true, + "nullable": false, + "unique": false, + "primary": false, + "description": "Заводской (серийный) номер", + "map": "Equipment.serialNumber", + "sync": false, + "label": null + }, + { + "name": "inventoryNumber", + "type": "string", + "required": true, + "nullable": false, + "unique": true, + "primary": false, + "description": "Инвентарный номер", + "map": "Equipment.inventoryNumber", + "sync": false, + "label": null + }, + { + "name": "equipmentType", + "type": "EquipmentType", + "required": true, + "nullable": false, + "unique": false, + "primary": false, + "description": "Тип оборудования", + "map": "Equipment.equipmentType", + "sync": false, + "label": null + }, + { + "name": "dateOfInspection", + "type": "date", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Дата проверки", + "map": "Equipment.dateOfInspection", + "sync": false, + "label": null + }, + { + "name": "periodicityTO", + "type": "EnumPeriodicityTO", + "required": true, + "nullable": false, + "unique": false, + "primary": false, + "description": "Периодичность ТО", + "map": "Equipment.periodicityTO", + "sync": false, + "label": null + }, + { + "name": "location", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Место эксплуатации / скважина / куст", + "map": "Equipment.location", + "sync": false, + "label": null + }, + { + "name": "status", + "type": "EquipmentStatus", + "required": true, + "nullable": false, + "unique": false, + "primary": false, + "description": "Текущий статус", + "map": "Equipment.status", + "sync": false, + "label": null + }, + { + "name": "commissionedAt", + "type": "date", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Год изготовления", + "map": "Equipment.commissionedAt", + "sync": false, + "label": null + }, + { + "name": "totalEngineHours", + "type": "decimal", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Общая наработка, моточасов", + "map": "Equipment.totalEngineHours", + "sync": false, + "label": null + }, + { + "name": "engineHoursSinceLastRepair", + "type": "decimal", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Наработка с последнего ремонта, моточасов", + "map": "Equipment.engineHoursSinceLastRepair", + "sync": false, + "label": null + }, + { + "name": "lastRepairAt", + "type": "date", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Дата последнего ремонта", + "map": "Equipment.lastRepairAt", + "sync": false, + "label": null + }, + { + "name": "notes", + "type": "text", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Примечания", + "map": "Equipment.notes", + "sync": false, + "label": null + }, + { + "name": "workAsPartOf", + "type": "laborOperation", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Работы в составе", + "map": "Equipment.workAsPartOf", + "sync": false, + "label": null + }, + { + "name": "fuelConsumed", + "type": "number", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Расход топлива", + "map": "Equipment.fuelConsumed", + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.EquipmentUpdate", + "description": "Тело запроса на обновление оборудования (частичное обновление)", + "fields": [ + { + "name": "name", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Название оборудования", + "map": "Equipment.name", + "sync": false, + "label": null + }, + { + "name": "serialNumber", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Заводской (серийный) номер", + "map": "Equipment.serialNumber", + "sync": false, + "label": null + }, + { + "name": "inventoryNumber", + "type": "string", + "required": false, + "nullable": true, + "unique": true, + "primary": false, + "description": "Инвентарный номер", + "map": "Equipment.inventoryNumber", + "sync": false, + "label": null + }, + { + "name": "equipmentType", + "type": "EquipmentType", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Тип оборудования", + "map": "Equipment.equipmentType", + "sync": false, + "label": null + }, + { + "name": "dateOfInspection", + "type": "date", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Дата проверки", + "map": "Equipment.dateOfInspection", + "sync": false, + "label": null + }, + { + "name": "periodicityTO", + "type": "EnumPeriodicityTO", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Периодичность ТО", + "map": "Equipment.periodicityTO", + "sync": false, + "label": null + }, + { + "name": "location", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Место эксплуатации / скважина / куст", + "map": "Equipment.location", + "sync": false, + "label": null + }, + { + "name": "status", + "type": "EquipmentStatus", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Текущий статус", + "map": "Equipment.status", + "sync": false, + "label": null + }, + { + "name": "commissionedAt", + "type": "date", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Год изготовления", + "map": "Equipment.commissionedAt", + "sync": false, + "label": null + }, + { + "name": "totalEngineHours", + "type": "decimal", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Общая наработка, моточасов", + "map": "Equipment.totalEngineHours", + "sync": false, + "label": null + }, + { + "name": "engineHoursSinceLastRepair", + "type": "decimal", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Наработка с последнего ремонта, моточасов", + "map": "Equipment.engineHoursSinceLastRepair", + "sync": false, + "label": null + }, + { + "name": "lastRepairAt", + "type": "date", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Дата последнего ремонта", + "map": "Equipment.lastRepairAt", + "sync": false, + "label": null + }, + { + "name": "notes", + "type": "text", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Примечания", + "map": "Equipment.notes", + "sync": false, + "label": null + }, + { + "name": "workAsPartOf", + "type": "laborOperation", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Работы в составе", + "map": "Equipment.workAsPartOf", + "sync": false, + "label": null + }, + { + "name": "fuelConsumed", + "type": "number", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Расход топлива", + "map": "Equipment.fuelConsumed", + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.EquipmentFilter", + "description": "Фильтры для списка оборудования", + "fields": [ + { + "name": "inventoryNumber", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Частичное совпадение по инвентарному номеру", + "map": null, + "sync": false, + "label": null + }, + { + "name": "serialNumber", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Частичное совпадение по заводскому номеру", + "map": null, + "sync": false, + "label": null + }, + { + "name": "name", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Частичное совпадение по наименованию", + "map": null, + "sync": false, + "label": null + }, + { + "name": "equipmentTypeCode", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Точное совпадение по коду вида оборудования", + "map": null, + "sync": false, + "label": null + }, + { + "name": "equipmentPeriodicityTO", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Точное совпадение по периодичности ТО оборудования", + "map": null, + "sync": false, + "label": null + }, + { + "name": "dateOfInspection", + "type": "date", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Дата проверки", + "map": null, + "sync": false, + "label": null + }, + { + "name": "status", + "type": "EquipmentStatus", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Фильтр по статусу", + "map": null, + "sync": false, + "label": null + }, + { + "name": "location", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Частичное совпадение по месту эксплуатации", + "map": null, + "sync": false, + "label": null + }, + { + "name": "workAsPartOf", + "type": "laborOperation", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Фильтр по трудовой операции", + "map": null, + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.EquipmentListRequest", + "description": "Запрос постраничного списка оборудования с фильтрацией", + "fields": [ + { + "name": "filter", + "type": "DTO.EquipmentFilter", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Фильтры", + "map": null, + "sync": false, + "label": null + }, + { + "name": "page", + "type": "DTO.PageRequest", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Параметры пагинации", + "map": null, + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.EquipmentListResponse", + "description": "Ответ постраничного списка оборудования", + "fields": [ + { + "name": "content", + "type": "DTO.Equipment[]", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Список оборудования", + "map": null, + "sync": false, + "label": null + }, + { + "name": "page", + "type": "DTO.PageInfo", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Метаданные пагинации", + "map": null, + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.Employee", + "description": "Сотрудник — полный response-объект", + "fields": [ + { + "name": "code", + "type": "string", + "required": false, + "nullable": false, + "unique": false, + "primary": true, + "description": "Номер сотрудника (Табельный номер)", + "map": "Employee.code", + "sync": false, + "label": null + }, + { + "name": "fullName", + "type": "string", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "ФИО сотрудника", + "map": "Employee.fullName", + "sync": false, + "label": "Полное имя" + }, + { + "name": "role", + "type": "Role", + "required": true, + "nullable": false, + "unique": false, + "primary": false, + "description": "Роль сотрудника", + "map": "Employee.role", + "sync": false, + "label": "Роль" + }, + { + "name": "position", + "type": "string", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Должность сотрудника", + "map": "Employee.position", + "sync": false, + "label": null + }, + { + "name": "boss", + "type": "DTO.Employee", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Руководитель", + "map": "Employee.boss", + "sync": false, + "label": null + }, + { + "name": "subordinates", + "type": "DTO.Employee[]", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Подчинённые", + "map": "Employee.subordinates", + "sync": false, + "label": null + }, + { + "name": "price", + "type": "number", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Стоимость часа работы сотрудника", + "map": "Employee.price", + "sync": false, + "label": null + }, + { + "name": "phoneNumber", + "type": "number", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Номер телефона", + "map": "Employee.phoneNumber", + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.EmployeeCreate", + "description": "Тело запроса на создание сотрудника", + "fields": [ + { + "name": "code", + "type": "string", + "required": true, + "nullable": false, + "unique": false, + "primary": false, + "description": "Номер сотрудника (Табельный номер)", + "map": "Employee.code", + "sync": false, + "label": null + }, + { + "name": "fullName", + "type": "string", + "required": true, + "nullable": false, + "unique": false, + "primary": false, + "description": "ФИО сотрудника", + "map": "Employee.fullName", + "sync": false, + "label": "Полное имя" + }, + { + "name": "role", + "type": "Role", + "required": true, + "nullable": false, + "unique": false, + "primary": false, + "description": "Роль сотрудника", + "map": "Employee.role", + "sync": false, + "label": "Роль" + }, + { + "name": "position", + "type": "string", + "required": true, + "nullable": false, + "unique": false, + "primary": false, + "description": "Должность сотрудника", + "map": "Employee.position", + "sync": false, + "label": null + }, + { + "name": "boss", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Код руководителя", + "map": "Employee.boss", + "sync": false, + "label": null + }, + { + "name": "price", + "type": "number", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Стоимость часа работы сотрудника", + "map": "Employee.price", + "sync": false, + "label": null + }, + { + "name": "phoneNumber", + "type": "number", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Номер телефона", + "map": "Employee.phoneNumber", + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.EmployeeUpdate", + "description": "Тело запроса на обновление сотрудника (частичное обновление)", + "fields": [ + { + "name": "fullName", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "ФИО сотрудника", + "map": "Employee.fullName", + "sync": false, + "label": "Полное имя" + }, + { + "name": "role", + "type": "Role", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Роль сотрудника", + "map": "Employee.role", + "sync": false, + "label": "Роль" + }, + { + "name": "position", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Должность сотрудника", + "map": "Employee.position", + "sync": false, + "label": null + }, + { + "name": "boss", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Код руководителя", + "map": "Employee.boss", + "sync": false, + "label": null + }, + { + "name": "price", + "type": "number", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Стоимость часа работы сотрудника", + "map": "Employee.price", + "sync": false, + "label": null + }, + { + "name": "phoneNumber", + "type": "number", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Номер телефона", + "map": "Employee.phoneNumber", + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.EmployeeFilter", + "description": "Фильтры для списка сотрудников", + "fields": [ + { + "name": "code", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Частичное совпадение по табельному номеру", + "map": null, + "sync": false, + "label": null + }, + { + "name": "fullName", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Частичное совпадение по ФИО", + "map": null, + "sync": false, + "label": null + }, + { + "name": "role", + "type": "Role", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Точное совпадение по роли", + "map": null, + "sync": false, + "label": null + }, + { + "name": "position", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Частичное совпадение по должности", + "map": null, + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.EmployeeListRequest", + "description": "Запрос постраничного списка сотрудников с фильтрацией", + "fields": [ + { + "name": "filter", + "type": "DTO.EmployeeFilter", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Фильтры", + "map": null, + "sync": false, + "label": null + }, + { + "name": "page", + "type": "DTO.PageRequest", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Параметры пагинации", + "map": null, + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.EmployeeListResponse", + "description": "Ответ постраничного списка сотрудников", + "fields": [ + { + "name": "content", + "type": "DTO.Employee[]", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Список сотрудников", + "map": null, + "sync": false, + "label": null + }, + { + "name": "page", + "type": "DTO.PageInfo", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Метаданные пагинации", + "map": null, + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.Part", + "description": "ЗИП/ТМЦ — полный response-объект", + "fields": [ + { + "name": "id", + "type": "uuid", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Идентификатор ЗИП/ТМЦ", + "map": "Part.id", + "sync": false, + "label": null + }, + { + "name": "name", + "type": "string", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Название", + "map": "Part.name", + "sync": false, + "label": null + }, + { + "name": "categories", + "type": "CategoryPart", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Категории", + "map": "Part.categories", + "sync": false, + "label": null + }, + { + "name": "price", + "type": "number", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Стоимость ЗИП/ТМЦ", + "map": "Part.price", + "sync": false, + "label": null + }, + { + "name": "description", + "type": "string", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Описание ЗИП/ТМЦ", + "map": "Part.description", + "sync": false, + "label": null + }, + { + "name": "serialNumber", + "type": "string", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Серийный номер запасных частей / инструментов / расходников", + "map": "Part.serialNumber", + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.PartCreate", + "description": "Тело запроса на создание ЗИП/ТМЦ", + "fields": [ + { + "name": "name", + "type": "string", + "required": true, + "nullable": false, + "unique": false, + "primary": false, + "description": "Название", + "map": "Part.name", + "sync": false, + "label": null + }, + { + "name": "categories", + "type": "CategoryPart", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Категории", + "map": "Part.categories", + "sync": false, + "label": null + }, + { + "name": "price", + "type": "number", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Стоимость ЗИП/ТМЦ", + "map": "Part.price", + "sync": false, + "label": null + }, + { + "name": "description", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Описание ЗИП/ТМЦ", + "map": "Part.description", + "sync": false, + "label": null + }, + { + "name": "serialNumber", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Серийный номер запасных частей / инструментов / расходников", + "map": "Part.serialNumber", + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.PartUpdate", + "description": "Тело запроса на обновление ЗИП/ТМЦ (частичное обновление)", + "fields": [ + { + "name": "name", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Название", + "map": "Part.name", + "sync": false, + "label": null + }, + { + "name": "categories", + "type": "CategoryPart", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Категории", + "map": "Part.categories", + "sync": false, + "label": null + }, + { + "name": "price", + "type": "number", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Стоимость ЗИП/ТМЦ", + "map": "Part.price", + "sync": false, + "label": null + }, + { + "name": "description", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Описание ЗИП/ТМЦ", + "map": "Part.description", + "sync": false, + "label": null + }, + { + "name": "serialNumber", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Серийный номер запасных частей / инструментов / расходников", + "map": "Part.serialNumber", + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.PartFilter", + "description": "Фильтры для списка ЗИП/ТМЦ", + "fields": [ + { + "name": "name", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Частичное совпадение по названию", + "map": null, + "sync": false, + "label": null + }, + { + "name": "categories", + "type": "CategoryPart", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Точное совпадение по категории", + "map": null, + "sync": false, + "label": null + }, + { + "name": "serialNumber", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Частичное совпадение по серийному номеру", + "map": null, + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.PartListRequest", + "description": "Запрос постраничного списка ЗИП/ТМЦ с фильтрацией", + "fields": [ + { + "name": "filter", + "type": "DTO.PartFilter", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Фильтры", + "map": null, + "sync": false, + "label": null + }, + { + "name": "page", + "type": "DTO.PageRequest", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Параметры пагинации", + "map": null, + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.PartListResponse", + "description": "Ответ постраничного списка ЗИП/ТМЦ", + "fields": [ + { + "name": "content", + "type": "DTO.Part[]", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Список ЗИП/ТМЦ", + "map": null, + "sync": false, + "label": null + }, + { + "name": "page", + "type": "DTO.PageInfo", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Метаданные пагинации", + "map": null, + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.CategoryResource", + "description": "Категория ресурсов — полный response-объект", + "fields": [ + { + "name": "id", + "type": "uuid", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Идентификатор категории ресурсов", + "map": "CategoryResource.id", + "sync": false, + "label": null + }, + { + "name": "part", + "type": "DTO.Part", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "ЗИП/ТМЦ", + "map": "CategoryResource.part", + "sync": false, + "label": null + }, + { + "name": "employee", + "type": "DTO.Employee", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Сотрудник", + "map": "CategoryResource.employee", + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.CategoryResourceCreate", + "description": "Тело запроса на создание категории ресурсов", + "fields": [ + { + "name": "partId", + "type": "uuid", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Идентификатор ЗИП/ТМЦ", + "map": "CategoryResource.part", + "sync": false, + "label": null + }, + { + "name": "employeeCode", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Код сотрудника", + "map": "CategoryResource.employee", + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.CategoryResourceUpdate", + "description": "Тело запроса на обновление категории ресурсов (частичное обновление)", + "fields": [ + { + "name": "partId", + "type": "uuid", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Идентификатор ЗИП/ТМЦ", + "map": "CategoryResource.part", + "sync": false, + "label": null + }, + { + "name": "employeeCode", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Код сотрудника", + "map": "CategoryResource.employee", + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.CategoryResourceFilter", + "description": "Фильтры для списка категорий ресурсов", + "fields": [ + { + "name": "partName", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Частичное совпадение по названию ЗИП/ТМЦ", + "map": null, + "sync": false, + "label": null + }, + { + "name": "employeeName", + "type": "string", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Частичное совпадение по ФИО сотрудника", + "map": null, + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.CategoryResourceListRequest", + "description": "Запрос постраничного списка категорий ресурсов с фильтрацией", + "fields": [ + { + "name": "filter", + "type": "DTO.CategoryResourceFilter", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Фильтры", + "map": null, + "sync": false, + "label": null + }, + { + "name": "page", + "type": "DTO.PageRequest", + "required": false, + "nullable": true, + "unique": false, + "primary": false, + "description": "Параметры пагинации", + "map": null, + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.CategoryResourceListResponse", + "description": "Ответ постраничного списка категорий ресурсов", + "fields": [ + { + "name": "content", + "type": "DTO.CategoryResource[]", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Список категорий ресурсов", + "map": null, + "sync": false, + "label": null + }, + { + "name": "page", + "type": "DTO.PageInfo", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Метаданные пагинации", + "map": null, + "sync": false, + "label": null + } + ] + }, + { + "name": "DTO.PriceList", + "description": "Регистр цен — response-объект", + "fields": [ + { + "name": "costOfWorkingHours", + "type": "number", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Стоимость часов работы", + "map": "Employee.price", + "sync": true, + "label": null + }, + { + "name": "partPrice", + "type": "number", + "required": false, + "nullable": false, + "unique": false, + "primary": false, + "description": "Стоимость ЗИП/ТМЦ", + "map": "Part.price", + "sync": true, + "label": null + } + ] + } + ], + "apis": [ + { + "name": "API.Equipment", + "description": "API управления справочником оборудования", + "endpoints": [ + { + "name": "getEquipmentById", + "label": "GET /equipments/{id}", + "method": "GET", + "path": "/equipments/{id}", + "description": "Получить оборудование по идентификатору", + "attributes": [ + { + "name": "id", + "type": "uuid", + "description": "Идентификатор оборудования" + }, + { + "name": "response", + "type": "DTO.Equipment", + "description": null + } + ] + }, + { + "name": "listEquipments", + "label": "POST /equipments/page", + "method": "POST", + "path": "/equipments/page", + "description": "Постраничный список оборудования с фильтрацией", + "attributes": [ + { + "name": "request", + "type": "DTO.EquipmentListRequest", + "description": null + }, + { + "name": "response", + "type": "DTO.EquipmentListResponse", + "description": null + } + ] + }, + { + "name": "createEquipment", + "label": "POST /equipments", + "method": "POST", + "path": "/equipments", + "description": "Создать единицу оборудования", + "attributes": [ + { + "name": "request", + "type": "DTO.EquipmentCreate", + "description": null + } + ] + }, + { + "name": "updateEquipment", + "label": "PUT /equipments/{id}", + "method": "PUT", + "path": "/equipments/{id}", + "description": "Обновить оборудование", + "attributes": [ + { + "name": "id", + "type": "uuid", + "description": "Идентификатор оборудования" + }, + { + "name": "request", + "type": "DTO.EquipmentUpdate", + "description": null + } + ] + }, + { + "name": "deleteEquipment", + "label": "DELETE /equipments/{id}", + "method": "DELETE", + "path": "/equipments/{id}", + "description": "Удалить единицу оборудования", + "attributes": [ + { + "name": "id", + "type": "uuid", + "description": "Идентификатор оборудования" + } + ] + } + ] + }, + { + "name": "API.Employee", + "description": "API управления справочником сотрудников", + "endpoints": [ + { + "name": "getEmployeeByCode", + "label": "GET /employees/{code}", + "method": "GET", + "path": "/employees/{code}", + "description": "Получить сотрудника по табельному номеру", + "attributes": [ + { + "name": "code", + "type": "string", + "description": "Табельный номер сотрудника" + }, + { + "name": "response", + "type": "DTO.Employee", + "description": null + } + ] + }, + { + "name": "listEmployees", + "label": "POST /employees/page", + "method": "POST", + "path": "/employees/page", + "description": "Постраничный список сотрудников с фильтрацией", + "attributes": [ + { + "name": "request", + "type": "DTO.EmployeeListRequest", + "description": null + }, + { + "name": "response", + "type": "DTO.EmployeeListResponse", + "description": null + } + ] + }, + { + "name": "createEmployee", + "label": "POST /employees", + "method": "POST", + "path": "/employees", + "description": "Создать сотрудника", + "attributes": [ + { + "name": "request", + "type": "DTO.EmployeeCreate", + "description": null + } + ] + }, + { + "name": "updateEmployee", + "label": "PUT /employees/{code}", + "method": "PUT", + "path": "/employees/{code}", + "description": "Обновить сотрудника", + "attributes": [ + { + "name": "code", + "type": "string", + "description": "Табельный номер сотрудника" + }, + { + "name": "request", + "type": "DTO.EmployeeUpdate", + "description": null + } + ] + }, + { + "name": "deleteEmployee", + "label": "DELETE /employees/{code}", + "method": "DELETE", + "path": "/employees/{code}", + "description": "Удалить сотрудника", + "attributes": [ + { + "name": "code", + "type": "string", + "description": "Табельный номер сотрудника" + } + ] + } + ] + }, + { + "name": "API.Part", + "description": "API управления справочником ЗИП/ТМЦ", + "endpoints": [ + { + "name": "getPartById", + "label": "GET /parts/{id}", + "method": "GET", + "path": "/parts/{id}", + "description": "Получить ЗИП/ТМЦ по идентификатору", + "attributes": [ + { + "name": "id", + "type": "uuid", + "description": "Идентификатор ЗИП/ТМЦ" + }, + { + "name": "response", + "type": "DTO.Part", + "description": null + } + ] + }, + { + "name": "listParts", + "label": "POST /parts/page", + "method": "POST", + "path": "/parts/page", + "description": "Постраничный список ЗИП/ТМЦ с фильтрацией", + "attributes": [ + { + "name": "request", + "type": "DTO.PartListRequest", + "description": null + }, + { + "name": "response", + "type": "DTO.PartListResponse", + "description": null + } + ] + }, + { + "name": "createPart", + "label": "POST /parts", + "method": "POST", + "path": "/parts", + "description": "Создать ЗИП/ТМЦ", + "attributes": [ + { + "name": "request", + "type": "DTO.PartCreate", + "description": null + } + ] + }, + { + "name": "updatePart", + "label": "PUT /parts/{id}", + "method": "PUT", + "path": "/parts/{id}", + "description": "Обновить ЗИП/ТМЦ", + "attributes": [ + { + "name": "id", + "type": "uuid", + "description": "Идентификатор ЗИП/ТМЦ" + }, + { + "name": "request", + "type": "DTO.PartUpdate", + "description": null + } + ] + }, + { + "name": "deletePart", + "label": "DELETE /parts/{id}", + "method": "DELETE", + "path": "/parts/{id}", + "description": "Удалить ЗИП/ТМЦ", + "attributes": [ + { + "name": "id", + "type": "uuid", + "description": "Идентификатор ЗИП/ТМЦ" + } + ] + } + ] + }, + { + "name": "API.CategoryResource", + "description": "API управления категориями ресурсов", + "endpoints": [ + { + "name": "getCategoryResourceById", + "label": "GET /category-resources/{id}", + "method": "GET", + "path": "/category-resources/{id}", + "description": "Получить категорию ресурсов по идентификатору", + "attributes": [ + { + "name": "id", + "type": "uuid", + "description": "Идентификатор категории ресурсов" + }, + { + "name": "response", + "type": "DTO.CategoryResource", + "description": null + } + ] + }, + { + "name": "listCategoryResources", + "label": "POST /category-resources/page", + "method": "POST", + "path": "/category-resources/page", + "description": "Постраничный список категорий ресурсов с фильтрацией", + "attributes": [ + { + "name": "request", + "type": "DTO.CategoryResourceListRequest", + "description": null + }, + { + "name": "response", + "type": "DTO.CategoryResourceListResponse", + "description": null + } + ] + }, + { + "name": "createCategoryResource", + "label": "POST /category-resources", + "method": "POST", + "path": "/category-resources", + "description": "Создать категорию ресурсов", + "attributes": [ + { + "name": "request", + "type": "DTO.CategoryResourceCreate", + "description": null + } + ] + }, + { + "name": "updateCategoryResource", + "label": "PUT /category-resources/{id}", + "method": "PUT", + "path": "/category-resources/{id}", + "description": "Обновить категорию ресурсов", + "attributes": [ + { + "name": "id", + "type": "uuid", + "description": "Идентификатор категории ресурсов" + }, + { + "name": "request", + "type": "DTO.CategoryResourceUpdate", + "description": null + } + ] + }, + { + "name": "deleteCategoryResource", + "label": "DELETE /category-resources/{id}", + "method": "DELETE", + "path": "/category-resources/{id}", + "description": "Удалить категорию ресурсов", + "attributes": [ + { + "name": "id", + "type": "uuid", + "description": "Идентификатор категории ресурсов" + } + ] + } + ] + }, + { + "name": "API.PriceList", + "description": "API получения регистра цен", + "endpoints": [ + { + "name": "getPriceList", + "label": "GET /price-list", + "method": "GET", + "path": "/price-list", + "description": "Получить регистр цен", + "attributes": [ + { + "name": "response", + "type": "DTO.PriceList", + "description": null + } + ] + } + ] + } + ] +} diff --git a/client/.dockerignore b/client/.dockerignore deleted file mode 100644 index 0e48e48..0000000 --- a/client/.dockerignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules -dist -.git -.env -.env.local -.env.*.local -npm-debug.log* diff --git a/client/.env.example b/client/.env.example index d849548..f0da5a7 100644 --- a/client/.env.example +++ b/client/.env.example @@ -2,4 +2,3 @@ VITE_API_URL=http://localhost:3000 VITE_KEYCLOAK_URL=https://sso.greact.ru VITE_KEYCLOAK_REALM=toir VITE_KEYCLOAK_CLIENT_ID=toir-frontend - diff --git a/client/.eslintrc.cjs b/client/.eslintrc.cjs deleted file mode 100644 index d6c9537..0000000 --- a/client/.eslintrc.cjs +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - root: true, - env: { browser: true, es2020: true }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:react-hooks/recommended', - ], - ignorePatterns: ['dist', '.eslintrc.cjs'], - parser: '@typescript-eslint/parser', - plugins: ['react-refresh'], - rules: { - 'react-refresh/only-export-components': [ - 'warn', - { allowConstantExport: true }, - ], - }, -} diff --git a/client/.gitignore b/client/.gitignore index 03a8a94..a547bf3 100644 --- a/client/.gitignore +++ b/client/.gitignore @@ -1,32 +1,22 @@ -# Dependencies -node_modules/ - -# Build -dist/ -dist-ssr/ - -# Environment -.env -.env.local -.env.*.local -*.local - # Logs -logs/ +logs *.log npm-debug.log* yarn-debug.log* +yarn-error.log* pnpm-debug.log* lerna-debug.log* -# OS files -.DS_Store -Thumbs.db +node_modules +dist +dist-ssr +*.local -# Editor / IDE +# Editor directories and files .vscode/* !.vscode/extensions.json -.idea/ +.idea +.DS_Store *.suo *.ntvs* *.njsproj diff --git a/client/Dockerfile b/client/Dockerfile deleted file mode 100644 index ff96fd3..0000000 --- a/client/Dockerfile +++ /dev/null @@ -1,27 +0,0 @@ -FROM node:20-alpine AS build - -WORKDIR /app - -COPY package*.json ./ -RUN npm ci - -COPY . . - -ARG VITE_API_URL -ARG VITE_KEYCLOAK_URL -ARG VITE_KEYCLOAK_REALM -ARG VITE_KEYCLOAK_CLIENT_ID - -ENV VITE_API_URL=$VITE_API_URL -ENV VITE_KEYCLOAK_URL=$VITE_KEYCLOAK_URL -ENV VITE_KEYCLOAK_REALM=$VITE_KEYCLOAK_REALM -ENV VITE_KEYCLOAK_CLIENT_ID=$VITE_KEYCLOAK_CLIENT_ID - -RUN npm run build - -FROM nginx:1.27-alpine AS runtime - -COPY nginx/default.conf /etc/nginx/conf.d/default.conf -COPY --from=build /app/dist /usr/share/nginx/html - -EXPOSE 80 diff --git a/client/README.md b/client/README.md index 0d6babe..7dbf7eb 100644 --- a/client/README.md +++ b/client/README.md @@ -4,27 +4,70 @@ This template provides a minimal setup to get React working in Vite with HMR and Currently, two official plugins are available: -- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh -- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh +- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Oxc](https://oxc.rs) +- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) + +## React Compiler + +The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation). ## Expanding the ESLint configuration -If you are developing a production application, we recommend updating the configuration to enable type aware lint rules: - -- Configure the top-level `parserOptions` property like this: +If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules: ```js -export default { - // other rules... - parserOptions: { - ecmaVersion: 'latest', - sourceType: 'module', - project: ['./tsconfig.json', './tsconfig.node.json'], - tsconfigRootDir: __dirname, +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + + // Remove tseslint.configs.recommended and replace with this + tseslint.configs.recommendedTypeChecked, + // Alternatively, use this for stricter rules + tseslint.configs.strictTypeChecked, + // Optionally, add this for stylistic rules + tseslint.configs.stylisticTypeChecked, + + // Other configs... + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, }, -} +]) ``` -- Replace `plugin:@typescript-eslint/recommended` to `plugin:@typescript-eslint/recommended-type-checked` or `plugin:@typescript-eslint/strict-type-checked` -- Optionally add `plugin:@typescript-eslint/stylistic-type-checked` -- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and add `plugin:react/recommended` & `plugin:react/jsx-runtime` to the `extends` list +You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules: + +```js +// eslint.config.js +import reactX from 'eslint-plugin-react-x' +import reactDom from 'eslint-plugin-react-dom' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + // Other configs... + // Enable lint rules for React + reactX.configs['recommended-typescript'], + // Enable lint rules for React DOM + reactDom.configs.recommended, + ], + languageOptions: { + parserOptions: { + project: ['./tsconfig.node.json', './tsconfig.app.json'], + tsconfigRootDir: import.meta.dirname, + }, + // other options... + }, + }, +]) +``` diff --git a/client/eslint.config.js b/client/eslint.config.js new file mode 100644 index 0000000..5e6b472 --- /dev/null +++ b/client/eslint.config.js @@ -0,0 +1,23 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' +import { defineConfig, globalIgnores } from 'eslint/config' + +export default defineConfig([ + globalIgnores(['dist']), + { + files: ['**/*.{ts,tsx}'], + extends: [ + js.configs.recommended, + tseslint.configs.recommended, + reactHooks.configs.flat.recommended, + reactRefresh.configs.vite, + ], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + }, +]) diff --git a/client/index.html b/client/index.html index e4b78ea..3269aca 100644 --- a/client/index.html +++ b/client/index.html @@ -2,9 +2,9 @@ - + - Vite + React + TS + client
diff --git a/client/nginx/default.conf b/client/nginx/default.conf deleted file mode 100644 index d11675a..0000000 --- a/client/nginx/default.conf +++ /dev/null @@ -1,27 +0,0 @@ -server { - listen 80; - server_name _; - - root /usr/share/nginx/html; - index index.html; - - location /api/ { - proxy_pass http://toir-server:3000/; - proxy_http_version 1.1; - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-Host $host; - } - - location / { - try_files $uri $uri/ /index.html; - } - - location = /healthz { - access_log off; - add_header Content-Type text/plain; - return 200 'ok'; - } -} diff --git a/client/package-lock.json b/client/package-lock.json index e60c40f..db719fd 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -10,24 +10,26 @@ "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", - "@mui/material": "^7.3.9", + "@mui/icons-material": "^7.3.5", + "@mui/material": "^7.3.5", "keycloak-js": "^26.2.3", - "ra-data-simple-rest": "^5.14.4", - "react": "^18.2.0", - "react-admin": "^5.14.4", - "react-dom": "^18.2.0" + "react": "^19.2.4", + "react-admin": "^5.14.5", + "react-dom": "^19.2.4" }, "devDependencies": { - "@types/react": "^18.2.55", - "@types/react-dom": "^18.2.19", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", - "@vitejs/plugin-react": "^4.2.1", - "eslint": "^8.56.0", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.5", - "typescript": "^5.2.2", - "vite": "^5.1.0" + "@eslint/js": "^9.39.4", + "@types/node": "^24.12.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^9.39.4", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.4.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.57.0", + "vite": "^8.0.1" } }, "node_modules/@babel/code-frame": { @@ -85,15 +87,12 @@ "url": "https://opencollective.com/babel" } }, - "node_modules/@babel/core/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "node_modules/@babel/core/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } + "license": "MIT" }, "node_modules/@babel/generator": { "version": "7.29.1", @@ -128,16 +127,6 @@ "node": ">=6.9.0" } }, - "node_modules/@babel/helper-compilation-targets/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "node_modules/@babel/helper-globals": { "version": "7.28.0", "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", @@ -178,16 +167,6 @@ "@babel/core": "^7.0.0" } }, - "node_modules/@babel/helper-plugin-utils": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz", - "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, "node_modules/@babel/helper-string-parser": { "version": "7.27.1", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", @@ -217,23 +196,23 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -245,42 +224,10 @@ "node": ">=6.0.0" } }, - "node_modules/@babel/plugin-transform-react-jsx-self": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz", - "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, - "node_modules/@babel/plugin-transform-react-jsx-source": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz", - "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/helper-plugin-utils": "^7.27.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0-0" - } - }, "node_modules/@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", + "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -331,6 +278,43 @@ "node": ">=6.9.0" } }, + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@emotion/babel-plugin": { "version": "11.13.5", "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz", @@ -350,12 +334,6 @@ "stylis": "4.2.0" } }, - "node_modules/@emotion/babel-plugin/node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "license": "MIT" - }, "node_modules/@emotion/cache": { "version": "11.14.0", "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz", @@ -483,397 +461,6 @@ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==", "license": "MIT" }, - "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -893,6 +480,19 @@ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", @@ -903,102 +503,143 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, "engines": { - "node": "*" + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" }, "engines": { - "node": ">=10.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "brace-expansion": "^1.1.7" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": "*" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -1015,13 +656,19 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "BSD-3-Clause" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", @@ -1302,42 +949,33 @@ } } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "node_modules/@napi-rs/wasm-runtime": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.2.tgz", + "integrity": "sha512-sNXv5oLJ7ob93xkZ1XnxisYhGYXfaG9f65/ZgYuAu3qt7b3NadcOEhLvx28hv31PgX8SZJRYrAIPQilQmFpLVw==", "dev": true, "license": "MIT", + "optional": true, "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" + "@tybys/wasm-util": "^0.10.1" }, - "engines": { - "node": ">= 8" + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "peerDependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1" } }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "node_modules/@oxc-project/types": { + "version": "0.122.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.122.0.tgz", + "integrity": "sha512-oLAl5kBpV4w69UtFZ9xqcmTi+GENWOcPF7FCrczTiBbmC0ibXxCwyvZGbO39rCVEuLGAZM84DH0pUIyyv/YJzA==", "dev": true, "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" + "funding": { + "url": "https://github.com/sponsors/Boshen" } }, "node_modules/@popperjs/core": { @@ -1350,31 +988,10 @@ "url": "https://opencollective.com/popperjs" } }, - "node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz", - "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ] - }, - "node_modules/@rollup/rollup-android-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz", - "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==", + "node_modules/@rolldown/binding-android-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-pv1y2Fv0JybcykuiiD3qBOBdz6RteYojRFY1d+b95WVuzx211CRh+ytI/+9iVyWQ6koTh5dawe4S/yRfOFjgaA==", "cpu": [ "arm64" ], @@ -1383,12 +1000,15 @@ "optional": true, "os": [ "android" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz", - "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==", + "node_modules/@rolldown/binding-darwin-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-cFYr6zTG/3PXXF3pUO+umXxt1wkRK/0AYT8lDwuqvRC+LuKYWSAQAQZjCWDQpAH172ZV6ieYrNnFzVVcnSflAg==", "cpu": [ "arm64" ], @@ -1397,12 +1017,15 @@ "optional": true, "os": [ "darwin" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz", - "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==", + "node_modules/@rolldown/binding-darwin-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-ZCsYknnHzeXYps0lGBz8JrF37GpE9bFVefrlmDrAQhOEi4IOIlcoU1+FwHEtyXGx2VkYAvhu7dyBf75EJQffBw==", "cpu": [ "x64" ], @@ -1411,26 +1034,15 @@ "optional": true, "os": [ "darwin" - ] - }, - "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz", - "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==", - "cpu": [ - "arm64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz", - "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==", + "node_modules/@rolldown/binding-freebsd-x64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.0-rc.12.tgz", + "integrity": "sha512-dMLeprcVsyJsKolRXyoTH3NL6qtsT0Y2xeuEA8WQJquWFXkEC4bcu1rLZZSnZRMtAqwtrF/Ib9Ddtpa/Gkge9Q==", "cpu": [ "x64" ], @@ -1439,12 +1051,15 @@ "optional": true, "os": [ "freebsd" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz", - "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==", + "node_modules/@rolldown/binding-linux-arm-gnueabihf": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.0-rc.12.tgz", + "integrity": "sha512-YqWjAgGC/9M1lz3GR1r1rP79nMgo3mQiiA+Hfo+pvKFK1fAJ1bCi0ZQVh8noOqNacuY1qIcfyVfP6HoyBRZ85Q==", "cpu": [ "arm" ], @@ -1453,194 +1068,135 @@ "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz", - "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==", - "cpu": [ - "arm" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz", - "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==", + "node_modules/@rolldown/binding-linux-arm64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-/I5AS4cIroLpslsmzXfwbe5OmWvSsrFuEw3mwvbQ1kDxJ822hFHIx+vsN/TAzNVyepI/j/GSzrtCIwQPeKCLIg==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz", - "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==", + "node_modules/@rolldown/binding-linux-arm64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-V6/wZztnBqlx5hJQqNWwFdxIKN0m38p8Jas+VoSfgH54HSj9tKTt1dZvG6JRHcjh6D7TvrJPWFGaY9UBVOaWPw==", "cpu": [ "arm64" ], "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz", - "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==", - "cpu": [ - "loong64" + "libc": [ + "musl" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz", - "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==", - "cpu": [ - "loong64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz", - "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==", + "node_modules/@rolldown/binding-linux-ppc64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-AP3E9BpcUYliZCxa3w5Kwj9OtEVDYK6sVoUzy4vTOJsjPOgdaJZKFmN4oOlX0Wp0RPV2ETfmIra9x1xuayFB7g==", "cpu": [ "ppc64" ], "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz", - "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==", - "cpu": [ - "ppc64" + "libc": [ + "glibc" ], - "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz", - "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==", - "cpu": [ - "riscv64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz", - "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz", - "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==", + "node_modules/@rolldown/binding-linux-s390x-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-nWwpvUSPkoFmZo0kQazZYOrT7J5DGOJ/+QHHzjvNlooDZED8oH82Yg67HvehPPLAg5fUff7TfWFHQS8IV1n3og==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz", - "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==", + "node_modules/@rolldown/binding-linux-x64-gnu": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.0-rc.12.tgz", + "integrity": "sha512-RNrafz5bcwRy+O9e6P8Z/OCAJW/A+qtBczIqVYwTs14pf4iV1/+eKEjdOUta93q2TsT/FI0XYDP3TCky38LMAg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz", - "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==", + "node_modules/@rolldown/binding-linux-x64-musl": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.0-rc.12.tgz", + "integrity": "sha512-Jpw/0iwoKWx3LJ2rc1yjFrj+T7iHZn2JDg1Yny1ma0luviFS4mhAIcd1LFNxK3EYu3DHWCps0ydXQ5i/rrJ2ig==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ "linux" - ] - }, - "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz", - "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==", - "cpu": [ - "x64" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz", - "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==", + "node_modules/@rolldown/binding-openharmony-arm64": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.0-rc.12.tgz", + "integrity": "sha512-vRugONE4yMfVn0+7lUKdKvN4D5YusEiPilaoO2sgUWpCvrncvWgPMzK00ZFFJuiPgLwgFNP5eSiUlv2tfc+lpA==", "cpu": [ "arm64" ], @@ -1649,12 +1205,32 @@ "optional": true, "os": [ "openharmony" - ] + ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz", - "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==", + "node_modules/@rolldown/binding-wasm32-wasi": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.0-rc.12.tgz", + "integrity": "sha512-ykGiLr/6kkiHc0XnBfmFJuCjr5ZYKKofkx+chJWDjitX+KsJuAmrzWhwyOMSHzPhzOHOy7u9HlFoa5MoAOJ/Zg==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^1.1.1" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@rolldown/binding-win32-arm64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-5eOND4duWkwx1AzCxadcOrNeighiLwMInEADT0YM7xeEOOFcovWZCq8dadXgcRHSf3Ulh1kFo/qvzoFiCLOL1Q==", "cpu": [ "arm64" ], @@ -1663,26 +1239,15 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz", - "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==", - "cpu": [ - "ia32" ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "engines": { + "node": "^20.19.0 || >=22.12.0" + } }, - "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz", - "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==", + "node_modules/@rolldown/binding-win32-x64-msvc": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.0-rc.12.tgz", + "integrity": "sha512-PyqoipaswDLAZtot351MLhrlrh6lcZPo2LSYE+VDxbVk24LVKAGOuE4hb8xZQmrPAuEtTZW8E6D2zc5EUZX4Lw==", "cpu": [ "x64" ], @@ -1691,26 +1256,22 @@ "optional": true, "os": [ "win32" - ] - }, - "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz", - "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==", - "cpu": [ - "x64" ], + "engines": { + "node": "^20.19.0 || >=22.12.0" + } + }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.7", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.7.tgz", + "integrity": "sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==", "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] + "license": "MIT" }, "node_modules/@tanstack/query-core": { - "version": "5.90.20", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.20.tgz", - "integrity": "sha512-OMD2HLpNouXEfZJWcKeVKUgQ5n+n3A2JFmBaScpNDUqSrQSjiveC7dKMe53uJUg1nDG16ttFPz2xfilz6i2uVg==", + "version": "5.96.1", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.96.1.tgz", + "integrity": "sha512-u1yBgtavSy+N8wgtW3PiER6UpxcplMje65yXnnVgiHTqiMwLlxiw4WvQDrXyn+UD6lnn8kHaxmerJUzQcV/MMg==", "license": "MIT", "funding": { "type": "github", @@ -1718,12 +1279,12 @@ } }, "node_modules/@tanstack/react-query": { - "version": "5.90.21", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.21.tgz", - "integrity": "sha512-0Lu6y5t+tvlTJMTO7oh5NSpJfpg/5D41LlThfepTixPYkJ0sE2Jj0m0f6yYqujBwIXlId87e234+MxG3D3g7kg==", + "version": "5.96.1", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.96.1.tgz", + "integrity": "sha512-2X7KYK5KKWUKGeWCVcqxXAkYefJtrKB7tSKWgeG++b0H6BRHxQaLSSi8AxcgjmUnnosHuh9WsFZqvE16P1WCzA==", "license": "MIT", "dependencies": { - "@tanstack/query-core": "5.90.20" + "@tanstack/query-core": "5.96.1" }, "funding": { "type": "github", @@ -1733,49 +1294,15 @@ "react": "^18 || ^19" } }, - "node_modules/@types/babel__core": { - "version": "7.20.5", - "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", - "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", "dev": true, "license": "MIT", + "optional": true, "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", - "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz", - "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__template": { - "version": "7.4.4", - "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", - "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.1.0", - "@babel/types": "^7.0.0" - } - }, - "node_modules/@types/babel__traverse": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz", - "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@babel/types": "^7.28.2" + "tslib": "^2.4.0" } }, "node_modules/@types/estree": { @@ -1792,6 +1319,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.12.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.1.tgz", + "integrity": "sha512-v6Ct1W1Fdz7xg5jYCg4FTrbNcIqzds2jv/HL6+5Rs/Cyjf0oljAgW59zvDZXyYG7nt9MLrAFJv9erP/fLjwt+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, "node_modules/@types/parse-json": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz", @@ -1805,23 +1342,22 @@ "license": "MIT" }, "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==", + "version": "19.2.14", + "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", + "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "license": "MIT", "dependencies": { - "@types/prop-types": "*", "csstype": "^3.2.2" } }, "node_modules/@types/react-dom": { - "version": "18.3.7", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.3.7.tgz", - "integrity": "sha512-MEe3UeoENYVFXzoXEWsvcpg6ZvlrFNlOQ7EOsvhI3CfAXwzPfO8Qwuxd40nepsYKqyyVQnTdEfv68q91yLcKrQ==", + "version": "19.2.3", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", + "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", "dev": true, "license": "MIT", "peerDependencies": { - "@types/react": "^18.0.0" + "@types/react": "^19.2.0" } }, "node_modules/@types/react-transition-group": { @@ -1833,13 +1369,6 @@ "@types/react": "*" } }, - "node_modules/@types/semver": { - "version": "7.7.1", - "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.1.tgz", - "integrity": "sha512-FmgJfu+MOcQ370SD0ev7EI8TlCAfKYU+B4m5T3yXc1CiRN94g/SZPtsCkk506aUDtlMnFZvasDwHHUcZUEaYuA==", - "dev": true, - "license": "MIT" - }, "node_modules/@types/trusted-types": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", @@ -1848,124 +1377,159 @@ "optional": true }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz", - "integrity": "sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", + "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/regexpp": "^4.5.1", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/type-utils": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "graphemer": "^1.4.0", - "ignore": "^5.2.4", + "@eslint-community/regexpp": "^4.12.2", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/type-utils": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", - "eslint": "^7.0.0 || ^8.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "@typescript-eslint/parser": "^8.58.0", + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.21.0.tgz", - "integrity": "sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", + "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4" + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/project-service": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", + "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.58.0", + "@typescript-eslint/types": "^8.58.0", + "debug": "^4.4.3" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz", - "integrity": "sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", + "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0" + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@typescript-eslint/type-utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz", - "integrity": "sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag==", + "node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", + "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", "dev": true, "license": "MIT", - "dependencies": { - "@typescript-eslint/typescript-estree": "6.21.0", - "@typescript-eslint/utils": "6.21.0", - "debug": "^4.3.4", - "ts-api-utils": "^1.0.1" - }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", + "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "debug": "^4.4.3", + "ts-api-utils": "^2.5.0" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.21.0.tgz", - "integrity": "sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", + "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", "dev": true, "license": "MIT", "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", @@ -1973,104 +1537,164 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz", - "integrity": "sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", + "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", "dev": true, - "license": "BSD-2-Clause", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/visitor-keys": "6.21.0", - "debug": "^4.3.4", - "globby": "^11.1.0", - "is-glob": "^4.0.3", - "minimatch": "9.0.3", - "semver": "^7.5.4", - "ts-api-utils": "^1.0.1" + "@typescript-eslint/project-service": "8.58.0", + "@typescript-eslint/tsconfig-utils": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.7.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", + "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" } }, "node_modules/@typescript-eslint/utils": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.21.0.tgz", - "integrity": "sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", + "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@types/json-schema": "^7.0.12", - "@types/semver": "^7.5.0", - "@typescript-eslint/scope-manager": "6.21.0", - "@typescript-eslint/types": "6.21.0", - "@typescript-eslint/typescript-estree": "6.21.0", - "semver": "^7.5.4" + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "eslint": "^7.0.0 || ^8.0.0" + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz", - "integrity": "sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", + "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "6.21.0", - "eslint-visitor-keys": "^3.4.1" + "@typescript-eslint/types": "8.58.0", + "eslint-visitor-keys": "^5.0.0" }, "engines": { - "node": "^16.0.0 || >=18.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/typescript-eslint" } }, - "node_modules/@ungap/structured-clone": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", "dev": true, - "license": "ISC" + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } }, "node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.0.1.tgz", + "integrity": "sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" + "@rolldown/pluginutils": "1.0.0-rc.7" }, "engines": { - "node": "^14.18.0 || >=16.0.0" + "node": "^20.19.0 || >=22.12.0" }, "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + "@rolldown/plugin-babel": "^0.1.7 || ^0.2.0", + "babel-plugin-react-compiler": "^1.0.0", + "vite": "^8.0.0" + }, + "peerDependenciesMeta": { + "@rolldown/plugin-babel": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + } } }, "node_modules/acorn": { @@ -2113,16 +1737,6 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", @@ -2146,16 +1760,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/array-union": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", - "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/attr-accept": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz", @@ -2197,9 +1801,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.8", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", - "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", + "version": "2.10.13", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz", + "integrity": "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2210,32 +1814,20 @@ } }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -2253,11 +1845,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -2323,9 +1915,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001779", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001779.tgz", - "integrity": "sha512-U5og2PN7V4DMgF50YPNtnZJGWVLFjjsN3zb6uMT5VGYIewieDj1upwfuVNXf4Kor+89c3iCRJnSzMD5LmTvsfA==", + "version": "1.0.30001784", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz", + "integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==", "dev": true, "funding": [ { @@ -2397,10 +1989,9 @@ "license": "MIT" }, "node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "dev": true, + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", "license": "MIT" }, "node_modules/cookie": { @@ -2432,6 +2023,15 @@ "node": ">=10" } }, + "node_modules/cosmiconfig/node_modules/yaml": { + "version": "1.10.3", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz", + "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==", + "license": "ISC", + "engines": { + "node": ">= 6" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -2536,38 +2136,22 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/diacritic": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/diacritic/-/diacritic-0.0.2.tgz", "integrity": "sha512-iQCeDkSPwkfwWPr+HZZ49WRrM2FSI9097Q9w7agyRCdLcF9Eh2Ek0sHKcmMWx2oZVBjRBE/sziGFjZu0uf1Jbg==", "license": "MIT" }, - "node_modules/dir-glob": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", - "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", - "dev": true, - "license": "MIT", - "dependencies": { - "path-type": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/dom-helpers": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/dom-helpers/-/dom-helpers-5.2.1.tgz", @@ -2602,9 +2186,9 @@ } }, "node_modules/electron-to-chromium": { - "version": "1.5.313", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", - "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "version": "1.5.331", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz", + "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==", "dev": true, "license": "ISC" }, @@ -2647,45 +2231,6 @@ "node": ">= 0.4" } }, - "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/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -2709,89 +2254,99 @@ } }, "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-plugin-react-hooks": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.6.2.tgz", - "integrity": "sha512-QzliNJq4GinDBcD8gPB5v0wh6g8q3SUi6EFF0x8N/BL9PoVs0atuGc47ozMRyOWAKdwaZ5OnbOEa3WR+dSGKuQ==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-7.0.1.tgz", + "integrity": "sha512-O0d0m04evaNzEPoSW+59Mezf8Qt0InfgGIBJnpC0h3NH/WjUAR7BIKUfysC6todmtiZ/A0oUVS8Gce0WhBrHsA==", "dev": true, "license": "MIT", + "dependencies": { + "@babel/core": "^7.24.4", + "@babel/parser": "^7.24.4", + "hermes-parser": "^0.25.1", + "zod": "^3.25.0 || ^4.0.0", + "zod-validation-error": "^3.5.0 || ^4.0.0" + }, "engines": { - "node": ">=10" + "node": ">=18" }, "peerDependencies": { - "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0" + "eslint": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0" } }, "node_modules/eslint-plugin-react-refresh": { - "version": "0.4.26", - "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.4.26.tgz", - "integrity": "sha512-1RETEylht2O6FM/MvgnyvT+8K21wLqDNg4qD51Zj3guhjt433XbnnkVttHMyaVyAFD03QSV4LPS5iE3VQmO7XQ==", + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-react-refresh/-/eslint-plugin-react-refresh-0.5.2.tgz", + "integrity": "sha512-hmgTH57GfzoTFjVN0yBwTggnsVUF2tcqi7RJZHqi9lIezSs4eFyAMktA68YD4r5kNw1mxyY4dmkyoFDb3FIqrA==", "dev": true, "license": "MIT", "peerDependencies": { - "eslint": ">=8.40" + "eslint": "^9 || ^10" } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -2799,62 +2354,38 @@ "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2919,36 +2450,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" - }, - "engines": { - "node": ">=8.6.0" - } - }, - "node_modules/fast-glob/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", @@ -2963,27 +2464,35 @@ "dev": true, "license": "MIT" }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } } }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/file-selector": { @@ -2998,19 +2507,6 @@ "node": ">= 12" } }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/filter-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", @@ -3044,31 +2540,23 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -3143,28 +2631,6 @@ "node": ">= 0.4" } }, - "node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3178,62 +2644,14 @@ "node": ">=10.13.0" } }, - "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/glob/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", + "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/globby": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", - "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", - "dev": true, - "license": "MIT", - "dependencies": { - "array-union": "^2.1.0", - "dir-glob": "^3.0.1", - "fast-glob": "^3.2.9", - "ignore": "^5.2.0", - "merge2": "^1.4.1", - "slash": "^3.0.0" - }, - "engines": { - "node": ">=10" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -3251,13 +2669,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -3304,6 +2715,23 @@ "node": ">= 0.4" } }, + "node_modules/hermes-estree": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz", + "integrity": "sha512-0wUoCcLp+5Ev5pDW2OriHC2MJCbwLwuRx+gAqMTOkGKJJiBCLjtrvy4PWUGn6MIVefecRpzoOZ/UV6iGdOr+Cw==", + "dev": true, + "license": "MIT" + }, + "node_modules/hermes-parser": { + "version": "0.25.1", + "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.25.1.tgz", + "integrity": "sha512-6pEjquH3rqaI6cYAXYPcz9MS4rY6R4ngRgrgfDshRptUZIc3lw0MCIJIGDj9++mfySOuPTHB4nrSW99BCvOPIA==", + "dev": true, + "license": "MIT", + "dependencies": { + "hermes-estree": "0.25.1" + } + }, "node_modules/hoist-non-react-statics": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz", @@ -3364,25 +2792,6 @@ "node": ">=18.0.0" } }, - "node_modules/inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", - "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, - "license": "ISC", - "dependencies": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "node_modules/inherits": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true, - "license": "ISC" - }, "node_modules/is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", @@ -3427,26 +2836,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -3567,6 +2956,279 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", + "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.32.0", + "lightningcss-darwin-arm64": "1.32.0", + "lightningcss-darwin-x64": "1.32.0", + "lightningcss-freebsd-x64": "1.32.0", + "lightningcss-linux-arm-gnueabihf": "1.32.0", + "lightningcss-linux-arm64-gnu": "1.32.0", + "lightningcss-linux-arm64-musl": "1.32.0", + "lightningcss-linux-x64-gnu": "1.32.0", + "lightningcss-linux-x64-musl": "1.32.0", + "lightningcss-win32-arm64-msvc": "1.32.0", + "lightningcss-win32-x64-msvc": "1.32.0" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.32.0.tgz", + "integrity": "sha512-YK7/ClTt4kAK0vo6w3X+Pnm0D2cf2vPHbhOXdoNti1Ga0al1P4TBZhwjATvjNwLEBCnKvjJc2jQgHXH0NEwlAg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", + "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.32.0.tgz", + "integrity": "sha512-U+QsBp2m/s2wqpUYT/6wnlagdZbtZdndSmut/NJqlCcMLTWp5muCrID+K5UJ6jqD2BFshejCYXniPDbNh73V8w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.32.0.tgz", + "integrity": "sha512-JCTigedEksZk3tHTTthnMdVfGf61Fky8Ji2E4YjUTEQX14xiy/lTzXnu1vwiZe3bYe0q+SpsSH/CTeDXK6WHig==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.32.0.tgz", + "integrity": "sha512-x6rnnpRa2GL0zQOkt6rts3YDPzduLpWvwAF6EMhXFVZXD4tPrBkEFqzGowzCsIWsPjqSK+tyNEODUBXeeVHSkw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.32.0.tgz", + "integrity": "sha512-0nnMyoyOLRJXfbMOilaSRcLH3Jw5z9HDNGfT/gwCPgaDjnx0i8w7vBzFLFR1f6CMLKF8gVbebmkUN3fa/kQJpQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.32.0.tgz", + "integrity": "sha512-UpQkoenr4UJEzgVIYpI80lDFvRmPVg6oqboNHfoH4CQIfNA+HOrZ7Mo7KZP02dC6LjghPQJeBsvXhJod/wnIBg==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.32.0.tgz", + "integrity": "sha512-V7Qr52IhZmdKPVr+Vtw8o+WLsQJYCTd8loIfpDaMRWGUZfBOYEJeyJIkqGIDMZPwPx24pUMfwSxxI8phr/MbOA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.32.0.tgz", + "integrity": "sha512-bYcLp+Vb0awsiXg/80uCRezCYHNg1/l3mt0gzHnWV9XP1W5sKa5/TCdGWaR/zBM2PeF/HbsQv/j2URNOiVuxWg==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.32.0.tgz", + "integrity": "sha512-8SbC8BR40pS6baCM8sbtYDSwEVQd4JlFTOlaD3gWGHfThTcABnNDBda6eTZeqbofalIJhFx0qKzgHJmcPTnGdw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.32.0", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.32.0.tgz", + "integrity": "sha512-Amq9B/SoZYdDi1kFrojnoqPLxYhQ4Wo5XiL8EVJrVsB8ARoC1PWW6VGtT0WKCemjy8aC+louJnjS7U18x3b06Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -3590,9 +3252,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "license": "MIT" }, "node_modules/lodash.merge": { @@ -3633,44 +3295,17 @@ "node": ">= 0.4" } }, - "node_modules/merge2": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", - "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/micromatch": { - "version": "4.0.8", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", - "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", - "dev": true, - "license": "MIT", - "dependencies": { - "braces": "^3.0.3", - "picomatch": "^2.3.1" - }, - "engines": { - "node": ">=8.6" - } - }, "node_modules/minimatch": { - "version": "9.0.3", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", - "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^2.0.1" + "brace-expansion": "^1.1.7" }, "engines": { - "node": ">=16 || 14 >=14.17" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, "node_modules/ms": { @@ -3720,9 +3355,9 @@ } }, "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", "dev": true, "license": "MIT" }, @@ -3759,16 +3394,6 @@ "node": ">= 0.4" } }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -3859,16 +3484,6 @@ "node": ">=8" } }, - "node_modules/path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", @@ -3901,13 +3516,13 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" @@ -3997,31 +3612,10 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/ra-core": { - "version": "5.14.4", - "resolved": "https://registry.npmjs.org/ra-core/-/ra-core-5.14.4.tgz", - "integrity": "sha512-kbZPQiZqyV/cz25kH5+CZ3PFzDMonqV1zBYSkIwdowBm8bFOqHYu4u5xj/R5N6Kb/x64gQButGCTmL+78uP7hg==", + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/ra-core/-/ra-core-5.14.5.tgz", + "integrity": "sha512-Ztis8Rb1f0XZbnT3ZQTP0W6dmpb26S/ml/IhtSKG299p6t7Pm70DE1N5QA1G+AM3gx3iz17LX5gJSv7+iuly2A==", "license": "MIT", "dependencies": { "date-fns": "^3.6.0", @@ -4042,41 +3636,29 @@ "react-router-dom": "^6.28.1 || ^7.1.1" } }, - "node_modules/ra-data-simple-rest": { - "version": "5.14.4", - "resolved": "https://registry.npmjs.org/ra-data-simple-rest/-/ra-data-simple-rest-5.14.4.tgz", - "integrity": "sha512-WdksZs/u+c2BSNx1RxiYjz+pAqB2j8VUeSP3gzk2Z1Rs0Idht7RN943DqImcskSVKvJ9NTjYIk4XQ4UcsvuJAA==", - "license": "MIT", - "dependencies": { - "query-string": "^7.1.3" - }, - "peerDependencies": { - "ra-core": "^5.0.0" - } - }, "node_modules/ra-i18n-polyglot": { - "version": "5.14.4", - "resolved": "https://registry.npmjs.org/ra-i18n-polyglot/-/ra-i18n-polyglot-5.14.4.tgz", - "integrity": "sha512-ssEmnII1sEujEhzMPRabvb4Ivlh/F9AOVcsWT5O1ks6fdEj6414K+/rsiPcAKpSYgeViWowMw+HRh92gsYylMA==", + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/ra-i18n-polyglot/-/ra-i18n-polyglot-5.14.5.tgz", + "integrity": "sha512-qP/qxAgyoJgxGJBqWZdliHc5vCK8WZM7YL3Jkre8URnmru9hPZD3vilQH4LSW/W+5yLfYoLk81597Gig9g9euw==", "license": "MIT", "dependencies": { "node-polyglot": "^2.2.2", - "ra-core": "^5.14.4" + "ra-core": "^5.14.5" } }, "node_modules/ra-language-english": { - "version": "5.14.4", - "resolved": "https://registry.npmjs.org/ra-language-english/-/ra-language-english-5.14.4.tgz", - "integrity": "sha512-DlLh6Kn5wrwwNfd7sGT8xUMzVwUMBesAIgf+y7p/TDcLsqyPkpBCH9hhBAW+s5SW8L0/ERuyrENvIV1dk0azvg==", + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/ra-language-english/-/ra-language-english-5.14.5.tgz", + "integrity": "sha512-5lB3O1/gNSgvKRDR1NA9yOEMSwGdrJYzv4etpLBaVVnAX268BHubZbm1pLmT7j4xEc9kvUQC58/rAnFDORqbiQ==", "license": "MIT", "dependencies": { - "ra-core": "^5.14.4" + "ra-core": "^5.14.5" } }, "node_modules/ra-ui-materialui": { - "version": "5.14.4", - "resolved": "https://registry.npmjs.org/ra-ui-materialui/-/ra-ui-materialui-5.14.4.tgz", - "integrity": "sha512-Gb4GPfhAGoR+2i/KjjjRwAZq84YbQqMB2lMaCxHxtCngcGsE1ljEj4aMVAyzBnr1G2Z4ht3eBUTirfwE2VpBjQ==", + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/ra-ui-materialui/-/ra-ui-materialui-5.14.5.tgz", + "integrity": "sha512-0FNrBL6k+Yu1PLRZOKAc6TDGcRk8ZAWBb0OnujlvdCOgYHgFKYtLa4nZ5L9a0EYsUsOogbmx4zKiETsJUYIfqg==", "license": "MIT", "dependencies": { "autosuggest-highlight": "^3.1.1", @@ -4109,22 +3691,25 @@ "react-router-dom": "^6.28.1 || ^7.1.1" } }, + "node_modules/ra-ui-materialui/node_modules/lodash": { + "version": "4.17.23", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", + "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "license": "MIT" + }, "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==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", + "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - }, "engines": { "node": ">=0.10.0" } }, "node_modules/react-admin": { - "version": "5.14.4", - "resolved": "https://registry.npmjs.org/react-admin/-/react-admin-5.14.4.tgz", - "integrity": "sha512-XKnANy0KU0nHP2sytzFjxzjw7NG8sxjuVn9KVITs/+Z8LRaMMh+D7WOpqinv86uMfMo3VQjLcbjC1IT10MpPXg==", + "version": "5.14.5", + "resolved": "https://registry.npmjs.org/react-admin/-/react-admin-5.14.5.tgz", + "integrity": "sha512-1XQxqL+ArkL3o5USs/AsC9fdQX4F/vzBJXwelq8jEXABcbx58d51Ug2WkXi+7WuxPrLZCOPW1kVD7l4+ahbeww==", "license": "MIT", "dependencies": { "@emotion/react": "^11.14.0", @@ -4132,10 +3717,10 @@ "@mui/icons-material": "^5.16.12 || ^6.0.0 || ^7.0.0", "@mui/material": "^5.16.12 || ^6.0.0 || ^7.0.0", "@tanstack/react-query": "^5.83.0", - "ra-core": "^5.14.4", - "ra-i18n-polyglot": "^5.14.4", - "ra-language-english": "^5.14.4", - "ra-ui-materialui": "^5.14.4", + "ra-core": "^5.14.5", + "ra-i18n-polyglot": "^5.14.5", + "ra-language-english": "^5.14.5", + "ra-ui-materialui": "^5.14.5", "react-hook-form": "^7.65.0", "react-router": "^6.28.1 || ^7.1.1", "react-router-dom": "^6.28.1 || ^7.1.1" @@ -4146,16 +3731,15 @@ } }, "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==", + "version": "19.2.4", + "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", + "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", "dependencies": { - "loose-envify": "^1.1.0", - "scheduler": "^0.23.2" + "scheduler": "^0.27.0" }, "peerDependencies": { - "react": "^18.3.1" + "react": "^19.2.4" } }, "node_modules/react-dropzone": { @@ -4188,9 +3772,9 @@ } }, "node_modules/react-hook-form": { - "version": "7.71.2", - "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.71.2.tgz", - "integrity": "sha512-1CHvcDYzuRUNOflt4MOq3ZM46AronNJtQ1S7tnX6YN4y72qhgiUItpacZUAQ0TyWYci3yz1X+rXaSxiuEm86PA==", + "version": "7.72.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.72.1.tgz", + "integrity": "sha512-RhwBoy2ygeVZje+C+bwJ8g0NjTdBmDlJvAUHTxRjTmSUKPYsKfMphkS2sgEMotsY03bP358yEYlnUeZy//D9Ig==", "license": "MIT", "engines": { "node": ">=18.0.0" @@ -4219,20 +3803,10 @@ "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", "license": "MIT" }, - "node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/react-router": { - "version": "7.13.1", - "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.13.1.tgz", - "integrity": "sha512-td+xP4X2/6BJvZoX6xw++A2DdEi++YypA69bJUV5oVvqf6/9/9nNlD70YO1e9d3MyamJEBQFEzk6mbfDYbqrSA==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.14.0.tgz", + "integrity": "sha512-m/xR9N4LQLmAS0ZhkY2nkPA1N7gQ5TUVa5n8TgANuDTARbn1gt+zLPXEm7W0XDTbrQ2AJSJKhoa6yx1D8BcpxQ==", "license": "MIT", "dependencies": { "cookie": "^1.0.1", @@ -4252,12 +3826,12 @@ } }, "node_modules/react-router-dom": { - "version": "7.13.1", - "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.13.1.tgz", - "integrity": "sha512-UJnV3Rxc5TgUPJt2KJpo1Jpy0OKQr0AjgbZzBFjaPJcFOb2Y8jA5H3LT8HUJAiRLlWrEXWHbF1Z4SCZaQjWDHw==", + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.14.0.tgz", + "integrity": "sha512-2G3ajSVSZMEtmTjIklRWlNvo8wICEpLihfD/0YMDxbWK2UyP5EGfnoIn9AIQGnF3G/FX0MRbHXdFcD+rL1ZreQ==", "license": "MIT", "dependencies": { - "react-router": "7.13.1" + "react-router": "7.14.0" }, "engines": { "node": ">=20.0.0" @@ -4318,123 +3892,61 @@ "node": ">=4" } }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rollup": { - "version": "4.59.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz", - "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==", + "node_modules/rolldown": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.0-rc.12.tgz", + "integrity": "sha512-yP4USLIMYrwpPHEFB5JGH1uxhcslv6/hL0OyvTuY+3qlOSJvZ7ntYnoWpehBxufkgN0cvXxppuTu5hHa/zPh+A==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@oxc-project/types": "=0.122.0", + "@rolldown/pluginutils": "1.0.0-rc.12" }, "bin": { - "rollup": "dist/bin/rollup" + "rolldown": "bin/cli.mjs" }, "engines": { - "node": ">=18.0.0", - "npm": ">=8.0.0" + "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.59.0", - "@rollup/rollup-android-arm64": "4.59.0", - "@rollup/rollup-darwin-arm64": "4.59.0", - "@rollup/rollup-darwin-x64": "4.59.0", - "@rollup/rollup-freebsd-arm64": "4.59.0", - "@rollup/rollup-freebsd-x64": "4.59.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.59.0", - "@rollup/rollup-linux-arm-musleabihf": "4.59.0", - "@rollup/rollup-linux-arm64-gnu": "4.59.0", - "@rollup/rollup-linux-arm64-musl": "4.59.0", - "@rollup/rollup-linux-loong64-gnu": "4.59.0", - "@rollup/rollup-linux-loong64-musl": "4.59.0", - "@rollup/rollup-linux-ppc64-gnu": "4.59.0", - "@rollup/rollup-linux-ppc64-musl": "4.59.0", - "@rollup/rollup-linux-riscv64-gnu": "4.59.0", - "@rollup/rollup-linux-riscv64-musl": "4.59.0", - "@rollup/rollup-linux-s390x-gnu": "4.59.0", - "@rollup/rollup-linux-x64-gnu": "4.59.0", - "@rollup/rollup-linux-x64-musl": "4.59.0", - "@rollup/rollup-openbsd-x64": "4.59.0", - "@rollup/rollup-openharmony-arm64": "4.59.0", - "@rollup/rollup-win32-arm64-msvc": "4.59.0", - "@rollup/rollup-win32-ia32-msvc": "4.59.0", - "@rollup/rollup-win32-x64-gnu": "4.59.0", - "@rollup/rollup-win32-x64-msvc": "4.59.0", - "fsevents": "~2.3.2" + "@rolldown/binding-android-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-arm64": "1.0.0-rc.12", + "@rolldown/binding-darwin-x64": "1.0.0-rc.12", + "@rolldown/binding-freebsd-x64": "1.0.0-rc.12", + "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.12", + "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.12", + "@rolldown/binding-linux-x64-musl": "1.0.0-rc.12", + "@rolldown/binding-openharmony-arm64": "1.0.0-rc.12", + "@rolldown/binding-wasm32-wasi": "1.0.0-rc.12", + "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.12", + "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.12" } }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "node_modules/rolldown/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-rc.12", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-rc.12.tgz", + "integrity": "sha512-HHMwmarRKvoFsJorqYlFeFRzXZqCt2ETQlEDOb9aqssrnVBB1/+xgTGtuTrIk5vzLNX1MjMtTf7W9z3tsSbrxw==", "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" - } + "license": "MIT" }, "node_modules/scheduler": { - "version": "0.23.2", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", - "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "license": "MIT", - "dependencies": { - "loose-envify": "^1.1.0" - } + "version": "0.27.0", + "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", + "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", + "license": "MIT" }, "node_modules/semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", "dev": true, "license": "ISC", "bin": { "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" } }, "node_modules/set-cookie-parser": { @@ -4483,16 +3995,6 @@ "node": ">=8" } }, - "node_modules/slash": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", - "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } - }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -4530,19 +4032,6 @@ "node": ">=4" } }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4587,37 +4076,34 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", "dev": true, "license": "MIT", "dependencies": { - "is-number": "^7.0.0" + "fdir": "^6.5.0", + "picomatch": "^4.0.3" }, "engines": { - "node": ">=8.0" + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" } }, "node_modules/ts-api-utils": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz", - "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { - "node": ">=16" + "node": ">=18.12" }, "peerDependencies": { - "typescript": ">=4.2.0" + "typescript": ">=4.8.4" } }, "node_modules/tslib": { @@ -4639,19 +4125,6 @@ "node": ">= 0.8.0" } }, - "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", @@ -4666,6 +4139,37 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz", + "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.0", + "@typescript-eslint/parser": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -4708,21 +4212,23 @@ } }, "node_modules/vite": { - "version": "5.4.21", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", - "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "version": "8.0.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.3.tgz", + "integrity": "sha512-B9ifbFudT1TFhfltfaIPgjo9Z3mDynBTJSUYxTjOQruf/zHH+ezCQKcoqO+h7a9Pw9Nm/OtlXAiGT1axBgwqrQ==", "dev": true, "license": "MIT", "dependencies": { - "esbuild": "^0.21.3", - "postcss": "^8.4.43", - "rollup": "^4.20.0" + "lightningcss": "^1.32.0", + "picomatch": "^4.0.4", + "postcss": "^8.5.8", + "rolldown": "1.0.0-rc.12", + "tinyglobby": "^0.2.15" }, "bin": { "vite": "bin/vite.js" }, "engines": { - "node": "^18.0.0 || >=20.0.0" + "node": "^20.19.0 || >=22.12.0" }, "funding": { "url": "https://github.com/vitejs/vite?sponsor=1" @@ -4731,23 +4237,33 @@ "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" + "@types/node": "^20.19.0 || >=22.12.0", + "@vitejs/devtools": "^0.1.0", + "esbuild": "^0.27.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, - "less": { + "@vitejs/devtools": { "optional": true }, - "lightningcss": { + "esbuild": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { "optional": true }, "sass": { @@ -4764,6 +4280,12 @@ }, "terser": { "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true } } }, @@ -4802,13 +4324,6 @@ "node": ">=0.10.0" } }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, - "license": "ISC" - }, "node_modules/yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", @@ -4816,15 +4331,6 @@ "dev": true, "license": "ISC" }, - "node_modules/yaml": { - "version": "1.10.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", - "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", - "license": "ISC", - "engines": { - "node": ">= 6" - } - }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -4837,6 +4343,29 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/zod": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz", + "integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, + "node_modules/zod-validation-error": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/zod-validation-error/-/zod-validation-error-4.0.2.tgz", + "integrity": "sha512-Q6/nZLe6jxuU80qb/4uJ4t5v2VEZ44lzQjPDhYJNztRQ4wyWc6VF3D3Kb/fAuPetZQnhS3hnajCf9CsWesghLQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18.0.0" + }, + "peerDependencies": { + "zod": "^3.25.0 || ^4.0.0" + } } } } diff --git a/client/package.json b/client/package.json index 2154763..e1048ec 100644 --- a/client/package.json +++ b/client/package.json @@ -6,29 +6,31 @@ "scripts": { "dev": "vite", "build": "vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint": "eslint .", "preview": "vite preview" }, "dependencies": { "@emotion/react": "^11.14.0", "@emotion/styled": "^11.14.1", - "@mui/material": "^7.3.9", + "@mui/icons-material": "^7.3.5", + "@mui/material": "^7.3.5", "keycloak-js": "^26.2.3", - "ra-data-simple-rest": "^5.14.4", - "react": "^18.2.0", - "react-admin": "^5.14.4", - "react-dom": "^18.2.0" + "react": "^19.2.4", + "react-admin": "^5.14.5", + "react-dom": "^19.2.4" }, "devDependencies": { - "@types/react": "^18.2.55", - "@types/react-dom": "^18.2.19", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", - "@vitejs/plugin-react": "^4.2.1", - "eslint": "^8.56.0", - "eslint-plugin-react-hooks": "^4.6.0", - "eslint-plugin-react-refresh": "^0.4.5", - "typescript": "^5.2.2", - "vite": "^5.1.0" + "@eslint/js": "^9.39.4", + "@types/node": "^24.12.0", + "@types/react": "^19.2.14", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "eslint": "^9.39.4", + "eslint-plugin-react-hooks": "^7.0.1", + "eslint-plugin-react-refresh": "^0.5.2", + "globals": "^17.4.0", + "typescript": "~5.9.3", + "typescript-eslint": "^8.57.0", + "vite": "^8.0.1" } } diff --git a/client/public/favicon.svg b/client/public/favicon.svg new file mode 100644 index 0000000..6893eb1 --- /dev/null +++ b/client/public/favicon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/client/public/icons.svg b/client/public/icons.svg new file mode 100644 index 0000000..e952219 --- /dev/null +++ b/client/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/client/public/vite.svg b/client/public/vite.svg deleted file mode 100644 index e7b8dfb..0000000 --- a/client/public/vite.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/client/src/App.css b/client/src/App.css new file mode 100644 index 0000000..f460279 --- /dev/null +++ b/client/src/App.css @@ -0,0 +1,184 @@ +.counter { + font-size: 16px; + padding: 5px 10px; + border-radius: 5px; + color: var(--accent); + background: var(--accent-bg); + border: 2px solid transparent; + transition: border-color 0.3s; + margin-bottom: 24px; + + &:hover { + border-color: var(--accent-border); + } + &:focus-visible { + outline: 2px solid var(--accent); + outline-offset: 2px; + } +} + +.hero { + position: relative; + + .base, + .framework, + .vite { + inset-inline: 0; + margin: 0 auto; + } + + .base { + width: 170px; + position: relative; + z-index: 0; + } + + .framework, + .vite { + position: absolute; + } + + .framework { + z-index: 1; + top: 34px; + height: 28px; + transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg) + scale(1.4); + } + + .vite { + z-index: 0; + top: 107px; + height: 26px; + width: auto; + transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg) + scale(0.8); + } +} + +#center { + display: flex; + flex-direction: column; + gap: 25px; + place-content: center; + place-items: center; + flex-grow: 1; + + @media (max-width: 1024px) { + padding: 32px 20px 24px; + gap: 18px; + } +} + +#next-steps { + display: flex; + border-top: 1px solid var(--border); + text-align: left; + + & > div { + flex: 1 1 0; + padding: 32px; + @media (max-width: 1024px) { + padding: 24px 20px; + } + } + + .icon { + margin-bottom: 16px; + width: 22px; + height: 22px; + } + + @media (max-width: 1024px) { + flex-direction: column; + text-align: center; + } +} + +#docs { + border-right: 1px solid var(--border); + + @media (max-width: 1024px) { + border-right: none; + border-bottom: 1px solid var(--border); + } +} + +#next-steps ul { + list-style: none; + padding: 0; + display: flex; + gap: 8px; + margin: 32px 0 0; + + .logo { + height: 18px; + } + + a { + color: var(--text-h); + font-size: 16px; + border-radius: 6px; + background: var(--social-bg); + display: flex; + padding: 6px 12px; + align-items: center; + gap: 8px; + text-decoration: none; + transition: box-shadow 0.3s; + + &:hover { + box-shadow: var(--shadow); + } + .button-icon { + height: 18px; + width: 18px; + } + } + + @media (max-width: 1024px) { + margin-top: 20px; + flex-wrap: wrap; + justify-content: center; + + li { + flex: 1 1 calc(50% - 8px); + } + + a { + width: 100%; + justify-content: center; + box-sizing: border-box; + } + } +} + +#spacer { + height: 88px; + border-top: 1px solid var(--border); + @media (max-width: 1024px) { + height: 48px; + } +} + +.ticks { + position: relative; + width: 100%; + + &::before, + &::after { + content: ""; + position: absolute; + top: -4.5px; + border: 5px solid transparent; + } + + &::before { + left: 0; + border-left-color: var(--border); + } + &::after { + right: 0; + border-right-color: var(--border); + } +} diff --git a/client/src/App.tsx b/client/src/App.tsx index 91a825f..00dbe06 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,49 +1,70 @@ -import { Admin, Resource } from 'react-admin'; -import dataProvider from './dataProvider'; -import authProvider from './auth/authProvider'; +import { Admin, Resource } from "react-admin"; +import { authProvider } from "./auth/authProvider"; +import { dataProvider } from "./dataProvider"; +import { CategoryResourceCreate } from "./resources/category-resource/CategoryResourceCreate"; +import { CategoryResourceEdit } from "./resources/category-resource/CategoryResourceEdit"; +import { CategoryResourceList } from "./resources/category-resource/CategoryResourceList"; +import { CategoryResourceShow } from "./resources/category-resource/CategoryResourceShow"; +import { EmployeeCreate } from "./resources/employee/EmployeeCreate"; +import { EmployeeEdit } from "./resources/employee/EmployeeEdit"; +import { EmployeeList } from "./resources/employee/EmployeeList"; +import { EmployeeShow } from "./resources/employee/EmployeeShow"; +import { EquipmentCreate } from "./resources/equipment/EquipmentCreate"; +import { EquipmentEdit } from "./resources/equipment/EquipmentEdit"; +import { EquipmentList } from "./resources/equipment/EquipmentList"; +import { EquipmentShow } from "./resources/equipment/EquipmentShow"; +import { PartCreate } from "./resources/part/PartCreate"; +import { PartEdit } from "./resources/part/PartEdit"; +import { PartList } from "./resources/part/PartList"; +import { PartShow } from "./resources/part/PartShow"; +import { PriceListCreate } from "./resources/price-list/PriceListCreate"; +import { PriceListEdit } from "./resources/price-list/PriceListEdit"; +import { PriceListList } from "./resources/price-list/PriceListList"; +import { PriceListShow } from "./resources/price-list/PriceListShow"; +import "./App.css"; -import { EquipmentTypeList } from './resources/equipment-type/EquipmentTypeList'; -import { EquipmentTypeCreate } from './resources/equipment-type/EquipmentTypeCreate'; -import { EquipmentTypeEdit } from './resources/equipment-type/EquipmentTypeEdit'; -import { EquipmentTypeShow } from './resources/equipment-type/EquipmentTypeShow'; - -import { EquipmentList } from './resources/equipment/EquipmentList'; -import { EquipmentCreate } from './resources/equipment/EquipmentCreate'; -import { EquipmentEdit } from './resources/equipment/EquipmentEdit'; -import { EquipmentShow } from './resources/equipment/EquipmentShow'; - -import { RepairOrderList } from './resources/repair-order/RepairOrderList'; -import { RepairOrderCreate } from './resources/repair-order/RepairOrderCreate'; -import { RepairOrderEdit } from './resources/repair-order/RepairOrderEdit'; -import { RepairOrderShow } from './resources/repair-order/RepairOrderShow'; - -const App = () => ( - - - - - -); - -export default App; +export default function App() { + return ( + + + + + + + + ); +} diff --git a/client/src/assets/hero.png b/client/src/assets/hero.png new file mode 100644 index 0000000000000000000000000000000000000000..cc51a3d20ad4bc961b596a6adfd686685cd84bb0 GIT binary patch literal 44919 zcma%i^5TDbT`tlgo2c`(n!ND-Q6MGAYIbZ-QCh5-QC^YozK_ne*b_MKK#O- zIWy zd$aJVZ?rl%;eiC7d#Sl-cWLv9rA0(UOX(@I3k&yyL+3GaQ4xpb1EGC|i|{byaTI># zBO=0pyZu5XO!hzGNPch4cx%6XJAJpDa<+98BOcYNo1=XER1sv!UW z^>ZDMp%FSmVnt)n^EIR+Nth`vRO^_=UF3EWv75ym{S;#2F8MPot@-y$>ioj!)a1bE zijXPQY;U`qNwl9|wl{W>{FhMSb<>m4{;8Udp4psl)NwFRo(W-T)Y6-qDf=L#U?g<@ zV+T|3+RuE~!E&nodKrkfPcOpJ)&1|p`Tbtd12@MSE8DjWkD|9M>GZsHLf>TTbLx)B z#5K5l%gS7s(yWk?Lj{Nvm`Z-s8xb-Xr`5-xRr%w8v>!oSz{dN*MmxbscQl#Z40qSd z!PQXs-utLEF&$@S#__Lo*pOhG{l(%jyCh-0ME8owiT>U~r&q@MaDRePL(aZAAff9= zBd@*7RZxmiqK^nZH7`bTjIEQw#Y=V6(h{$>7ZIf=7S0;$8~4NXLd4T;Ai~C8&3k-; zYEtJWq6x$#5rrCJ%zspgO z((R)&>BIkkr^qQSEZljO*B+ZDvTeBKJ9N%8Ej=U+62GI)dc|ZMEM66~W12v&QFAIS zoDs`J`wjsl?WdE(NTnjCO!^yB>{yU-2UPT`&FOyVQVmxy#un2Po>GiPPfzd0M^d_i z+Kr}dPhIfsDLd~jOiJ(sHTN;2u)@MaX&0AdXR;BAwr_;1sR;)MM+&{XTzNnKWH@0a zoy9ApaUt=>jjHICu3W42)5;nzHS!M3?aOvZfv-sIc%wc9#l0uHFc}aS4JSrIDOQ?4ri_bS?pjH{U{6qr+6m z--%u=5oc&PxE==-I$~$5gw}yiu_y_o?|ag2+rAgSg%G)}EU}r%*A|v|pjbE`lxJpU zy0{?;(US(i-TiKq6s_(KTYy|YVi&!plMT)EJ4wMU{C7Y;!Xow1nJ+X@ks@r0v25R; z*o$8AP*G*f3$UlYR~18PxKyPj9vU#v)4#GgEx4*?KOhlh>0%3M$-LN7&b*0fXgm$k zH78>bObkx^3_K+RY;G+Usy6L}p9iT!hlnJCmR=;=JL1TdtB#vL!RTJ1TABQx8Ux0w zl^{Jkf(hU>-jr59iK_v-PkV!WwG!LvW<@{3{IbbSiWBrX@S8^`8JFRrc+(AqsUIvm zCTstACtCZ~qy-5^Gr@_z#X!N1*1vH=7@8oL4AEOxWl^YW&LW|1$1J?gG061vk1epe zRI_*s(lrX?-2#tCt_`)p?{zZC+)onl60CU~%4!vPA}h0+fB9ucNkTQ3u29((9Wq=> z^JUm|{_2-=?dMKu&9)#x{lgPOCM`U1^tXDbmZ%I$0fw7|Y-@3Tyj1LGfk$lvzYC85 z=R()QEER%Dz=mTMZ=7E?K74&?)4b~-uj34rKwb~7vU(48%+1xYc^VYn| zncI4NL8xEnmi>eM9EK&~si%*s|BX@zKIUU?cAWA5pdc`xEZIF1Ce=Wcg3#AP?N~p# zD7mfb{oR=ZPE^jgwD3G< z#8h1K&u&zKD4q*Pxt0ta#d}bm;QqZ!hFift22a~7c529SkmFQyN-*H zzQck2cL5iH2@d@Lhq4$~_!wMWL6(&mNq=7HhT}YYI$pVVZeQr>)4>qObE$PPNZ2!0 z&7?y_upwfiefj8-`B$ju)}QKTz*Zs<$Lb?XHBo(jyU(405&`EL({mgxA$Ov49U|rN z2@(l@n`1vzG(v=!u4AZ*0s}~H4{VgcNOJ1rB?Kg!=)mGHKWeC|MHb>aiQ4Qd+gq7|??WH7;?J+kYL8z# z@juTBhW#n3rN))N7T1~)qr~Es;2rln6_U>_Ejxj(E5%Cpoc^vfw64mua!ADSZ8i|+ zB}g?u(dtvesTegnG!9K33T)4eq>)>ZFp?L>R8Qp#(J=bxz2mscD;ZNoJB@ZUqPpI>o7VgScniW4c()#;@;-9PfR`b(r+#4c; z;1-)`!?b}4A3v^zVtGa(a;O%bzu(ZG;(l4+W^vU|a&n*xV0kU$uFQ!5!aWy)^q4^r zn!-6hfj79_B#>GGNvQiKMD?xyW>F&GS>3y?Ric*xp4cz3FH3Gd1z|e+Vuug7*Ya48 zL~K*l5zo1XRuWm%S~GzE4LQyuRsH1&L`Gz-%>!ZTYn9K_Ttz+Pa@9hKob^)gmLVN` zKJz}C50X$$>G1Q_p;%C}B?<9h`60%vwalt2*Ymd44dGF(oOa2mJQuPQmE~Yurn0UC z6(+5$posAd@e$nvJQFL^C~E0E4IH`B68)j#L_u|Ex5mNE8a8{>gAGcIFVS|K?g77# zE@R|9nR>Rw3(5}{d~HnPpooZ*XZC$5FYt20 z3Ydvy9t)XHw8qFCd;mt8r$e?RQ%MiUF@}!oDGG#E6xxV z=z>11f!msSqbAZYnSvt}&J+QXZCU5b`0!gi_R}Z@Qq2d2Mwc z%9aWfp&x2UGbLDvtjGb*p>4O(#}UE+QhYmf0&Vc_Ay<~3V0zym%`Lk}-3MOz<%)%#Pl z<=OjGrvuBq318+CJ-{30QA1-O@<-O!-zFNM^&wp}iWGG$B&eIYtF)Rs4;5FK=>Aa9 zyTJdUgpK$di~MI|ZC=Vkd^V6T5h^z))sl~Dq7~stg?&l_LW6N1>0nX=aS46Ks+vj7 zr#P2~h=M-LLX2!W_k&dv^Tm2}o9vK&uKMDMmPkEcj7~C78vw2XJx^s8uo(Lw>9ET2 zzXG^MDxZzwh4y=Hs@h^Y2$ntYP+GSm>#cM9ZiUR^>tiFtIol3wi8=y~L2f@Bun;{B zr@yZMir9Ur@yw@7ni+Jd*Oc9hFx zK$M%P9+XKj>`spPB?k6^h1pok(_k*E$fr(SnXlXEnE{ODRWuWqB2u+8*2z?-wl+WC zntSCtFwpr0nF!avN+7`^Pt@XDvec7%ipuHYXg%5TXDAXv;U-33A(vzDB8V%0%j-R@ zk!2mox%%pJ<_M$o0lf*YButy@IP%9Zz=UDDlr|NuSNW*bYB{&18Xj|$eVP~(lx>y3 zgjJh3l1)5_uw6CTgk`ABQVoCHT$nbFS*edKLAbhRxLyzMI-{#6H!q_O@+mM7#~@Kw zWFDq#m<+NGVr`grM*Mh=Dq@8Tzl-$WKFWsWruYa^v`B30wDORai8q&__SDBzc?K#o z^UN`hN&IN;bep+mS1Z}i#zurS+Vl`B&+6`B#XK@l^8+&2+e@&zII(kdzid}Lm^AE5 zqjZ+3N*0O?1%{glymHcUP?g3vB#mH9MA)__>pUakjX+4jPuRS$9mmbImM8^= zOGMzKSY0_htZs;&-)|di4DJjSjVQ}hf2vq`u?G4@2@M(y#8xp{#1&$)ZW$rlUwG%{ z-S3I$D5~^(7stnQ#qh(0D6TnSA5R2*0u@x*22u1y%V5wYfW$b@)H*9X9{5!1Gw0`$ z4^fR@T%cw74(zCoPNP98@iS+WaFoE>g!a7#s-iwfRHKJSou%<97*I%619(655MjTr z6;k$p>T1-|cb9V=`;0i>gjBf%t=3jn_oC874-1o3(J|G-g$c?a=wn!m?U?CAd4WKW zm>=k4ApUHFtra|}Wl_G|#Y@n(Qv*q-frfU@rg{K1dLr%5(jA(Als7lSt8bue+zbab zVF0VKb`8x4k`2s^D1=P<^mk&LXhA!1jsr46^sGC@bsZfT)hZq4gnT+I+aHp`_XRE{ zDgx9ExOOSGF^DuVB_iQ8s$S{7agA7rKLtYG0nVl0q1kdJPQ3g#tw9qL?gP!_e~V$R z7B*H7J0{kp*t0|SM#+|$l6`>>9*GXki2@B!1?#&`s}t$D9D05bdTLaq__DzJ3hhhx z4>Z*xjuhGkL>lPDr8KhXi~8N*3~eqgebLTG`3g)&9`ESMo4O`ywJ{RymGvLXG}!Y?yAZ!5^Y19ukC`n~3GM7)2v! zx|C7WvVV`|+~>K~FRJPdp3VTPY##;_7#_^stFuo>5ewhPn5=@ApsXs_<27I&gPv>g~?s5SHzci&*$xeFVsI6?MsNJwojSpg9-+xbDwNanO9CUPbs06^E~@ zW3}{)@boKx;MgISD4?gb;X2~Nzv6Vu z_d;=oiM*wq!ou(NN8Zrg1ZYYlE==ylKlarfHe9u21xL{BI8t!pRC1^0=DGRrV0_Q@ zC#L85xcROt(T$6-@Y|KI-@7cgFD>WF?-)WG5jRleK;pn&=Rb9nZ+_@Mx-Fk~VSb{E zq@Ay=ub)@s&Mz*$+FSlG0WrrMKZI+3YuZ5k`RZGGO+r;}6mJy$DM;>AadvNZ=5yf|1r(je z0NIXNIS||Cv*MHEs{?>y+_cZmakNb+;cq-QqDcP%tMf{NmoE%a zN}Y33Vukiwxzm0dhmNsZQ>TsfYfZ-XZJv?ZTQ(=j1nt6FMd#;_K1oqQ{yq$GC6%)U zZU3B>;dh0p{DE?0kaj|iKj8?vvgC|-pv7<_WZBV7+B?`x+~3_las0^52<3d}UOOFD z7O7yf($skvy4y{NCq)B!Z=x|~NnJN+V(IV6LPL~?ORfvDDj*}q67_9}bTd~ci zlKmqOV)pG2tgWwY4Xr65@I8rddMwBV71bVAeGxT?v8-f6l9tsu9MFYr4r+BQr%mT; zO=G1)NW}SP4_kI0273Ew)qtwOwo=X-`1?bJ^>I^-9FXhSX17W>;{G^F+<9U(<%-*JPc!x>jH zSpfzK?Tx3%`#8Qlql2)Lf)TAiKHBQ5IOieg6~2NY7g@9IFI!7$DETtUG^srTsi2YS zc$`cq59-bK0{Yv})|#O4%XrxCkS29A6q~iTWNRlF;SlDMr$~v5hgerQQg_UB>M>2% zI6J+NtM*`(N7ghI_emz^lYyF_O8LW&&6oX-gU1h39L7r@8tpHA@>FGx*W=fR6E@q@ zg{!zJeVuJaQCuA=1@IE7|3##J$1oumJ5vky^UJEjKU#$)KuHS7B;vs(wJ%$?>4zlr z<=b*ca@HsJ!Osy3xBOqrn__D7pqhw2^7;n0$R~Z;twx??hrssk#C1cMtRHfFzhTG1 zE{;!Tmiq;ZD9#2W4(M?+!*~v>l$%5;__SINKTNAEIBf46X8185dhp4TD9_K#gp?em zl9d>E%I2x(q#pB8rt!89i!Mi7sMMmaZ?N?eM2!JHoQ{QdAoSm@`@TtaEkw{)WuZe^ zzrVO3sL=ewi4YYv1t!gfQ_Xo()Is9PQtqh!#?v&Mscaiz6wb$F>GjZE1xw7d5)*24 zu~!(MAawsNH*G-kU-c=3l(?|JJl0^q#LV(WKmSHC=#5YKstmI(V=6c4>73kKDwk3F zD!sjK#(*WYb8j>uP??1gq4SEU63;>Pk_#yOYu7(GAy4!ABPQY-WoeY1I=l2&k9RM( z;&F-Ki}KoHAb;HXNP-^_3u`-L$+~dmP7LmypyE23q+IsyIAyGbu{1T^)Y7+m(;oN@;N26N#9X<& zwqI@>wi=7v)<%`#h|WWx1pPuT%3Hx zTmHj4u@(m6TMc`y;_9#P8As?uJeu-!|Lgzd>}uWMUo5{kA<)1ndxs@UZR32fT6pJHGaO!4QH(eAa5+t zS1N59EQ1r6i z<(E$QmAL~w+VkGpLI9*Hnm0tLT@_hjW9JWQXev%DVG3YZJ@}x78{*jc{asC?1L_)h zF^DC#%H`1`O_VrpaQ}@~&1zbs5~&ja^i#ZVXwP!}j8mnEV@;<{Ahw)4%S3LKNFJ3i zaiK4p7j50(Gg`7o7JU5p$cw9Ok3@$*lZ@g;nFZi|2gmE)4`U4Rnm2m{vKk-zbX%kA zCoK32`kIhZtyUTzRW&2mT0PG|s|zU{4QPllcC91scP>F97ZXap<9Bv#F$2P|qk;b&2$rxv~0fH76P8hs?SUZLs6n%pW)x z{94NZ^zuBrMOvmx1jBKr7I^C(e7yj;&kgD*7xRHBhV0n=;gNznW(J%ArEdQ3v2RnW zr(kstOqa&TJ`*F&kJM}we0``YRAQ>!`T?;}wzZgRk(fa^)#2*9%Z+psyrobKU%nac znGGN&)Npn`s=}e$R4yL6IsRDDSF=Ps)Z;1?NH}K#C*jVV4dx0@(DMhJqOL*I6)&L4 z9cLFcW!bbaiw~-ib4#2tjht6tOE}{zD6zU{xlC2$ zI>jGRD=rdrA25&Qq4jqQAhS4A^TEeuR}+ZLmIn&KRN3!3YkB-ej*-b9-c-AE)S%N> zf?x6evrm$2MOQ(b0-<^gvSC_6oBe@p+i`Ajxy1G91_dbm9z>* z`v6e3>~L1a-C*c2`$0^HXjr4(?IN{jFy+;}uvyb!LNh16HAJ)d@63e8GRMmWrMZ&F zv_aLU&4#ktx$@=QM^zZSdGAFn^&JpWIEc06k(WFQd*!&PpmY;wf3>)TvXQM+vqd#z zyU8VT;5@(~T!27u_1N3Z<{-f&SNd-M>^C*BK>cKP5&U7*KXmq@FP2FiN4aT+-1iF~ zfRiPbO{*ky%`uehvD+s~XnH7V{jvXcN8((ts-<3M-#N&I$MX3xlZ!UGg+fiN+}`r5 zkj3AjM%Sj6BRHE5?Q@(GmaEXx+0)r!TPtcgyrsy<^`_Wc*hwyr-;OCdQ4#vF=h5Xj!r_#p6O*Q* z)GM*S@GP^XHnavtL<^TD>&W%F)LS4nt}T73^w2{aE8S?2vByR~WOdM+N!yff<@?z8 zI#ww-Zu3B+Dw2VJIAV7nOX9!ujfO>l`;d|vXtw#0QXN#ak`$I0n8kN5(2;87J-CD? zHmL*sL>eCfe*GTXwvDI2D~K%nI37JKu}-!Po8ExO7L8{#pw*RuB`6KEDkQxqNdG4R zbz*yTL(6Iv2z+#WI#BgSE1!LJckdfI7H#~xxtSQ;JHtJbofI^}g8L7|Kn}2;V?6dd zK9bChE}t-w#v@|YYe!RB4PsH{@hW+RWHlR3f&YL23-N7 zB={^p7mTZ^ud}HaFV%4UvxHK!)luf%KBVaoi+}5rSQwa@bCw;vYHCGARWld==<7kL z=59v02kEeG3Rm_z)Zc3=MXmaA)I9-9T+O+St{6L3)`@2_41VCAA&8E3bj5sZx5x4s zmtI{uQpw=7HHzdjnUy|za5p(fC=*%NXWhuB(Dh_u6(6Y_e%!8tO&OI$^_@sEYZMc) z<_`+vf$U0(c!m5aMnvIZvM^uI5SEj)Z(;;xrCT_CmpZM4!RQ9UsISG;<-MiaiPA(v1+;q7waq z#DaO&yeXX-esRlYcP9QBezojM(;1VYYslzFHa5kqnhTql9tB)(1PR83ymJM)zr}u2 zA!bL-PF~HWs6_&|a2T`59w8gMCgzI0ZUSUfQfl;Ojkd&KMV<)NhcnfxuOH2mUXuwQ zAM*!OvW!{`MXjm7TIXfL-k+n%0dP~x1% zi$3~@96_CUQxT;Gzf^B~3kR0u=7eg2I4Fgw5M>k5m~x;XrP_^xUNLYFvz1}cRTX7r z0lHVaPz&tCq!B@(_+nwtq0RK$#IV+@P;sE{>RX8Bn-rrhrkj}46K*PBvhLdC@?i7h zJjx#Hk>f+3F<_Y0nGofcP^IE@)+(L~Q4*1fl-B_6231_D^dqI(^dhIc= z=LA*Dx+nYb(z7F472oY=W@o*6`ujtJZ|o#z!EAVr%)^Fux|HNxTtvhvDsp6UwTFwJ zM*F1zvWTTAmTD7v5DPy;dkkH$be+d!3z!mh9?~B zP;G9Vwc=}F40A(Sds~L)9PeFHO$%36su`>ADF4lttX|1!{}kJEkmfex*_yNVfSVdD*&UI|G|lX40rxwlAPgKpuk`23wH2sCfRuKK%fnp1R#=<@<9%+; zML4y^o|%u9_V0m5cLefgy9n<{uobfvYeu+aZKo0Ktc|gWw&pasMBNnfI2UHbKn{9O z)8)imqR}+@&r{T;xui0wrvTi{YW)CT-RWebe0G8{202Acf|Llgnqf=$=%XtXfK4Qv z=zT1j1nI9*CySKsm0?}}<#3SfXM2MsnAkgZs>SG?0o-+s-LK%L80d)#K;3u!6;8=5 zX@g4Fm=G<8m!gGW=R{0399feKC9Xe6!If(%Vf-@0mQ7tBX0NzqmY|9qPu^277yohID3?W6U;XA5NfW2T%outqW~PhQ+n&nro#DcM$Z$THW`N zvNBz|DwU7qm-tFK?Q`5dA&PTB@?7}m0eDq==POEw^{A`Fa?qK z&48UqJjKg|to+>?O{Xf0(K=JOzIa?8#vDp}6Rf^uG9;_RQ>Sv54OQdMjViE9g742S zMhS8Ye+*}NihDGfGuOzbNvx`CgC7KR%vHu{O-ehz$6LT4Mk3SiWVM?^5C{rNs<(ci zqw`nSS8I-1*=qA%mSmm%)UgQ`dsW)FynP!Cpz`|ATE_}k?|*Q37_<7=60FiHwB(_h zw5+MMx={v+RgSy*%jLa^{Rki@+7`oxIZt}@^zY`)n@lMhgAPv!!2u;Sa^;2L@?^x z%A-Mrjx%teimuzTAPSO;F~lr&gy>_G4IY{^P*NEOF|%r&ntw4|Ix}Z6Za4>|Vq}%A z6pcxIPQ@tDsnqjX?bEekhr8)RQoOi)#Gg%k8s-M;;psx6&rT16qf|d(x zQm|i=dq2&*4+`a7Tfs#LSH|);MEHt+!b{0d7;B0PK<1QGH_ynoq!E*2hGkz#6O9hV z?$@wob1i#9kmr+^>ORB=Br!O}1{@=Or zo%h~IPq;QRxJrZG=B=N=LCa3_ths#xboN?(E~BHD0#-A0HRWBd% zQcIeW%y@>zZ8l81ks#C7e+hpvP3-w#+7K8!Z#+falSF*kz#{e>Br}RGNxX7AU1lVi zBM!bs|1pEQkrg!e8V!3s{|$r6OO-b5{0em=IHTj>B%>xTM{2fQAz|zH#Py4>+?xni_0O!81gn!QL~C|A^iO>kV^4a_%tZvJM}($5)k4nG z1`n!DqAq7NrQbVbxd2VW=*}I~?A_RaioH~%?eBYLjJ5@FW1Pu+UAm(%H!%U>%pk7} zejlDzFG%i?NWK}?hzUWsKEW}sW!hRv85emvYXb>bj9PjkEJUSs#y-}~vu{`L=EN&3c~hF@`6?yd zt*{wD)SEe5tJzqXKE$Yy+1IchWywJgfw_Q4!wv!!5v&6E{)Mf7)=|Ty$5R8b@U^UT zH*#GGHSYPR@bGZ$75&;Bj!Dh8Z%`1MNltRwF(-lxD(>)-*7(HhmG5nQ+i+Z`;k`|g z%h9)2??XolklwMj)H3$J>HaS9heUSwj9nb|SnvxxR~23MWzjJ&wWNu0GHR|_`D@uU zJcWrzlRcU6ndDlgFI8Lbxu<+@@QxstO@yNH$yd+_nh{q=e4eP<==cK*H3z8Y(t_9COqt4~v_Qlm%pPjo%wZFKfn|@@9(-C_ zTK~A)tQ3f~*E*=hg0)-;lGt;ScvIjOMibwZ4x zJ_UAlwx$oR%6XV>upP2|637WYo24&Q}Y_fL*yf-Q)J=sU0Ln?t+}=J zO{6MCeh7$_?fo>?^zii23s=e9C&jWN+3Wk&N8il?$Rn1TVg8b_3$+-c4t1EpM3jNP1tx-~ZtZSw|kM3YHhY<3yn%Vn1xhDJu% z4Dv4H$I&nplNH^mY?|6wy=hopGrWsK{z&zWzg~2L(?_BXd*1qJV>321H#9~{E*{+K z!e9TFLZas6aujoB{o2~V*B17dvd{&Iqsk3=Epw1yoDK19=8B`6=j}^sM*D%B$mSlQ zX#nr4DX~ji#!=Nj_)ias_^{Y(lA?qcE`a>{=4^TOc?#56oiVbq2ANi8i&=TNn?&pk zt`VtbWh*T;WGoa9?%8a=={cj52ay?-Yi9r)62hP4b&xzbC(HecT>GQPlc<;0Z%*7x zZodr#pCg`OB3`dw!hrntXAoJmo=QMs$@kx$r(LhAPd=epl?(E@ zTyv?TwckxHOeIZy3=>WJv}?OuzDp~badvrF4_ zZAYU~d}%i=v{4M&=+*K|6X*V2+1Qvjc2Ko9YD}ENS~}lpu>xTCv^#n6e-9qt zhV_&E$RMR>%`RQ@$54%E!G$j!61RAW5b~GSPP)}#v)oupgLY4;dEuZK@1+Gg;XV}I$rIL*jyWr z%#b+Fa2-|41c5tm(GN?a8dVl1zFisqiPky)WPO?`%oSsK(Hf&IDaL(r`%S z-2Wn#BoRnHfqGV*!s*;zG-l;5+rkmw$u*-sA!lNdlNI=^8=bE^h^& zEODXG-PWduHouXLwjF4F!(35IXa!Q$a@o0)hwQe^4f(f-JAX*4-Cow;VDb*TZdS@H zqUd9T*+%su%e6L7M5t%M=UJ7V9HyWKQT0MWs3COo66`!uFnY3gmQjYiy2x8XhO@)> z$~WPw(}UW1aF~-s=CIaPH+8kG4exyi}ai$+h{shB*3W0rRF7=mD$#s zvR#Q@SDXD3D^=`Ph`BRQ^{vl_$cFGe&)d~zCy%|q@PdImLSty)@pAQ1>&enPc=}Hc zxK|095i`i|VQrKL0815&JK&dK9DdZJTv=}cxe}!(rRTVQA zz>Br`kSb^ePLUvOWki3xxKlM4deNqbyEV}je3vb|B;s5&FGql9?_#CDoYdH0y-F&x zmmEfNh6h@>F{QJ{ho4NR2lD=9hGNH2oIC_rb$IML zpQS^1(_7Yop5+Vhy%+YHF|E`%=bc9rjv2?=;WM~G<|FyL6?u#%TieI6z;E_?35N=+ z0Ixo25mhW*iKUS!M5jj`B4Aoh4{hmH(BZwuOSArZaffRMr0bkL=(zyx)q{3nGIFCt zP?|CQYOzYk5rJl?01bIJjV$ahRJVSWd3!3Z>FXU+^up2{FBnzM>P|-;XGsVkL5`RF z^7=C zeC2+{=kIBc)0DD5`G_YoUabnci0OMA>;XphacRZ#+lS*D8?ARGW7fDCOLMwkx#)by zx#YDL*_I7FjrWyjTBGud;0GL)qpsT(*rB1J-_=`Uw&ydA;1-mYlcj^y@4#eC#Oae{ zJMzbmnKyLiYBU&+6!x)+AHU8|r(4I|5gXO|yvLXkB8XQ!H zX2baRkI_{jpLFvC2dRbFcD)-@6RwWk6)$7O2aHGPQ4w5Ljz{X^ANl66!{l)US^OWr z7AZob!By7dm7H-cRkSe7adHaySI*vu#vJk0AzD%0Oj~;1NL0@B4>hMui3vafOxJH( z4|j*!N321k^8ELv`Q|voWIy=68f3oF19ight;SN>tLXSx=j7MN<#sD^G zXN=O6OXa?}ym}R~{&5qmA3br7O-gH%p>*6pf0>seX8#r;TT_si#b~RwReA-by-m5@KaM)U^CF;34yDGKb(cEIZa6%3o05E4cb7* z+;9{Ba~%6OZ?QP*qY4Lw{;`lW{Fw2)eDG(3ZA~DV=!e=H;w!?-D#OdFS1(gG zyzFg7o63quNB{kdv#R(Yms~Bi4g9(oQwOYZYF`fcDwZ;-e&+u6T3W7QyfyOLH~hV{ zcv{U@RWmFQUhZo-NV~bPb^B)Ma;IYLenRx_^`LpLomh?w_P?t)9#vU4oFt$%US2J7 zG3u77_b6!)XWOBm!OJr?p02gOc^iVO`vx^92i{QobuWO~{!bcylk#?ZolipoAuKZr5iYfc{YDSBTuZQWm0!K#TmjNYXzrs)cQG&h zs{O^UW3-$Pb6!s4t@cgj;iXW3B7S7t=z3bJhFpwR45Ez8fI41>sx74>ekw!_IkXfy zaL5ml)#=(w-DYW8AfCLQ1e{;|xE}b|M;gTf5I`}KA*Be@mJHPc`IVnmN zKzM}j2YhkQ(rua?wS`rnM9N_)A*)+I#aruc65|6j1X`K72zoM*5Z~k)`YpJg5u#T# z1UnK~t?@aOUqv`d{*9m0_V4EBFisI{SFXLr&WLI~tQ zdF3Fs&^^1nyLsQF`roY8z^SLRWCE{Et)_#r$;h|s@RR6~(s*+?KO^%8-RISZ$H2>s zU{yd|BIT`kpIB5PjcsOqU)MkLBt+l-ru8wdyMpf~uKXlS!ZkG8fCc|ZBT$+q#M{LXUTT@!$(pFyi+Z!=WrIl!ht(fbk6;GJYVD*)Qw*}LClLT+2yS_;POgF zq9xDxnSU7MfAAHf5i3~pi3m+?P6Eyb=Wi3&phKKk`PYcAC-FI3!sn7~p9jc`Cj$Q8 zuHDipWtBYU8|yeb(Ipdt&#=;h?}Loqf`0}UBZ!p$r;RqQfsXP)&wO+4Vflp$K6?&Q z;twAQ9bh;;J&DQ?%~cJxeA4^Usg3;(?o`E|Mm8(tG|Ayr6JOM1hW!Z zqxD=krm74NT!{cb)MHL-r<17RXDy8XM(g;r)EeD?j?WYa&0OkUiQjcxzi13nL8K!H zeDiiC=kH~xEt7u3fCSK42D#NOh42IayWdgWtoKjlQnwdQM6un!^>Q};JNS3NxvanR zz__R3*d{xY)ysy%#g0*R>YHm?_pI#R?Qj044R??sFMD2~Kf4zvu{NBA_$usENKfTS z4Gaw@rs*oK9f_aLy@FV(2ZI);S8rim-Z8N3*Dz@+q80$8+CUpR`}czcAl9#Nm*w` z3|4wuio*VcAN5^%L%@{ESF$qq8bp%5q0YxJqK_}=U17JDLBB@&VnLzg8n{M7<51&(7bIU0jO&t zore{7s{$>&?z~!j{}cowSNOHUwt9R85(Umm&g{Vt?c}9`e7nV{JA^-{`()zWc}mP< z`6vz@TnCDyM`=+5RT8M76SsxK1reI)_I0bypU)^%KHehFfB%DUBrq5-5*yhuSmA{K zg;^?iEVP{?k%jiZ^P{_rUv90*a`V}0T|DlP7nH#NEk?)g@D!tQ88(Hzh=ZT!Ipr*U z`$%5ehv&a@uTgn1q`VV-gj@&HX?$b+@rmi(FbA5?fQfs@S1S0_0zft0jJDHE{%Koh zJ}Yt3x&j;YrLThxA1C?y%Im9L>9sWfg@~pxH)IpP6d7j^Rp84-`?w#;l8_>mLOU$b zsHSafe6DIKD~U7^dD|Fa5hAcEABzc6^Ktz%I<)h8d7rUL$;n|Or^b9< zreSTSTbv4S4e zb+4F~=Rivm>wW8;?bgzr-caIP$LEvo{?<~D?wb*f zZzmBM!r>(u$Kar};P##{zdSDu1fuBpt zTQBv*X8N3?HakuultkMtd4Q8C_V4LnBc ze2rw!s6?G6Uf98Phn-$ud5-UQXr(!yslCjt!C&F2N z42*250>QOtI?~TE?4s8%=3ts;Mezd=8L2BMI?lDT` zd+-%YaKTWgiUykY6;X$SH8WzJweL&qkIL~-{r2?12=un^tCjyE$j^eWlG=R)b31$4 zkO%>Vx<_(5UEW5hTP8D@Bgr(i{ZlwprU{UL2MxN=FqS}t>rLg&(9wFi5&|a?mrz&# zoRbHGs<#$=Op@a|-xV_Vm;kCqZ$2nWvjFWH`@0g7A6!LRVAWKP@LcmdKUJmGD^juJxC{MLX2GZvG;>X!!?68TZ^|$=XepiPnI_ zw7cM~+XO<*d*G+10HH=PNat07nZYlXwM@rPmO7qLXF!Qson(VS$82|Sra<}4PZMZ7c8b7fmPo~Zh5UZ z8?C7AAgO@JmB^Lw$JuK7FPee+iUh%!WLW-D7|TxUKs2)mc23L(zxnOpF{>7~e|-~t zbXysjma)vW3S8&i124Twu-3@uWC36HbFS0tID++G@BkdO@4}9WIp8^;aod!0VE$I4 z5;fO>p#q#OGeyM@^ah^>oA=vc>$sD!WAYKOo00&|IytaQ`xdy*D`N*(3eq_ZuzOw$ zIBQjakA4H}(SHCUoigxU#Jzd`lQpGIf8|7aJx@rPiiDYsd|b{%#vtYR4|TP4qD1Ui#tqq>Y+bmSmg z+z30qxeji#D!^@KHArVQG7@eAhbcu6u%r+A~fUC79DP7T;iz6qqP>aA;GauX-0lUmB1ZVAH z_OsO>oKgUmQ;vh}^my3zVKK~m?Sv9DSJi{!$pfW;*{indelQza2iBidfaQ!sAexo| zPK*$(r)0pcX@wB7vWcC5TJYAZW`DlNGS@ng&Z~hyBLySeI*x!{=iCE7!y4GTv>AMt zmVuXk1^f9L2wK_(A#2#*o0AMKbJJ1-)?5j{o7qg$W{F&hT>Bxi_OzG<&uGuwKfjIf z$8B($p21eRx!}LF0QN3t8K+Sl1g>acoYKfv&v!w}2zD;Lm^6TFX*IadD*~B*3&<8Iz)iOh_N{4x&{fS4xV()0>{SrXIL-de)42zC zT=V_D`JV&mh9hz%a_#%5IRC#BbG?4r5j;ncCegYJHs2kk*xSgs93s}2gYC39u$_8}eepBkHv2-_F}GWG%{AYX9!um( z774GGer*__v8MIZZRi0t{)o=TgM;mtgF{f1@A>Sz*Fx&rV%=tyvBa#2@k$NsUcfkLVHNCNR0SThtHEXFUGQ5}559VhEa7VgnO+;XOl8R) z%Wx(0a#?bB4$McCF=BOQNu+&*GB>nFO;-tl$tt@+bD%d&8R!Sg)$+h*Oc|`77zD05 z=fG#tCGgZOV8n^t5G*xc(g?vTo4GIKKD&%d**)j7>{Y)Q0*q_GcafZ(glY&jsRQqM z)!@Cj7`$|=A!5S=kQ&?p|CQIkb#@k5Pf7rLmK{rG+yvJdSHROK^H{-|CMw+`awT%@ zBWQ2>Wx)0DUyZXwKRL#4{2rn<7lEzz2@uW50;g%|u<6SquzBoJ5PTL4Zu7EX_mb-@ zfvaYuSP3C3Tfl2!IUHQq%CcF;D@!W5l`_f#vPDg>Tfd4+@?2)!WB*nO$4%~YO1av6 z|HX`-3`$wndx0f!=eQ=RDFbDU<8}*PQf5q6@yebw(48^63up|Kz{1zkz~Y^H*g5$u ztp3awJmzJAXjTqe?pLw{ui~l#b}z)Ge=+P?S`TjX3&C;5ZT98Z7uKs|%l{TQAW*QA zQ3{?5%D|nyrS`97ZxzETkSr(!kA;`ObzTN+85<27zl>zr@nNvlJPndr*BOalJbldW zu6yaFmM`e$BoKNp?wt8yTI}ZU_T=vV6@1xJ-`n6Sm`~adn_P~fyN+s9%uO*1JRQwsS zy2CV;K){ZzwL=TRdSV_|>*_e|G@89Q9&<}rdS3$v);7U@(+ZF+$p?GQR9N%L0dSh0 z4i*|mVaMbcu$dAM`_~jgqII+MPTY@kTN}S4J(fV|O~%z{ny00>v^pL$ZwolGwgY^% z8$dj*7|f>zGtxW@J2ayi+2+IMua3g{&%;@gbp!&J-GZ>yb&OL=S!PosuYp}vM#mDC8kv z={xzL#a84DIWH+YwACWibOs&j&=}|mlLzjGDJs6O;`J-A>x(9^(`HL|ta0Y3WG?Dr4Y$zkNVR1QH)TfuKp4eVoC>%nyj zmd!RpuyGR{SXU3nEf_IRJqs2SPO_651J;w0!C`tTh-RmOn?Wkei0?p>umO%+)p+L} zRT#9^|D-}UE`h*b)D(8Sm*HPyeqc>Wc+`d_aQ?g*Hmg^{mJjd3?!|Xt-w>+`8rkakE=YB&z+1l(r1Pu5XUQGz-?bWl8CI%Y<5uLF1N{Uq z^+f2X9JJI?J;Y_Ls7=fnbQG-LYhugy3t&GbnH^+2OSN-BGQWhqL9isEhGn1C?29rY zHDsi^t_^}$H$a4W3xus}VSjFffK_tvSyT?eYpPkwUkSbjmF%Qd!#?(Nht`*a``k>h zo0I`A)3aF?n+|3Z!eFP?aR^va0It(2!SS~famu?$wP99*>Tv!5>mAH8~(xn2clZT5LzmBLKbNSHi8lK4_j##EKS?8yVYQS@cx z8UtI@8(BJk58QM!VB7c@Muu6O*MO&P8OuPM*&BjouZD8i%ib`7#?`Qwy-oHQGcsMt zvRn3630P6XveibAu~hwlNjvx%RKf10g>Z093&d_G9T$tvD*Eta`X zRSAG)ujj(Hj|xFF?+kd(y9{o#&w+Se9(XLg12QAbLTe#JAO|n@wg@s|>HNkPh}iHQ z_%APmgY3kFnKi=E9c>V{z6rb+-G{I>55U{75JJ|<*$FIV+3g*$7=Ik>7`g5oe+F#7 zP2)5YYwZ}=FDQi_U)%+UcOHOX=zS2pQ4YIjH^I?O3fQ+)9(ygaV=3L-1VYc?{^iCm z4sE+B+h=k+9B1z>`!F1|RS$si>-lUMUceHwIWJ|MP(pmNnGffMmQ*Fhmh6v5VEQX{Fbt; zl##Fh@(M<}b=>MXbWH;U88t$vaT`cMaayu1HPo zl;i_Y(DA`h$D1ypD{me?wBar+dp{B;4R8k?)o{=q6wi{NYA{i|3zowhz;0v{h{v{q zNcSQLXU4tDCu%@Zl}3 zj3XLguW==W7`HI;t>@}peU=t;yc1^H0=v|NatLE2(x0wA(h~} z^ghQIK`ZMZa2fk`c|H4mEd;V|-RlcWEtq zTQozcNi9Tfd;k#}+Zftm?{Yb(vmW3269lfR1liJ32wqbLksBT`(yd`{mPR47L&PmDOIx~kY4K6{@vN{ld!#?}nA7SgTa`sj%0+ZM8 zv5R;X=BUPij>Ic;2MIby!)824qAEbuy95) zXulzaZ(g;5X#)dU*6POX(M(qjWzT0NtWqmvxB*+$tHI{I1_(541vlL+u+%&TYrYJE z9TVfhW7ZXLoR$vTzfS!B*?SM5s+P4~ch_HMF9RwFm=o$+>e6KnC?YvXFs-%se{Q|^8|^-)>fZYAxqsSwuQ0o+Yfi=-a{^;_ zzx}*lf87HKx_3})+mEaxy~wugWzd#r^on$%pY&u5`8Gqypkuj5N0DaSPa;Y#S^Fi+ z3W(HviA*zY)h9un-fI%^cPKeNgb=yTo&?n%xj+5di@w0EAg7f*2vfNMpS>60E7^iX zy+@2*Q}l;%+GZT5k4+-O^gSZ!c!AXz@~jB$P5an|NHuwl)7BqQ;xNrHpL;F!P%m-EKEeG>UE;$`*4-3ZLLnd!@JcCukz}DunxbU;%kiV zJrSwhQWdXz1N(o7VFJ42I}Z|69|kj9zjMMadd@9AlAVdHW7I5Bq5#jQ;5vzFvr_8vpA`z&0FY+u$3CaeLZSfvC zM+n^P`;nmEjU;aI(UCzC(>|PW7-7yh!;G8c8ep;3Q)Z(`IsA4qT(8UgPrua?q|{&@ zEPJzui@nAkxJm!;019nB(8w`BLfOZH&m5t0G1e^l=Sxpa;jH5*&e}|o;0_V3zDJek zr*9XIaKF@PjD+_Uk~JU0N8$=R_B7-8)+z)@cfeb=0rC59BSEVVfg2{^vT%&Z^&u?h z_rQq%J~ZcCgx1_3QKS1hD116WILSaY)RFX8mpVcL8iCy&Xia+-`atxth&? zLFD=dCxl1fw7eUM>YS~A1#bc+FR6NjD7C?PcO6`I)xr9w5+v)~NB+?lNIpp7YSNEF z>v0qxpC)Y>L8{?<6rC7D43RIFZIo@^hg>4md`nJDhnX8rHtgYC^JI+v)1VqB2>j`{ zUV^sW7YJ5t4T{majRGznLiV2{(cEK$EEJG__#LuLhfwS|fl?CM94q?S;w{dc7-6sH zSq{?$A0#2}qvLN-e1Z!T+(v{-7yPBJ!%wOe-qM%p%V{JPMZ|U%_c%FB}&1 z!&2}S)ovOkTUl~2w+}6sHYPqZl15c8HghRS0=wfoPaIxf27kF5aFQtPED3q+@nP@_ zZz(OW^6I})uUGY``0cAb=PFy;>Lq^;G6Eq)roOCC{q$!$Y@gwdT{C=1SVO39xwE?K zJ3mITTtC$3?}P#WHI{;9E8Gje??;F#2a#ra2Y!1m!$GtHZW8BN*e^)tCQfXtK@sUf z?vXdhGJlJ_W1NQcp}=+sXNgYpkB%YFx}P*=l3)_jb_wjZZ$N84(g zeir%D@2#{(KqSv{pdjf`H;p<2$h90~IA7^Lg?y_K78c;dw8V7`7kqv}h5HzaY)4S- zJwc<-2x`5)&?xl*70#nLZP88k|1KQ2*O9n(z-`ZE1S+&3P^lRyMo*EhF$K?6LvUKq zha-Y7a9H3W^yjs+g$~lQQdoFEj6{~Zn*z58f*Vc6W^f~}2lg$>#esDxY&~)QVFMU9k!Jcgg~lo1wBajQWi$392o&(IXdQEtOh%osZ$TfdLBHDu@>j@S|AHz%Z3cU8Tv8Avl74E}BvL2_bA0tU?5Z-GCVK4lS z<-D5AzXP3l%~0hlCrXW`8p|qYSGf4kZW?j9y&JioxkkXnizMdx!E*CyBp-N)Gp?^A zZeD!D+uD#<|FCte|I@6qUQdD(_TMK_y#oF9ao9P-8(U{Mv)!Y(y7kXa*!mqOpeOPD z|2XjN_)I?*ca@qE#~dSDDnGjfM*I(PRIrBtXb2}3_9I?-nDpQ|eB~~|RxA%T+ltww zwVP-o{KRg+Pr4aJR^2GJ??WNcYNmM)k?R1m&H9mVJ&e4gBLrikD03yva2`YcF><&D z1Cv$WlTLs7qm|ra{pQ8TCwel>-Xg)^InqqHT(nW-+r1-vA0)A*3*|C_QujfWoR~l% z;eIiVN;MwSM6W~0F@6oZ&6V&LZ%3$n7d#|rgcGko-2NMgP<;*mpN8PIWD2%I-;$IK z`ENsgPA$u?6PpqCO+aUId3P~PV7XD2YXssmBA5Vk!FW*;+e2&f5vbZgcI0hVvHSDz z{s+IT;&nD&{iD>0v5)`KakftHnAnaI=uJ7&6J*Gz(snIYIY(~DJZ z5^L*s&P20b*h1%Uiv{*@uXE{FGXhztfCHPovvZ(5w~=7yCai^@!DZnPyw?vPQLmrv zC%|nd%B{e3qkiosO3$TlAyBp*sRwVP*zpxIEnlL{X#zE#pOJ4lOcXneT#F$R*Vm}< zqUScqv-e` z%ALkh>NJ2_mm#Fm4pGVv;3{4RFWEY>1aA>0{T^=1`*2v`4hic`m~LP;)3<2AAMZoPkykwxZa>TM)b#(Oq?z=XSGs)cDY6?wDOrDRLaV}M6a{uYD03ab zS*Ly?*g;ggllZ!gBGcd%0wiw1aVJ>^>1*(oYC?c)8&XZlQYiMqf898o7xt3{c>puA zA$oJ$**(9wbUB@qa8E2+*V)qoFmqqM66ueBR8kPIYW)P=W&4l8cYdx zP6+qIZOIT~l*W*5!rddQ8IGbAu-$nUo}$fg+1?E2?M;Z&xQDaWZ;@m14#f_`k~>HM<>tuO$W6mK!B&9|Blk=|5v9<=Z`&Q_LHdg;)2rysBoSjitRy-$0W`= zzQ;xXG31%NMyUK91WP=mFQW|}VvUGUe1I&=yGYW1i@?nja9lXRtcMX1tl|9YP@H`l zDtx6xsu}Dq3R1IU*`vaoEV3+F)Hpm@I6#gsm1-slZ5*5YQsB#F;R10Qouy`S?@5ID zrXr*oJ;p_sPZ4#2<35A0KMM0YDX;z(Yg68P18=3~Mw{)mIIuPg67zhqWrjT@=7g|# z>aLkS*iCgid+r5^*^zAWN_=J*#AXN5InL~L>A&5fWGBlZk0kdO%*d4s#c^3WYI7=K zA=pd8Is~VMJqTVuf<*2nfd{(~CVvY-vbR{ydVtJzSZ+LvK5*wvIt@fM zrS)12zn|peby!~gP23IO-lx??)*q4s74Ka3lx~6f>iTc_sk3~ja*zIyntKx4W;hYS zx>I{6H%EZ+(|0x`s6?@R0W2)QCbmdyxv&5ibL9k<>sR9B_&CAkZkr;{m(9eL+v%TM z@@gym9zGlTk;>f$>hKe|iPs}V;|)&iu7KOFD>$*`0wU#}A>ZN!F8B_k+IIkD!X z#@jN?pYuWh|J8CoA0kyA!)@ixBe)##5p8k5px*Bbs@#Xr;5+&^aeV-n-3{;*Yi3_e zIJa}o(RWBv8-nO2%L-zkIN?dw->U@4S=c(d< zbE)(CY+mI)-cxAbgEF^%BH1xC_>Un`^AY?cI^npj9$pen@Yr(&?oxHgws?%x{iE>v zVU$M5XE2$6m&IOn=3Rp3ybJ7$-a9Ls=rsT;^9sr4L@+DEG6-h)KxTFlqg!r87nl30 z$d~&qR4_Y*H5i#WTnbk*l=!o$;dwE-zjznR9Pr%J20t48(v0pRVgGBy z?3#k@qDMF;^csf*?!rKzlj?P-&M9Fc%84SEHo~nO;cN>RfBlvN8_DuqcQT=k$6lgS zZgPtwRT(~_T)r6Wq>)^7*0-ELMzgcSuwS?l#}+)Hzvm@RYP2I%qn6SpOp09e`%qBrIz;yW8DdnPBShv7+;%syow6boA0k=r2?~z&Ax35b zp=-Y2m|!eT)pMu zrPS9JqwhcR;<3E?53LWc_iXf0ZK^M_8cqw5y9w=udC(JRf%?2MYQu3jxS$15+SlMM zc^g{%wbbULAwJKKg#~ua@?=80W2P&1&T@z3oKULYh<59YZ^yTP=fWm>C8=+4E3&x0 z!Q36WzyIX`xk+Sh+fP0ICRhkQh2z3r_-=WJ48s9rnLLA=< z*Xeon?_J-%8WavQt2w2#+-t~gdjlNB>qsb%LvBtIOqSe)@?2{BWZ@k)JV2hs3wV*Z z%FRuNq<|k}_(R!b6_-*aKQ9HlXZuj~BC&PHZa#PHne9u|>I><45%k=Tfrb>{$-hBI z9Lv7pM3n;;4o=kOl|xsc9)|_)v$RNuMQ;!+(T7~iK6aOAZWpXj`CIUn?3nZxZFSR-cP2$@68=YsvI;D0{w>EiMRz{M;1C z^QU0zOnVa9lThSO!y(~j78)=Tyic~ukKUKWNLg!nDgu=*AzZ7mChJ&NTIac!3Oo_u z)xSs03vKn#Tov|SdATR-cAbIdl2m9c%76sF7c_*5p(AvWxh-{pBE%?UAp)8Qa(z6t( zFK}5lGP4ueq%W6KzL)xo`n*c$^IwB5|0UQ6_rQPkDAF`PpxkK)soLG}mZIa^N`mAB zoOp57Ut0;<)*}!l_d3W=>MDHpbi!5a0>ZT~Am<&-YN3?2! zc_hH!LI-klH{Fzp3Xg7_wS9}jYb%&w%JE0B39JK)>ZqMZ!brFi z@tUuYsPPth!sj4HA}S*gitT)MM5r!M6;6k&z)2{~r}jNJjE=ct*KBueo@vEGV%%hw zvcM_q;q#`?i(zvR9F(wyIOO!W%7q5B1kS-s_#Tc4y`cIEUh9UCa$pFjtRBEes;MpC zaEKRI{nam}m3uDYw)=8{pF}&Nw6CJfVG2<)18`qDf+Ki_%EeK8r*& zi>Ni7&2Dn3S5kbD*e6)Ph*f%SB#Wc&nc+{PaR|{Yjrt4oNnAr%I6#3vmCcMw&k2Vp zpFdRQXG29W8`|^F!FJJeSS+~@t@$-jqETI${}hpNGE{^zpeRUUyCfd=d&-b*dKcdE zHO(a_Z#a+iP4PsQSN~J>_SI+Goz?R%>a2==Z?mHm5o)(letZD+zT-&L?1RdJ6zt@4 zf&#TYZNVC-2^2zZUK}iz-XVAQ0`WSJVX(NK03Zf(LLnrm^|w|$_O$Ax?tj!%Y(Ic(-7oN1(+|f5BQ$EhgrQI?bOr07 zKED_W0?G9FZGTs8a!Yn@JPQ$Uiv?unMl-SHVpOX9IYg_WbSxH1H1caMEQF@eSrXP* zSgg7Ub-{cVCQzE6O3w>mBzOxJ3m+5J=F`ZYgS~T;sbL1N_bQSos|cq;RKN)`!hWz9 ztw6NyRm7XL3LyHa7E{OLx%q(k*zPb&vJys+#nL*a3bLdBHC~Lg0*qJQ0Cyci7qj2?qYTdl;;&< zztCkI7V3iif;Vtl@_sU8S3fVV`kP(jX@oid}rpkl^=$ z;krz?%9bNu_hv=vk_D(i($6Bi@7MZ`FV&`>O+>%bGZKWnzczOfk14TX^Wk6 z9NC`6asts%m>&z#dG6F+!yrD_2jYBwP!ddr)Vx5JJs>{k+oRs%3O4V+Wz=wcbnKkz z0mV5vP@Q)chlFpynuOI<@NQy|2ye;i@1~TPLnL6^+XD9`lVsOlkv+MEgY!F}KChgJ zw1_Nw9*JirON!=bRDFICTO1%sqqExl( zL1#qaB zpwd_Qy-l|o@r7!-x0u}?T3=BwJ-X7Gl~ zE+Nl!5M_2F(57>?@!1lM20?1RHzfJJAuZ@f?K23{0>KcQ=SkG+OFsu=>nt0hRewgV zoUn3X16lqU)*sXab69RTN3GmEg#v$8kB-0vUR?E$Qgj3^n;S2^+H+t*6AmqHf#}R& z$nvF-rHRD81vyZfpH8E1I;8nxAU->otW*inY(5EO0yU~2Xf7;(I-SSmx603tV|jku z`y}TDu+d#fD3MJLSS@}5GvSBO5I#ennMR~rMvc1wYQmW$tiI4(mJZd0Tzo4W@(aRP z)m)kdr9~&9x;Pe!ivw{&{4CsLOIyPYE*9Ua$mQeoRbv&2@yNfDd-ec4Q#~ z(YfxdjVlVpvQUBS+!!|D^=*#gB%4=I7tEQIm>m%$ClJI70sIk*fpBZk!9|yQSRj6O zDE0{!u~ZTz!8Ee+1vK&okSG#i&Iy2uP&zx#k*BIqCX3U`%!{P+a-g%Y90n`OS-J{m zmn7!;lkGYOvn4lRvGg9ah+GdYJI_*Jl!Y>&ESyXYof_c6R3g?;77mahN-$V`8ZyE@ zP+1ZM)umC;SWHyBA{oY;GGVki2FJznZ+fT~T^#5c<89FW2dRb8S5BC0Pq}wwQz5K( z6(RM&3)Fi~pe1Aq^+7|p6gGu(Uejz7=}M=sM6uIIQ0_*Z=M?IEh7qv0mBsWW1l?Kt zG+EKc#E^r5AhEYd)p?0P@t4%5v!NgqNzN&l2KxvoFNlZE@>48pU>6^^aKMd`ujm|4 z0)TXu_sT6IP^EsMFh3sqmy|(8Fat^g1Pp@N`EmjYJW>6lmu)k>L=@&F6sS?-(pqo^ za&r>N;uo=5PZ|C&i1P)q6)IdKQ(KS)**P)va}o;?=q;>d@l)+ZMNE9PmgKMr0JVi_ zEM@D+lKZe;{usK#)ht%ag%0!=*FtaU8K^Euh78#)xdnl27WdHFLZ}g~sxKyzT|ktv zG!Y65=x-46!GX0T=8Hn0yxg1JmDWl8Y-d5xRj&^NUuN+H=y$qgwWDvVyYjh4gCCN+ zjn`$tWm^*>Rqmn6VF;IfKjKRC2Q)>Dp&{TS>ioZ=<$+j37ZJ7+A!?Kp3P20wFFyVl5a0-Q@*rgBO+gS=cheu5H&$KVArcSN`83 z>m;&QApZWog`7afu!R8{3ksmWw2}q(rRS13F3g4e{8*w{YIt-GH<`szuh!yxYIq!x zCPIZoQ(|r)S+N`(THFH1HE*H2s1jNvw%ob%;j63u^vasu`!sft!D$d z%92PDSYH~@1DJp+2~%5NK$N?b+USyW?4IKcjYTA~i&LPoFqYmE!QeuAZusPGJ|An(yUL=us0oMYf+B4_PU0;%V1x53)o)ECowrNd`+>QC*l0MS&C|f=U>z zswF|qhV1-sXp`6)uc?9QifcHr>Mf3~d<0E8CdVJcLJ6FWGFV+mjg!bgAOLd0L<}NX zFyB}Pjpg(jk%r;gd?JVt9NkzAll4W=6-mXxwYgATMg+Yq5(j@shyMCdm~Tye5U6#& zrn%yQ8c&>l+qF4s+$37_RZW=kLnNpUB2lRqQL@hwEB6L@h65qrc#y z-zd&|d_twm2b{5*Mve0ql-m!Z;LrftB0l1j(QBBktA(_%7bN&SVY{IV#!FkEyQByw z)^_8R;d`X(z9Ru{hW7F_Cahxf+;QmpGdQrS0DA?)Aw}e>ydVxTf&l~#evn@n3Q7I| zBGz0ky=zipo?noTNIowFz$^d$VzusS5VzD%V{s-_g;QC|2^TsrTvC7iONm_5ptrmTh9YHbWy}5*r=h+e8*V?mhw~4;Fj#t?&W(YxU#2G!xsSYp%n1aXak3e+VOy^DtOeNewv*`)}@g+hrxJL5=?$dhT+Ee=SglC!iRb$c_RBOuYHd`t*CSwi7K$@&dNFR z90`i=5ib6SNVNx%k}r`c-_JxgOLqXp#|BaBI)LWzF*Jnrk+^FJ`I=GKzDHwIPuk5l1Fyy42fzcWckC%_MgSkbuBo$;xSy;_u}yC z258ec2bPz^YQt5?3x~7DtG_ZIN{hp&hT`a^D#$PPV|1#%A_6MQsBwRv4ZE#%B(gbB zrJt3T2E%mYX&l>93H8;1&{!FbeJdhi@?$QHf6T<8^~um#8w&fqIn8Y)uX(qc`8B3i z4Sbq)HD&B*(b0Dq*$3a?ockDZ4BsI^;T__n-y>S`4I)WYW2Ac!A@vNo2ZvDOGJw{Q zk7y)XZ9VxB&5_e+4E%~3x6i0N{uyOfUs31#85LF^Q13B~O1lX-h}L6|fCEdT;s$)X zjklq*q=?#JB?^wx?78kn$u+ab096`1t}qKBG+_sVX2cU z!g0JMtGx2}De^+m=0vVNN`i?nSXB!Bg9W~@+)~EuKNljq~=w5AAJD-#mUd2v-<`A1|Gs4q?m(pZ{?L#xVhaAg@(7bd`RT@#D9 zaJ^g zn+tGkTQO{QmB4s?9(Ak`=zkvz&D8<#GQ69D``?TU@&xXmQ*Tv$P)RlHKNF_>urW&W z2?C^^!hJ(O&X|8jOV}r5X!Q}LK1YJ=0Fo8@5hM4SYBy5U-l5iMoQQP-*Au>=BkmKf zM1IEQ@Xx6A{DiZ1lPIy7Mxpr>YFtN=r8SH?pHVu08cusIlid%3>e5J9ZM*{KZI5VR zFM#9r>nODyp*l{KS`2wQhYJU2uSg~^h=Kf~U=r3099W&(X1F1P7gyz#e{7Lk93f(` zvbf;z_vO%8LDaam0@{mDLt|+Q4A-7vL4QLU^);4c!+Fy)cbEvfK}{iydIFF1|Z6u-<3j?FU{w z_8(O5cf8%2*$3UWKF}kpf8?jrFyC|rMjK9n+x5sv^dedR zQzWdpFj$|0!y8XQ=lhf3wwXI2R>?%v?5BK$sdv!p39#N?2162N(@nW>5xopI(KhNl z!PvJl5cYd>o3B>A;N5EG?^uW4P0mesX^ODjQ`F@kb{;l6t6;vN0@mbayhUHZW7{jF zDSSb-%QQ}NHwWB1jKsbD2ormXB*g*5%l0Equ^UzPV`%W6MxFlN|-Sx;`}$6GM};UbCbC8TMM zvsGNal8+!eKMZ2?U7))rj%w1R#>%)LUa#hrUsZ7z>oPa_p{hrFX)c_1U4tG`sp^tw z99&%t`;E5{B-#t}bq&329QF{IuFr<;o-@#29|I@xY9^w=N>^Fz)pAQdG}i=?pyt4ET^6ji zR4{Qh`za4cx0K<;&N?FDWE|WON1q@1-by<2>h1PtTX|ym-#A${I`uCXv+o&Oi>2MP z-%|t+$xCn)y?|poO6fZ;fz9Si@DRHX@7*M#Y9nY4`2}Y!2av8jiZ}%>OQ0Ju(yx&y z*N1GaQMS_Ra?l5~M}K4?f%b&YXbR`{6PQBviND~i#YYsGOyHu|M-*E0quiknO+gdz zmT953Qb2=l1~gVA!gljj8t{{8;6IP-gCoc}{04SgFXPz8dX|Nvu`)K%Nv?($SLKyo zXE7AX7tvpxS75mIG#s~e;_wfpFkD+i4Z9saJKy5yh8D76#V}f13EgE}icA%Ze>j8v zt21D=qlC@)ANV02$9Ggwr)-AR_97hGkcI;r5@GTaS^OUpm{3}7D}d?dEVxQufF+5s zt>_t;Z_b0owp(gPexdg#`AHifnd@1ICGe&H1Gq?m<}UFX%I=WLZC!rlflyo-=jmFUA{|Rjo6S$fD8SU|( z(Gu|)&0)Xbf;W-t@vkU3LXSs(#s&AUIDPN~&O3fWD+zXx%1s)m^I`ZyHV%JZi4&V| zLw7|stVvL7oIau0b`b7jH|h1Pwg^SuT~>MJH&Rp=Cy4k?Z(M`3~z)2K$)UrHRN6AX)t&M}xk7;n&T?^w4r=Ynygv2!q zUecFgur3kiTe7f!eH8o^T41&{okTYd2i7N$Ko`POrU3!+?Qj++TH3~mb2n<1&eJ6MLWfDnID2O?X?8blYllXmSQmDF1`|t6uNjm~gZq!)Dj1 zI~MePSZ*#LN^!V@ zoMA+2u_X^4(nOgXGf5b0;iuS4RGI^4i5eKJkH-lyqSPHZ@Y&k{lT8`07cIewJykfV zc7su^?apEx-jqcIb()c}&CYVTN;JV$tOfQv>TrDLdANwS&}TP5XDt`MO@WjA+2)Sw zZY7>*{`+caSeL8G#<=Ilcb>-a-6brx>L$?wf7vb~$2{2Ys)ZwcudZU3ad;gKv^$y* zq1=lIsUcL^lEn|6LZ1EzQkBM#sxXWMxjw{6_aaa411>mC5upy@R_a%DBut|%mfNu9 zD=zwcMfC|1R`bs&F#JRU`vrA=M8GDasQ3PWQ-*J8u)YAJP093~o`S)O3fOMBf+IiH z;H2!k$qfBBLHRn9ybu7d{Pv6f%G{una{ZHjqVM3a?K;fY*TQaV3yy8R058c~FxhYh z2iK*+jI8~!?S&+u`Sd&!hCjwrhpnK;M7T+vN3c>m9nZ#bu_8KthU|ScTqLXEuUwC# zJ9FV7bAdW^Cj8_ZVX`@$Xtj*aD`V+e9JzAD>MM5@{&LsgE!z&;9W_K*<#3UzLzwD4 zmLF^UV+I$R=(dzh>*#qk$O{$x8+Bsr^S@LicN~q>ZmzQ1k$2BxOAZXzXTx2h6;9%f z@Q`eQuk1BAN>tJJl@I$p6*RaJ#cr!W@ZKlz6@QK}i9wXwki`%Dj7*}|Or=RA$n>$A zrZ9#a-4S+k!H%fUxSq_#TR-DU6p?GdN1XHeMB+-sYWf*@2S4Jh`4`kUf5171Pq-EL zugEfd!4{oZkhmMJ%Z0DZ6BeQ}`=KgdN2ErC*CTo5cU7FW4T+qTdtcxw`Vcl-8sRS1 z1(!XYj4+PxK8FMAl8GwoVYR)O1Tq&EM5vAuWw0d?^;Nh8N3m+SOPz!9rbH&9CnV0m zVmk?`LL;1{N@2IB2v$4u>3yf*y_e`$>=aIjmcxlUxWB>`mLuyS(+FqD^K|Syf|Rep zQ??l{;!W_A>x8p-13hnqx6Cyd(BERPE&&I=Pk5W=aXECTcanFjnZMN+w+1)(X_r@- z{gi|gyGm(ryNnQ(M|6#EP;G~oTr)ydZX;6jK927pXR$pW`s?H9JGp{rjb}u)*AS&N zh!nL^T=e{idjAhZt;2{E?M4QPY|7pdB*_mU-(Vb9LZ)#e@eA6MCU7nOE1FM!!X^K| zpvr-)ztt4-4}PNh1;s}`q4?-9%8yN=$>(R}m=2QbDIf=Q7H;D0u-ks6&286hUR;$| ze&?YAA_uKiNj)|{U4fhEb)wg59Q+{*MjLWS46ETof@dR^LjqUd0B}Az=+uX@i4AF|2pzljs)0iRjjg z&h?PKM4wv=f29_Ls9q<5y$%-=bPu^Y7LRolyNCe!E_(lCgztL@XNfxcyHa4aC$H;5 z)-#how5ZtZ?j0A&a&i)lNIBS#VC4sN%{$2z+(CqP7Y$N%aFed5L8^_# z!~+ytV7-&RAE^uQl)i#6h1Up?=|PU(6zY9GW$ zXbzepVx7jVl)sR;{){V;KeO!x&stBT(s~L-#*@f7Fo8-U)-DU<%HUFN)A$18uRa$-lTx$Tbn9(VB$SZ%Gw@ttJRcjhtLwAh&e7ikhr(E^xn z&W7>UIJipHAW-QtJY;L&qi}%;H49d|v*9CON4CBKmOIjkL@%@m;m>+}nsCrRzk-mtnW-9Erv|Bxt`!f^IMT zWFNBZ1e+bD_k1-jo$IbgqX5~PY$DBJPhD5B&zpdezA3)nyQp3)xS{W(T2}8Ue!A0Lt^y~uy6Bp| zAYpxp812`H*!L3Any(O|b{C#<%|x*`i1=?IT>S>z_SO)s()U1O9HMp&o-&u|x?Uz{ z(uEYQ5tjJRS^bKm)5uW%fJB*oB+3pTokTW$-w-bQeMEiW09*3f8a0g$I=3l=6Vkt+ z!fqOQhF_3pFom4`pV1oj7Ze(g;(E-#(rd$Q8RpM8caCgi z6A5btcfTw|s*~`^H<10mKpnM=I&dw#h+N%>YLAQO(uG5AyoM~0#xe}ta1&R=8uSU8%PLlQHO71L>r*eMr2lxP{k)m zJw)`X^B(b9eTY#VMxy2b;&flaTka}}NEb4U`U^V?#`TBaPyg;j_Vw+tb*abN)10Nw zcDT@W3{~lXi{vHt|A(qRK$O-~q#F&;HGhjlonE@0w-KaD!m4(gxr0c}E_f@}(?Hlj z-x=pD&e4EbN!PfUg%aXaxXoCm&>sH@S^GwjC`Z><<{P!9DU2iEU<{p!A8|YFXS794 z;a2+3XpR1gOM$=OywhJ$ZTAJGmYlGTB2#A!7d$6Xe0chPliw#^T$NXN<=-lPa!qnR z@(n#fO3g&8NhGkRVY54rMDRQUl^ftBUWz3BTVy%QsFqOYt-;Y-?nrjT`T0vU#VNINuu6vG}8m?wzUdxY~rBVKK#Z}$BjM3viU zJj0p${*12luehG{Gdk$J%RxV*C4i{a{xfP%d_?Ynzal|-5NFLlOkQ;R z%-af(S9s;$6_1rDGG9l4w8IIbY$XY4H4$hVLNy!Mv1pA>oRBz89k`x^wiw}B z&FmaknG)EEXORfrN4owK1S+(^Pw^t+^@&=Qn~9_@z(ejl32+zL+zxokUm)vRPn67A z+XiM~{S`aO`aVXHEp>MNaikC-rBTf@oj{h!AYyf&QhiRs{0uRA50Gm7xFA^PLREA5 z-QVo3X0Da=YWb>G*83?};iP&yBDFecKx=}xLIWbTJBik>Bh$Eti2fBa=^7**c#Zh| z-N-Q;M4a9W_{d*@A6@H{tE^d6FTCET7y30vhTm5(*7$7jK5_H zLhJtQ7@N(A?q zKKCAy44=SeNA|t5L7iUxJ)^&wUAJx&4{8dBkfyL+ZhINIB4lLc>pJ3iyJn(Vvm2@&Q>?(-p>%sxXEOm2tF%eMU#jXBH0V zNce*53IB?gkpGEhzptpWpGJ}C&u!($K5ygo5?tazv$qCEb|%7nM*^Ir3K2?{G;Cip3FUQ0xBg0Xh}5}CcAlt8 zyOmzMf|P@gNeEsbl%B`x+@WLFkYWB92}Grdy04LAI*hpeFOhv{0I_O)$TAv7n(;g2 zS`3j8KSP?~TN2erM6OQ|O=25O!t5k=mc+cGwKVv?*YjKb8-A^#TAzFWP=e9b!Wga2 znsk#}h^0X$PWuMjaQW;WN5Mk5F`c5NRgeH1NEk|Mv+p z4)+k1J}1F_LD#nf*~YJsV)y|5>gN%uOV{|oJ%p&X(sjH|M0*=~hewcaJc_2UDO_}) z!YS2BCaxJuACR~26G~0Kp!MVw?xg*UdpTTa;1_fz{(^I!Q)u@6OHYZ-&%C%Qukgx$ zXYp66F?WkDq{5BE&{(`mN%@zjcjl$S?SjBgeMtJh!jQ>!JxqyfeF0TF!*VszWtwaGSl zie%$kNH*$X0}^+Q@-2H2yZ;^vtOt;5)r&&AVH#B4Aj_u!3=o)e%fz(6yiC|mc ztyoI~&UM7jEIPx_<;ncnv4abYzh9qg7SGG0AAshzhCi?uW$-iz0%_(TL4EQR8GVqHLoH> zy`HG_D(oe55w3QH#Fd0X>l)GL6Qmt@h#=(#66F>mu)B!gPn2eG4e6$L$O1n=010&N zv8P0(kC0+?AE!xBGmLsrU^Rp?r%@Cf`G8`ZPbjgS###Gexec$q6)@c#54&A?u-lWB1G@KUHCLglh5E+9s;6G=psN&D|2LH`C4xa(qkpM>*1(hfdE zmI+-ygXajR!7Ib;ISKAF`v2c^*%FA-d`QImgs$~{oHBcfaE&(Pm_McW--DC%S-Q?Q zk!*0A1|crwatEmfeROSyQ1AW)o$H7}0vkR}wi@BUtqk z(n%n=i7{WLYD8*Zq0Zh#V)=rJNwUFRqOvNlhktyks%fOw(7$H76RgeuJ~e-;v1NM20C@U$Ym8)@&!yK93;P z^YB%yftOq*0u<_zr1cD0hn^QkX|>g)**C@4r#~^fd9hpO+0DKUAI2vCOeQG`5hUQv6&Is4Mj5r-G4ecDlROlM$-$A4X4LJ58b1a|&g4 zUvSQeNbC47$g>zm_K~;9HYZDL{t}soU*nAJ01`>4i>>;QbnrT|4nJVR606mTOrkh0 zmKmbj1YeaZL};}jN%s-`t}6)LcL{!q=iseS2`{BmBFgg1QTk0~;Rff63q89+tAk#6 zRmVI$(U|tqq9*pS-Gzi_HWw3LST&{gSQPu-52*Be<(FX6mK&|zQI%?V|4bo?VW!y~ zoH_msr!0vkEgm39tq$QTtwi>XNYd{jF{SHZ&`HF3i>}diqW%tqX&zq6+j@LSsFKKj2C9-!YFs5jZN^CwjL>}zM5s5AZS;hQ zwTrASQR|_bD71cwY|DEnuzXEoL&wb?lQ`ZbI(vtV!!J?dIEs=JA5i7+7ZTPlR6ioe zWR$3Fg2ZYNnoy^fP^N=u!E@YD&qAz5v_FfNNzYlFWU(J1|&c_j8ZhHnt4QU@PdI;M67@jAB=soTol@2_%>Y&`ufI_)H)O)Qly zT>T3D-#1yDG>qsrL7$!_)B9|H!IjXTaXfC!DEVuDtZSq*d~&3Kaa}aL1-kTj{f5W~F-f%m9kLmWbfSh*+ng`BMWL&TWxm96-M3 z1Sz;DcyNhA*}z3qhb#)|)P}61o)lJ*|2&cF7V1LxN!{+FPW=(h!9UP@htNfQ#{H{b zP!sf?l-nCLN57_HY$4BQ3Z;RwL@JYL4S9nyuN5Ng4I%L&j~P<0Q>3h)A=P0JNw&{$ z&yEzeWhbs$wjtGd5Q(-u^qmGMRG*NW13%xS(E7G@50T_F?QcX5h3NMjheV-EJDJ@O zV*jN3N}>*9$aEc(Vqd27IO0yWka}JxLVZDD`iP_^QXHNO$uj{nnO-~DPRE^;bV0t$ z0@CPx&bgNQ&7(EqHGQ6euE{D&{7K25e~C8DKHYHMj@l!oZ=}yA z61}jEn)9UE&(5JNa9R{_)mbL!byBl?s8S!IHS8k{X+IOeenExf5sFV9q1yI)eeNIk zPALDu3KaZ;QR+P}ty>u`!!or+WQ!`lRU|t+LayrsDoK$gIrJiv-Y@o^qfq`0DaEfT zf({K4B`L3(&~>z3+(%8wTQr{EqmcM5>I42N>4Ca)2e=>i1@|w1Phsv$v}$%~`)$+( zzmgm-tGzP6S!AmW^gNGpBI+z6xJ*)@?2V9aKTe;wfa}(zQtf&X`{xD;$&-mFZ=LC( zM>mSxSBNB^6Nx?{GA6+oVAY2_)jZvVjA)M7L{0b{ zo%13JJ!eoIxQ3eGHRvMW(Yd`LmHG<0n73%YctB)(2z~qq6bCGzJ?bs)+CC+s9ieOb zO3pjqbDVB2Q>gOi-1Pw|*pKLp{24C_e#AiHk0>~~H(Y6BR`RL}6#SZ?*O*V_IL(+! z{TD^OwuHQ+aGGiYcx~M}m$G)cLJv2q_pelG1#eqDCutZ92naJfON{F!YJPp#pQ0z4) z?M*4RBgpX>CuKPyQ)8TSWd)mTI}ELDAGG$pq;l!|l2T2uc}T=MMEeYhZ$b)fljk{2 z1U`p+w|S&GJx8%8h2Zo#1@wEas}XnY`{?&sB-;!jkq9%_;|1=KYUN^8rs@Tev=M3c zBhcE=b}q|A)MKP(pP|xslL&cC+SeMx*3lTbiX!hBQTMgyRwd-`y0VM5m_2mF(Ye!g zYKt+GQvHOs*gaCPTj;*Lht}{nbi|eE?=e;U zlX);v8Cg}J;8%?ln?ZHD-MEQKj#X=!&jPp|sfNh3J^Ced;U-BJ6nYye?B~`hBay=< z>WCog&%Z-c#1UGekI)%?EWV+gM6#`ndLU0VgA7u!Tv<<7jiSVFiHLAmh_cdeQwm=RXC6t& zU+lU{g!mX*B0Kh2V8YFJofSgN;DVIhfE3HJRgXXKa#u8YVdm8(7T1lf+$NV0h@ zeXQxK5jw_W$={ZGt;@04lYzG@^fb~aaFqHB|$*U?*@LPfU z8|@#8{f*iRzZL0w&2$+;ZP2=ezPhLlDZJ<|yp#f0Y2X}Mqu)S(?ErO=Cdnx_h8>|P zY#;UKj?jDk3z5hNv_%uiM7%_G$R_Q(i@I~KNa1nQ{WIhenPxhTN&zj42#`AllI)+z z2rv616niXFC{CgIsryK_A0%~aK&s;q%Kg?!Wlqq(FC-^gva|lLEFgnHlX3+tKr&klag0epy0QNmhin3jUnrG zP2p>#4Es@eb^-Zb6VMS!Hk{i=y?Td8caunS9gnqUw8tFDAVG5kg})b%(G>E%cnx%1 zqR=?{E$Sn`qtJLCO&4BE(|tXW5G%imvok30m?okk0uNZC*Onwtnqc(=_v{T)mFJM0 z+oL#7SsA!NA^JFy9iAb@W=KA}+;dHeX6cS&@}0C+Po>kM zk*-5a)F#RTh@gFVpn``YUZRA~fzP`&`jBo&`)H4QPsF-UukF!|hR=Tjts(Ew5xs*F zQvXGs({xVDXb9diHHMg!ys82PzXz218!f5=R!mHUMZS|1)|+tu(k_L;q*|liqMFoJ z=f%%xzp@K`ycr!ae?dpoPiT!erqK2idT)Fo;yp$cZCB*Ggs#{lv|f0Raw4GKtNWq= zn}T1VKKMInmn!y{MODB$DNdabCAU{`=*~T^Om3w*>Iqn{1ZOUjBh&%-DroMbbAeAju|Cc|}@2=j?_B&3ll=5#}W+X7NZ zS*O!}_v}YWl`hJDxsJ1>u(`PP0!`uU6JSJ{zY&cT=9l@-)Ad+GXY9T#u~HZI22B@t z>3V&U9BSv4w}*dyk?{O*ad_1#?5#qLNotpy2n2T;D-;ZSaz*%zqB$ z>RA-}Orb)(Bn2AIqu#%IB$G&-chz6|5&D?FqAlt(+B9Z#UOPlR&)A3WNP6JG6)y1X zpf%D&q_jaH{vyhFd^B)@NNrYz9B!O^AYpr!>zJ6zTtBH7<;teuT(rvbn39PoE;ywT z`Q>{}BhPhCUQaqRK*wB_^}*5{264x>k5np8J{hE^H`{576srLl6z*rL#*ldGvGmMl z5n&elEQ+^66{%w;b{#3qMC(3DLGVhcm%nY6ylo~OubR%kniPEfxw&YX0t{kH|f?J3_qa~ckG~#bWq=z!4)f%;rhV!qXi++bf3bD&c zxiy~OAVtd_uOp-|hltRIQRFcvrYLMMQ{*>`yAF?0;l(C41KPi=yQA zDd|a7&7e@4`{`It&yhl;cuVrIqteQi?au90Q!-l1#jYeLQlkz={K>V3@Aw}*-<$3>H*D0jhjY!V)mQ9z8#&Rlvy9e08tH5=MRPMMGpbAI{ zr`irtm~Rvnnqb?DZ0BiGuk%Q8d4dv8Qj%`-k{;mpDs}@a@S3LI4dB6wo3xMgysD;U z{Pwnu9?1?*kx0t6A#@#OzD(u=bc_k;FTFwg#T^v-&p>~TZYUSc=#Dp|>+&bGXx@{u zKQQa#54E)#lac~Zpg_TY50$|inpVv_Q>*3!p4|EweOLd22b!PIL+Y(2=m1R@KBDL9 zPo(bNqATtYr2(r%I`2vKy^*{nw=k7@Eh5u(Sb9qHJV+tBE+9`e2lhZwV$+D2b3G@C zEC*yHHplfJz63<(N!CQ*J}*$_wSilwdJy~PCZyA6CtCI+mB_V#4Y7%!a~zFC-UgHh z&Y>Y>19|S_XpZD@;C0lU+d+M}33U-BI@iylTnQY_kX$8qB2)*g(EHz^#*h77 znZzE+iU@2V%>^o672)O?y(~wQ>oO|~D(1N?kcu@Bnev$I91-9!GTcUpC|^hm)s0h~ za;y@M6>+ZO@mMZ~@%U?!^#Bs>dL&)IT?$OX9QxMKq+?7<5lhx0vwbQA&)x!e zNilP~SatA%OqgZ67*Oav30=e%YJykL5VcL@x`X!Ek7x`(94_@&TB{T&Q1DMcZMgYF zZP17Ldi4=1{Xd{9>Sxr29H2VHgx1K9XrV`S@GDdWZAoFLI%o+c{?kOp8$wP+9F{v7 zP@tml-gQ!PpX_rQZ>g77D4rf;MVo3jOkw$|7`5=~3d!_4o2+mOAxAYO4*#WIt3;xM zQUqf+tyqf&$)ED%R+=M|=71EmxW6^UaY*`Ib6t$c^&Lln#~doWwk3Cao3=?OMa_c* zoNvu>8xz%9;6JovXbovznZ@|&&jYrmd6tjK*4 zU78(Khs~l{y^Fin{kR|ZnjNyt`R< zdlO_k%%Iqloxq;px>c795^$^6bt}De4ctEU5Y52{NK^HrR=rL)f=Lv5O`-V$6ZNpZ zRK0#e`HL%1py2-uecGQ-=%Nqm+AhC`F8Tu+LibR4b{n-suEoC7Vh&U7zb-jUcHLs@ zJ~nRQu7C^*w|Taoi%#MZ;QXAz^)1}A?3Hjo{&WZOT;^nufX%eIbD+eVkFzM&g;yOr%5vLPp8FKi>_(Azx=-A;_;ntCWu;plNXpk|O~!8XJ!X-3rk_-;frz5*2iR#sV6pg_Sd6xG4&>h@@piI+S{aeOT4fozW5)2 z#GS%!&lNFUNhT%AD*)uUOd`j5nh3C8icdEzdt@Y)yj>wou+hI)706cPg&9aTuY8Nu>nS5DAFCd;*dG(w# zr`e5YYgNh+fC2>yekEuOTT`_}Zg%Imj#Ajaj0(SHBF28{HRWOx6WnzQ?^A7grGiBn zL5=uhIpQt!qFmYBrNDFMt39F0fE4>-Sr(i<2zVHPC%rf=Q0coRBwHS^Ecshb4aiCd zr+H1Tr*!;bWVso{RqHNo&t~1V>g{2j`cR{>s8vW+fdU1;PSmQ`PxM@QqfU1k94_}> zm$s+dR=r4fG$74xOnO^W9S3D~fZL}Y%TnLmubSpGfP8OKwXPE~rpjw#C0aj}@SY7< zcx07Hl}BH%pX?U@ST?@SRvGEI2C*&Fp6)||`+^J{q}V(k&UH6x`v6HY%ga|Zzzs+eRs|9MaKTx`lZlikqEY5R%}gn7?6;ktN*;b3zPA!(+?J|S$5`SJ5H+=g{nY-g5Mn~Jhr|m z@tjwcc&%s>tRLj%yUz`$+6@igv3<0Y=`dxEx44hEZ(GE$MQh!MT<2L_`nJ)W?rhje zw0^vkV*ji=%WbqST{WU*)0rz4?cZoE<`ptkpg@5F1qyzP_zyN4`RKUL%sc=9002ov JPDHLkV1myZcL)Fg literal 0 HcmV?d00001 diff --git a/client/src/assets/vite.svg b/client/src/assets/vite.svg new file mode 100644 index 0000000..5101b67 --- /dev/null +++ b/client/src/assets/vite.svg @@ -0,0 +1 @@ +Vite diff --git a/client/src/auth/authProvider.ts b/client/src/auth/authProvider.ts index e8bbaac..18a010a 100644 --- a/client/src/auth/authProvider.ts +++ b/client/src/auth/authProvider.ts @@ -1,45 +1,44 @@ -import { AuthProvider } from 'react-admin'; -import { - forceReauthentication, - getIdentity, - getRealmRoles, - getValidAccessToken, - initKeycloak, - logoutFromKeycloak, -} from './keycloak'; +import type { AuthProvider } from 'react-admin'; +import { getKeycloak, initKeycloak } from './keycloak'; -const authProvider: AuthProvider = { +export const authProvider: AuthProvider = { login: async () => { await initKeycloak(); + await getKeycloak().login(); }, - logout: async () => { - await logoutFromKeycloak(); + await getKeycloak().logout({ redirectUri: window.location.origin }); }, - checkAuth: async () => { - await getValidAccessToken(); + await initKeycloak(); + if (!getKeycloak().authenticated) { + await getKeycloak().login(); + } }, - checkError: async (error) => { - const status = error?.status; - + const status = error?.status ?? error?.response?.status; if (status === 401) { - await forceReauthentication(); + getKeycloak().clearToken(); return Promise.reject(error); } - if (status === 403) { - return Promise.resolve(); + return Promise.reject(error); } - return Promise.resolve(); }, - - getIdentity: async () => getIdentity(), - - getPermissions: async () => getRealmRoles(), + getIdentity: async () => { + await initKeycloak(); + const tokenParsed = getKeycloak().tokenParsed as Record | undefined; + return { + id: String(tokenParsed?.sub ?? 'anonymous'), + fullName: typeof tokenParsed?.name === 'string' ? tokenParsed.name : (typeof tokenParsed?.preferred_username === 'string' ? tokenParsed.preferred_username : 'User'), + avatar: undefined, + }; + }, + getPermissions: async () => { + await initKeycloak(); + const tokenParsed = getKeycloak().tokenParsed as { realm_access?: { roles?: unknown } } | undefined; + const roles = tokenParsed?.realm_access?.roles; + return Array.isArray(roles) ? roles.filter((role): role is string => typeof role === 'string') : []; + }, }; - -export default authProvider; - diff --git a/client/src/auth/keycloak.ts b/client/src/auth/keycloak.ts index 2103915..bb778c4 100644 --- a/client/src/auth/keycloak.ts +++ b/client/src/auth/keycloak.ts @@ -1,96 +1,43 @@ -import Keycloak, { KeycloakTokenParsed } from 'keycloak-js'; +import Keycloak from 'keycloak-js'; import { env } from '../config/env'; -interface RealmAccessTokenParsed extends KeycloakTokenParsed { - realm_access?: { - roles: string[]; - }; -} - const keycloak = new Keycloak({ url: env.keycloakUrl, realm: env.keycloakRealm, clientId: env.keycloakClientId, }); -let keycloakInitPromise: Promise | null = null; -let refreshInFlight: Promise | null = null; +let initPromise: Promise | null = null; +let refreshPromise: Promise | null = null; + +export async function initKeycloak(): Promise { + if (!initPromise) { + initPromise = keycloak.init({ + onLoad: 'login-required', + pkceMethod: 'S256', + checkLoginIframe: false, + }); + } + + return initPromise; +} + +export async function getAccessToken(): Promise { + await initKeycloak(); + if (!keycloak.authenticated) return null; + + if (!refreshPromise) { + refreshPromise = keycloak + .updateToken(30) + .then(() => keycloak.token ?? null) + .finally(() => { + refreshPromise = null; + }); + } + + return refreshPromise; +} export function getKeycloak() { return keycloak; } - -export async function initKeycloak() { - if (!keycloakInitPromise) { - keycloakInitPromise = keycloak - .init({ - onLoad: 'login-required', - pkceMethod: 'S256', - checkLoginIframe: false, - }) - .then((authenticated) => { - if (!authenticated) { - return keycloak.login({ redirectUri: window.location.href }); - } - }); - } - - await keycloakInitPromise; -} - -async function refreshAccessToken(minValiditySeconds = 30) { - if (!refreshInFlight) { - refreshInFlight = keycloak - .updateToken(minValiditySeconds) - .then(() => undefined) - .finally(() => { - refreshInFlight = null; - }); - } - - await refreshInFlight; -} - -export async function getValidAccessToken(minValiditySeconds = 30): Promise { - await initKeycloak(); - - if (!keycloak.authenticated) { - await keycloak.login({ redirectUri: window.location.href }); - throw new Error('User is not authenticated'); - } - - await refreshAccessToken(minValiditySeconds); - - if (!keycloak.token) { - throw new Error('Missing access token'); - } - - return keycloak.token; -} - -export async function forceReauthentication() { - keycloak.clearToken(); - await keycloak.login({ redirectUri: window.location.href }); -} - -export async function logoutFromKeycloak() { - await keycloak.logout({ redirectUri: window.location.origin }); -} - -export function getRealmRoles(): string[] { - const parsed = keycloak.tokenParsed as RealmAccessTokenParsed | undefined; - const roles = parsed?.realm_access?.roles; - return Array.isArray(roles) ? roles : []; -} - -export function getIdentity() { - const parsed = keycloak.tokenParsed as RealmAccessTokenParsed | undefined; - const id = parsed?.sub ?? 'unknown'; - const fullName = - parsed?.name ?? - parsed?.preferred_username ?? - parsed?.email ?? - 'Unknown User'; - - return { id, fullName }; -} diff --git a/client/src/config/env.ts b/client/src/config/env.ts index 818f12e..f20968e 100644 --- a/client/src/config/env.ts +++ b/client/src/config/env.ts @@ -1,24 +1,6 @@ -const REQUIRED_ENV_KEYS = [ - 'VITE_API_URL', - 'VITE_KEYCLOAK_URL', - 'VITE_KEYCLOAK_REALM', - 'VITE_KEYCLOAK_CLIENT_ID', -] as const; - -type RequiredEnvKey = (typeof REQUIRED_ENV_KEYS)[number]; - -function readRequiredEnv(key: RequiredEnvKey): string { - const value = import.meta.env[key]; - if (!value || !value.trim()) { - throw new Error(`Missing required environment variable: ${key}`); - } - return value; -} - export const env = { - apiUrl: readRequiredEnv('VITE_API_URL'), - keycloakUrl: readRequiredEnv('VITE_KEYCLOAK_URL'), - keycloakRealm: readRequiredEnv('VITE_KEYCLOAK_REALM'), - keycloakClientId: readRequiredEnv('VITE_KEYCLOAK_CLIENT_ID'), -} as const; - + apiUrl: import.meta.env.VITE_API_URL ?? 'http://localhost:3000', + keycloakUrl: import.meta.env.VITE_KEYCLOAK_URL ?? 'https://sso.greact.ru', + keycloakRealm: import.meta.env.VITE_KEYCLOAK_REALM ?? 'toir', + keycloakClientId: import.meta.env.VITE_KEYCLOAK_CLIENT_ID ?? 'toir-frontend', +}; diff --git a/client/src/dataProvider.ts b/client/src/dataProvider.ts index 07952bd..27240c1 100644 --- a/client/src/dataProvider.ts +++ b/client/src/dataProvider.ts @@ -1,146 +1,174 @@ -import { DataProvider, fetchUtils } from 'react-admin'; -import { getValidAccessToken } from './auth/keycloak'; -import { env } from './config/env'; +import type { DataProvider } from "react-admin"; +import { env } from "./config/env"; +import { getAccessToken } from "./auth/keycloak"; -const apiUrl = env.apiUrl; +async function fetchJson( + url: string, + options: RequestInit = {}, +): Promise<{ json: any; headers: Headers; status: number }> { + const headers = new Headers( + options.headers ?? { Accept: "application/json" }, + ); + const token = await getAccessToken(); + if (token) { + headers.set('Authorization', `Bearer ${token}`); + } + if (!headers.has("Content-Type") && options.body) { + headers.set("Content-Type", "application/json"); + } -const httpClient = async (url: string, options: fetchUtils.Options = {}) => { - const token = await getValidAccessToken(); - const headers = new Headers(options.headers ?? { Accept: 'application/json' }); - headers.set('Authorization', `Bearer ${token}`); - - return fetchUtils.fetchJson(url, { - ...options, - headers, - }); -}; - -function buildQueryString(query: Record) { - const search = new URLSearchParams(); - Object.entries(query).forEach(([key, val]) => { - if (val === undefined || val === null || val === '') return; - if (Array.isArray(val)) { - val.forEach((v) => { - if (v === undefined || v === null || v === '') return; - search.append(key, String(v)); - }); - return; + const response = await fetch(url, { ...options, headers }); + if (!response.ok) { + const error = new Error( + "Request failed with status " + response.status, + ) as Error & { status?: number; body?: unknown }; + error.status = response.status; + try { + error.body = await response.json(); + } catch { + error.body = null; } - search.set(key, String(val)); - }); - return search.toString(); + throw error; + } + + if (response.status === 204) { + return { json: null, headers: response.headers, status: response.status }; + } + + const json = await response.json(); + return { json, headers: response.headers, status: response.status }; } -const dataProvider: DataProvider = { +function appendSearchParam( + searchParams: URLSearchParams, + key: string, + value: unknown, +): void { + if (Array.isArray(value)) { + value.forEach((entry) => appendSearchParam(searchParams, key, entry)); + return; + } + if (value === undefined || value === null || value === "") { + return; + } + searchParams.append(key, String(value)); +} + +function buildListUrl(resource: string, params: any): string { + const resourcePath = resource === "equipment" ? "equipments" : resource; + const searchParams = new URLSearchParams(); + searchParams.set( + "_start", + String((params.pagination.page - 1) * params.pagination.perPage), + ); + searchParams.set( + "_end", + String(params.pagination.page * params.pagination.perPage), + ); + searchParams.set("_sort", params.sort.field); + searchParams.set("_order", params.sort.order); + Object.entries(params.filter ?? {}).forEach(([key, value]) => { + appendSearchParam(searchParams, key, value); + }); + const queryString = searchParams.toString(); + return ( + env.apiUrl + "/" + resourcePath + (queryString ? "?" + queryString : "") + ); +} + +export const dataProvider: DataProvider = { getList: async (resource, params) => { - const { page, perPage } = params.pagination!; - const { field, order } = params.sort!; - const start = (page - 1) * perPage; - const end = page * perPage; - - const query: Record = { - _start: start, - _end: end, - _sort: field, - _order: order, - ...(params.filter ?? {}), - }; - - const queryString = buildQueryString(query); - const url = `${apiUrl}/${resource}?${queryString}`; - const { json, headers } = await httpClient(url); - - const contentRange = headers.get('Content-Range'); + if (resource === "price-list") { + const { json } = await fetchJson(env.apiUrl + "/price-list"); + return { data: [json], total: 1 }; + } + const { json, headers } = await fetchJson(buildListUrl(resource, params)); + const contentRange = headers.get("Content-Range"); const total = contentRange - ? parseInt(contentRange.split('/').pop() || '0', 10) - : json.length; - - return { data: json, total }; + ? Number( + contentRange.split("/").pop() ?? + (Array.isArray(json) ? json.length : 0), + ) + : Array.isArray(json) + ? json.length + : 0; + return { data: Array.isArray(json) ? json : [], total }; }, - getOne: async (resource, params) => { - const { json } = await httpClient(`${apiUrl}/${resource}/${params.id}`); + const resourcePath = resource === "equipment" ? "equipments" : resource; + const url = + resource === "price-list" + ? env.apiUrl + "/price-list" + : env.apiUrl + "/" + resourcePath + "/" + params.id; + const { json } = await fetchJson(url); return { data: json }; }, - getMany: async (resource, params) => { - const query = params.ids.map((id) => `id=${id}`).join('&'); - const { json } = await httpClient(`${apiUrl}/${resource}?${query}`); - return { data: json }; + if (resource === "price-list") { + const { json } = await fetchJson(env.apiUrl + "/price-list"); + return { data: params.ids.includes("price-list") ? [json] : [] }; + } + const records = await Promise.all( + params.ids.map((id) => + dataProvider.getOne(resource, { id, meta: params.meta } as any), + ), + ); + return { data: records.map((result) => result.data) }; }, - - getManyReference: async (resource, params) => { - const { page, perPage } = params.pagination!; - const { field, order } = params.sort!; - const start = (page - 1) * perPage; - const end = page * perPage; - - const query: Record = { - _start: start, - _end: end, - _sort: field, - _order: order, - [params.target]: params.id, - ...(params.filter ?? {}), - }; - - const queryString = buildQueryString(query); - const url = `${apiUrl}/${resource}?${queryString}`; - const { json, headers } = await httpClient(url); - - const contentRange = headers.get('Content-Range'); - const total = contentRange - ? parseInt(contentRange.split('/').pop() || '0', 10) - : json.length; - - return { data: json, total }; - }, - + getManyReference: async (resource, params) => + dataProvider.getList(resource, { + pagination: params.pagination, + sort: params.sort, + filter: { ...(params.filter ?? {}), [params.target]: params.id }, + meta: params.meta, + } as any), create: async (resource, params) => { - const { json } = await httpClient(`${apiUrl}/${resource}`, { - method: 'POST', + const resourcePath = resource === "equipment" ? "equipments" : resource; + const { json } = await fetchJson(env.apiUrl + "/" + resourcePath, { + method: "POST", body: JSON.stringify(params.data), }); return { data: json }; }, - update: async (resource, params) => { - const { json } = await httpClient(`${apiUrl}/${resource}/${params.id}`, { - method: 'PATCH', - body: JSON.stringify(params.data), - }); + const resourcePath = resource === "equipment" ? "equipments" : resource; + const { json } = await fetchJson( + env.apiUrl + "/" + resourcePath + "/" + params.id, + { method: "PATCH", body: JSON.stringify(params.data) }, + ); return { data: json }; }, - updateMany: async (resource, params) => { - const responses = await Promise.all( + const results = await Promise.all( params.ids.map((id) => - httpClient(`${apiUrl}/${resource}/${id}`, { - method: 'PATCH', - body: JSON.stringify(params.data), - }) - ) + dataProvider.update(resource, { + id, + data: params.data, + previousData: {}, + meta: params.meta, + } as any), + ), ); - return { data: responses.map(({ json }) => json.id) }; + return { data: results.map((result) => result.data.id) }; }, - delete: async (resource, params) => { - const { json } = await httpClient(`${apiUrl}/${resource}/${params.id}`, { - method: 'DELETE', - }); - return { data: json }; - }, - - deleteMany: async (resource, params) => { - const responses = await Promise.all( - params.ids.map((id) => - httpClient(`${apiUrl}/${resource}/${id}`, { - method: 'DELETE', - }) - ) + const resourcePath = resource === "equipment" ? "equipments" : resource; + const { json } = await fetchJson( + env.apiUrl + "/" + resourcePath + "/" + params.id, + { method: "DELETE" }, ); - return { data: responses.map(({ json }) => json.id) }; + return { data: json ?? { id: params.id } }; + }, + deleteMany: async (resource, params) => { + const results = await Promise.all( + params.ids.map((id) => + dataProvider.delete(resource, { + id, + previousData: {}, + meta: params.meta, + } as any), + ), + ); + return { data: results.map((result) => result.data.id) }; }, }; - -export default dataProvider; diff --git a/client/src/index.css b/client/src/index.css new file mode 100644 index 0000000..3cdb51d --- /dev/null +++ b/client/src/index.css @@ -0,0 +1,110 @@ +:root { + --text: #6b6375; + --text-h: #08060d; + --bg: #fff; + --border: #e5e4e7; + --code-bg: #f4f3ec; + --accent: #aa3bff; + --accent-bg: rgba(170, 59, 255, 0.1); + --accent-border: rgba(170, 59, 255, 0.5); + --social-bg: rgba(244, 243, 236, 0.5); + --shadow: + rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px; + + --sans: system-ui, "Segoe UI", Roboto, sans-serif; + --heading: system-ui, "Segoe UI", Roboto, sans-serif; + --mono: ui-monospace, Consolas, monospace; + + font: 18px/145% var(--sans); + letter-spacing: 0.18px; + color-scheme: light dark; + color: var(--text); + background: var(--bg); + font-synthesis: none; + text-rendering: optimizeLegibility; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + + @media (max-width: 1024px) { + font-size: 16px; + } +} + +@media (prefers-color-scheme: dark) { + :root { + --text: #9ca3af; + --text-h: #f3f4f6; + --bg: #16171d; + --border: #2e303a; + --code-bg: #1f2028; + --accent: #c084fc; + --accent-bg: rgba(192, 132, 252, 0.15); + --accent-border: rgba(192, 132, 252, 0.5); + --social-bg: rgba(47, 48, 58, 0.5); + --shadow: + rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px; + } + + #social .button-icon { + filter: invert(1) brightness(2); + } +} + +#root { + max-width: 100%; + margin: 0 auto; + text-align: center; + border-inline: 1px solid var(--border); + min-height: 100svh; + display: flex; + flex-direction: column; + box-sizing: border-box; +} + +body { + margin: 0; +} + +h1, +h2 { + font-family: var(--heading); + font-weight: 500; + color: var(--text-h); +} + +h1 { + font-size: 56px; + letter-spacing: -1.68px; + margin: 32px 0; + @media (max-width: 1024px) { + font-size: 36px; + margin: 20px 0; + } +} +h2 { + font-size: 24px; + line-height: 118%; + letter-spacing: -0.24px; + margin: 0 0 8px; + @media (max-width: 1024px) { + font-size: 20px; + } +} +p { + margin: 0; +} + +code, +.counter { + font-family: var(--mono); + display: inline-flex; + border-radius: 4px; + color: var(--text-h); +} + +code { + font-size: 15px; + line-height: 135%; + padding: 4px 8px; + background: var(--code-bg); +} diff --git a/client/src/main.tsx b/client/src/main.tsx index be3fe9e..179e31b 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,26 +1,16 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; +import { StrictMode } from 'react'; +import { createRoot } from 'react-dom/client'; +import './index.css'; import App from './App'; import { initKeycloak } from './auth/keycloak'; -const root = ReactDOM.createRoot(document.getElementById('root')!); - async function bootstrap() { await initKeycloak(); - - root.render( - + createRoot(document.getElementById('root')!).render( + - , + , ); } -bootstrap().catch((error) => { - console.error('Failed to initialize authentication', error); - - root.render( - -
Authentication initialization failed. Check your environment variables.
-
, - ); -}); +void bootstrap(); diff --git a/client/src/resources/category-resource/CategoryResourceCreate.tsx b/client/src/resources/category-resource/CategoryResourceCreate.tsx new file mode 100644 index 0000000..2b4e846 --- /dev/null +++ b/client/src/resources/category-resource/CategoryResourceCreate.tsx @@ -0,0 +1,25 @@ +import { + AutocompleteInput, + Create, + ReferenceInput, + SimpleForm, +} from "react-admin"; +import { employeeOptionText, partOptionText } from "../shared/enums"; +export const CategoryResourceCreate = () => ( + + + + ({ q: searchText })} + /> + + + ({ q: searchText })} + /> + + + +); diff --git a/client/src/resources/category-resource/CategoryResourceEdit.tsx b/client/src/resources/category-resource/CategoryResourceEdit.tsx new file mode 100644 index 0000000..adbb383 --- /dev/null +++ b/client/src/resources/category-resource/CategoryResourceEdit.tsx @@ -0,0 +1,25 @@ +import { + AutocompleteInput, + Edit, + ReferenceInput, + SimpleForm, +} from "react-admin"; +import { employeeOptionText, partOptionText } from "../shared/enums"; +export const CategoryResourceEdit = () => ( + + + + ({ q: searchText })} + /> + + + ({ q: searchText })} + /> + + + +); diff --git a/client/src/resources/category-resource/CategoryResourceList.tsx b/client/src/resources/category-resource/CategoryResourceList.tsx new file mode 100644 index 0000000..3d6e504 --- /dev/null +++ b/client/src/resources/category-resource/CategoryResourceList.tsx @@ -0,0 +1,53 @@ +import { + AutocompleteInput, + CreateButton, + Datagrid, + FilterButton, + List, + ReferenceField, + ReferenceInput, + TextField, + TextInput, + TopToolbar, +} from "react-admin"; +import { employeeOptionText, partOptionText } from "../shared/enums"; +const categoryResourceFilters = [ + , + + ({ q: searchText })} + /> + , + + ({ q: searchText })} + /> + , +]; +export const CategoryResourceList = () => ( + + + + + } + > + + + + + + + + + + +); diff --git a/client/src/resources/category-resource/CategoryResourceShow.tsx b/client/src/resources/category-resource/CategoryResourceShow.tsx new file mode 100644 index 0000000..47809f4 --- /dev/null +++ b/client/src/resources/category-resource/CategoryResourceShow.tsx @@ -0,0 +1,14 @@ +import { ReferenceField, Show, SimpleShowLayout, TextField } from "react-admin"; +export const CategoryResourceShow = () => ( + + + + + + + + + + + +); diff --git a/client/src/resources/employee/EmployeeCreate.tsx b/client/src/resources/employee/EmployeeCreate.tsx new file mode 100644 index 0000000..7a3e264 --- /dev/null +++ b/client/src/resources/employee/EmployeeCreate.tsx @@ -0,0 +1,28 @@ +import { + AutocompleteInput, + Create, + NumberInput, + ReferenceInput, + SelectInput, + SimpleForm, + TextInput, +} from "react-admin"; +import { employeeOptionText, roleChoices } from "../shared/enums"; +export const EmployeeCreate = () => ( + + + + + + + + ({ q: searchText })} + /> + + + + + +); diff --git a/client/src/resources/employee/EmployeeEdit.tsx b/client/src/resources/employee/EmployeeEdit.tsx new file mode 100644 index 0000000..e004bca --- /dev/null +++ b/client/src/resources/employee/EmployeeEdit.tsx @@ -0,0 +1,27 @@ +import { + AutocompleteInput, + Edit, + NumberInput, + ReferenceInput, + SelectInput, + SimpleForm, + TextInput, +} from "react-admin"; +import { employeeOptionText, roleChoices } from "../shared/enums"; +export const EmployeeEdit = () => ( + + + + + + + ({ q: searchText })} + /> + + + + + +); diff --git a/client/src/resources/employee/EmployeeList.tsx b/client/src/resources/employee/EmployeeList.tsx new file mode 100644 index 0000000..85e535d --- /dev/null +++ b/client/src/resources/employee/EmployeeList.tsx @@ -0,0 +1,38 @@ +import { + Datagrid, + List, + ReferenceField, + SelectArrayInput, + SelectField, + TextField, + TextInput, +} from "react-admin"; +import { ResourceListActions } from "../shared/ListActions"; +import { roleChoices } from "../shared/enums"; +const employeeFilters = [ + , + , + , +]; +export const EmployeeList = () => ( + } + > + + + + + + + + + + + +); diff --git a/client/src/resources/employee/EmployeeShow.tsx b/client/src/resources/employee/EmployeeShow.tsx new file mode 100644 index 0000000..45d16c3 --- /dev/null +++ b/client/src/resources/employee/EmployeeShow.tsx @@ -0,0 +1,22 @@ +import { + ReferenceField, + SelectField, + Show, + SimpleShowLayout, + TextField, +} from "react-admin"; +import { roleChoices } from "../shared/enums"; +export const EmployeeShow = () => ( + + + + + + + + + + + + +); diff --git a/client/src/resources/equipment-type/EquipmentTypeCreate.tsx b/client/src/resources/equipment-type/EquipmentTypeCreate.tsx deleted file mode 100644 index 4068f40..0000000 --- a/client/src/resources/equipment-type/EquipmentTypeCreate.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Create, SimpleForm, TextInput, NumberInput } from 'react-admin'; - - -export const EquipmentTypeCreate = () => ( - - - - - - - - - -); diff --git a/client/src/resources/equipment-type/EquipmentTypeEdit.tsx b/client/src/resources/equipment-type/EquipmentTypeEdit.tsx deleted file mode 100644 index b44ed79..0000000 --- a/client/src/resources/equipment-type/EquipmentTypeEdit.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Edit, SimpleForm, TextInput, NumberInput } from 'react-admin'; - - -export const EquipmentTypeEdit = () => ( - - - - - - - - - -); diff --git a/client/src/resources/equipment-type/EquipmentTypeList.tsx b/client/src/resources/equipment-type/EquipmentTypeList.tsx deleted file mode 100644 index 5a98f6c..0000000 --- a/client/src/resources/equipment-type/EquipmentTypeList.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { - List, - Datagrid, - TextField, - TextInput, - TopToolbar, - FilterButton, - CreateButton, - ExportButton, - NumberField -} from 'react-admin'; - - -const equipmentTypeFilters = [ - , - , - -]; - -const EquipmentTypeListActions = () => ( - - - - - -); - -export const EquipmentTypeList = () => ( - } filters={equipmentTypeFilters} sort={{ field: 'code', order: 'ASC' }}> - - - - - - - - -); diff --git a/client/src/resources/equipment-type/EquipmentTypeShow.tsx b/client/src/resources/equipment-type/EquipmentTypeShow.tsx deleted file mode 100644 index f96ec96..0000000 --- a/client/src/resources/equipment-type/EquipmentTypeShow.tsx +++ /dev/null @@ -1,13 +0,0 @@ -import { Show, SimpleShowLayout, TextField, NumberField } from 'react-admin'; - -export const EquipmentTypeShow = () => ( - - - - - - - - - -); diff --git a/client/src/resources/equipment/EquipmentCreate.tsx b/client/src/resources/equipment/EquipmentCreate.tsx index fd177e1..c8186d4 100644 --- a/client/src/resources/equipment/EquipmentCreate.tsx +++ b/client/src/resources/equipment/EquipmentCreate.tsx @@ -1,28 +1,44 @@ -import { Create, SimpleForm, TextInput, NumberInput, DateInput, SelectInput, ReferenceInput, AutocompleteInput } from 'react-admin'; - -const statusChoices = [ - { id: 'Active', name: 'В эксплуатации' }, - { id: 'Repair', name: 'В ремонте' }, - { id: 'Reserve', name: 'В резерве' }, - { id: 'WriteOff', name: 'Списано' }, -]; +import { + Create, + DateInput, + NumberInput, + SelectInput, + SimpleForm, +} from "react-admin"; +import { + equipmentStatusChoices, + equipmentTypeChoices, + laborOperationChoices, + periodicityChoices, +} from "../shared/enums"; +import { PlainInput } from "../shared/inputs"; export const EquipmentCreate = () => ( - - - - - record.code ? `${record.code} — ${record.name ?? record.code}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} /> - - - - - - - - + + + + + + + + + + + + + + + ); diff --git a/client/src/resources/equipment/EquipmentEdit.tsx b/client/src/resources/equipment/EquipmentEdit.tsx index 98a3a1e..655db37 100644 --- a/client/src/resources/equipment/EquipmentEdit.tsx +++ b/client/src/resources/equipment/EquipmentEdit.tsx @@ -1,29 +1,36 @@ -import { Edit, SimpleForm, TextInput, NumberInput, DateInput, SelectInput, ReferenceInput, AutocompleteInput } from 'react-admin'; - -const statusChoices = [ - { id: 'Active', name: 'В эксплуатации' }, - { id: 'Repair', name: 'В ремонте' }, - { id: 'Reserve', name: 'В резерве' }, - { id: 'WriteOff', name: 'Списано' }, -]; +import { + DateInput, + Edit, + NumberInput, + SelectInput, + SimpleForm, +} from "react-admin"; +import { + equipmentStatusChoices, + equipmentTypeChoices, + laborOperationChoices, + periodicityChoices, +} from "../shared/enums"; +import { PlainInput } from "../shared/inputs"; export const EquipmentEdit = () => ( - - - - - - record.code ? `${record.code} — ${record.name ?? record.code}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} /> - - - - - - - - + + + + + + + + + + + + + + + ); diff --git a/client/src/resources/equipment/EquipmentList.tsx b/client/src/resources/equipment/EquipmentList.tsx index 62be3e7..ac5bf76 100644 --- a/client/src/resources/equipment/EquipmentList.tsx +++ b/client/src/resources/equipment/EquipmentList.tsx @@ -1,66 +1,78 @@ import { - List, + CreateButton, Datagrid, + DateField, + FilterButton, + List, + NumberField, + SelectArrayInput, + SelectField, TextField, TextInput, TopToolbar, - FilterButton, - CreateButton, - ExportButton, - NumberField, - DateField, - SelectField, - ReferenceField, - SelectArrayInput, - ReferenceInput, - AutocompleteInput -} from 'react-admin'; - -const statusChoices = [ - { id: 'Active', name: 'В эксплуатации' }, - { id: 'Repair', name: 'В ремонте' }, - { id: 'Reserve', name: 'В резерве' }, - { id: 'WriteOff', name: 'Списано' }, -]; +} from "react-admin"; +import { + equipmentStatusChoices, + equipmentTypeChoices, + laborOperationChoices, + periodicityChoices, +} from "../shared/enums"; const equipmentFilters = [ - , - , - , - , - - record.code ? `${record.code} — ${record.name ?? record.code}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} /> - , - , - , - + , + , + , + , + , + , + , + , + , ]; -const EquipmentListActions = () => ( - - - - - -); - export const EquipmentList = () => ( - } filters={equipmentFilters} sort={{ field: 'inventoryNumber', order: 'ASC' }}> + + + + + } + > - - - - - - - - - - - - - - + + + + + + + + ); diff --git a/client/src/resources/equipment/EquipmentShow.tsx b/client/src/resources/equipment/EquipmentShow.tsx index fc099e4..2ace8b7 100644 --- a/client/src/resources/equipment/EquipmentShow.tsx +++ b/client/src/resources/equipment/EquipmentShow.tsx @@ -1,28 +1,35 @@ -import { Show, SimpleShowLayout, TextField, NumberField, DateField, SelectField, ReferenceField } from 'react-admin'; - -const statusChoices = [ - { id: 'Active', name: 'В эксплуатации' }, - { id: 'Repair', name: 'В ремонте' }, - { id: 'Reserve', name: 'В резерве' }, - { id: 'WriteOff', name: 'Списано' }, -]; +import { + DateField, + NumberField, + SelectField, + Show, + SimpleShowLayout, + TextField, +} from "react-admin"; +import { + equipmentStatusChoices, + equipmentTypeChoices, + laborOperationChoices, + periodicityChoices, +} from "../shared/enums"; export const EquipmentShow = () => ( - - - - - - - - - - - - - - + + + + + + + + + + + + + + + ); diff --git a/client/src/resources/part/PartCreate.tsx b/client/src/resources/part/PartCreate.tsx new file mode 100644 index 0000000..5e36960 --- /dev/null +++ b/client/src/resources/part/PartCreate.tsx @@ -0,0 +1,15 @@ +import { Create, NumberInput, SelectInput, SimpleForm } from "react-admin"; +import { categoryPartChoices } from "../shared/enums"; +import { PlainInput } from "../shared/inputs"; + +export const PartCreate = () => ( + + + + + + + + + +); diff --git a/client/src/resources/part/PartEdit.tsx b/client/src/resources/part/PartEdit.tsx new file mode 100644 index 0000000..db0de4c --- /dev/null +++ b/client/src/resources/part/PartEdit.tsx @@ -0,0 +1,15 @@ +import { Edit, NumberInput, SelectInput, SimpleForm } from "react-admin"; +import { categoryPartChoices } from "../shared/enums"; +import { PlainInput } from "../shared/inputs"; + +export const PartEdit = () => ( + + + + + + + + + +); diff --git a/client/src/resources/part/PartList.tsx b/client/src/resources/part/PartList.tsx new file mode 100644 index 0000000..5955c03 --- /dev/null +++ b/client/src/resources/part/PartList.tsx @@ -0,0 +1,34 @@ +import { + Datagrid, + List, + NumberField, + SelectArrayInput, + SelectField, + TextField, + TextInput, +} from "react-admin"; +import { ResourceListActions } from "../shared/ListActions"; +import { categoryPartChoices } from "../shared/enums"; +const partFilters = [ + , + , + , +]; +export const PartList = () => ( + } + > + + + + + + + +); diff --git a/client/src/resources/part/PartShow.tsx b/client/src/resources/part/PartShow.tsx new file mode 100644 index 0000000..ebc5087 --- /dev/null +++ b/client/src/resources/part/PartShow.tsx @@ -0,0 +1,19 @@ +import { + NumberField, + SelectField, + Show, + SimpleShowLayout, + TextField, +} from "react-admin"; +import { categoryPartChoices } from "../shared/enums"; +export const PartShow = () => ( + + + + + + + + + +); diff --git a/client/src/resources/price-list/PriceListCreate.tsx b/client/src/resources/price-list/PriceListCreate.tsx new file mode 100644 index 0000000..4098393 --- /dev/null +++ b/client/src/resources/price-list/PriceListCreate.tsx @@ -0,0 +1,9 @@ +import { Create, NumberInput, SimpleForm } from "react-admin"; +export const PriceListCreate = () => ( + + + + + + +); diff --git a/client/src/resources/price-list/PriceListEdit.tsx b/client/src/resources/price-list/PriceListEdit.tsx new file mode 100644 index 0000000..aa11228 --- /dev/null +++ b/client/src/resources/price-list/PriceListEdit.tsx @@ -0,0 +1,9 @@ +import { Edit, NumberInput, SimpleForm } from "react-admin"; +export const PriceListEdit = () => ( + + + + + + +); diff --git a/client/src/resources/price-list/PriceListList.tsx b/client/src/resources/price-list/PriceListList.tsx new file mode 100644 index 0000000..2ab6c99 --- /dev/null +++ b/client/src/resources/price-list/PriceListList.tsx @@ -0,0 +1,20 @@ +import { Datagrid, List, NumberField, TextField, TextInput } from "react-admin"; +import { ResourceListActions } from "../shared/ListActions"; +const priceListFilters = [ + , +]; +export const PriceListList = () => ( + + } + perPage={1} + > + + + + + + +); diff --git a/client/src/resources/price-list/PriceListShow.tsx b/client/src/resources/price-list/PriceListShow.tsx new file mode 100644 index 0000000..403c6e8 --- /dev/null +++ b/client/src/resources/price-list/PriceListShow.tsx @@ -0,0 +1,10 @@ +import { NumberField, Show, SimpleShowLayout, TextField } from "react-admin"; +export const PriceListShow = () => ( + + + + + + + +); diff --git a/client/src/resources/repair-order/RepairOrderCreate.tsx b/client/src/resources/repair-order/RepairOrderCreate.tsx deleted file mode 100644 index 890734a..0000000 --- a/client/src/resources/repair-order/RepairOrderCreate.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Create, SimpleForm, TextInput, NumberInput, DateInput, SelectInput, ReferenceInput, AutocompleteInput } from 'react-admin'; - -const repairKindChoices = [ - { id: 'TO', name: 'Техническое обслуживание' }, - { id: 'TR', name: 'Текущий ремонт' }, - { id: 'TRE', name: 'Текущий расширенный ремонт' }, - { id: 'KR', name: 'Капитальный ремонт' }, - { id: 'AR', name: 'Аварийный ремонт' }, - { id: 'MP', name: 'Метрологическая поверка' }, -]; - -const statusChoices = [ - { id: 'Draft', name: 'Черновик' }, - { id: 'Approved', name: 'Утверждена' }, - { id: 'InWork', name: 'В работе' }, - { id: 'Done', name: 'Выполнена' }, - { id: 'Cancelled', name: 'Отменена' }, -]; - -export const RepairOrderCreate = () => ( - - - - - record.inventoryNumber ? `${record.inventoryNumber} — ${record.name ?? record.inventoryNumber}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} /> - - - - - - - - - - - - -); diff --git a/client/src/resources/repair-order/RepairOrderEdit.tsx b/client/src/resources/repair-order/RepairOrderEdit.tsx deleted file mode 100644 index 043730d..0000000 --- a/client/src/resources/repair-order/RepairOrderEdit.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Edit, SimpleForm, TextInput, NumberInput, DateInput, SelectInput, ReferenceInput, AutocompleteInput } from 'react-admin'; - -const repairKindChoices = [ - { id: 'TO', name: 'Техническое обслуживание' }, - { id: 'TR', name: 'Текущий ремонт' }, - { id: 'TRE', name: 'Текущий расширенный ремонт' }, - { id: 'KR', name: 'Капитальный ремонт' }, - { id: 'AR', name: 'Аварийный ремонт' }, - { id: 'MP', name: 'Метрологическая поверка' }, -]; - -const statusChoices = [ - { id: 'Draft', name: 'Черновик' }, - { id: 'Approved', name: 'Утверждена' }, - { id: 'InWork', name: 'В работе' }, - { id: 'Done', name: 'Выполнена' }, - { id: 'Cancelled', name: 'Отменена' }, -]; - -export const RepairOrderEdit = () => ( - - - - - - record.inventoryNumber ? `${record.inventoryNumber} — ${record.name ?? record.inventoryNumber}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} /> - - - - - - - - - - - - -); diff --git a/client/src/resources/repair-order/RepairOrderList.tsx b/client/src/resources/repair-order/RepairOrderList.tsx deleted file mode 100644 index 0e1e9f3..0000000 --- a/client/src/resources/repair-order/RepairOrderList.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { - List, - Datagrid, - TextField, - TextInput, - TopToolbar, - FilterButton, - CreateButton, - ExportButton, - NumberField, - DateField, - SelectField, - ReferenceField, - SelectArrayInput, - SelectInput, - ReferenceInput, - AutocompleteInput -} from 'react-admin'; - -const repairKindChoices = [ - { id: 'TO', name: 'Техническое обслуживание' }, - { id: 'TR', name: 'Текущий ремонт' }, - { id: 'TRE', name: 'Текущий расширенный ремонт' }, - { id: 'KR', name: 'Капитальный ремонт' }, - { id: 'AR', name: 'Аварийный ремонт' }, - { id: 'MP', name: 'Метрологическая поверка' }, -]; - -const statusChoices = [ - { id: 'Draft', name: 'Черновик' }, - { id: 'Approved', name: 'Утверждена' }, - { id: 'InWork', name: 'В работе' }, - { id: 'Done', name: 'Выполнена' }, - { id: 'Cancelled', name: 'Отменена' }, -]; - -const repairOrderFilters = [ - , - , - - record.inventoryNumber ? `${record.inventoryNumber} — ${record.name ?? record.inventoryNumber}` : (record.name ?? record.id)} filterToQuery={(searchText) => ({ q: searchText })} /> - , - , - , - , - , - -]; - -const RepairOrderListActions = () => ( - - - - - -); - -export const RepairOrderList = () => ( - } filters={repairOrderFilters} sort={{ field: 'number', order: 'ASC' }}> - - - - - - - - - - - - - - - - - -); diff --git a/client/src/resources/repair-order/RepairOrderShow.tsx b/client/src/resources/repair-order/RepairOrderShow.tsx deleted file mode 100644 index 78f2a79..0000000 --- a/client/src/resources/repair-order/RepairOrderShow.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import { Show, SimpleShowLayout, TextField, NumberField, DateField, SelectField, ReferenceField } from 'react-admin'; - -const repairKindChoices = [ - { id: 'TO', name: 'Техническое обслуживание' }, - { id: 'TR', name: 'Текущий ремонт' }, - { id: 'TRE', name: 'Текущий расширенный ремонт' }, - { id: 'KR', name: 'Капитальный ремонт' }, - { id: 'AR', name: 'Аварийный ремонт' }, - { id: 'MP', name: 'Метрологическая поверка' }, -]; - -const statusChoices = [ - { id: 'Draft', name: 'Черновик' }, - { id: 'Approved', name: 'Утверждена' }, - { id: 'InWork', name: 'В работе' }, - { id: 'Done', name: 'Выполнена' }, - { id: 'Cancelled', name: 'Отменена' }, -]; -export const RepairOrderShow = () => ( - - - - - - - - - - - - - - - - - - -); diff --git a/client/src/resources/shared/ListActions.tsx b/client/src/resources/shared/ListActions.tsx new file mode 100644 index 0000000..5d05c32 --- /dev/null +++ b/client/src/resources/shared/ListActions.tsx @@ -0,0 +1,17 @@ +import type { ReactElement } from "react"; +import { CreateButton, FilterButton, TopToolbar } from "react-admin"; + +interface ListActionsProps { + filters?: ReactElement[]; + hasCreate?: boolean; +} + +export const ResourceListActions = ({ + filters, + hasCreate = true, +}: ListActionsProps) => ( + + {filters ? : null} + {hasCreate ? : null} + +); diff --git a/client/src/resources/shared/enums.ts b/client/src/resources/shared/enums.ts new file mode 100644 index 0000000..fb65721 --- /dev/null +++ b/client/src/resources/shared/enums.ts @@ -0,0 +1,66 @@ +export const equipmentStatusChoices = [ + "Active", + "Repair", + "Reserve", + "WriteOff", +].map((value) => ({ id: value, name: value })); +export const laborOperationChoices = ["Manual", "MachineManual", "Machine"].map( + (value) => ({ id: value, name: value }), +); +export const periodicityChoices = [ + "Ежедневное", + "Еженедельное", + "Ежемесячное", + "Полугодовое", + "Годовое", +].map((value) => ({ id: value, name: value })); +export const roleChoices = ["Исполнитель", "Подписант", "Пользователь"].map( + (value) => ({ id: value, name: value }), +); +export const categoryPartChoices = [ + "Расходник", + "Запчасть", + "Инструмент", + "Спецодежда", +].map((value) => ({ id: value, name: value })); +export const equipmentTypeChoices = [ + "Производственное", + "Энергетическое", + "Насосное", + "Компрессорное", +].map((value) => ({ id: value, name: value })); + +export const equipmentOptionText = ( + record?: Record | null, +): string => { + if (!record) return ""; + const inventoryNumber = + typeof record.inventoryNumber === "string" ? record.inventoryNumber : ""; + const name = typeof record.name === "string" ? record.name : inventoryNumber; + return inventoryNumber + ? inventoryNumber + " — " + (name || inventoryNumber) + : typeof record.name === "string" + ? record.name + : String(record.id ?? ""); +}; + +export const employeeOptionText = ( + record?: Record | null, +): string => { + if (!record) return ""; + if (typeof record.code === "string") { + const fallback = + typeof record.fullName === "string" ? record.fullName : record.code; + return record.code + " — " + fallback; + } + if (typeof record.fullName === "string") return record.fullName; + return String(record.id ?? ""); +}; + +export const partOptionText = ( + record?: Record | null, +): string => { + if (!record) return ""; + if (typeof record.name === "string") return record.name; + return String(record.id ?? ""); +}; diff --git a/client/src/resources/shared/inputs.tsx b/client/src/resources/shared/inputs.tsx new file mode 100644 index 0000000..9625110 --- /dev/null +++ b/client/src/resources/shared/inputs.tsx @@ -0,0 +1,3 @@ +import { TextInput } from "react-admin"; + +export const PlainInput = TextInput; diff --git a/client/src/vite-env.d.ts b/client/src/vite-env.d.ts index 7de5aa4..11f02fe 100644 --- a/client/src/vite-env.d.ts +++ b/client/src/vite-env.d.ts @@ -1,12 +1 @@ /// - -interface ImportMetaEnv { - readonly VITE_API_URL: string; - readonly VITE_KEYCLOAK_URL: string; - readonly VITE_KEYCLOAK_REALM: string; - readonly VITE_KEYCLOAK_CLIENT_ID: string; -} - -interface ImportMeta { - readonly env: ImportMetaEnv; -} diff --git a/client/tsconfig.app.json b/client/tsconfig.app.json new file mode 100644 index 0000000..20e839c --- /dev/null +++ b/client/tsconfig.app.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo", + "target": "ES2023", + "useDefineForClassFields": true, + "lib": ["ES2023", "DOM", "DOM.Iterable"], + "module": "ESNext", + "types": ["vite/client"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + "jsx": "react-jsx", + + /* Linting */ + "strict": true, + "noUnusedLocals": false, + "noUnusedParameters": false, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true + }, + "include": ["src"] +} diff --git a/client/tsconfig.json b/client/tsconfig.json index a7fc6fb..1ffef60 100644 --- a/client/tsconfig.json +++ b/client/tsconfig.json @@ -1,25 +1,7 @@ { - "compilerOptions": { - "target": "ES2020", - "useDefineForClassFields": true, - "lib": ["ES2020", "DOM", "DOM.Iterable"], - "module": "ESNext", - "skipLibCheck": true, - - /* Bundler mode */ - "moduleResolution": "bundler", - "allowImportingTsExtensions": true, - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx", - - /* Linting */ - "strict": true, - "noUnusedLocals": true, - "noUnusedParameters": true, - "noFallthroughCasesInSwitch": true - }, - "include": ["src"], - "references": [{ "path": "./tsconfig.node.json" }] + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.node.json" } + ] } diff --git a/client/tsconfig.node.json b/client/tsconfig.node.json index 97ede7e..8a67f62 100644 --- a/client/tsconfig.node.json +++ b/client/tsconfig.node.json @@ -1,11 +1,26 @@ { "compilerOptions": { - "composite": true, - "skipLibCheck": true, + "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo", + "target": "ES2023", + "lib": ["ES2023"], "module": "ESNext", + "types": ["node"], + "skipLibCheck": true, + + /* Bundler mode */ "moduleResolution": "bundler", - "allowSyntheticDefaultImports": true, - "strict": true + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "moduleDetection": "force", + "noEmit": true, + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "erasableSyntaxOnly": true, + "noFallthroughCasesInSwitch": true, + "noUncheckedSideEffectImports": true }, "include": ["vite.config.ts"] } diff --git a/client/vite.config.ts b/client/vite.config.ts index 5a33944..8b0f57b 100644 --- a/client/vite.config.ts +++ b/client/vite.config.ts @@ -1,7 +1,7 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react' -// https://vitejs.dev/config/ +// https://vite.dev/config/ export default defineConfig({ plugins: [react()], }) diff --git a/docker-compose.yml b/docker-compose.yml index 156d278..a23bc93 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -5,92 +5,18 @@ services: restart: unless-stopped environment: POSTGRES_USER: ${POSTGRES_USER:-postgres} - POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-change-me} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-postgres} POSTGRES_DB: ${POSTGRES_DB:-toir} + ports: + - "${POSTGRES_PORT:-5432}:5432" healthcheck: - test: - [ - "CMD-SHELL", - "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-toir}", - ] + test: ["CMD-SHELL", "pg_isready -U ${POSTGRES_USER:-postgres} -d ${POSTGRES_DB:-toir}"] interval: 10s timeout: 5s retries: 5 start_period: 10s - ports: - - "${POSTGRES_PORT:-5432}:5432" volumes: - postgres-data:/var/lib/postgresql/data - networks: - - app - - server: - build: - context: ./server - dockerfile: Dockerfile - container_name: toir-server - restart: unless-stopped - depends_on: - postgres: - condition: service_healthy - environment: - PORT: 3000 - DATABASE_URL: postgresql://${POSTGRES_USER:-postgres}:${POSTGRES_PASSWORD:-change-me}@postgres:5432/${POSTGRES_DB:-toir} - CORS_ALLOWED_ORIGINS: ${CORS_ALLOWED_ORIGINS:-http://localhost:8080,https://toir.greact.ru} - KEYCLOAK_ISSUER_URL: ${KEYCLOAK_ISSUER_URL:-https://sso.greact.ru/realms/toir} - KEYCLOAK_AUDIENCE: ${KEYCLOAK_AUDIENCE:-toir-backend} - KEYCLOAK_JWKS_URL: ${KEYCLOAK_JWKS_URL:-} - healthcheck: - test: - [ - "CMD", - "node", - "-e", - "fetch('http://127.0.0.1:3000/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))", - ] - interval: 15s - timeout: 5s - retries: 5 - start_period: 20s - expose: - - "3000" - networks: - - app - - proxy - - client: - build: - context: ./client - dockerfile: Dockerfile - args: - VITE_API_URL: ${VITE_API_URL:-/api} - VITE_KEYCLOAK_URL: ${VITE_KEYCLOAK_URL:-https://sso.greact.ru} - VITE_KEYCLOAK_REALM: ${VITE_KEYCLOAK_REALM:-toir} - VITE_KEYCLOAK_CLIENT_ID: ${VITE_KEYCLOAK_CLIENT_ID:-toir-frontend} - container_name: toir-client - restart: unless-stopped - depends_on: - server: - condition: service_healthy - healthcheck: - test: ["CMD-SHELL", "wget -qO- http://127.0.0.1/healthz >/dev/null 2>&1 || exit 1"] - interval: 15s - timeout: 5s - retries: 5 - start_period: 10s - ports: - - "${CLIENT_PORT:-8080}:80" - expose: - - "80" - networks: - - app - - proxy volumes: postgres-data: - -networks: - app: - driver: bridge - proxy: - external: true diff --git a/docs/AID_EXPORT_README.md b/docs/AID_EXPORT_README.md index c10a546..288f625 100644 --- a/docs/AID_EXPORT_README.md +++ b/docs/AID_EXPORT_README.md @@ -1,27 +1,29 @@ # AID: экспорт OpenAPI и генератор приложения -В репозитории добавлены **сервисы-экспортёры** для интеграции с **AID** (или любым другим клиентом по HTTP): автоматическое получение **OpenAPI 3.0** из доменного **api-format** и выдача **сгенерированного fullstack-приложения** из **DSL** без ручного копирования файлов. +В репозитории добавлены **сервисы-экспортёры** для интеграции с **AID** (или любым другим клиентом по HTTP): автоматическое получение **OpenAPI 3.0** из доменного **api-format**. --- -## Что сделано +## Что работает | Компонент | Назначение | |-----------|------------| | **`POST /aid/export/openapi`** (NestJS) | На вход JSON **api-format** → на выход документ **OpenAPI 3.0** в поле `openapi`. | -| **`POST /aid/export/app`** (NestJS) | На вход текст **DSL** → либо JSON-бандл всех сгенерированных файлов (`files`), либо запись в рабочую копию репозитория (`apply: true`, опционально). | | **`tools/api-format-to-openapi/`** | CLI и промпт для LLM: тот же конвертер, что вызывает Nest. | -| **`generation/generate.mjs`** | Новый флаг **`--print-bundle-json`**: вывод в stdout JSON с `entityCount`, `enumCount`, `files` — без записи на диск (аналог «сухого» экспорта для AID). | | **`server/src/aid-export/`** | Модуль Nest: контроллер, сервис, краткая справка в `README.md` рядом с кодом. | -Ветка с этими изменениями: **`add_aid_exporters`**. +## Что временно не работает + +| Компонент | Статус | +|-----------|--------| +| **`POST /aid/export/app`** (DSL → бандл/apply) | **Non-operative.** The backing script (`generation/generate.mjs`) was removed during the architecture migration to api.dsl-first LLM generation. The endpoint returns 500 with a descriptive error. | --- ## Требования к запуску -1. Репозиторий клонирован целиком (есть `generation/`, `tools/`, `server/`, `client/`). -2. Backend запускается из каталога **`server/`** (`npm run start` / `start:dev`), чтобы относительные пути `../generation/generate.mjs` и `../tools/api-format-to-openapi/convert.mjs` были корректны. +1. Репозиторий клонирован целиком (есть `tools/`, `server/`, `client/`). +2. Backend запускается из каталога **`server/`** (`npm run start` / `start:dev`), чтобы относительные пути `../tools/api-format-to-openapi/convert.mjs` были корректны. 3. Для режима OpenAPI через LLM на сервере нужны **`OPENAI_API_KEY`** (и при необходимости `OPENAI_MODEL`, `OPENAI_BASE_URL`). --- @@ -31,7 +33,6 @@ | Переменная | Зачем | |------------|--------| | `AID_EXPORT_API_KEY` | Если задана, к **`/aid/export/*`** нужен заголовок **`X-AID-Export-Key`** с тем же значением. | -| `AID_GENERATOR_ALLOW_APPLY` | Должна быть **`1`** или **`true`**, иначе **`POST /aid/export/app`** с **`apply: true`** вернёт **403** (защита от случайной перезаписи репозитория на сервере). | | `OPENAI_API_KEY` | Для `POST /aid/export/openapi` с **`"mode": "llm"`**. | Остальное как для обычного бэкенда (`DATABASE_URL`, `PORT` и т.д.). @@ -69,40 +70,19 @@ X-AID-Export-Key: <если задан AID_EXPORT_API_KEY> Пример входа для теста: `tools/api-format-to-openapi/examples/api-format.example.json` (подставьте как значение `apiFormat`). -### 2. Генератор приложения из DSL +### 2. Генератор приложения из DSL (non-operative) **`POST /aid/export/app`** -```json -{ - "dsl": "domain TOiR {\n ...\n}\n", - "apply": false -} -``` - -- **`apply: false`** (рекомендуется для AID): в ответе **`files`** — объект «путь от корня репо → текст файла». Диск на сервере не меняется. -- **`apply: true`**: выполняется запись файлов как у `npm run generate:from-dsl` с `--apply`; нужен **`AID_GENERATOR_ALLOW_APPLY=1`**. - -**Ответ (бандл):** `{ "applied": false, "entityCount": N, "enumCount": M, "files": { ... } }` -**Ответ (apply):** `{ "applied": true, "message": "Generated ..." }` - -Эталон DSL: `examples/TOiR.domain.dsl`. +> **Non-operative.** This endpoint depended on `generation/generate.mjs` which was removed +> during the migration to api.dsl-first LLM generation. It currently returns HTTP 500 +> with a descriptive error message. Restoring this endpoint requires implementing a new +> backing script compatible with the current `domain/*.api.dsl` pipeline. --- ## CLI (без Nest) -### Пошаговая демонстрация в терминале - -```bash -cd tools/api-format-to-openapi -npm run demo -# или с паузой после каждого шага (Enter): -npm run demo:pause -``` - -Показывает входной **api-format**, логику маппинга, запуск конвертера и структуру **OpenAPI**; результат — `demo-output/openapi.json`. - ### api-format → OpenAPI ```bash @@ -119,46 +99,22 @@ node convert.mjs --mode llm --in your-api-format.json --out ../../openapi.llm.js Подробнее: **`tools/api-format-to-openapi/README.md`**. -### DSL → JSON-бандл - -Из **корня репозитория**: - -```bash -node generation/generate.mjs --print-bundle-json --dsl examples/TOiR.domain.dsl > bundle.json -``` - -Из **`server/`**: - -```bash -npm run generate:bundle-json > ../bundle.json -``` - -Применить генератор к файлам на диске (как раньше): - -```bash -cd server -npm run generate:from-dsl -``` - --- ## Типичный сценарий для AID 1. AID уже сформировал **api-format** (как у вас принято после DTO). 2. AID вызывает **`POST /aid/export/openapi`** → получает **OpenAPI 3.0** → сохраняет в проект / отдаёт в Swagger / в реестр. -3. Для кода: AID передаёт **DSL** в **`POST /aid/export/app`** с **`apply: false`** → забирает **`files`** → применяет у себя (git apply, распаковка, PR). -4. Запись **`apply: true`** на общем сервере используйте только в доверенной среде и с **`AID_GENERATOR_ALLOW_APPLY`**. --- ## Где смотреть код и короткую справку - Полное описание эндпоинтов рядом с реализацией: **`server/src/aid-export/README.md`** -- Общий dev-workflow (в т.ч. упоминание AID): **`generation/dev-workflow.md`** --- ## Ограничения и дальнейшие шаги - Пример **api-format** в репозитории — **учебный**; под ваш продакшен-формат может понадобиться расширить маппинг в `convert.mjs` или отточить промпт **`llm-system.md`**. -- Ответ **`/aid/export/app`** с большим числом сущностей может быть объёмным; при необходимости добавьте сжатие, отдельное хранилище артефактов или пагинацию по файлам — контракт с AID лучше зафиксировать отдельно. +- **`/aid/export/app`** requires a new backing implementation compatible with the `domain/*.api.dsl` pipeline to be restored. See `docs/future-work.md` for planned future work. diff --git a/docs/api-dsl-conventions.md b/docs/api-dsl-conventions.md new file mode 100644 index 0000000..4b29e3e --- /dev/null +++ b/docs/api-dsl-conventions.md @@ -0,0 +1,162 @@ +# api.dsl Conventions + +Grammar and authoring conventions for `domain/*.api.dsl` files. +See `domain/toir.api.dsl` as the canonical example. + +--- + +## File location and naming + +- All api.dsl files live in `domain/`. +- Naming: `.api.dsl` (e.g. `toir.api.dsl`). +- Multiple api.dsl files are allowed but not required. + +--- + +## Two top-level block types + +An api.dsl file contains two types of top-level blocks: + +1. `dto` — defines the shape of a data transfer object +2. `api` — declares a group of API endpoints for one resource + +--- + +## dto block + +``` +dto DTO. { + description "Human-readable description"; + + attribute { + description "Field description"; + type ; + is required; // or: is nullable; + map .; // links to a field in domain/*.api.dsl + } +} +``` + +### DTO naming convention + +| DTO name | Purpose | +|----------|---------| +| `DTO.` | Full response shape (GET by id, list items) | +| `DTO.Create` | Create request body | +| `DTO.Update` | Update request body (partial — all fields nullable) | +| `DTO.ListRequest` | Paginated list request (filters + page) | +| `DTO.ListResponse` | Paginated list response (content + page info) | + +### Types + +Same scalar types as the domain DSL: +`uuid`, `string`, `text`, `integer`, `decimal`, `date`, `boolean`, or an enum name from +`domain/*.api.dsl`. + +Cross-DTO references: `DTO.` or `DTO.[]`. + +Standard pagination types (not entity-specific; treated as well-known): +`DTO.Filter[]`, `DTO.PageRequest`, `DTO.PageInfo`. + +### is required vs is nullable + +Unlike the domain DSL (where these drive DB constraints), in api.dsl these drive the +TypeScript DTO property modifier: + +- `is required` → `field!: type` in the generated class +- `is nullable` → `field?: type` in the generated class +- Neither modifier → treated as optional (`field?: type`) + +### map directive + +`map Entity.field` links the DTO attribute to a domain entity field. + +Rules: +- The entity and field must exist in `domain/*.api.dsl`. +- The scalar type must match the domain DSL field type (nullability may differ). +- Omit `map` only for pagination helper types (`DTO.Filter[]`, `DTO.PageRequest`, etc.) + that have no direct entity field counterpart. + +### Conflict resolution + +If the type declared in a `dto` attribute conflicts with the domain DSL field type, +the domain DSL is correct. Fix the api.dsl. + +--- + +## api block + +``` +api API. { + description "API group description"; + + endpoint { + label "METHOD /path"; + description "Endpoint description"; + + // For endpoints with a request body: + attribute request { + type DTO.; + } + + // For endpoints with a response body: + attribute response { + type DTO.; + } + + // For path parameters: + attribute { + type ; + } + } +} +``` + +### api block naming + +`API.` (e.g. `API.Equipment`, `API.RepairOrder`) + +### endpoint label + +`label "METHOD /path"` is the authoritative declaration of HTTP verb and route. + +| Label pattern | NestJS decorator | Notes | +|---------------|-----------------|-------| +| `"GET /resource/{id}"` | `@Get(':id')` | | +| `"POST /resource"` | `@Post()` | Create | +| `"POST /resource/page"` | `@Post('page')` | Paginated list (body-based filter) | +| `"PUT /resource/{id}"` | `@Put(':id')` | Full or partial update | +| `"DELETE /resource/{id}"` | `@Delete(':id')` | | + +Do not infer HTTP verbs from endpoint names. Always read the `label`. + +### Path parameters + +Declared as a plain `attribute` inside the endpoint block (not wrapped in `request` +or `response`): + +``` +attribute id { + type uuid; +} +``` + +### Standard 5-endpoint CRUD pattern + +| Endpoint name | Label | Body | +|---------------|-------|------| +| `lists` | `"POST //page"` | request: `DTO.ListRequest`, response: `DTO.ListResponse` | +| `get` | `"GET //{id}"` | path param `id`, response: `DTO.` | +| `create` | `"POST /"` | request: `DTO.Create` | +| `update` | `"PUT //{id}"` | path param `id`, request: `DTO.Update` | +| `delete` | `"DELETE //{id}"` | path param `id` | + +--- + +## Constraints + +1. Every `map Entity.field` must resolve to an existing entity + field in `domain/*.api.dsl`. +2. Types in api.dsl must be compatible with domain DSL field types (same scalar type). +3. api.dsl must not define entities or enums. Those belong in `domain/*.api.dsl`. +4. An api.dsl may omit domain entity fields from a DTO (e.g. no PK in Create DTO). + It must not add fields that don't exist in the domain model. diff --git a/docs/future-work.md b/docs/future-work.md new file mode 100644 index 0000000..7905272 --- /dev/null +++ b/docs/future-work.md @@ -0,0 +1,154 @@ +# Future Work — Deferred Items + +This file tracks engineering improvements that are deliberately deferred due to the +current stage of the project. They are not forgotten — they are acknowledged technical +debt that should be addressed before scaling. + +--- + +## Rule 7 — Tracing, Telemetry, Cost/Latency Observability + +**Status:** Deferred. No LLM calls are instrumented. + +**Why it matters (Anthropic / Google / Microsoft guidance):** +Without observability, you cannot: +- Know which prompts are expensive (token count, latency) +- Detect prompt regressions via cost drift +- Attribute generation failures to specific prompt versions +- Track improvement over time + +**What needs to be built:** + +### 7.1 — Generation log + +Create `tools/generation-log.mjs` that wraps any LLM generation call and writes a +structured JSON entry to `logs/generation.jsonl`: + +```json +{ + "timestamp": "2026-04-03T10:00:00.000Z", + "entity": "Equipment", + "artifact": "backend", + "prompt_version": "1.0", + "model": "...", + "input_tokens": 4200, + "output_tokens": 1800, + "latency_ms": 3200, + "validation_passed": true, + "eval_passed": true +} +``` + +### 7.2 — Cost budget alerts + +Add a threshold check (e.g., warn if input_tokens > 8000 for a single entity generation). +This enforces the context budget from `prompts/general-prompt.md §CONTEXT BUDGET`. + +### 7.3 — Prompt version tracking + +Add `` comments to all prompt files (already started in +`backend-rules.md` and `frontend-rules.md`). Increment version on any non-trivial change. +Log the prompt versions alongside the generation log entry. + +### 7.4 — Drift detection + +Compare generation log entries across runs. If token count for the same entity increases +by >20% without a DSL change, flag it as context rot. + +**Effort estimate:** Medium. 2–3 days to build the logging layer. Zero effort for +prompt versioning (already partially done). + +**Trigger:** Implement before the system is used for more than 10 entities or before +any production deployment. + +--- + +## Rule 8 — Risk Controls and Red-Teaming + +**Status:** Deferred. No sanitization or adversarial testing exists. + +**Why it matters (Anthropic / Google / Microsoft guidance):** +LLM-generated code at scale introduces risks that do not exist in hand-written code: +- **Prompt injection**: malicious content in DSL `description` fields could steer + generation (e.g., `description "Ignore previous instructions and..."`) +- **Generated credential leakage**: LLM may hallucinate hardcoded secrets that look + real (e.g., `apiKey: 'sk-...'`) +- **Missing auth guards**: already caught by Rule 4 validator, but adversarial prompts + could bypass it by generating valid-looking guard syntax that is semantically inactive +- **Supply chain**: generated package imports could reference non-existent or malicious + packages if the LLM hallucinates + +**What needs to be built:** + +### 8.1 — DSL input sanitization + +In `tools/api-summary.mjs`, before building the summary, check all `description` and +`label` fields for injection patterns: + +```javascript +function sanitizeDslString(value, fieldPath) { + const injectionPatterns = [ + /ignore previous/i, + /disregard.*instruction/i, + /you are now/i, + /system:/i, + ]; + for (const pattern of injectionPatterns) { + if (pattern.test(value)) { + throw new Error(`Potential prompt injection in DSL field ${fieldPath}: "${value}"`); + } + } + return value; +} +``` + +### 8.2 — Generated code security scan + +Add to `tools/validate-generation.mjs` (or a separate `tools/security-scan.mjs`): + +```javascript +// Check no hardcoded secrets leaked into generated code +function validateNoSecretLeakage() { + const patterns = [ + /sk-[a-zA-Z0-9]{20,}/, // OpenAI key pattern + /[a-zA-Z0-9+/]{40}={0,2}/, // Base64 secret-like + /password\s*=\s*['"][^'"]{4,}['"]/, // Hardcoded password + /apiKey\s*=\s*['"][^'"]{4,}['"]/, // Hardcoded API key + ]; + // Run against all generated files... +} +``` + +### 8.3 — UseGuards completeness audit + +Beyond the current validator check (UseGuards present), add: verify that the guard +constructor arguments are non-empty and match the expected guard class names. A guard +call like `@UseGuards()` (empty) passes the current regex but provides no protection. + +### 8.4 — Red-team fixture + +Create `tools/eval/fixtures/_adversarial/` with a fixture that includes a DSL snippet +containing a benign injection attempt (e.g., a `description` field with "ignore format +rules") and verifies the generation still produces spec-compliant output. + +### 8.5 — Generated import allowlist + +Maintain a list of approved npm packages that generated code may import. Flag any +import not on the allowlist as a manual review item. + +**Effort estimate:** Medium-High. 3–5 days. Security scan and sanitization are low +effort; red-team fixtures and import allowlisting are higher effort. + +**Trigger:** Implement before any external user can influence `domain/*.api.dsl` content +(i.e., before a UI or API to edit the DSL is exposed). + +--- + +## Tracking + +| Rule | Status | Priority | Trigger | +|------|--------|----------|---------| +| Rule 7 — Telemetry | Deferred | Medium | Before >10 entities or production deployment | +| Rule 8 — Risk controls | Deferred | High | Before DSL editing is exposed to external users | + +Last updated: 2026-04-03 diff --git a/docs/generation-playbook.md b/docs/generation-playbook.md new file mode 100644 index 0000000..f7dd9ae --- /dev/null +++ b/docs/generation-playbook.md @@ -0,0 +1,237 @@ +# Generation Playbook + +Step-by-step instructions for generating and regenerating artifacts in this repository. +Read `AGENTS.md` and `docs/source-of-truth.md` first. + +--- + +## Pipeline overview + +``` +Tier 1 — Single Source of Truth (hand-authored, never generated) + domain/toir.api.dsl ──┐ enums, DTOs, endpoints, HTTP methods, + │ entity field mappings, primary keys + │ + ▼ +Tier 2 — Deterministic Preprocessing (npm scripts, no LLM) + api-summary.json ←─ npm run generate:api-summary + openapi.json ←─ npm run generate:openapi (auxiliary) + │ + ▼ +Tier 1 (api.dsl) + Tier 2 (context) + prompts/*.md ──► LLM Generation + prompts/general-prompt.md + prompts/backend-rules.md ──► server/src/modules// + prompts/frontend-rules.md ──► client/src/resources// + prompts/prisma-rules.md ──► server/prisma/schema.prisma + prompts/auth-rules.md ──► (auth seam reference) + prompts/runtime-rules.md ──► (env/docker reference) + prompts/validation-rules.md ──► (validation gate reference) + │ + ▼ +Tier 4 — Validation Gate + node tools/validate-generation.mjs --artifacts-only +``` + +--- + +## Prerequisites + +Before any generation run: + +1. `domain/*.api.dsl` is current and valid. +2. Refresh the Tier 2 intermediate context: + ```bash + npm run generate:api-summary + ``` +3. Run `node tools/validate-generation.mjs --artifacts-only` to confirm the baseline passes. + +--- + +## Standard generation workflow + +### Step 1 — Refresh Tier 2 derived artifacts + +```bash +# From repo root +npm run generate:api-summary +``` + +### Step 2 — Read generation inputs (context budget) + +> **`prompts/general-prompt.md` is the master generation prompt.** It contains all core +> type mappings, naming conventions, DTO/controller/service/frontend rules, mutation +> boundaries, and the complete generation workflow. Load it as the single entrypoint. +> +> For artifact-specific details (Prisma FK rules, auth JWKS chain, detailed validation +> groups), load the relevant companion file: `prompts/prisma-rules.md`, +> `prompts/auth-rules.md`, `prompts/validation-rules.md`, or `prompts/runtime-rules.md`. +> +> See `prompts/general-prompt.md §CONTEXT BUDGET` for the full budget model. + +1. `prompts/general-prompt.md` — master generation prompt (always load) +2. `api-summary.json §` — compact entity index (fast-path context anchor) +3. `domain/*.api.dsl §API.` — **only the api block + its referenced DTOs + enums** (entity-scoped) +4. **If needed:** `prompts/prisma-rules.md` (Prisma) or `prompts/auth-rules.md` (auth seams) + +Before generating any DTO or component: **quote the relevant DSL field definitions verbatim first**, then generate from those quotes. This prevents training-data contamination. + +### Step 3 — Generate Prisma schema + +Generate `server/prisma/schema.prisma` from `domain/*.api.dsl` following `prompts/prisma-rules.md`. + +If the schema changed, run Prisma migration in `server/`: + +```bash +cd server +npx prisma migrate dev --name +``` + +### Step 4 — Generate backend modules + +For each `api` block in `domain/*.api.dsl`, generate: + +1. `server/src/modules//.module.ts` +2. `server/src/modules//dto/create-.dto.ts` + — fields from the `DTO.Create` block in api.dsl +3. `server/src/modules//dto/update-.dto.ts` + — fields from the `DTO.Update` block in api.dsl +4. `server/src/modules//.service.ts` + — CRUD operations using Prisma; respect type mappings from `prompts/backend-rules.md` +5. `server/src/modules//.controller.ts` + — one method per `endpoint` in the `api` block; HTTP verb and path from the `label` + +Register the module in `server/src/app.module.ts`. + +### Step 5 — Generate frontend resources + +For each `api` block in `domain/*.api.dsl`, generate: + +1. `client/src/resources//List.tsx` + — columns from `DTO.` (response shape) +2. `client/src/resources//Create.tsx` + — fields from `DTO.Create` +3. `client/src/resources//Edit.tsx` + — fields from `DTO.Update` +4. `client/src/resources//Show.tsx` + — fields from `DTO.` + +Register the resource in `client/src/App.tsx`. + +### Step 6 — Verify (two-stage gate) + +**Stage 1 — Structural gate:** + +```bash +node tools/validate-generation.mjs --artifacts-only +``` + +Checks file existence, field names, class-validator decorators, auth guards, and RA component types. + +Full structural verification (requires installed deps): + +```bash +node tools/validate-generation.mjs +``` + +**Stage 2 — Eval harness:** + +```bash +npm run eval:generation +``` + +Fixture-based semantic checks. See `tools/eval/README.md`. + +Both must pass before the task is complete. + +--- + +## Adding a new entity + +1. Add the entity's enums, DTOs, and `api` block to `domain/toir.api.dsl`. +2. Run `npm run generate:api-summary`. +3. **Before generating:** create `tools/eval/fixtures//meta.json`, `backend.assertions.json`, and `frontend.assertions.json` with expected patterns. +4. Run `npm run eval:generation` — it will fail (entity files don't exist yet). That's expected. +5. Generate backend and frontend artifacts (Steps 3–5). +6. Run `npm run eval:generation` again — now it should pass. +7. Run `node tools/validate-generation.mjs --artifacts-only` — both gates must pass. + +> This is **eval-driven development**: write the failing eval first (step 3), then generate to make it pass (step 6). A passing eval confirms the LLM followed the rules. + +--- + +## Changing an existing entity + +**If the domain model changes** (new field, changed type, new FK, new enum): + +1. Update `domain/toir.api.dsl` (enums, DTO attributes, `map` references). +2. Run `npm run generate:api-summary`. +3. Regenerate `server/prisma/schema.prisma`, run migration. +4. Regenerate the affected modules and resources (Steps 4–6). + +**If only the API contract changes** (different nullability, new endpoint, different HTTP method): + +1. Update `domain/toir.api.dsl` only. +2. Run `npm run generate:api-summary` to refresh `api-summary.json`. +3. Run Steps 4–6. No Prisma migration needed. + +--- + +## Artifact traceability matrix + +| Artifact | DSL source | Prompt rule | Validator check | +| ---------------------------------------------- | ---------------------- | ---------------------------------------------- | --------------------------------------- | +| `server/prisma/schema.prisma` | `domain/*.api.dsl` | `prompts/prisma-rules.md` | `§validateBuildChecks` (file exists) | +| `api-summary.json` | `domain/*.api.dsl` | — (deterministic) | `§validateBuildChecks` (freshness) | +| `server/src/modules//*.ts` | `domain/*.api.dsl` | `prompts/backend-rules.md` | `§validateApiDslCoverage` | +| `server/src/modules//dto/create-.dto.ts` | `DTO.Create` fields | `prompts/backend-rules.md §DTO-field-coverage` | `§validateApiDslCoverage` (field names) | +| `server/src/modules//dto/update-.dto.ts` | `DTO.Update` fields | `prompts/backend-rules.md §DTO-field-coverage` | `§validateApiDslCoverage` (field names) | +| `client/src/resources//*.tsx` | `domain/*.api.dsl` | `prompts/frontend-rules.md` | `§validateApiDslCoverage` | +| `client/src/auth/keycloak.ts` | — (Tier 4 handwritten) | `prompts/auth-rules.md` | `§validateAuthChecks` | +| `toir-realm.json` | — (Tier 4 handwritten) | `prompts/auth-rules.md` | `§validateRealmChecks` | +| `docker-compose.yml` | — (Tier 4 handwritten) | `prompts/runtime-rules.md` | `§validateRuntimeContractChecks` | + +--- + +## Verification commands reference + +| Command | When to use | +| ----------------------------------------------------- | ------------------------------------------ | +| `npm run generate:api-summary` | After any change to `domain/*.api.dsl` | +| `npm run generate:openapi` | To regenerate OpenAPI 3.0.3 documentation | +| `node tools/validate-generation.mjs --artifacts-only` | After every generation run (required) | +| `node tools/validate-generation.mjs` | Before committing; requires installed deps | +| `node tools/validate-generation.mjs --run-runtime` | End-to-end; requires running DB | + +--- + +## OpenRouter invocation + +Set environment variables before any LLM-mode tool call: + +``` +OPENAI_API_KEY= +OPENAI_BASE_URL=https://openrouter.ai/api/v1 +OPENAI_MODEL= +``` + +The `OPENAI_BASE_URL` variable is consumed by `tools/api-format-to-openapi/convert.mjs --mode llm`. +For agent-driven generation via Cursor or direct API calls, the standard workflow above +applies regardless of model provider. + +--- + +## Auxiliary tools + +### `tools/api-summary-to-openapi.mjs` + +Generates `openapi.json` (OpenAPI 3.0.3) from `api-summary.json` deterministically. +This is a documentation/integration artifact, not part of the core generation pipeline. + +```bash +npm run generate:openapi +``` + +### `tools/api-format-to-openapi/` + +Auxiliary integration tool for external consumers using the `api-format` JSON schema. +Not connected to the main DSL pipeline. See `docs/AID_EXPORT_README.md` for details. diff --git a/docs/repository-structure.md b/docs/repository-structure.md index bc2fdbd..7016a4e 100644 --- a/docs/repository-structure.md +++ b/docs/repository-structure.md @@ -28,7 +28,7 @@ Root-level files stay limited to repository-level artifacts such as: - `README.md` - `package.json` - `docker-compose.yml` -- `domain-summary.json` +- `api-summary.json` - `toir-realm.json` - `.gitignore` diff --git a/docs/source-of-truth.md b/docs/source-of-truth.md new file mode 100644 index 0000000..5a40a29 --- /dev/null +++ b/docs/source-of-truth.md @@ -0,0 +1,96 @@ +# Source-of-Truth Hierarchy + +This document is the authoritative reference for which files own which decisions. + +--- + +## Tier 1: Authoritative sources (hand-authored; never generated) + +### `domain/*.api.dsl` + +**Single source of truth for the entire domain and API contract:** + +- Entity names and structure +- Attribute names, scalar types, descriptions +- Primary keys (including natural string keys) +- Foreign keys and relations +- Enum definitions and their values +- Database-level constraints: `is required`, `is unique`, `default` +- DTO shapes per operation (Create, Update, Read, ListRequest, ListResponse) +- Which fields appear in each DTO and with what TypeScript modifier (`!` or `?`) +- HTTP method and path for each endpoint (via `label "METHOD /path"`) +- Endpoint names (camelCase identifiers) +- Pagination request/response contract + +**Drives:** `server/prisma/schema.prisma`, `server/src/modules/`, `client/src/resources/`, +`server/src/app.module.ts`, `client/src/App.tsx`. + +### `prompts/*.md` and `AGENTS.md` + +**Authoritative for:** + +- Agent generation workflow and reading order +- Auth seam patterns (Keycloak, JWT, PKCE S256, JWKS resolution chain) +- Runtime conventions (env examples, docker-compose topology) +- Framework scaffold baseline requirements (NestJS CLI, Vite React TypeScript) +- Filtering and sorting contract +- Naming conventions and implicit rules (pluralization, sort field priority, type mappings) +- Mutation boundaries (what agents must not overwrite) + +--- + +## Tier 2: Deterministic derivatives (never edit manually) + +| File | Generated from | Command | +| ----------------- | ------------------ | ------------------------------ | +| `api-summary.json` | `domain/*.api.dsl` | `npm run generate:api-summary` | + +These files are regenerated from their sources. Manual edits are overwritten on the +next generation run. + +--- + +## Tier 3: LLM-generated artifacts (never edit manually after generation) + +| Zone | Generated from | +| -------------------------------- | ------------------------------------------------ | +| `server/prisma/schema.prisma` | `domain/*.api.dsl` + `prompts/prisma-rules.md` | +| `server/src/modules//` | `domain/*.api.dsl` + `prompts/backend-rules.md` | +| `client/src/resources//` | `domain/*.api.dsl` + `prompts/frontend-rules.md` | +| `server/src/app.module.ts` | Module list derived from api.dsl `api` blocks | +| `client/src/App.tsx` | Resource list derived from api.dsl `api` blocks | + +To change these files: update `domain/*.api.dsl` and regenerate. + +--- + +## Tier 4: Handwritten (not generated; not derived) + +- Auth seams: `client/src/auth/`, `server/src/auth/` +- `toir-realm.json` +- `docker-compose.yml` +- `server/.env.example`, `client/.env.example` +- Framework config: `nest-cli.json`, `tsconfig*.json`, `vite.config.*`, etc. +- All `prompts/*.md`, `AGENTS.md`, `domain/dsl-spec.md`, `domain/*.api.dsl` + +--- + +## Validation gate + +### Stage 1 — Structural gate + +``` +node tools/validate-generation.mjs --artifacts-only +``` + +Verifies that generated artifacts satisfy the contracts declared in Tier 1 sources. + +### Stage 2 — Eval harness + +``` +npm run eval:generation +``` + +Fixture-based semantic checks from `tools/eval/fixtures/`. + +Both stages must pass before any generation task is considered complete. diff --git a/domain-summary.json b/domain-summary.json deleted file mode 100644 index cf1e7e0..0000000 --- a/domain-summary.json +++ /dev/null @@ -1,324 +0,0 @@ -{ - "sourceFiles": [ - "domain/TOiR.domain.dsl" - ], - "entities": [ - { - "name": "EquipmentType", - "primaryKey": "code", - "fields": [ - { - "name": "code", - "type": "string", - "required": true, - "unique": true, - "default": null - }, - { - "name": "name", - "type": "string", - "required": true, - "unique": false, - "default": null - }, - { - "name": "manufacturer", - "type": "string", - "required": false, - "unique": false, - "default": null - }, - { - "name": "maintenanceIntervalHours", - "type": "integer", - "required": false, - "unique": false, - "default": null - }, - { - "name": "overhaulIntervalHours", - "type": "integer", - "required": false, - "unique": false, - "default": null - } - ], - "foreignKeys": [] - }, - { - "name": "Equipment", - "primaryKey": "id", - "fields": [ - { - "name": "id", - "type": "uuid", - "required": false, - "unique": false, - "default": null - }, - { - "name": "inventoryNumber", - "type": "string", - "required": true, - "unique": true, - "default": null - }, - { - "name": "serialNumber", - "type": "string", - "required": false, - "unique": false, - "default": null - }, - { - "name": "name", - "type": "string", - "required": true, - "unique": false, - "default": null - }, - { - "name": "equipmentTypeCode", - "type": "string", - "required": true, - "unique": false, - "default": null - }, - { - "name": "status", - "type": "EquipmentStatus", - "required": true, - "unique": false, - "default": "Active" - }, - { - "name": "location", - "type": "string", - "required": false, - "unique": false, - "default": null - }, - { - "name": "commissionedAt", - "type": "date", - "required": false, - "unique": false, - "default": null - }, - { - "name": "totalEngineHours", - "type": "decimal", - "required": false, - "unique": false, - "default": null - }, - { - "name": "engineHoursSinceLastRepair", - "type": "decimal", - "required": false, - "unique": false, - "default": null - }, - { - "name": "lastRepairAt", - "type": "date", - "required": false, - "unique": false, - "default": null - }, - { - "name": "notes", - "type": "text", - "required": false, - "unique": false, - "default": null - } - ], - "foreignKeys": [ - { - "field": "equipmentTypeCode", - "references": { - "entity": "EquipmentType", - "field": "code" - } - } - ] - }, - { - "name": "RepairOrder", - "primaryKey": "id", - "fields": [ - { - "name": "id", - "type": "uuid", - "required": false, - "unique": false, - "default": null - }, - { - "name": "number", - "type": "string", - "required": true, - "unique": true, - "default": null - }, - { - "name": "equipmentId", - "type": "uuid", - "required": true, - "unique": false, - "default": null - }, - { - "name": "repairKind", - "type": "RepairKind", - "required": true, - "unique": false, - "default": null - }, - { - "name": "status", - "type": "RepairOrderStatus", - "required": true, - "unique": false, - "default": "Draft" - }, - { - "name": "plannedAt", - "type": "date", - "required": true, - "unique": false, - "default": null - }, - { - "name": "startedAt", - "type": "date", - "required": false, - "unique": false, - "default": null - }, - { - "name": "completedAt", - "type": "date", - "required": false, - "unique": false, - "default": null - }, - { - "name": "contractor", - "type": "string", - "required": false, - "unique": false, - "default": null - }, - { - "name": "engineHoursAtRepair", - "type": "decimal", - "required": false, - "unique": false, - "default": null - }, - { - "name": "description", - "type": "text", - "required": false, - "unique": false, - "default": null - }, - { - "name": "notes", - "type": "text", - "required": false, - "unique": false, - "default": null - } - ], - "foreignKeys": [ - { - "field": "equipmentId", - "references": { - "entity": "Equipment", - "field": "id" - } - } - ] - } - ], - "enums": [ - { - "name": "EquipmentStatus", - "values": [ - { - "name": "Active", - "label": "В эксплуатации" - }, - { - "name": "Repair", - "label": "В ремонте" - }, - { - "name": "Reserve", - "label": "В резерве" - }, - { - "name": "WriteOff", - "label": "Списано" - } - ] - }, - { - "name": "RepairKind", - "values": [ - { - "name": "TO", - "label": "Техническое обслуживание" - }, - { - "name": "TR", - "label": "Текущий ремонт" - }, - { - "name": "TRE", - "label": "Текущий расширенный ремонт" - }, - { - "name": "KR", - "label": "Капитальный ремонт" - }, - { - "name": "AR", - "label": "Аварийный ремонт" - }, - { - "name": "MP", - "label": "Метрологическая поверка" - } - ] - }, - { - "name": "RepairOrderStatus", - "values": [ - { - "name": "Draft", - "label": "Черновик" - }, - { - "name": "Approved", - "label": "Утверждена" - }, - { - "name": "InWork", - "label": "В работе" - }, - { - "name": "Done", - "label": "Выполнена" - }, - { - "name": "Cancelled", - "label": "Отменена" - } - ] - } - ] -} diff --git a/domain/TOiR.domain.dsl b/domain/TOiR.domain.dsl deleted file mode 100644 index c7002c8..0000000 --- a/domain/TOiR.domain.dsl +++ /dev/null @@ -1,236 +0,0 @@ -/* - КИС ТОиР — демонстрационная схема доменной модели - Сущности: Equipment (Оборудование), EquipmentType (Вид оборудования), RepairOrder (Заявка на ремонт) -*/ - -enum EquipmentStatus { - value Active { - label "В эксплуатации"; - } - value Repair { - label "В ремонте"; - } - value Reserve { - label "В резерве"; - } - value WriteOff { - label "Списано"; - } -} - -enum RepairKind { - value TO { - label "Техническое обслуживание"; - } - value TR { - label "Текущий ремонт"; - } - value TRE { - label "Текущий расширенный ремонт"; - } - value KR { - label "Капитальный ремонт"; - } - value AR { - label "Аварийный ремонт"; - } - value MP { - label "Метрологическая поверка"; - } -} - -enum RepairOrderStatus { - value Draft { - label "Черновик"; - } - value Approved { - label "Утверждена"; - } - value InWork { - label "В работе"; - } - value Done { - label "Выполнена"; - } - value Cancelled { - label "Отменена"; - } -} - -entity EquipmentType { - description "Вид (марка) оборудования — нормативный справочник НСИ"; - - attribute code { - key primary; - description "Код вида оборудования"; - type string; - is required; - is unique; - } - - attribute name { - description "Наименование вида"; - type string; - is required; - } - - attribute manufacturer { - description "Производитель"; - type string; - } - - attribute maintenanceIntervalHours { - description "Периодичность ТО, моточасов"; - type integer; - } - - attribute overhaulIntervalHours { - description "Периодичность КР, моточасов"; - type integer; - } -} - -entity Equipment { - description "Единица оборудования — объект ремонта и технического обслуживания"; - - attribute id { - type uuid; - key primary; - } - - attribute inventoryNumber { - description "Инвентарный номер"; - type string; - is required; - is unique; - } - - attribute serialNumber { - description "Заводской (серийный) номер"; - type string; - } - - attribute name { - description "Наименование единицы оборудования"; - type string; - is required; - } - - attribute equipmentTypeCode { - type string; - key foreign { - relates EquipmentType.code; - } - is required; - } - - attribute status { - description "Текущий статус"; - type EquipmentStatus; - default Active; - is required; - } - - attribute location { - description "Место эксплуатации / скважина / куст"; - type string; - } - - attribute commissionedAt { - description "Дата ввода в эксплуатацию"; - type date; - } - - attribute totalEngineHours { - description "Общая наработка, моточасов"; - type decimal; - } - - attribute engineHoursSinceLastRepair { - description "Наработка с последнего ремонта, моточасов"; - type decimal; - } - - attribute lastRepairAt { - description "Дата последнего ремонта"; - type date; - } - - attribute notes { - description "Примечания"; - type text; - } -} - -entity RepairOrder { - description "Заявка на ремонт — формируется по ППР или по факту обнаруженного дефекта"; - - attribute id { - type uuid; - key primary; - } - - attribute number { - description "Номер заявки"; - type string; - is required; - is unique; - } - - attribute equipmentId { - type uuid; - key foreign { - relates Equipment.id; - } - is required; - } - - attribute repairKind { - description "Вид ремонта"; - type RepairKind; - is required; - } - - attribute status { - type RepairOrderStatus; - default Draft; - is required; - } - - attribute plannedAt { - description "Плановая дата начала"; - type date; - is required; - } - - attribute startedAt { - description "Фактическая дата начала"; - type date; - } - - attribute completedAt { - description "Фактическая дата завершения"; - type date; - } - - attribute contractor { - description "Подрядная организация (если внешний ремонт)"; - type string; - } - - attribute engineHoursAtRepair { - description "Наработка на момент ремонта, моточасов"; - type decimal; - } - - attribute description { - description "Описание работ / дефекта"; - type text; - } - - attribute notes { - description "Примечания"; - type text; - } -} - diff --git a/domain/dsl-spec.md b/domain/dsl-spec.md index bfd4fed..edc5db9 100644 --- a/domain/dsl-spec.md +++ b/domain/dsl-spec.md @@ -1,34 +1,42 @@ # DSL Language Specification -This document describes the single DSL (Domain Specific Language) used to specify fullstack CRUD applications. The only required DSL input is `domain/*.dsl`. +This document describes the DSL system used to specify fullstack CRUD applications. -`domain-summary.json` is a derived artifact generated from this DSL to stabilize LLM-first generation and feed the lightweight validation gate. It must never replace the DSL as the source of truth. The active prompt corpus that consumes this contract lives in `prompts/`. +`domain/*.api.dsl` is the single source of truth for the entire domain model and API +contract. It drives Prisma schema generation, NestJS module generation, and React Admin +resource generation. + +`api-summary.json` is a derived artifact generated from the api.dsl to stabilize +LLM-first generation and feed the lightweight validation gate. It must never replace the +DSL as the source of truth. The active prompt corpus that consumes this contract lives in +`prompts/`. --- -# DSL Responsibility +# DSL Architecture -The domain DSL defines only: +## `domain/*.api.dsl` -- domain model -- relations -- enums +The api.dsl is the authoritative source of truth for: -The domain DSL is the single source of truth for: +- entities and their attributes +- scalar types and enums +- primary keys and foreign keys +- database-level constraints (required, unique, default) +- relations between entities +- DTO shapes per operation (Create, Update, Read, List) +- nullability and requiredness of each DTO attribute per operation +- HTTP methods and paths for each endpoint +- endpoint names and groupings +- pagination request/response contracts -- entities -- attributes -- primary keys -- foreign keys -- enums +The api.dsl drives: Prisma schema, NestJS controller/service/DTO generation, +and React Admin resource generation. -The following layers are always derived from the domain DSL and must not be authored as standalone authoritative DSL inputs: +Constraint: every `map Entity.field` or `sync Entity.field` reference in `domain/*.api.dsl` +must resolve to an entity and field defined within the same api.dsl file. -- DTO -- API -- UI - -Optional extension mechanism: +## Optional extension mechanism ```text overrides/ @@ -40,7 +48,8 @@ Override rules: - Overrides are optional. - The generator must work without them. -- Overrides may refine derived API or UI behavior, but they must not duplicate or redefine entities, attributes, primary keys, foreign keys, relations, or enums. +- Overrides may refine derived API or UI behavior, but they must not duplicate or redefine + entities, attributes, primary keys, foreign keys, relations, or enums. --- @@ -77,13 +86,15 @@ attribute name { **Modifiers:** -- `type` — required; one of: `string`, `uuid`, `integer`, `decimal`, `date`, `text`, or an enum name. +- `type` — required; one of: `string`, `uuid`, `integer`, `decimal`, `date`, `text`, `boolean`, `number`, or an enum name. - `key primary` — this attribute is the primary key. - `key foreign { relates Entity.field }` — foreign key to another entity's field. - `is required` — non-nullable. - `is unique` — unique constraint. +- `is nullable` — explicitly nullable. - `default Value` — default value (for enums or literals). - `description "..."` — human-readable description. +- `label "..."` — display label for UI. --- @@ -164,19 +175,21 @@ attribute equipmentTypeCode { ## DSL → Prisma -| DSL Concept | Prisma Result | -| ------------ | --------------------------- | -| entity | model | -| attribute | field | -| enum | enum | -| key primary | @id or @id @default(uuid()) | -| key foreign | relation + references | -| type string | String | -| type uuid | String @id @default(uuid()) | -| type integer | Int | -| type decimal | Decimal | -| type date | DateTime | -| type text | String | +| DSL Concept | Prisma Result | +| ------------- | --------------------------- | +| entity | model | +| attribute | field | +| enum | enum | +| key primary | @id or @id @default(uuid()) | +| key foreign | relation + references | +| type string | String | +| type uuid | String @id @default(uuid()) | +| type integer | Int | +| type number | Float | +| type decimal | Decimal | +| type date | DateTime | +| type text | String | +| type boolean | Boolean | --- @@ -203,15 +216,26 @@ API paths are derived from entity name: PascalCase → kebab-case, pluralized (e | attribute | Form field / column | | type string | TextInput, TextField | | type integer/decimal | NumberInput, NumberField | +| type number | NumberInput, NumberField | | type date | DateInput, DateField | +| type boolean | BooleanInput, BooleanField | | enum | SelectInput with choices | | foreign key | ReferenceInput, ReferenceField | --- -# Derived Layer Mapping +# API DSL Layer Mapping -- **Create DTO** — derived from domain attributes and must not include generated primary keys (for example no `id` for uuid PKs). -- **Update DTO** — derived from the same domain attributes with all fields optional for partial updates. -- **API response shape** — derived from domain attributes and must expose React Admin-compatible identifiers when needed. -- **UI field mapping** — derived from attribute types, descriptions, enums, and foreign keys without a separate UI DSL. +DTO shapes and endpoint contracts are declared in `domain/*.api.dsl`. The api.dsl +is the authoritative source for: + +- **Create DTO** — declared as `dto DTO.Create` in api.dsl. Must not include + server-generated primary keys (e.g. no `id` for uuid PKs). Required/nullable per field + is explicit in the api.dsl, not inferred. +- **Update DTO** — declared as `dto DTO.Update` in api.dsl. All fields are + typically nullable for partial update semantics. +- **API response shape** — declared as `dto DTO.` in api.dsl. Must expose + React Admin-compatible `id` field. +- **UI field mapping** — derived from the DTO shapes in api.dsl, not from domain + attributes directly. The Create form uses `DTO.Create` fields; the Edit form + uses `DTO.Update` fields; List and Show use `DTO.` fields. diff --git a/domain/toir.api.dsl b/domain/toir.api.dsl new file mode 100644 index 0000000..a767190 --- /dev/null +++ b/domain/toir.api.dsl @@ -0,0 +1,1291 @@ +// ============================================================================= +// Enums (справочно — не дублируются, только ссылки) +// ============================================================================= + +enum EquipmentStatus { + value Active { + label "В эксплуатации"; + } + value Repair { + label "В ремонте"; + } + value Reserve { + label "В резерве"; + } + value WriteOff { + label "Списано"; + } +} + +enum laborOperation { + description "Трудовые операции"; + value Manual { + label "Ручные"; + } + value MachineManual { + label "Машинно-ручные"; + } + value Machine { + label "Машинные"; + } +} + +enum EnumPeriodicityTO { + value Ежедневное { + label "Ежедневное"; + } + value Еженедельное { + label "Еженедельное"; + } + value Ежемесячное { + label "Ежемесячное"; + } + value Полугодовое { + label "Полугодовое"; + } + value Годовое { + label "Годовое"; + } +} + +enum Role { + value Исполнитель { + label "Исполнитель"; + } + value Подписант { + label "Подписант"; + } + value Пользователь { + label "Пользователь"; + } +} + +enum CategoryPart { + value Расходник { + label "Расходник"; + } + value Запчасть { + label "Запчасть"; + } + value Инструмент { + label "Инструмент"; + } + value Спецодежда { + label "Спецодежда"; + } +} + +enum EquipmentType { + description "Типы оборудования"; + value Производственное { + label "производственное"; + } + value Энергетическое { + label "энергетическое"; + } + value Насосное { + label "насосное"; + } + value Компрессорное { + label "компрессорное"; + } +} + +// ============================================================================= +// DTO — Equipment +// ============================================================================= + +dto DTO.Equipment { + description "Оборудование — полный response-объект"; + + attribute id { + type uuid; + map Equipment.id; + description "Идентификатор оборудования"; + } + + attribute name { + type string; + map Equipment.name; + description "Название оборудования"; + } + + attribute serialNumber { + type string; + map Equipment.serialNumber; + description "Заводской (серийный) номер"; + } + + attribute inventoryNumber { + type string; + map Equipment.inventoryNumber; + description "Инвентарный номер"; + is unique; + } + + attribute equipmentType { + type EquipmentType; + map Equipment.equipmentType; + description "Тип оборудования"; + is required; + } + + attribute dateOfInspection { + type date; + map Equipment.dateOfInspection; + description "Дата проверки"; + } + + attribute periodicityTO { + type EnumPeriodicityTO; + map Equipment.periodicityTO; + description "Периодичность ТО"; + is required; + } + + attribute location { + type string; + map Equipment.location; + description "Место эксплуатации / скважина / куст"; + } + + attribute status { + type EquipmentStatus; + map Equipment.status; + description "Текущий статус"; + is required; + } + + attribute commissionedAt { + type date; + map Equipment.commissionedAt; + description "Год изготовления"; + } + + attribute totalEngineHours { + type decimal; + map Equipment.totalEngineHours; + description "Общая наработка, моточасов"; + } + + attribute engineHoursSinceLastRepair { + type decimal; + map Equipment.engineHoursSinceLastRepair; + description "Наработка с последнего ремонта, моточасов"; + } + + attribute lastRepairAt { + type date; + map Equipment.lastRepairAt; + description "Дата последнего ремонта"; + } + + attribute notes { + type text; + map Equipment.notes; + description "Примечания"; + } + + attribute workAsPartOf { + type laborOperation; + map Equipment.workAsPartOf; + description "Работы в составе"; + } + + attribute fuelConsumed { + type number; + map Equipment.fuelConsumed; + description "Расход топлива"; + } +} + +dto DTO.EquipmentCreate { + description "Тело запроса на создание оборудования"; + + attribute name { + type string; + map Equipment.name; + description "Название оборудования"; + is required; + } + + attribute serialNumber { + type string; + map Equipment.serialNumber; + description "Заводской (серийный) номер"; + is required; + } + + attribute inventoryNumber { + type string; + map Equipment.inventoryNumber; + description "Инвентарный номер"; + is required; + is unique; + } + + attribute equipmentType { + type EquipmentType; + map Equipment.equipmentType; + description "Тип оборудования"; + is required; + } + + attribute dateOfInspection { + type date; + map Equipment.dateOfInspection; + description "Дата проверки"; + is nullable; + } + + attribute periodicityTO { + type EnumPeriodicityTO; + map Equipment.periodicityTO; + description "Периодичность ТО"; + is required; + } + + attribute location { + type string; + map Equipment.location; + description "Место эксплуатации / скважина / куст"; + is nullable; + } + + attribute status { + type EquipmentStatus; + map Equipment.status; + description "Текущий статус"; + is required; + } + + attribute commissionedAt { + type date; + map Equipment.commissionedAt; + description "Год изготовления"; + is nullable; + } + + attribute totalEngineHours { + type decimal; + map Equipment.totalEngineHours; + description "Общая наработка, моточасов"; + is nullable; + } + + attribute engineHoursSinceLastRepair { + type decimal; + map Equipment.engineHoursSinceLastRepair; + description "Наработка с последнего ремонта, моточасов"; + is nullable; + } + + attribute lastRepairAt { + type date; + map Equipment.lastRepairAt; + description "Дата последнего ремонта"; + is nullable; + } + + attribute notes { + type text; + map Equipment.notes; + description "Примечания"; + is nullable; + } + + attribute workAsPartOf { + type laborOperation; + map Equipment.workAsPartOf; + description "Работы в составе"; + is nullable; + } + + attribute fuelConsumed { + type number; + map Equipment.fuelConsumed; + description "Расход топлива"; + is nullable; + } +} + +dto DTO.EquipmentUpdate { + description "Тело запроса на обновление оборудования (частичное обновление)"; + + attribute name { + type string; + map Equipment.name; + description "Название оборудования"; + is nullable; + } + + attribute serialNumber { + type string; + map Equipment.serialNumber; + description "Заводской (серийный) номер"; + is nullable; + } + + attribute inventoryNumber { + type string; + map Equipment.inventoryNumber; + description "Инвентарный номер"; + is nullable; + is unique; + } + + attribute equipmentType { + type EquipmentType; + map Equipment.equipmentType; + description "Тип оборудования"; + is nullable; + } + + attribute dateOfInspection { + type date; + map Equipment.dateOfInspection; + description "Дата проверки"; + is nullable; + } + + attribute periodicityTO { + type EnumPeriodicityTO; + map Equipment.periodicityTO; + description "Периодичность ТО"; + is nullable; + } + + attribute location { + type string; + map Equipment.location; + description "Место эксплуатации / скважина / куст"; + is nullable; + } + + attribute status { + type EquipmentStatus; + map Equipment.status; + description "Текущий статус"; + is nullable; + } + + attribute commissionedAt { + type date; + map Equipment.commissionedAt; + description "Год изготовления"; + is nullable; + } + + attribute totalEngineHours { + type decimal; + map Equipment.totalEngineHours; + description "Общая наработка, моточасов"; + is nullable; + } + + attribute engineHoursSinceLastRepair { + type decimal; + map Equipment.engineHoursSinceLastRepair; + description "Наработка с последнего ремонта, моточасов"; + is nullable; + } + + attribute lastRepairAt { + type date; + map Equipment.lastRepairAt; + description "Дата последнего ремонта"; + is nullable; + } + + attribute notes { + type text; + map Equipment.notes; + description "Примечания"; + is nullable; + } + + attribute workAsPartOf { + type laborOperation; + map Equipment.workAsPartOf; + description "Работы в составе"; + is nullable; + } + + attribute fuelConsumed { + type number; + map Equipment.fuelConsumed; + description "Расход топлива"; + is nullable; + } +} + +dto DTO.EquipmentFilter { + description "Фильтры для списка оборудования"; + + attribute inventoryNumber { + type string; + description "Частичное совпадение по инвентарному номеру"; + is nullable; + } + + attribute serialNumber { + type string; + description "Частичное совпадение по заводскому номеру"; + is nullable; + } + + attribute name { + type string; + description "Частичное совпадение по наименованию"; + is nullable; + } + + attribute equipmentTypeCode { + type string; + description "Точное совпадение по коду вида оборудования"; + is nullable; + } + + attribute equipmentPeriodicityTO { + type string; + description "Точное совпадение по периодичности ТО оборудования"; + is nullable; + } + + attribute dateOfInspection { + type date; + description "Дата проверки"; + is nullable; + } + + attribute status { + type EquipmentStatus; + description "Фильтр по статусу"; + is nullable; + } + + attribute location { + type string; + description "Частичное совпадение по месту эксплуатации"; + is nullable; + } + + attribute workAsPartOf { + type laborOperation; + description "Фильтр по трудовой операции"; + is nullable; + } +} + +dto DTO.EquipmentListRequest { + description "Запрос постраничного списка оборудования с фильтрацией"; + + attribute filter { + type DTO.EquipmentFilter; + description "Фильтры"; + is nullable; + } + + attribute page { + type DTO.PageRequest; + description "Параметры пагинации"; + is nullable; + } +} + +dto DTO.EquipmentListResponse { + description "Ответ постраничного списка оборудования"; + + attribute content { + type DTO.Equipment[]; + description "Список оборудования"; + } + + attribute page { + type DTO.PageInfo; + description "Метаданные пагинации"; + } +} + +// ============================================================================= +// DTO — Employee +// ============================================================================= + +dto DTO.Employee { + description "Сотрудник — полный response-объект"; + + attribute code { + type string; + map Employee.code; + description "Номер сотрудника (Табельный номер)"; + key primary; + } + + attribute fullName { + type string; + map Employee.fullName; + description "ФИО сотрудника"; + label "Полное имя"; + } + + attribute role { + type Role; + map Employee.role; + description "Роль сотрудника"; + label "Роль"; + is required; + } + + attribute position { + type string; + map Employee.position; + description "Должность сотрудника"; + } + + attribute boss { + type DTO.Employee; + map Employee.boss; + description "Руководитель"; + is nullable; + } + + attribute subordinates { + type DTO.Employee[]; + map Employee.subordinates; + description "Подчинённые"; + } + + attribute price { + type number; + map Employee.price; + description "Стоимость часа работы сотрудника"; + } + + attribute phoneNumber { + type number; + map Employee.phoneNumber; + description "Номер телефона"; + } +} + +dto DTO.EmployeeCreate { + description "Тело запроса на создание сотрудника"; + + attribute code { + type string; + map Employee.code; + description "Номер сотрудника (Табельный номер)"; + is required; + } + + attribute fullName { + type string; + map Employee.fullName; + description "ФИО сотрудника"; + label "Полное имя"; + is required; + } + + attribute role { + type Role; + map Employee.role; + description "Роль сотрудника"; + label "Роль"; + is required; + } + + attribute position { + type string; + map Employee.position; + description "Должность сотрудника"; + is required; + } + + attribute boss { + type string; + map Employee.boss; + description "Код руководителя"; + is nullable; + } + + attribute price { + type number; + map Employee.price; + description "Стоимость часа работы сотрудника"; + is nullable; + } + + attribute phoneNumber { + type number; + map Employee.phoneNumber; + description "Номер телефона"; + is nullable; + } +} + +dto DTO.EmployeeUpdate { + description "Тело запроса на обновление сотрудника (частичное обновление)"; + + attribute fullName { + type string; + map Employee.fullName; + description "ФИО сотрудника"; + label "Полное имя"; + is nullable; + } + + attribute role { + type Role; + map Employee.role; + description "Роль сотрудника"; + label "Роль"; + is nullable; + } + + attribute position { + type string; + map Employee.position; + description "Должность сотрудника"; + is nullable; + } + + attribute boss { + type string; + map Employee.boss; + description "Код руководителя"; + is nullable; + } + + attribute price { + type number; + map Employee.price; + description "Стоимость часа работы сотрудника"; + is nullable; + } + + attribute phoneNumber { + type number; + map Employee.phoneNumber; + description "Номер телефона"; + is nullable; + } +} + +dto DTO.EmployeeFilter { + description "Фильтры для списка сотрудников"; + + attribute code { + type string; + description "Частичное совпадение по табельному номеру"; + is nullable; + } + + attribute fullName { + type string; + description "Частичное совпадение по ФИО"; + is nullable; + } + + attribute role { + type Role; + description "Точное совпадение по роли"; + is nullable; + } + + attribute position { + type string; + description "Частичное совпадение по должности"; + is nullable; + } +} + +dto DTO.EmployeeListRequest { + description "Запрос постраничного списка сотрудников с фильтрацией"; + + attribute filter { + type DTO.EmployeeFilter; + description "Фильтры"; + is nullable; + } + + attribute page { + type DTO.PageRequest; + description "Параметры пагинации"; + is nullable; + } +} + +dto DTO.EmployeeListResponse { + description "Ответ постраничного списка сотрудников"; + + attribute content { + type DTO.Employee[]; + description "Список сотрудников"; + } + + attribute page { + type DTO.PageInfo; + description "Метаданные пагинации"; + } +} + +// ============================================================================= +// DTO — Part (ЗИП/ТМЦ) +// ============================================================================= + +dto DTO.Part { + description "ЗИП/ТМЦ — полный response-объект"; + + attribute id { + type uuid; + map Part.id; + description "Идентификатор ЗИП/ТМЦ"; + } + + attribute name { + type string; + map Part.name; + description "Название"; + } + + attribute categories { + type CategoryPart; + map Part.categories; + description "Категории"; + is nullable; + } + + attribute price { + type number; + map Part.price; + description "Стоимость ЗИП/ТМЦ"; + } + + attribute description { + type string; + map Part.description; + description "Описание ЗИП/ТМЦ"; + } + + attribute serialNumber { + type string; + map Part.serialNumber; + description "Серийный номер запасных частей / инструментов / расходников"; + } +} + +dto DTO.PartCreate { + description "Тело запроса на создание ЗИП/ТМЦ"; + + attribute name { + type string; + map Part.name; + description "Название"; + is required; + } + + attribute categories { + type CategoryPart; + map Part.categories; + description "Категории"; + is nullable; + } + + attribute price { + type number; + map Part.price; + description "Стоимость ЗИП/ТМЦ"; + is nullable; + } + + attribute description { + type string; + map Part.description; + description "Описание ЗИП/ТМЦ"; + is nullable; + } + + attribute serialNumber { + type string; + map Part.serialNumber; + description "Серийный номер запасных частей / инструментов / расходников"; + is nullable; + } +} + +dto DTO.PartUpdate { + description "Тело запроса на обновление ЗИП/ТМЦ (частичное обновление)"; + + attribute name { + type string; + map Part.name; + description "Название"; + is nullable; + } + + attribute categories { + type CategoryPart; + map Part.categories; + description "Категории"; + is nullable; + } + + attribute price { + type number; + map Part.price; + description "Стоимость ЗИП/ТМЦ"; + is nullable; + } + + attribute description { + type string; + map Part.description; + description "Описание ЗИП/ТМЦ"; + is nullable; + } + + attribute serialNumber { + type string; + map Part.serialNumber; + description "Серийный номер запасных частей / инструментов / расходников"; + is nullable; + } +} + +dto DTO.PartFilter { + description "Фильтры для списка ЗИП/ТМЦ"; + + attribute name { + type string; + description "Частичное совпадение по названию"; + is nullable; + } + + attribute categories { + type CategoryPart; + description "Точное совпадение по категории"; + is nullable; + } + + attribute serialNumber { + type string; + description "Частичное совпадение по серийному номеру"; + is nullable; + } +} + +dto DTO.PartListRequest { + description "Запрос постраничного списка ЗИП/ТМЦ с фильтрацией"; + + attribute filter { + type DTO.PartFilter; + description "Фильтры"; + is nullable; + } + + attribute page { + type DTO.PageRequest; + description "Параметры пагинации"; + is nullable; + } +} + +dto DTO.PartListResponse { + description "Ответ постраничного списка ЗИП/ТМЦ"; + + attribute content { + type DTO.Part[]; + description "Список ЗИП/ТМЦ"; + } + + attribute page { + type DTO.PageInfo; + description "Метаданные пагинации"; + } +} + +// ============================================================================= +// DTO — CategoryResource +// ============================================================================= + +dto DTO.CategoryResource { + description "Категория ресурсов — полный response-объект"; + + attribute id { + type uuid; + map CategoryResource.id; + description "Идентификатор категории ресурсов"; + } + + attribute part { + type DTO.Part; + map CategoryResource.part; + description "ЗИП/ТМЦ"; + is nullable; + } + + attribute employee { + type DTO.Employee; + map CategoryResource.employee; + description "Сотрудник"; + is nullable; + } +} + +dto DTO.CategoryResourceCreate { + description "Тело запроса на создание категории ресурсов"; + + attribute partId { + type uuid; + map CategoryResource.part; + description "Идентификатор ЗИП/ТМЦ"; + is nullable; + } + + attribute employeeCode { + type string; + map CategoryResource.employee; + description "Код сотрудника"; + is nullable; + } +} + +dto DTO.CategoryResourceUpdate { + description "Тело запроса на обновление категории ресурсов (частичное обновление)"; + + attribute partId { + type uuid; + map CategoryResource.part; + description "Идентификатор ЗИП/ТМЦ"; + is nullable; + } + + attribute employeeCode { + type string; + map CategoryResource.employee; + description "Код сотрудника"; + is nullable; + } +} + +dto DTO.CategoryResourceFilter { + description "Фильтры для списка категорий ресурсов"; + + attribute partName { + type string; + description "Частичное совпадение по названию ЗИП/ТМЦ"; + is nullable; + } + + attribute employeeName { + type string; + description "Частичное совпадение по ФИО сотрудника"; + is nullable; + } +} + +dto DTO.CategoryResourceListRequest { + description "Запрос постраничного списка категорий ресурсов с фильтрацией"; + + attribute filter { + type DTO.CategoryResourceFilter; + description "Фильтры"; + is nullable; + } + + attribute page { + type DTO.PageRequest; + description "Параметры пагинации"; + is nullable; + } +} + +dto DTO.CategoryResourceListResponse { + description "Ответ постраничного списка категорий ресурсов"; + + attribute content { + type DTO.CategoryResource[]; + description "Список категорий ресурсов"; + } + + attribute page { + type DTO.PageInfo; + description "Метаданные пагинации"; + } +} + +// ============================================================================= +// DTO — PriceList +// ============================================================================= + +dto DTO.PriceList { + description "Регистр цен — response-объект"; + + attribute costOfWorkingHours { + type number; + description "Стоимость часов работы"; + sync Employee.price; + } + + attribute partPrice { + type number; + description "Стоимость ЗИП/ТМЦ"; + sync Part.price; + } +} + +// ============================================================================= +// API — Equipment +// ============================================================================= + +api API.Equipment { + description "API управления справочником оборудования"; + + endpoint getEquipmentById { + label "GET /equipments/{id}"; + description "Получить оборудование по идентификатору"; + attribute id { + type uuid; + description "Идентификатор оборудования"; + } + attribute response { + type DTO.Equipment; + } + } + + endpoint listEquipments { + label "POST /equipments/page"; + description "Постраничный список оборудования с фильтрацией"; + attribute request { + type DTO.EquipmentListRequest; + } + attribute response { + type DTO.EquipmentListResponse; + } + } + + endpoint createEquipment { + label "POST /equipments"; + description "Создать единицу оборудования"; + attribute request { + type DTO.EquipmentCreate; + } + } + + endpoint updateEquipment { + label "PUT /equipments/{id}"; + description "Обновить оборудование"; + attribute id { + type uuid; + description "Идентификатор оборудования"; + } + attribute request { + type DTO.EquipmentUpdate; + } + } + + endpoint deleteEquipment { + label "DELETE /equipments/{id}"; + description "Удалить единицу оборудования"; + attribute id { + type uuid; + description "Идентификатор оборудования"; + } + } +} + +// ============================================================================= +// API — Employee +// ============================================================================= + +api API.Employee { + description "API управления справочником сотрудников"; + + endpoint getEmployeeByCode { + label "GET /employees/{code}"; + description "Получить сотрудника по табельному номеру"; + attribute code { + type string; + description "Табельный номер сотрудника"; + } + attribute response { + type DTO.Employee; + } + } + + endpoint listEmployees { + label "POST /employees/page"; + description "Постраничный список сотрудников с фильтрацией"; + attribute request { + type DTO.EmployeeListRequest; + } + attribute response { + type DTO.EmployeeListResponse; + } + } + + endpoint createEmployee { + label "POST /employees"; + description "Создать сотрудника"; + attribute request { + type DTO.EmployeeCreate; + } + } + + endpoint updateEmployee { + label "PUT /employees/{code}"; + description "Обновить сотрудника"; + attribute code { + type string; + description "Табельный номер сотрудника"; + } + attribute request { + type DTO.EmployeeUpdate; + } + } + + endpoint deleteEmployee { + label "DELETE /employees/{code}"; + description "Удалить сотрудника"; + attribute code { + type string; + description "Табельный номер сотрудника"; + } + } +} + +// ============================================================================= +// API — Part (ЗИП/ТМЦ) +// ============================================================================= + +api API.Part { + description "API управления справочником ЗИП/ТМЦ"; + + endpoint getPartById { + label "GET /parts/{id}"; + description "Получить ЗИП/ТМЦ по идентификатору"; + attribute id { + type uuid; + description "Идентификатор ЗИП/ТМЦ"; + } + attribute response { + type DTO.Part; + } + } + + endpoint listParts { + label "POST /parts/page"; + description "Постраничный список ЗИП/ТМЦ с фильтрацией"; + attribute request { + type DTO.PartListRequest; + } + attribute response { + type DTO.PartListResponse; + } + } + + endpoint createPart { + label "POST /parts"; + description "Создать ЗИП/ТМЦ"; + attribute request { + type DTO.PartCreate; + } + } + + endpoint updatePart { + label "PUT /parts/{id}"; + description "Обновить ЗИП/ТМЦ"; + attribute id { + type uuid; + description "Идентификатор ЗИП/ТМЦ"; + } + attribute request { + type DTO.PartUpdate; + } + } + + endpoint deletePart { + label "DELETE /parts/{id}"; + description "Удалить ЗИП/ТМЦ"; + attribute id { + type uuid; + description "Идентификатор ЗИП/ТМЦ"; + } + } +} + +// ============================================================================= +// API — CategoryResource +// ============================================================================= + +api API.CategoryResource { + description "API управления категориями ресурсов"; + + endpoint getCategoryResourceById { + label "GET /category-resources/{id}"; + description "Получить категорию ресурсов по идентификатору"; + attribute id { + type uuid; + description "Идентификатор категории ресурсов"; + } + attribute response { + type DTO.CategoryResource; + } + } + + endpoint listCategoryResources { + label "POST /category-resources/page"; + description "Постраничный список категорий ресурсов с фильтрацией"; + attribute request { + type DTO.CategoryResourceListRequest; + } + attribute response { + type DTO.CategoryResourceListResponse; + } + } + + endpoint createCategoryResource { + label "POST /category-resources"; + description "Создать категорию ресурсов"; + attribute request { + type DTO.CategoryResourceCreate; + } + } + + endpoint updateCategoryResource { + label "PUT /category-resources/{id}"; + description "Обновить категорию ресурсов"; + attribute id { + type uuid; + description "Идентификатор категории ресурсов"; + } + attribute request { + type DTO.CategoryResourceUpdate; + } + } + + endpoint deleteCategoryResource { + label "DELETE /category-resources/{id}"; + description "Удалить категорию ресурсов"; + attribute id { + type uuid; + description "Идентификатор категории ресурсов"; + } + } +} + +// ============================================================================= +// API — PriceList +// ============================================================================= + +api API.PriceList { + description "API получения регистра цен"; + + endpoint getPriceList { + label "GET /price-list"; + description "Получить регистр цен"; + attribute response { + type DTO.PriceList; + } + } +} \ No newline at end of file diff --git a/generation/generate.mjs b/generation/generate.mjs deleted file mode 100644 index fa36c3f..0000000 --- a/generation/generate.mjs +++ /dev/null @@ -1,751 +0,0 @@ -import fs from 'node:fs'; -import path from 'node:path'; -import { fileURLToPath } from 'node:url'; - -// Always resolve repo root relative to this script location -// /generation/generate.mjs -> root is parent folder of generation/ -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const ROOT = path.resolve(__dirname, '..'); - -function readFile(p) { - return fs.readFileSync(p, 'utf8'); -} - -function writeFile(p, content) { - fs.mkdirSync(path.dirname(p), { recursive: true }); - fs.writeFileSync(p, content, 'utf8'); -} - -function toKebab(s) { - return s - .replace(/([a-z0-9])([A-Z])/g, '$1-$2') - .replace(/_/g, '-') - .toLowerCase(); -} - -function pluralize(resource) { - // Minimal heuristic; can be improved later. - if (resource === 'equipment') return 'equipment'; - if (resource.endsWith('s')) return `${resource}es`; - return `${resource}s`; -} - -function upperFirst(s) { - return s ? s[0].toUpperCase() + s.slice(1) : s; -} - -function lowerFirst(s) { - return s ? s[0].toLowerCase() + s.slice(1) : s; -} - -function toIdentifierFromKebab(kebab) { - // "repair-order" -> "repairOrder" - return kebab.replace(/-([a-z])/g, (_, c) => c.toUpperCase()); -} - -function parseBlocks(text, kind) { - // kind: 'enum' | 'entity' - const blocks = []; - const lines = text.split(/\r?\n/); - for (let i = 0; i < lines.length; i++) { - const line = lines[i]; - const m = line.match(new RegExp(`^\\s*${kind}\\s+([A-Za-z_][A-Za-z0-9_]*)\\s*\\{\\s*$`)); - if (!m) continue; - const name = m[1]; - let depth = 0; - const start = i; - let end = i; - for (let j = i; j < lines.length; j++) { - const l = lines[j]; - if (l.includes('{')) depth += (l.match(/\{/g) || []).length; - if (l.includes('}')) depth -= (l.match(/\}/g) || []).length; - if (depth === 0 && j > i) { - end = j; - break; - } - } - const body = lines.slice(start + 1, end).join('\n'); - blocks.push({ name, body, startLine: start, endLine: end }); - i = end; - } - return blocks; -} - -function parseEnum(body) { - const values = []; - const labels = {}; - const re = /value\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{([\s\S]*?)\}/gm; - let m; - while ((m = re.exec(body))) { - values.push(m[1]); - const label = (m[2].match(/label\s+"([^"]+)"/m) || [])[1]; - labels[m[1]] = label || m[1]; - } - return { values, labels }; -} - -function parseEntity(body) { - const attrs = []; - const entityLabel = (body.match(/description\s+"([^"]+)"/m) || [])[1]; - const lines = body.split(/\r?\n/); - for (let i = 0; i < lines.length; i++) { - const m = lines[i].match(/^\s*attribute\s+([A-Za-z_][A-Za-z0-9_]*)\s*\{\s*$/); - if (!m) continue; - const name = m[1]; - let depth = 0; - const start = i; - let end = i; - for (let j = i; j < lines.length; j++) { - const l = lines[j]; - if (l.includes('{')) depth += (l.match(/\{/g) || []).length; - if (l.includes('}')) depth -= (l.match(/\}/g) || []).length; - if (depth === 0 && j > i) { - end = j; - break; - } - } - const abody = lines.slice(start + 1, end).join('\n'); - const type = (abody.match(/^\s*type\s+([A-Za-z_][A-Za-z0-9_]*)\s*;/m) || [])[1]; - const isRequired = /^\s*is required\s*;/m.test(abody); - const isUnique = /^\s*is unique\s*;/m.test(abody); - const isPrimary = /^\s*key primary\s*;/m.test(abody); - const defaultValue = (abody.match(/^\s*default\s+([A-Za-z_][A-Za-z0-9_]*)\s*;/m) || [])[1]; - const foreignRel = (abody.match(/relates\s+([A-Za-z_][A-Za-z0-9_]*)\.([A-Za-z_][A-Za-z0-9_]*)\s*;/m) || []).slice(1); - const description = (abody.match(/description\s+"([^"]+)"/m) || [])[1]; - - attrs.push({ - name, - type, - label: description || name, - isRequired, - isUnique, - isPrimary, - defaultValue, - foreign: foreignRel.length ? { entity: foreignRel[0], field: foreignRel[1] } : null, - }); - i = end; - } - - const pk = attrs.find((a) => a.isPrimary); - if (!pk) throw new Error('Entity missing primary key attribute'); - - return { attributes: attrs, primaryKey: pk.name, label: entityLabel }; -} - -function parseDomainDSL(dslText) { - const enums = {}; - const entities = {}; - - for (const b of parseBlocks(dslText, 'enum')) { - enums[b.name] = parseEnum(b.body); - } - for (const b of parseBlocks(dslText, 'entity')) { - entities[b.name] = parseEntity(b.body); - } - return { enums, entities }; -} - -function getEntityAttrNames(entity) { - return new Set(entity.attributes.map((a) => a.name)); -} - -function getBestSortField(entity, pk) { - const attrs = getEntityAttrNames(entity); - if (attrs.has('inventoryNumber')) return 'inventoryNumber'; - if (attrs.has('number')) return 'number'; - if (attrs.has('code')) return 'code'; - if (attrs.has('name')) return 'name'; - return pk; -} - -function getReferenceDisplayExpr(foreignEntity) { - const attrs = getEntityAttrNames(foreignEntity); - if (attrs.has('inventoryNumber')) { - return "(record) => record.inventoryNumber ? `${record.inventoryNumber} — ${record.name ?? record.inventoryNumber}` : (record.name ?? record.id)"; - } - if (attrs.has('code')) { - return "(record) => record.code ? `${record.code} — ${record.name ?? record.code}` : (record.name ?? record.id)"; - } - if (attrs.has('number')) { - return "(record) => record.number ? `${record.number} — ${record.name ?? record.number}` : (record.name ?? record.id)"; - } - if (attrs.has('name')) { - return "(record) => record.name ?? record.id"; - } - return "(record) => record.id"; -} - -function getAttributeLabel(attr, allEntities) { - if (attr.label && attr.label !== attr.name) return attr.label; - if (attr.name === 'status') return 'Статус'; - if (attr.name === 'equipmentId') return 'Оборудование'; - if (attr.name === 'equipmentTypeCode') return 'Вид оборудования'; - if (attr.foreign) return allEntities[attr.foreign.entity]?.label || attr.name; - return attr.name; -} - -function prismaScalarType(dslType) { - switch (dslType) { - case 'string': - case 'text': - return 'String'; - case 'uuid': - return 'String'; - case 'integer': - return 'Int'; - case 'decimal': - return 'Decimal'; - case 'date': - return 'DateTime'; - default: - // enum type name - return dslType; - } -} - -function generatePrismaEnum(name, values) { - return `enum ${name} {\n${values.map((v) => ` ${v}`).join('\n')}\n}\n`; -} - -function generatePrismaModel(name, entity, allEntities) { - const lines = []; - lines.push(`model ${name} {`); - for (const attr of entity.attributes) { - if (attr.foreign) { - // Keep scalar FK field, relation field added below - } - const scalar = prismaScalarType(attr.type); - const optional = attr.isRequired || attr.isPrimary ? '' : '?'; - const parts = [` ${attr.name} ${scalar}${optional}`]; - - if (attr.isPrimary) { - if (attr.type === 'uuid' || attr.name === 'id') parts.push('@id @default(uuid())'); - else parts.push('@id'); - } - if (attr.isUnique && !attr.isPrimary) parts.push('@unique'); - if (attr.defaultValue) parts.push(`@default(${attr.defaultValue})`); - - lines.push(`${parts.join(' ')}`); - } - - // Add relations for foreign keys - for (const attr of entity.attributes.filter((a) => a.foreign)) { - const relEntity = attr.foreign.entity; - const relField = attr.foreign.field; - const relName = lowerFirst(relEntity); - // relation field must not collide; fallback to relEntity name if needed - const relationFieldName = entity.attributes.some((a) => a.name === relName) ? `${relName}Ref` : relName; - lines.push( - ` ${relationFieldName} ${relEntity} @relation(fields: [${attr.name}], references: [${relField}])` - ); - } - - // Add back-relations (required by Prisma when a relation field exists) - // For each other entity that has a FK pointing to this model, create a list field. - for (const [otherName, otherEntity] of Object.entries(allEntities)) { - for (const fk of otherEntity.attributes.filter((a) => a.foreign)) { - if (fk.foreign.entity !== name) continue; - const candidate = pluralize(lowerFirst(otherName)); - const fieldName = lines.some((l) => l.startsWith(` ${candidate} `)) - ? `${candidate}List` - : candidate; - // Avoid duplicates if multiple FKs exist (basic de-dupe) - if (lines.some((l) => l.startsWith(` ${fieldName} `))) continue; - lines.push(` ${fieldName} ${otherName}[]`); - } - } - - lines.push('}'); - return `${lines.join('\n')}\n`; -} - -function ensurePrismaSchema({ enums, entities }, prismaPath, apply) { - const existing = fs.existsSync(prismaPath) ? readFile(prismaPath) : ''; - const hasGenerator = /generator\s+client\s*\{/m.test(existing); - const header = hasGenerator - ? existing.split(/\n(?=enum|model)\b/)[0].trimEnd() + '\n\n' - : `generator client {\n provider = "prisma-client-js"\n}\n\ndatasource db {\n provider = "postgresql"\n url = env("DATABASE_URL")\n}\n\n`; - - const out = [header]; - - // Preserve existing enum/model blocks not in DSL? For now, regenerate from DSL only. - for (const [name, e] of Object.entries(enums)) out.push(generatePrismaEnum(name, e.values) + '\n'); - for (const [name, ent] of Object.entries(entities)) out.push(generatePrismaModel(name, ent, entities) + '\n'); - - const next = out.join('').trimEnd() + '\n'; - if (!apply) return { changed: next !== existing, content: next }; - writeFile(prismaPath, next); - return { changed: true, content: next }; -} - -function renderBackendModule(entityName, entity, resourceName, pk) { - const className = entityName; - const moduleName = `${className}Module`; - const serviceName = `${className}Service`; - const controllerName = `${className}Controller`; - const folder = toKebab(entityName); - - // DTOs - const enumTypes = entity.attributes - .filter((a) => !['string', 'text', 'uuid', 'integer', 'decimal', 'date'].includes(a.type)) - .map((a) => a.type); - - const enumUnion = (typeName) => { - // Unknown labels in DSL aren't needed; keep as string union to avoid importing Prisma enums. - return `'${typeName}'`; - }; - - const dtoType = (attr) => { - switch (attr.type) { - case 'uuid': - case 'string': - case 'text': - return 'string'; - case 'integer': - return 'number'; - case 'decimal': - return 'string'; - case 'date': - return 'string'; - default: - // enum - return 'string'; - } - }; - - const createDtoLines = []; - createDtoLines.push(`export class Create${className}Dto {`); - for (const a of entity.attributes) { - if (a.isPrimary && a.type === 'uuid') continue; // generated - const opt = a.isRequired && !(a.isPrimary && a.type !== 'uuid') ? '!' : '?'; - createDtoLines.push(` ${a.name}${opt}: ${dtoType(a)};`); - } - createDtoLines.push('}'); - - const updateDtoLines = []; - updateDtoLines.push(`export class Update${className}Dto {`); - if (pk !== 'id') updateDtoLines.push(` id?: string;`); - for (const a of entity.attributes) { - if (pk !== 'id' && a.name === 'id') continue; - updateDtoLines.push(` ${a.name}?: ${dtoType(a)};`); - } - updateDtoLines.push('}'); - - const controller = `import { Controller, Get, Post, Patch, Delete, Param, Body, Query, Res } from '@nestjs/common';\nimport { Response } from 'express';\nimport { Roles } from '../../auth/decorators/roles.decorator';\nimport { RealmRole } from '../../auth/roles/realm-role.enum';\nimport { ${serviceName} } from './${folder}.service';\nimport { Create${className}Dto } from './dto/create-${folder}.dto';\nimport { Update${className}Dto } from './dto/update-${folder}.dto';\n\n@Controller('${resourceName}')\nexport class ${controllerName} {\n constructor(private readonly service: ${serviceName}) {}\n\n @Roles(RealmRole.Viewer, RealmRole.Editor, RealmRole.Admin)\n @Get()\n async findAll(@Query() query: any, @Res() res: Response) {\n const result = await this.service.findAll(query);\n res.set('Content-Range', \`${resourceName} \${query._start || 0}-\${query._end || result.total}/\${result.total}\`);\n res.set('Access-Control-Expose-Headers', 'Content-Range');\n return res.json(result.data);\n }\n\n @Roles(RealmRole.Viewer, RealmRole.Editor, RealmRole.Admin)\n @Get(':${pk}')\n findOne(@Param('${pk}') id: string) {\n return this.service.findOne(id);\n }\n\n @Roles(RealmRole.Editor, RealmRole.Admin)\n @Post()\n create(@Body() dto: Create${className}Dto) {\n return this.service.create(dto);\n }\n\n @Roles(RealmRole.Editor, RealmRole.Admin)\n @Patch(':${pk}')\n update(@Param('${pk}') id: string, @Body() dto: Update${className}Dto) {\n return this.service.update(id, dto);\n }\n\n @Roles(RealmRole.Admin)\n @Delete(':${pk}')\n remove(@Param('${pk}') id: string) {\n return this.service.remove(id);\n }\n}\n`; - - const service = `import { Injectable } from '@nestjs/common';\nimport { Prisma } from '@prisma/client';\nimport { PrismaService } from '../../prisma/prisma.service';\nimport { Create${className}Dto } from './dto/create-${folder}.dto';\nimport { Update${className}Dto } from './dto/update-${folder}.dto';\n\nfunction serializeRecord(record: any) {\n return {\n ...record,\n${entity.attributes - .filter((a) => a.type === 'decimal') - .map((a) => ` ${a.name}: record.${a.name}?.toString() ?? null,`) - .join('\n')}\n${entity.attributes - .filter((a) => a.type === 'date') - .map((a) => ` ${a.name}: record.${a.name}?.toISOString() ?? null,`) - .join('\n')}\n };\n}\n\n@Injectable()\nexport class ${serviceName} {\n constructor(private readonly prisma: PrismaService) {}\n\n async findAll(query: { _start?: string; _end?: string; _sort?: string; _order?: string; [key: string]: any }) {\n const start = parseInt(query._start) || 0;\n const end = parseInt(query._end) || 10;\n const take = end - start;\n const skip = start;\n const sortField = query._sort || '${getBestSortField(entity, pk)}';\n const sortOrder = (query._order || 'ASC').toLowerCase() as 'asc' | 'desc';\n\n const where: any = {};\n\n if (query.q) {\n const q = String(query.q);\n const ors: any[] = [];\n ${entity.attributes - .filter((a) => ['string', 'text'].includes(a.type)) - .slice(0, 6) - .map((a) => `ors.push({ ${a.name}: { contains: q, mode: 'insensitive' } });`) - .join('\n ')}\n if (ors.length) where.OR = ors;\n }\n\n ${entity.attributes - .filter((a) => ['string', 'text'].includes(a.type) && !a.foreign) - .map((a) => `if (query.${a.name}) where.${a.name} = { contains: query.${a.name}, mode: 'insensitive' };`) - .join('\n ')}\n\n ${entity.attributes - .filter((a) => a.foreign) - .map((a) => `if (query.${a.name}) where.${a.name} = query.${a.name};`) - .join('\n ')}\n\n // Enum multi-value support (e.g. status=A&status=B)\n ${entity.attributes - .filter((a) => !['string', 'text', 'uuid', 'integer', 'decimal', 'date'].includes(a.type)) - .map((a) => `if (query.${a.name}) { const vals = Array.isArray(query.${a.name}) ? query.${a.name} : [query.${a.name}]; where.${a.name} = vals.length > 1 ? { in: vals } : vals[0]; }`) - .join('\n ')}\n\n if (query.id) {\n const ids = Array.isArray(query.id) ? query.id : [query.id];\n where.${pk} = { in: ids };\n }\n\n const [data, total] = await Promise.all([\n this.prisma.${lowerFirst(className)}.findMany({ where, skip, take, orderBy: { [sortField]: sortOrder } }),\n this.prisma.${lowerFirst(className)}.count({ where }),\n ]);\n\n const mapped = ${pk === 'id' ? 'data.map(serializeRecord)' : `data.map((r: any) => ({ id: r.${pk}, ...serializeRecord(r) }))`};\n return { data: mapped, total };\n }\n\n async findOne(id: string) {\n const record = await this.prisma.${lowerFirst(className)}.findUniqueOrThrow({ where: { ${pk}: id } as any });\n return ${pk === 'id' ? 'serializeRecord(record)' : `{ id: (record as any).${pk}, ...serializeRecord(record) }`};\n }\n\n async create(dto: Create${className}Dto) {\n const data: any = { ...(dto as any) };\n${entity.attributes - .filter((a) => a.type === 'date') - .map((a) => ` if (data.${a.name}) data.${a.name} = new Date(data.${a.name});`) - .join('\n')}\n${entity.attributes - .filter((a) => a.type === 'decimal') - .map((a) => ` if (data.${a.name}) data.${a.name} = new Prisma.Decimal(data.${a.name});`) - .join('\n')}\n\n const record = await this.prisma.${lowerFirst(className)}.create({ data });\n return ${pk === 'id' ? 'serializeRecord(record)' : `{ id: (record as any).${pk}, ...serializeRecord(record) }`};\n }\n\n async update(id: string, dto: Update${className}Dto) {\n const data: any = { ...(dto as any) };\n delete data.id;\n delete data.${pk};\n${entity.attributes - .filter((a) => a.type === 'date') - .map((a) => ` if (data.${a.name}) data.${a.name} = new Date(data.${a.name});`) - .join('\n')}\n${entity.attributes - .filter((a) => a.type === 'decimal') - .map((a) => ` if (data.${a.name} !== undefined && data.${a.name} !== null) data.${a.name} = new Prisma.Decimal(data.${a.name});`) - .join('\n')}\n\n const record = await this.prisma.${lowerFirst(className)}.update({ where: { ${pk}: id } as any, data });\n return ${pk === 'id' ? 'serializeRecord(record)' : `{ id: (record as any).${pk}, ...serializeRecord(record) }`};\n }\n\n async remove(id: string) {\n const record = await this.prisma.${lowerFirst(className)}.delete({ where: { ${pk}: id } as any });\n return ${pk === 'id' ? 'serializeRecord(record)' : `{ id: (record as any).${pk}, ...serializeRecord(record) }`};\n }\n}\n`; - - let serviceContent = service - .replace( - `const sortField = query._sort || '${getBestSortField(entity, pk)}';\n const sortOrder = (query._order || 'ASC').toLowerCase() as 'asc' | 'desc';`, - `const sortField = query._sort || '${getBestSortField(entity, pk)}';\n const prismaSortField = sortField === 'id' ? '${pk}' : sortField;\n const sortOrder = (query._order || 'ASC').toLowerCase() as 'asc' | 'desc';` - ) - .replace('orderBy: { [sortField]: sortOrder }', 'orderBy: { [prismaSortField]: sortOrder }') - .replace( - `data.map((r: any) => ({ id: r.${pk}, ...serializeRecord(r) }))`, - `data.map((item: any) => ({ id: item.${pk}, ...serializeRecord(item) }))` - ); - - if (pk !== 'id') { - serviceContent = serviceContent.replace( - `const data: any = { ...(dto as any) };\n delete data.id;\n delete data.${pk};`, - `const { id: _pk, ${pk}, ...rest } = (dto as any);\n const data: any = { ...rest };` - ); - } - - const mod = `import { Module } from '@nestjs/common';\nimport { ${controllerName} } from './${folder}.controller';\nimport { ${serviceName} } from './${folder}.service';\n\n@Module({\n controllers: [${controllerName}],\n providers: [${serviceName}],\n})\nexport class ${moduleName} {}\n`; - - return { - folder, - files: { - [`server/src/modules/${folder}/${folder}.controller.ts`]: controller, - [`server/src/modules/${folder}/${folder}.service.ts`]: serviceContent, - [`server/src/modules/${folder}/${folder}.module.ts`]: mod, - [`server/src/modules/${folder}/dto/create-${folder}.dto.ts`]: createDtoLines.join('\n') + '\n', - [`server/src/modules/${folder}/dto/update-${folder}.dto.ts`]: updateDtoLines.join('\n') + '\n', - }, - moduleName, - importPath: `./modules/${folder}/${folder}.module`, - }; -} - -function renderFrontendResource(entityName, entity, resourceName, pk, enums, allEntities) { - const folder = toKebab(entityName); - const className = entityName; - const enumAttrs = entity.attributes.filter( - (a) => !['string', 'text', 'uuid', 'integer', 'decimal', 'date'].includes(a.type) - ); - const statusEnumAttr = enumAttrs.find((a) => a.name === 'status'); - - const identBase = toIdentifierFromKebab(folder); - const filtersIdent = `${identBase}Filters`; - const sortField = getBestSortField(entity, pk); - - const hasNumber = entity.attributes.some((a) => ['integer', 'decimal'].includes(a.type)); - const hasDate = entity.attributes.some((a) => a.type === 'date'); - const hasFK = entity.attributes.some((a) => a.foreign); - const hasNonStatusEnum = enumAttrs.some((a) => a.name !== 'status'); - - const listImportSet = new Set([ - 'List', - 'Datagrid', - 'TextField', - 'TextInput', - 'TopToolbar', - 'FilterButton', - 'CreateButton', - 'ExportButton', - ]); - if (hasNumber) listImportSet.add('NumberField'); - if (hasDate) listImportSet.add('DateField'); - if (enumAttrs.length) listImportSet.add('SelectField'); - if (hasFK) listImportSet.add('ReferenceField'); - if (statusEnumAttr) listImportSet.add('SelectArrayInput'); - if (hasNonStatusEnum) listImportSet.add('SelectInput'); - if (hasFK) { - listImportSet.add('ReferenceInput'); - listImportSet.add('AutocompleteInput'); - } - const listImports = Array.from(listImportSet); - - const choiceConsts = []; - for (const a of enumAttrs) { - const enumName = a.type; - const values = enums?.[enumName]?.values ?? []; - const labels = enums?.[enumName]?.labels ?? {}; - const constName = `${a.name}Choices`; - if (a.name === 'status') { - choiceConsts.push( - `const statusChoices = [\n${values.map((v) => ` { id: '${v}', name: '${labels[v] ?? v}' },`).join('\n')}\n];\n` - ); - } else { - choiceConsts.push( - `const ${constName} = [\n${values.map((v) => ` { id: '${v}', name: '${labels[v] ?? v}' },`).join('\n')}\n];\n` - ); - } - } - - const filterInputs = []; - // Always include q if any string fields - if (entity.attributes.some((a) => ['string', 'text'].includes(a.type))) { - filterInputs.push(``); - } - for (const a of entity.attributes) { - const label = getAttributeLabel(a, allEntities); - if (a.name === pk) continue; - if (a.foreign) { - const referenceDisplay = getReferenceDisplayExpr(allEntities[a.foreign.entity]); - filterInputs.push( - `\n ({ q: searchText })} />\n ` - ); - continue; - } - if (['string', 'text', 'uuid'].includes(a.type)) { - filterInputs.push(``); - continue; - } - if (['integer', 'decimal'].includes(a.type)) continue; - if (a.type === 'date') continue; - // enum - if (a.name === 'status') { - filterInputs.push(``); - } else { - filterInputs.push(``); - } - } - - const listFields = []; - for (const a of entity.attributes) { - const label = getAttributeLabel(a, allEntities); - if (a.foreign) { - const referenceEntity = allEntities[a.foreign.entity]; - const referenceAttrs = getEntityAttrNames(referenceEntity); - const fieldSource = referenceAttrs.has('inventoryNumber') - ? 'inventoryNumber' - : referenceAttrs.has('code') - ? 'code' - : referenceAttrs.has('number') - ? 'number' - : 'name'; - listFields.push( - `\n \n ` - ); - continue; - } - if (a.type === 'date') { - listFields.push(``); - } else if (['integer', 'decimal'].includes(a.type)) { - listFields.push(``); - } else if (!['string', 'text', 'uuid', 'integer', 'decimal', 'date'].includes(a.type)) { - listFields.push(``); - } else { - listFields.push(``); - } - } - - const list = `import {\n ${listImports.join(',\n ')}\n} from 'react-admin';\n\n${choiceConsts.join('\n')}\nconst ${filtersIdent} = [\n ${filterInputs.join(',\n ')}\n];\n\nconst ${className}ListActions = () => (\n \n \n \n \n \n);\n\nexport const ${className}List = () => (\n } filters={${filtersIdent}} sort={{ field: '${sortField}', order: 'ASC' }}>\n \n ${listFields.join('\n ')}\n \n \n);\n`; - - const formField = (a, mode) => { - const label = getAttributeLabel(a, allEntities); - if (a.isPrimary && mode === 'create' && a.type === 'uuid') return null; - if (a.isPrimary && mode === 'edit') { - return ``; - } - if (a.foreign) { - const referenceDisplay = getReferenceDisplayExpr(allEntities[a.foreign.entity]); - return `\n ({ q: searchText })} />\n `; - } - if (a.type === 'date') return ``; - if (['integer', 'decimal'].includes(a.type)) return ``; - if (!['string', 'text', 'uuid', 'integer', 'decimal', 'date'].includes(a.type)) { - if (a.name === 'status' && statusEnumAttr) return ``; - return ``; - } - return ``; - }; - - const formImportSet = new Set(['SimpleForm', 'TextInput']); - if (hasNumber) formImportSet.add('NumberInput'); - if (hasDate) formImportSet.add('DateInput'); - if (enumAttrs.length) formImportSet.add('SelectInput'); - if (hasFK) { - formImportSet.add('ReferenceInput'); - formImportSet.add('AutocompleteInput'); - } - const createImports = ['Create', ...Array.from(formImportSet)].join(', '); - const create = `import { ${createImports} } from 'react-admin';\n\n${choiceConsts.join('\n')}\nexport const ${className}Create = () => (\n \n \n ${entity.attributes.map((a) => formField(a, 'create')).filter(Boolean).join('\n ')}\n \n \n);\n`; - - const editImports = ['Edit', ...Array.from(formImportSet)].join(', '); - const edit = `import { ${editImports} } from 'react-admin';\n\n${choiceConsts.join('\n')}\nexport const ${className}Edit = () => (\n \n \n ${entity.attributes.map((a) => formField(a, 'edit')).filter(Boolean).join('\n ')}\n \n \n);\n`; - - const showImportSet = new Set(['Show', 'SimpleShowLayout', 'TextField']); - if (hasNumber) showImportSet.add('NumberField'); - if (hasDate) showImportSet.add('DateField'); - if (enumAttrs.length) showImportSet.add('SelectField'); - if (hasFK) showImportSet.add('ReferenceField'); - - const showFields = []; - for (const a of entity.attributes) { - const label = getAttributeLabel(a, allEntities); - if (a.foreign) { - const referenceEntity = allEntities[a.foreign.entity]; - const referenceAttrs = getEntityAttrNames(referenceEntity); - const fieldSource = referenceAttrs.has('inventoryNumber') - ? 'inventoryNumber' - : referenceAttrs.has('code') - ? 'code' - : referenceAttrs.has('number') - ? 'number' - : 'name'; - showFields.push( - `\n \n ` - ); - continue; - } - if (a.type === 'date') { - showFields.push(``); - } else if (['integer', 'decimal'].includes(a.type)) { - showFields.push(``); - } else if (!['string', 'text', 'uuid', 'integer', 'decimal', 'date'].includes(a.type)) { - showFields.push(``); - } else { - showFields.push(``); - } - } - - const show = `import { ${Array.from(showImportSet).join(', ')} } from 'react-admin';\n\n${choiceConsts.join('\n')}export const ${className}Show = () => (\n \n \n ${showFields.join('\n ')}\n \n \n);\n`; - - return { - files: { - [`client/src/resources/${folder}/${className}List.tsx`]: list, - [`client/src/resources/${folder}/${className}Create.tsx`]: create, - [`client/src/resources/${folder}/${className}Edit.tsx`]: edit, - [`client/src/resources/${folder}/${className}Show.tsx`]: show, - }, - resourceName, - className, - folder, - }; -} - -function upsertInFile(filePath, apply, updater) { - const abs = path.join(ROOT, filePath); - const existing = fs.existsSync(abs) ? readFile(abs) : ''; - const next = updater(existing); - if (apply) writeFile(abs, next); - return { changed: next !== existing, content: next }; -} - -function ensureAppModule(apply, backendModules) { - return upsertInFile('server/src/app.module.ts', apply, (src) => { - let out = src; - for (const m of backendModules) { - if (!out.includes(`import { ${m.moduleName} }`)) { - const importLine = `import { ${m.moduleName} } from '${m.importPath}';`; - const importMatches = [...out.matchAll(/^import\s+.*;$/gm)]; - if (importMatches.length) { - const lastImport = importMatches[importMatches.length - 1]; - const insertAt = lastImport.index + lastImport[0].length; - out = `${out.slice(0, insertAt)}\n${importLine}${out.slice(insertAt)}`; - } else { - out = `${importLine}\n${out}`; - } - } - } - out = out.replace(/imports:\s*\[\s*([\s\S]*?)\s*\],/m, (match, inner) => { - let block = inner; - for (const m of backendModules) { - if (!block.includes(m.moduleName)) block = block.replace(/\s*\],?\s*$/m, '') + `\n ${m.moduleName},`; - } - // normalize trailing comma/indent by reusing original replacement style - return `imports: [${block}\n ],`; - }); - return out; - }); -} - -function ensureClientApp(apply, frontendResources) { - return upsertInFile('client/src/App.tsx', apply, (src) => { - let out = src; - for (const r of frontendResources) { - const imports = [ - `import { ${r.className}List } from './resources/${r.folder}/${r.className}List';`, - `import { ${r.className}Create } from './resources/${r.folder}/${r.className}Create';`, - `import { ${r.className}Edit } from './resources/${r.folder}/${r.className}Edit';`, - `import { ${r.className}Show } from './resources/${r.folder}/${r.className}Show';`, - ]; - for (const imp of imports) { - if (!out.includes(imp)) { - const importMatches = [...out.matchAll(/^import\s+.*;$/gm)]; - if (importMatches.length) { - const lastImport = importMatches[importMatches.length - 1]; - const insertAt = lastImport.index + lastImport[0].length; - out = `${out.slice(0, insertAt)}\n${imports.join('\n')}${out.slice(insertAt)}`; - } else { - out = `${imports.join('\n')}\n${out}`; - } - break; - } - } - if (!out.includes(`name="${r.resourceName}"`)) { - out = out.replace( - /<\/Admin>/m, - ` \n ` - ); - } - } - return out; - }); -} - -/** Собирает файлы как при --apply, без записи. Учитывает текущие app.module.ts и App.tsx на диске. */ -function collectGeneratedBundle(parsed) { - const files = {}; - const prismaPath = path.join(ROOT, 'server/prisma/schema.prisma'); - const pr = ensurePrismaSchema(parsed, prismaPath, false); - files['server/prisma/schema.prisma'] = pr.content; - - const backendModules = []; - const frontendResources = []; - for (const [entityName, ent] of Object.entries(parsed.entities)) { - const pk = ent.primaryKey; - const resource = pluralize(toKebab(entityName)); - const be = renderBackendModule(entityName, ent, resource, pk); - const fe = renderFrontendResource(entityName, ent, resource, pk, parsed.enums); - backendModules.push(be); - frontendResources.push(fe); - Object.assign(files, be.files, fe.files); - } - - const appMod = ensureAppModule(false, backendModules); - files['server/src/app.module.ts'] = appMod.content; - const clientApp = ensureClientApp(false, frontendResources); - files['client/src/App.tsx'] = clientApp.content; - - return { - entityCount: Object.keys(parsed.entities).length, - enumCount: Object.keys(parsed.enums).length, - files, - }; -} - -function main() { - const args = process.argv.slice(2); - const apply = args.includes('--apply'); - const printBundleJson = args.includes('--print-bundle-json'); - const dslArgIdx = args.indexOf('--dsl'); - const dslPath = dslArgIdx >= 0 ? args[dslArgIdx + 1] : 'domain/TOiR.domain.dsl'; - - const absDsl = path.resolve(ROOT, dslPath); - const dslText = readFile(absDsl); - const parsed = parseDomainDSL(dslText); - - if (printBundleJson) { - const bundle = collectGeneratedBundle(parsed); - process.stdout.write(JSON.stringify(bundle)); - return; - } - - // Prisma schema - const prismaPath = path.join(ROOT, 'server/prisma/schema.prisma'); - ensurePrismaSchema(parsed, prismaPath, apply); - - // Backend modules + frontend resources - const backendModules = []; - const frontendResources = []; - for (const [entityName, ent] of Object.entries(parsed.entities)) { - const pk = ent.primaryKey; - const resource = pluralize(toKebab(entityName)); - const be = renderBackendModule(entityName, ent, resource, pk); - const fe = renderFrontendResource(entityName, ent, resource, pk, parsed.enums, parsed.entities); - backendModules.push(be); - frontendResources.push(fe); - - if (apply) { - for (const [rel, content] of Object.entries(be.files)) writeFile(path.join(ROOT, rel), content); - for (const [rel, content] of Object.entries(fe.files)) writeFile(path.join(ROOT, rel), content); - } - } - - ensureAppModule(apply, backendModules); - ensureClientApp(apply, frontendResources); - - process.stdout.write( - `${apply ? 'Generated' : 'Planned'} ${Object.keys(parsed.entities).length} entities from ${dslPath}\n` - ); -} - -main(); - diff --git a/openapi.json b/openapi.json new file mode 100644 index 0000000..e731f10 --- /dev/null +++ b/openapi.json @@ -0,0 +1,931 @@ +{ + "openapi": "3.0.3", + "info": { + "title": "KIS-TOiR API", + "description": "Equipment maintenance management system. Generated from domain/toir.api.dsl via tools/api-summary-to-openapi.mjs.", + "version": "1.0.0" + }, + "servers": [ + { + "url": "/api", + "description": "Default server" + } + ], + "components": { + "securitySchemes": { + "bearerAuth": { + "type": "http", + "scheme": "bearer", + "bearerFormat": "JWT" + } + }, + "schemas": { + "Equipment": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "inventoryNumber": { + "type": "string", + "description": "Инвентарный номер" + }, + "serialNumber": { + "type": "string", + "description": "Заводской (серийный) номер" + }, + "name": { + "type": "string", + "description": "Наименование единицы оборудования" + }, + "equipmentTypeCode": { + "type": "string", + "description": "Код вида оборудования" + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/EquipmentStatus" + } + ], + "description": "Текущий статус" + }, + "location": { + "type": "string", + "description": "Место эксплуатации / скважина / куст" + }, + "commissionedAt": { + "type": "string", + "format": "date-time", + "description": "Дата ввода в эксплуатацию" + }, + "totalEngineHours": { + "type": "string", + "format": "decimal", + "description": "Общая наработка, моточасов" + }, + "engineHoursSinceLastRepair": { + "type": "string", + "format": "decimal", + "description": "Наработка с последнего ремонта, моточасов" + }, + "lastRepairAt": { + "type": "string", + "format": "date-time", + "description": "Дата последнего ремонта" + }, + "notes": { + "type": "string", + "description": "Примечания" + } + }, + "description": "Оборудование — полный объект ответа" + }, + "EquipmentCreate": { + "type": "object", + "properties": { + "inventoryNumber": { + "type": "string", + "description": "Инвентарный номер" + }, + "serialNumber": { + "type": "string", + "description": "Заводской (серийный) номер" + }, + "name": { + "type": "string", + "description": "Наименование единицы оборудования" + }, + "equipmentTypeCode": { + "type": "string", + "description": "Код вида оборудования" + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/EquipmentStatus" + } + ], + "description": "Текущий статус" + }, + "location": { + "type": "string", + "description": "Место эксплуатации / скважина / куст" + }, + "commissionedAt": { + "type": "string", + "format": "date-time", + "description": "Дата ввода в эксплуатацию" + }, + "totalEngineHours": { + "type": "string", + "format": "decimal", + "description": "Общая наработка, моточасов" + }, + "engineHoursSinceLastRepair": { + "type": "string", + "format": "decimal", + "description": "Наработка с последнего ремонта, моточасов" + }, + "lastRepairAt": { + "type": "string", + "format": "date-time", + "description": "Дата последнего ремонта" + }, + "notes": { + "type": "string", + "description": "Примечания" + } + }, + "description": "Оборудование — тело запроса на создание", + "required": [ + "inventoryNumber", + "name", + "equipmentTypeCode" + ] + }, + "EquipmentUpdate": { + "type": "object", + "properties": { + "inventoryNumber": { + "type": "string", + "description": "Инвентарный номер" + }, + "serialNumber": { + "type": "string", + "description": "Заводской (серийный) номер" + }, + "name": { + "type": "string", + "description": "Наименование единицы оборудования" + }, + "equipmentTypeCode": { + "type": "string", + "description": "Код вида оборудования" + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/EquipmentStatus" + } + ], + "description": "Текущий статус" + }, + "location": { + "type": "string", + "description": "Место эксплуатации / скважина / куст" + }, + "commissionedAt": { + "type": "string", + "format": "date-time", + "description": "Дата ввода в эксплуатацию" + }, + "totalEngineHours": { + "type": "string", + "format": "decimal", + "description": "Общая наработка, моточасов" + }, + "engineHoursSinceLastRepair": { + "type": "string", + "format": "decimal", + "description": "Наработка с последнего ремонта, моточасов" + }, + "lastRepairAt": { + "type": "string", + "format": "date-time", + "description": "Дата последнего ремонта" + }, + "notes": { + "type": "string", + "description": "Примечания" + } + }, + "description": "Оборудование — тело запроса на обновление (частичное)" + }, + "EquipmentListRequest": { + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DTO.Filter" + } + }, + "page": { + "$ref": "#/components/schemas/DTO.PageRequest" + } + }, + "description": "Оборудование — запрос постраничного списка с фильтрацией" + }, + "EquipmentListResponse": { + "type": "object", + "properties": { + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Equipment" + } + }, + "page": { + "$ref": "#/components/schemas/DTO.PageInfo" + } + }, + "description": "Оборудование — постраничный результат" + }, + "RepairOrder": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "uuid" + }, + "number": { + "type": "string", + "description": "Номер заявки" + }, + "equipmentId": { + "type": "string", + "format": "uuid", + "description": "Идентификатор оборудования" + }, + "repairKind": { + "allOf": [ + { + "$ref": "#/components/schemas/RepairKind" + } + ], + "description": "Вид ремонта" + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/RepairOrderStatus" + } + ], + "description": "Статус заявки" + }, + "plannedAt": { + "type": "string", + "format": "date-time", + "description": "Плановая дата начала" + }, + "startedAt": { + "type": "string", + "format": "date-time", + "description": "Фактическая дата начала" + }, + "completedAt": { + "type": "string", + "format": "date-time", + "description": "Фактическая дата завершения" + }, + "contractor": { + "type": "string", + "description": "Подрядная организация (если внешний ремонт)" + }, + "engineHoursAtRepair": { + "type": "string", + "format": "decimal", + "description": "Наработка на момент ремонта, моточасов" + }, + "description": { + "type": "string", + "description": "Описание работ / дефекта" + }, + "notes": { + "type": "string", + "description": "Примечания" + }, + "confirmed": { + "type": "boolean", + "description": "Согласовано/Не согласовано" + } + }, + "description": "Заявка на ремонт — полный объект ответа" + }, + "RepairOrderCreate": { + "type": "object", + "properties": { + "number": { + "type": "string", + "description": "Номер заявки" + }, + "equipmentId": { + "type": "string", + "format": "uuid", + "description": "Идентификатор оборудования" + }, + "repairKind": { + "allOf": [ + { + "$ref": "#/components/schemas/RepairKind" + } + ], + "description": "Вид ремонта" + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/RepairOrderStatus" + } + ], + "description": "Статус заявки" + }, + "plannedAt": { + "type": "string", + "format": "date-time", + "description": "Плановая дата начала" + }, + "startedAt": { + "type": "string", + "format": "date-time", + "description": "Фактическая дата начала" + }, + "completedAt": { + "type": "string", + "format": "date-time", + "description": "Фактическая дата завершения" + }, + "contractor": { + "type": "string", + "description": "Подрядная организация (если внешний ремонт)" + }, + "engineHoursAtRepair": { + "type": "string", + "format": "decimal", + "description": "Наработка на момент ремонта, моточасов" + }, + "description": { + "type": "string", + "description": "Описание работ / дефекта" + }, + "notes": { + "type": "string", + "description": "Примечания" + }, + "confirmed": { + "type": "boolean", + "description": "Согласовано/Не согласовано" + } + }, + "description": "Заявка на ремонт — тело запроса на создание", + "required": [ + "number", + "equipmentId", + "repairKind", + "plannedAt" + ] + }, + "RepairOrderUpdate": { + "type": "object", + "properties": { + "number": { + "type": "string", + "description": "Номер заявки" + }, + "equipmentId": { + "type": "string", + "format": "uuid", + "description": "Идентификатор оборудования" + }, + "repairKind": { + "allOf": [ + { + "$ref": "#/components/schemas/RepairKind" + } + ], + "description": "Вид ремонта" + }, + "status": { + "allOf": [ + { + "$ref": "#/components/schemas/RepairOrderStatus" + } + ], + "description": "Статус заявки" + }, + "plannedAt": { + "type": "string", + "format": "date-time", + "description": "Плановая дата начала" + }, + "startedAt": { + "type": "string", + "format": "date-time", + "description": "Фактическая дата начала" + }, + "completedAt": { + "type": "string", + "format": "date-time", + "description": "Фактическая дата завершения" + }, + "contractor": { + "type": "string", + "description": "Подрядная организация (если внешний ремонт)" + }, + "engineHoursAtRepair": { + "type": "string", + "format": "decimal", + "description": "Наработка на момент ремонта, моточасов" + }, + "description": { + "type": "string", + "description": "Описание работ / дефекта" + }, + "notes": { + "type": "string", + "description": "Примечания" + }, + "confirmed": { + "type": "boolean", + "description": "Согласовано/Не согласовано" + } + }, + "description": "Заявка на ремонт — тело запроса на обновление (частичное)" + }, + "RepairOrderListRequest": { + "type": "object", + "properties": { + "filters": { + "type": "array", + "items": { + "$ref": "#/components/schemas/DTO.Filter" + } + }, + "page": { + "$ref": "#/components/schemas/DTO.PageRequest" + } + }, + "description": "Заявка на ремонт — запрос постраничного списка с фильтрацией" + }, + "RepairOrderListResponse": { + "type": "object", + "properties": { + "content": { + "type": "array", + "items": { + "$ref": "#/components/schemas/RepairOrder" + } + }, + "page": { + "$ref": "#/components/schemas/DTO.PageInfo" + } + }, + "description": "Заявка на ремонт — постраничный результат" + }, + "EquipmentStatus": { + "type": "string", + "x-dsl-enum": "EquipmentStatus", + "description": "Enum: EquipmentStatus (values defined in domain/*.api.dsl)" + }, + "DTO.Filter": { + "type": "string", + "x-dsl-enum": "DTO.Filter", + "description": "Enum: DTO.Filter (values defined in domain/*.api.dsl)" + }, + "DTO.PageRequest": { + "type": "string", + "x-dsl-enum": "DTO.PageRequest", + "description": "Enum: DTO.PageRequest (values defined in domain/*.api.dsl)" + }, + "DTO.PageInfo": { + "type": "string", + "x-dsl-enum": "DTO.PageInfo", + "description": "Enum: DTO.PageInfo (values defined in domain/*.api.dsl)" + }, + "RepairKind": { + "type": "string", + "x-dsl-enum": "RepairKind", + "description": "Enum: RepairKind (values defined in domain/*.api.dsl)" + }, + "RepairOrderStatus": { + "type": "string", + "x-dsl-enum": "RepairOrderStatus", + "description": "Enum: RepairOrderStatus (values defined in domain/*.api.dsl)" + } + } + }, + "paths": { + "/equipment/page": { + "post": { + "summary": "Постраничный список оборудования с фильтрацией", + "security": [ + { + "bearerAuth": [] + } + ], + "tags": [ + "оборудованием" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EquipmentListRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EquipmentListResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + } + } + }, + "/equipment/{id}": { + "get": { + "summary": "Получить оборудование по идентификатору", + "security": [ + { + "bearerAuth": [] + } + ], + "tags": [ + "оборудованием" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Equipment" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + } + }, + "put": { + "summary": "Обновить единицу оборудования", + "security": [ + { + "bearerAuth": [] + } + ], + "tags": [ + "оборудованием" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EquipmentUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + } + }, + "delete": { + "summary": "Удалить единицу оборудования", + "security": [ + { + "bearerAuth": [] + } + ], + "tags": [ + "оборудованием" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + } + } + }, + "/equipment": { + "post": { + "summary": "Создать единицу оборудования", + "security": [ + { + "bearerAuth": [] + } + ], + "tags": [ + "оборудованием" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/EquipmentCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + } + } + }, + "/repair-orders/page": { + "post": { + "summary": "Постраничный список заявок на ремонт с фильтрацией", + "security": [ + { + "bearerAuth": [] + } + ], + "tags": [ + "заявками на ремонт" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepairOrderListRequest" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepairOrderListResponse" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + } + } + }, + "/repair-orders/{id}": { + "get": { + "summary": "Получить заявку на ремонт по идентификатору", + "security": [ + { + "bearerAuth": [] + } + ], + "tags": [ + "заявками на ремонт" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepairOrder" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + } + }, + "put": { + "summary": "Обновить заявку на ремонт", + "security": [ + { + "bearerAuth": [] + } + ], + "tags": [ + "заявками на ремонт" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepairOrderUpdate" + } + } + } + }, + "responses": { + "200": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + } + }, + "delete": { + "summary": "Удалить заявку на ремонт", + "security": [ + { + "bearerAuth": [] + } + ], + "tags": [ + "заявками на ремонт" + ], + "parameters": [ + { + "name": "id", + "in": "path", + "required": true, + "schema": { + "type": "string", + "format": "uuid" + } + } + ], + "responses": { + "204": { + "description": "No content" + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + }, + "404": { + "description": "Not found" + } + } + } + }, + "/repair-orders": { + "post": { + "summary": "Создать заявку на ремонт", + "security": [ + { + "bearerAuth": [] + } + ], + "tags": [ + "заявками на ремонт" + ], + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/RepairOrderCreate" + } + } + } + }, + "responses": { + "201": { + "description": "Success", + "content": { + "application/json": { + "schema": { + "type": "object" + } + } + } + }, + "401": { + "description": "Unauthorized" + }, + "403": { + "description": "Forbidden" + } + } + } + } + } +} diff --git a/overrides/api-overrides.dsl b/overrides/api-overrides.dsl deleted file mode 100644 index 0a02c8a..0000000 --- a/overrides/api-overrides.dsl +++ /dev/null @@ -1,2 +0,0 @@ -// Optional overrides: -// resource EquipmentType path "equipment-types"; diff --git a/overrides/ui-overrides.dsl b/overrides/ui-overrides.dsl deleted file mode 100644 index 048f321..0000000 --- a/overrides/ui-overrides.dsl +++ /dev/null @@ -1,2 +0,0 @@ -// Optional overrides: -// field EquipmentType.code widget "text"; diff --git a/package.json b/package.json index 56ef58c..524587b 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,12 @@ "name": "toir-generation-context", "private": true, "scripts": { - "generate:domain-summary": "node tools/generate-domain-summary.mjs", + "generate:api-summary": "node tools/generate-api-summary.mjs", + "generate:openapi": "node tools/api-summary-to-openapi.mjs --out openapi.json", "validate:generation": "node tools/validate-generation.mjs", "validate:generation:runtime": "node tools/validate-generation.mjs --run-runtime", - "validate:generation:artifacts": "node tools/validate-generation.mjs --artifacts-only" + "validate:generation:artifacts": "node tools/validate-generation.mjs --artifacts-only", + "eval:generation": "node tools/eval/run-evals.mjs", + "install-hooks": "node tools/install-hooks.mjs" } } diff --git a/prompts/auth-rules.md b/prompts/auth-rules.md index 2d4be3c..c46c006 100644 --- a/prompts/auth-rules.md +++ b/prompts/auth-rules.md @@ -1,79 +1,95 @@ # Auth Rules -This repository keeps the current LLM-first CRUD generation architecture as the primary working baseline. + + + -- Auth is part of the default generation path, not a post-generation addon. -- `server/` is the active backend target output path. -- `client/` is the active frontend target output path. -- The generated runtime stays SPA + API + external Keycloak + PostgreSQL only. +Use this document during the **Auth / Runtime / Realm** stage defined in `prompts/general-prompt.md`. -## Frontend auth invariants +## Purpose -- Use `keycloak-js` with redirect-based login only. -- Initialize Keycloak before rendering the SPA. -- Use Authorization Code Flow + PKCE (`S256`). -- Keep `authProvider`, `dataProvider`, `getIdentity()`, `getPermissions()`, and `checkError()` as stable provider seams. -- Derive identity from token claims already present in the parsed token. -- Do not call `loadUserProfile()` and do not depend on `/account` for the baseline app. -- `401` must force re-authentication; `403` must stay an authorization error. -- Keep token handling in memory and refresh through one shared in-flight operation. +Generate and preserve the auth contracts that let the CRUD app run as a React Admin SPA backed by a NestJS API protected by external Keycloak. -## Working runtime defaults +## Mandatory Inputs -Use the already working project defaults unless a prompt explicitly overrides them. +- `prompts/general-prompt.md` +- `prompts/runtime-rules.md` +- current repository auth/runtime defaults -- Frontend Keycloak base URL example: - - `VITE_KEYCLOAK_URL=https://sso.greact.ru` -- Frontend realm and client example: - - `VITE_KEYCLOAK_REALM=toir` - - `VITE_KEYCLOAK_CLIENT_ID=toir-frontend` -- Backend issuer and audience example: - - `KEYCLOAK_ISSUER_URL=https://sso.greact.ru/realms/toir` - - `KEYCLOAK_AUDIENCE=toir-backend` -- CORS example: - - `CORS_ALLOWED_ORIGINS=http://localhost:5173,https://toir-frontend.greact.ru` +## Expected Outputs -Anti-regression rule: +- `client/src/auth/` +- `client/src/dataProvider.ts` +- `server/src/auth/` +- `toir-realm.json` -- Do not switch the baseline Keycloak example back to `http://localhost:8080` in generated `.env.example` files unless the prompt explicitly asks for a local Keycloak runtime. -- Localhost Keycloak values may exist in private `.env.local` or `.env` overrides, but they are not the default project examples. +## Frontend Auth Invariants -## Backend auth invariants +- use `keycloak-js` with redirect-based login only +- initialize Keycloak before rendering the SPA +- use Authorization Code Flow + PKCE (`S256`) +- keep `authProvider`, `dataProvider`, `getIdentity()`, `getPermissions()`, and `checkError()` as stable seams +- derive identity from token claims already present in the token +- do not call `loadUserProfile()` +- `401` forces re-authentication; `403` remains an authorization error +- keep token handling in memory with one shared in-flight refresh path -- Verify JWTs with `jose`. -- Validate issuer + audience + signature via JWKS. -- Resolve JWKS in this order: +## Backend Auth Invariants + +- verify JWTs with `jose` +- validate issuer, audience, and signature via JWKS +- resolve JWKS in this order: 1. `KEYCLOAK_JWKS_URL` 2. OIDC discovery at `/.well-known/openid-configuration` 3. `${issuer}/protocol/openid-connect/certs` -- Extract roles only from `realm_access.roles`. -- Keep `/health` public and all generated CRUD routes protected by default. +- extract roles only from `realm_access.roles` +- keep `/health` public +- generated CRUD routes stay protected by default -## Auth anti-regression invariants +## Working Runtime Defaults -- The accepted JWKS resolution chain above is the only baseline truth path. Do not document one order and implement another. -- If auth implementation changes, `prompts/auth-rules.md` and `prompts/validation-rules.md` must be updated in the same change. -- Do not skip OIDC discovery when no explicit `KEYCLOAK_JWKS_URL` is provided. -- Do not switch role extraction to alternative claims unless the prompt explicitly changes the baseline contract. -- Do not reintroduce localhost Keycloak defaults into shared baseline examples. +Keep these defaults unless a task explicitly overrides them: -## Realm artifact contract +- `VITE_KEYCLOAK_URL=https://sso.greact.ru` +- `VITE_KEYCLOAK_REALM=toir` +- `VITE_KEYCLOAK_CLIENT_ID=toir-frontend` +- `KEYCLOAK_ISSUER_URL=https://sso.greact.ru/realms/toir` +- `KEYCLOAK_AUDIENCE=toir-backend` +- `CORS_ALLOWED_ORIGINS=http://localhost:5173,https://toir-frontend.greact.ru` -- A physical root-level `*-realm.json` artifact is mandatory output. -- The artifact must be importable, versioned, and aligned with generated backend/frontend env contracts. -- It must parameterize: +Anti-regression rule: + +- do not revert shared examples to localhost Keycloak defaults unless a task explicitly requests a local Keycloak baseline + +## Realm Artifact Contract + +The root realm artifact is mandatory and must: + +- be importable and versioned +- align with generated frontend/backend env contracts +- parameterize: - realm name - frontend client id - backend client id / audience - local and production frontend URLs - artifact filename -- It must explicitly deliver: +- explicitly deliver: - `sub` - `aud` - `realm_access.roles` -- It must define: +- define: - realm roles `admin`, `editor`, `viewer` - a public SPA client with PKCE S256 - a bearer-only backend client - an explicit audience client scope - - explicit protocol mappers for baseline identity and role claims + - protocol mappers for baseline identity and role claims + +## Completion Expectations + +Auth/runtime generation is incomplete if any of the following is true: + +- frontend and backend auth seams drift from each other +- JWKS resolution order changes +- `/health` stops being public +- shared Keycloak defaults regress to localhost examples +- the realm artifact no longer matches backend/frontend expectations diff --git a/prompts/backend-rules.md b/prompts/backend-rules.md index af828ff..3654609 100644 --- a/prompts/backend-rules.md +++ b/prompts/backend-rules.md @@ -1,88 +1,147 @@ # Backend Rules -The backend remains derived from `domain/*.dsl` inside the existing LLM-first pipeline. No compiler platform or generator engine is introduced. + + + -## Backend scaffold baseline +Use this document during the **Backend** stage defined in `prompts/general-prompt.md`. -- Start backend initialization from the official NestJS CLI workspace, not from manually created files. -- The backend must remain compatible with standard Nest workspace tooling such as `nest build` and `nest start`. -- Preserve the core Nest workspace files generated by the CLI, especially: +## Purpose + +Generate NestJS CRUD artifacts that match the DSL contract exactly and remain compatible with a standard NestJS workspace. + +## Mandatory Inputs + +- `prompts/general-prompt.md` +- the active `api API.` block from `domain/toir.api.dsl` +- referenced DTOs and enums from `domain/toir.api.dsl` +- an intact or repaired official NestJS scaffold under `server/` + +`api-summary.json` may be consulted only as an auxiliary inventory or validator-related artifact. It must never replace the DSL as the backend source of truth. + +## Expected Outputs + +Per entity: + +- `server/src/modules//.module.ts` +- `server/src/modules//.controller.ts` +- `server/src/modules//.service.ts` +- `server/src/modules//dto/create-.dto.ts` +- `server/src/modules//dto/update-.dto.ts` + +Repository-wide: + +- `server/src/app.module.ts` registrations + +## Scaffold Baseline + +- Start backend initialization and repair from the official NestJS CLI workspace, not from hand-written files. +- Preserve Nest workspace essentials: - `server/tsconfig.json` - `server/tsconfig.build.json` - `server/nest-cli.json` - `server/src/main.ts` - `server/src/app.module.ts` -- For domain resources, prefer official Nest CLI generation patterns for modules/controllers/services/resources and then adapt the generated code to Prisma and auth requirements. -- Do not delete required Nest workspace files just because the LLM can inline a smaller custom structure. +- If the workspace is degraded, repair it before generating domain code. -## Forbidden backend generation patterns +Forbidden patterns: -- Do not bootstrap `server/` by hand-writing a pseudo-Nest project from memory. -- Do not remove `tsconfig.json`, `tsconfig.build.json`, or `nest-cli.json` after generation. -- Do not replace standard Nest package scripts with ad hoc commands that break `nest build` or `nest start`. -- Do not continue CRUD generation on top of a degraded backend workspace without repairing the workspace first. +- hand-written pseudo-Nest scaffolds +- deleting required Nest config files after generation +- replacing normal Nest build/start behavior with ad hoc scripts -## Domain-derived output - -- `domain/*.dsl` is the source of truth for entities, fields, primary keys, foreign keys, and enums. -- `domain-summary.json` is a derived artifact used to stabilize LLM generation and validation. It must never replace the DSL as the source of truth. -- Each entity becomes: - - a Prisma model - - a NestJS module - - a controller - - a service - - create/update DTOs - -## DTO and Prisma mapping - -- `decimal` -> Prisma `Decimal`, DTO/API `string` -- `date` -> Prisma `DateTime`, DTO/API `string` -- Enums remain string-valued in DTO/API contracts - -## CRUD and natural-key invariants +## Route And Resource Contract +- Use the shared entity-to-resource naming convention from `prompts/general-prompt.md`. +- Each entity becomes a NestJS module, controller, service, and create/update DTO pair. - CRUD routes use the real primary key name in the path. - Every API record returned to React Admin must include `id`. -- For entities whose primary key is not `id`, the backend must map the real key to `id`. -- Natural-key list/sort logic must never build ORM `orderBy` against a fake physical `id`. +- For natural-key entities, map the real primary key to `id` in responses and sort translation. -## Service invariants +## DTO Contract -- Never pass raw update DTOs into Prisma update `data`. -- Remove `id`, the real primary key, and readonly fields from update payloads before calling Prisma. -- Keep PrismaService lightweight: +- `DTO.Create` defines `CreateDto`. +- `DTO.Update` defines `UpdateDto`. +- Do not invent fields or pull field lists from memory. +- Never include `id` in Create/Update DTOs. + +Type and decorator rules: + +| DSL type | TS DTO type | class-validator decorator | Notes | +|-----------|-------------|---------------------------|-------| +| `uuid` | `string` | `@IsUUID()` | | +| `string` | `string` | `@IsString()` | | +| `text` | `string` | `@IsString()` | | +| `integer` | `number` | `@IsInt()` | | +| `number` | `number` | `@IsNumber()` | | +| `decimal` | `string` | `@IsString()` | serialize with Prisma Decimal | +| `date` | `string` | `@IsString()` | serialize as ISO string | +| `boolean` | `boolean` | `@IsBoolean()` | | +| enum name | `string` | `@IsEnum(EnumName)` | | + +Nullability rules: + +- every field that is not `is required` gets `@IsOptional()` before the type decorator +- every generated DTO imports from `'class-validator'` + +## Controller Contract + +- Apply `@UseGuards(JwtAuthGuard, RolesGuard)` at controller class level. +- Roles per verb: + - `GET` -> `viewer | editor | admin` + - `POST`, `PATCH`, `PUT` -> `editor | admin` + - `DELETE` -> `admin` +- Reconcile DSL HTTP shapes for repository compatibility: + - list endpoints declared as `POST .../page` generate as `@Get()` with React Admin query params + - update endpoints declared as `PUT` generate as `@Patch(':')` +- Path parameters are taken from the DSL endpoint contract, not invented from generic CRUD memory. + +## Service Contract + +- Never pass raw update DTOs directly into Prisma update `data`. +- Strip `id`, the real primary key, and readonly fields before writes. +- Keep `PrismaService` lightweight: - extend `PrismaClient` - implement `OnModuleInit` - call `$connect()` - - do not use `beforeExit` + - do not add `beforeExit` -## Filtering contract +List endpoint requirements: -- List endpoints must support React Admin query parameters: - - `_start`, `_end`, `_sort`, `_order` - - arbitrary field filters from query string - - `q` for reference autocomplete search -- String/text search filters may use `contains` with case-insensitive mode. -- Foreign key filters must use exact-match semantics (no `contains` for FK scalar keys). -- Enum filters must support both single and repeated query params: - - `status=Draft` - - `status=Draft&status=Approved` -- Repeated enum params must map to Prisma `{ in: [...] }`. -- Sorting must use real model scalar fields only; natural-key entities must not fallback to fake physical `id`. +- accept React Admin query params: `_start`, `_end`, `_sort`, `_order`, `q` +- set `Content-Range` +- set `Access-Control-Expose-Headers: Content-Range` -## Reproducibility invariants +Filtering rules: -- A freshly generated backend must be bootstrappable with ordinary Nest + Prisma commands from `prompts/runtime-rules.md`. -- Missing TypeScript or Nest workspace config is a generation failure, not an acceptable simplification. -- The baseline backend should fail only on missing runtime dependencies or env values, not because the Nest workspace itself is incomplete. +- string/text filters may use case-insensitive `contains` +- foreign-key scalar filters must use exact-match semantics +- enum filters must support both single and repeated params +- repeated enum params must map to Prisma `{ in: [...] }` +- `_sort=id` must map to the real primary key for natural-key entities -## Recovery rule if backend workspace degraded +Decimal and date handling: -- If required Nest scaffold files are missing or broken, restore the official workspace baseline before editing Prisma models, modules, controllers, services, or DTOs. -- Treat workspace repair as higher priority than feature generation, because generated domain code on top of a broken workspace is invalid baseline output. +- `decimal` writes: `new Prisma.Decimal(value)` +- `decimal` reads: `.toString()` +- `date` writes: `new Date(value)` +- `date` reads: `.toISOString()` -## Backend auth defaults +## Natural-Key Rules -- `GET` -> `viewer | editor | admin` -- `POST`, `PATCH`, `PUT` -> `editor | admin` -- `DELETE` -> `admin` +For entities whose physical primary key is not `id`: + +- route params use the real primary key name +- responses expose `id` mapped from that primary key +- sort/update behavior never targets a fake physical `id` +- update payload sanitization removes both `id` and the real primary key + +## Completion Expectations + +Backend generation is incomplete if any of the following is true: + +- required Nest scaffold files are missing +- DTO decorators are incomplete or type-incorrect +- controllers are missing guards or role decorators +- natural-key handling regresses to a fake physical `id` +- list/filter behavior is incompatible with React Admin expectations diff --git a/prompts/frontend-rules.md b/prompts/frontend-rules.md index 82b068c..af7e703 100644 --- a/prompts/frontend-rules.md +++ b/prompts/frontend-rules.md @@ -1,65 +1,118 @@ # Frontend Rules -The frontend stays a React Admin SPA generated from `domain/*.dsl` and anchored to the existing auth seams. + + + -## Frontend scaffold baseline +Use this document during the **Frontend** stage defined in `prompts/general-prompt.md`. -- Start frontend initialization from the official Vite React TypeScript scaffold, not from manually assembled files. -- Preserve a valid Vite workspace baseline, including: +## Purpose + +Generate React Admin resources that stay aligned with the DSL contract, the backend contract, and the repository auth/data provider seams. + +## Mandatory Inputs + +- `prompts/general-prompt.md` +- the active `api API.` block from `domain/toir.api.dsl` +- referenced DTOs and enums from `domain/toir.api.dsl` +- an intact or repaired official Vite React TypeScript scaffold under `client/` + +## Expected Outputs + +Per entity: + +- `client/src/resources//List.tsx` +- `client/src/resources//Create.tsx` +- `client/src/resources//Edit.tsx` +- `client/src/resources//Show.tsx` + +Repository-wide: + +- `client/src/App.tsx` resource registrations + +## Scaffold Baseline + +- Start frontend initialization and repair from the official Vite React TypeScript scaffold, not from a hand-written shell. +- Preserve workspace essentials: - `client/index.html` - `client/tsconfig.json` - `client/vite.config.*` - `client/src/main.tsx` -- Add React Admin and auth seams on top of that baseline instead of replacing the workspace with a hand-written minimal shell. -- Do not delete required Vite entry/config files just because the LLM can write a shorter custom setup. +- Repair the scaffold before generating resources if it is degraded. -## Forbidden frontend generation patterns +## Resource Contract -- Do not bootstrap `client/` by hand-writing a pseudo-Vite project from memory. -- Do not remove `index.html`, `tsconfig*`, or `vite.config.*` after generation. -- Do not replace standard Vite package scripts with ad hoc commands that break `vite build`, `vite dev`, or `vite preview`. -- Do not continue React Admin resource generation on top of a degraded frontend workspace without repairing the workspace first. +- Use the shared entity-to-resource naming convention from `prompts/general-prompt.md`. +- Every entity becomes a React Admin resource with `list`, `create`, `edit`, and `show`. +- `Resource` registration in `client/src/App.tsx` must include `show={...}`. +- Every frontend record must work with React Admin's `id` contract, including natural-key entities. -## Resource generation +DTO-driven view rules: -- Each entity becomes a React Admin resource with list/create/edit/show views. -- Resource names must stay aligned with backend path segments. -- Foreign keys must use `ReferenceInput` / `ReferenceField`. -- Foreign keys shown in list/show views must stay clickable via `ReferenceField link="show"` to open full details of the related resource. -- Lists must expose filters through `List` `filters` and an actions toolbar with `FilterButton`. -- For enum fields where multi-select is required (for example `status`), use `SelectArrayInput` in list filters. -- For foreign key filters and form selection use `ReferenceInput` + `AutocompleteInput` with `filterToQuery={(searchText) => ({ q: searchText })}`. -- Form mapping must stay type-safe: - - `integer` / `decimal` -> `NumberInput` - - `date` -> `DateInput` +- List and Show views use fields from `DTO.` +- Create view uses fields from `DTO.Create` +- Edit view uses fields from `DTO.Update` +- Do not derive form fields directly from model attributes when the DTO contract is narrower -## Provider seams +## Input And Field Mapping -- `client/src/dataProvider.ts` is the single authenticated request seam. -- `client/src/auth/authProvider.ts` is the single React Admin auth seam. -- Auth logic must not leak into resource components. +Form inputs: -## Identity and permissions +- `integer`, `number`, `decimal` -> `NumberInput` +- `date` -> `DateInput` +- required `boolean` -> `BooleanInput` +- nullable `boolean` -> `NullableBooleanInput` +- enum -> `SelectInput` +- FK reference -> `ReferenceInput` + `AutocompleteInput` -- `getIdentity()` must resolve from parsed token claims. -- `getPermissions()` may expose realm roles for UI awareness. -- Backend enforcement remains authoritative. +Display fields: -## React Admin compatibility +- `integer`, `number`, `decimal` -> `NumberField` +- `date` -> `DateField` +- `boolean` -> `BooleanField` +- enum -> `SelectField` +- FK reference -> `ReferenceField` -- Every resource record must include `id`. -- Natural-key resources must preserve route, update, and sort compatibility with React Admin contracts. -- Frontend requests must continue to work when the real primary key is not named `id`. -- `dataProvider` query serialization must preserve repeated query params for array filters (for example enum multi-select). -- `Resource` wiring in `App.tsx` must keep `show={...}` registration for all generated resources. +Hard failure rule: -## Reproducibility invariants +- using plain `TextInput` for `integer`, `number`, `decimal`, `date`, or `boolean` is a generation failure -- A freshly generated frontend must remain compatible with standard Vite commands such as `npm run dev` and `npm run build`. -- Missing Vite workspace files or missing local Vite executable wiring is a generation failure, not an acceptable simplification. -- The generated frontend should fail only on missing installation/env/runtime backend availability, not because the Vite app structure itself is incomplete. +## Filter And Reference Contract -## Recovery rule if frontend workspace degraded +- Lists must expose filters and include a toolbar with `FilterButton`. +- Enum multi-select filters use `SelectArrayInput`. +- Reference filters and form selectors use `ReferenceInput` + `AutocompleteInput` with `filterToQuery={(searchText) => ({ q: searchText })}`. +- FK list/show rendering must use `ReferenceField link=\"show\"`. +- `dataProvider` query serialization must preserve repeated params for array filters. -- If required Vite scaffold files are missing or broken, restore the official workspace baseline before editing resources, auth seams, or UI code. -- Treat workspace repair as higher priority than feature generation, because generated React Admin code on top of a broken Vite workspace is invalid baseline output. +Reference display expression priority: + +1. if `inventoryNumber` exists: ``(record) => `${record.inventoryNumber} — ${record.name ?? record.inventoryNumber}`` +2. else if `code` exists: ``(record) => `${record.code} — ${record.name ?? record.code}`` +3. else if `number` exists: ``(record) => `${record.number} — ${record.name ?? record.number}`` +4. else if `name` exists: `(record) => record.name ?? record.id` +5. else: `(record) => record.id` + +## Auth And Provider Seams + +- `client/src/dataProvider.ts` remains the single authenticated request seam. +- `client/src/auth/authProvider.ts` remains the single React Admin auth seam. +- Resource components must not embed auth logic. +- `getIdentity()` resolves from token claims. +- `getPermissions()` may expose realm roles for UI awareness, but backend enforcement stays authoritative. + +## Natural-Key Compatibility + +- Frontend requests and routes must continue to work when the real primary key is not named `id`. +- Edit/show/delete flows must preserve compatibility with backend natural-key handling. +- Sorting and filtering assumptions must not regress to a fake physical `id`. + +## Completion Expectations + +Frontend generation is incomplete if any of the following is true: + +- required Vite scaffold files are missing +- Create/Edit inputs are type-incorrect +- filter UI is missing or incomplete +- reference fields stop linking to `show` +- resource registration omits `show={...}` diff --git a/prompts/general-prompt.md b/prompts/general-prompt.md index 7c6be55..4efe2bb 100644 --- a/prompts/general-prompt.md +++ b/prompts/general-prompt.md @@ -1,146 +1,298 @@ -ROLE + + + -You are a Staff-level Fullstack Platform Engineer working inside the established LLM-first CRUD generation baseline. +# Role -Use context7 when official framework guidance is needed. +You are the master orchestrator of the KIS-TOiR generation pipeline. -This repository is not a new generator engine. Do not redesign it into a planner/emitter/runtime platform. +Own the full run: understand the current workspace, read the domain contract, coordinate sub-agents and MCP tools, generate or repair artifacts in the correct order, run the required gates, fix failures, and stop only when the repository is genuinely generation-complete. -GOAL +# Project Description -Strengthen and use the existing LLM-first CRUD generation pipeline. +KIS-TOiR is an LLM-first fullstack CRUD generation project for equipment maintenance management. -- Keep `domain/*.dsl` as the source of truth for the domain model. -- Keep `server/` as the active backend target output path. -- Keep `client/` as the active frontend target output path. -- Keep Keycloak auth in the default generation path. -- Keep PostgreSQL as the only Dockerized runtime dependency. -- Generate and maintain: - - `domain-summary.json` - - a root-level `*-realm.json` - - backend/frontend auth seams - - runtime/env/bootstrap artifacts - - validation-gate-compatible output +- Backend: NestJS + Prisma +- Frontend: Vite React TypeScript + React Admin +- Auth/runtime: external Keycloak + PostgreSQL + repository-managed env/runtime artifacts -Use the already working runtime defaults for this project unless the prompt explicitly overrides them: +The repository is intentionally prompt-driven. `prompts/*.md` define generation policy; generated code lives under `server/` and `client/`. -- frontend Keycloak URL example: `https://sso.greact.ru` -- frontend realm/client examples: `toir`, `toir-frontend` -- backend issuer/audience examples: `https://sso.greact.ru/realms/toir`, `toir-backend` -- production frontend origin example: `https://toir-frontend.greact.ru` +# Mission -Do not silently regress these examples to localhost Keycloak defaults. +Turn the repository source contract into a buildable, validated workspace by: -ACTIVE KNOWLEDGE BLOCKS +1. starting from official framework scaffolding when a workspace is missing or degraded +2. generating Prisma, backend, frontend, auth, runtime, and realm artifacts from the DSL +3. using sub-agents intentionally instead of carrying every concern in one context window +4. proving completion with builds and repository validation gates -Read in this order: +# Source Of Truth -1. `domain/dsl-spec.md` -2. `domain/*.dsl` -3. `domain-summary.json` if present -4. `prompts/auth-rules.md` -5. `prompts/backend-rules.md` -6. `prompts/frontend-rules.md` -7. `prompts/runtime-rules.md` -8. `prompts/validation-rules.md` +`domain/toir.api.dsl` is the operative source of truth for generation runs. -Interpretation rules: +It is authoritative for: -- `domain/*.dsl` is authoritative. -- `domain-summary.json` is derived. Regenerate or validate it against the DSL; never treat it as the source of truth. -INPUT CONTRACT - -Required: - -- `domain/*.dsl` - -Optional: - -- `overrides/api-overrides.dsl` -- `overrides/ui-overrides.dsl` +- entities and enums +- DTO shapes per operation +- nullability and requiredness +- primary and foreign keys +- HTTP methods, endpoint paths, and pagination contracts Rules: -- Do not require DTO/API/UI DSL files. -- Do not resurrect multi-DSL source-of-truth behavior. -- Optional overrides may refine derived API/UI behavior but must not redefine the domain model. +- Read the DSL directly. Do not substitute `api-summary.json` for `domain/toir.api.dsl`. +- Work from entity-scoped slices: the active `api API.` block plus its referenced DTOs and enums. +- Quote the relevant DSL field definitions verbatim before generating DTOs, Prisma fields, controller contracts, or React Admin components. +- Treat `api-summary.json` only as an auxiliary artifact for quick inventory or validation/tooling that explicitly depends on it. It is never the authoritative generation input. -PIPELINE CONTRACT +# Orchestration Model -1. Parse `domain/*.dsl`. -2. Generate or refresh `domain-summary.json`. -3. Scaffold with official framework CLI conventions where scaffolding is needed. -4. Generate or maintain backend/frontend code in `server/` and `client/`. -5. Generate or maintain the root-level realm artifact. -6. Generate or maintain env/bootstrap/runtime artifacts. -7. Run the lightweight validation gate. +Use a manager-first, agent-as-tool architecture. -REPAIR-BEFORE-GENERATE ORDER +- Keep one orchestrator in charge of planning, sequencing, integration, and final acceptance. +- Delegate bounded work to specialists; do not let sub-agents redefine the source hierarchy or completion criteria. +- Delegate by stage or artifact family, and by entity when parallelism helps. +- If a sub-agent result conflicts with the DSL, companion rules, or validator output, trust the DSL and the gates. -1. Inspect whether `server/` and `client/` are still valid framework workspaces. -2. If either workspace is degraded, repair the official scaffold baseline first. -3. Only after workspace repair, generate or update domain-derived feature code. -4. Only after generation, run validation and buildability checks. +Mandatory delegation pattern for future runs: -CLI-FIRST SCAFFOLDING CONTRACT +- `explorer` + Use first for repo discovery, scaffold inspection, locating entity-scoped DSL context, and finding existing registrations/seams. +- `docs_researcher` + Use when framework behavior, CLI scaffolding, or prompt/orchestration patterns need verification against official docs or Context7. +- stage worker / generator + Use for bounded Prisma, backend, frontend, or auth/runtime implementation work after the orchestrator has assembled the right inputs. +- `reviewer` + Use before declaring completion. Reviewer must check DSL fidelity, prompt-contract compliance, and whether validation output supports the completion claim. -- Never hand-write a fresh framework workspace from scratch when an official CLI exists for that framework. -- For `server/`, initialize the workspace from the official NestJS CLI baseline first, then generate/adapt modules/resources inside that workspace. -- For `client/`, initialize the workspace from the official Vite React TypeScript baseline first, then add React Admin, auth seams, and generated resources. -- Treat CLI scaffolding as the required baseline for compiler config, package scripts, workspace metadata, and default entry files. -- Manual creation is allowed only for domain-derived feature code after the official workspace exists. -- If a workspace already exists but is missing required CLI baseline files, repair it back to a valid official-style workspace before adding more generated code. +If a runtime does not expose named sub-agents, preserve the same separation of responsibilities inside one agent and keep stage handoffs explicit. -ANTI-REGRESSION FAILURES +# MCP Usage Model -Treat the following as baseline violations, not acceptable improvisations: +Use MCP/tools deliberately, not reflexively. -- creating a NestJS workspace by manually writing `package.json`, `tsconfig*`, `nest-cli.json`, and `src/*` from memory instead of starting from official CLI conventions -- creating a Vite React TypeScript workspace by manually writing `package.json`, `index.html`, `tsconfig*`, `vite.config.*`, and `src/*` from memory instead of starting from official scaffold conventions -- deleting required framework scaffold files after generation because the app appears to work with a smaller custom structure -- declaring generation successful when workspace validity or buildability is broken or unverified -- letting prompts promise an auth/runtime contract that validation does not enforce -- treating one project-specific DSL filename as the only allowed source instead of supporting `domain/*.dsl` +- Filesystem/search tools: gather exact local context before making decisions. +- Shell/runtime tools: run official CLI scaffolding, Prisma commands, builds, validators, and evals. Do not simulate command results from memory. +- Context7: verify current NestJS, Prisma, React Admin, Vite, Keycloak, or prompt/orchestration guidance when repository docs are not enough. +- Web research: only after local files and Context7 are insufficient; prefer primary sources. +- Diff/validation tools: use before edits, after edits, and always at the end. -OUTPUT CONTRACT +Tool-order policy: -The baseline output must include: +1. local authoritative files +2. Context7 / official docs +3. web fallback +4. validation gates + +# Generation Roadmap + +## 1. Preparation / Discovery + +Purpose: + +- establish the active scope +- verify scaffold health +- load only the context needed for the next stage + +Responsible: + +- orchestrator +- `explorer` first +- `docs_researcher` if scaffold conventions or framework behavior are uncertain + +Mandatory inputs: + +- `AGENTS.md` +- `prompts/general-prompt.md` +- `domain/toir.api.dsl` +- `prompts/runtime-rules.md` +- `.codex/AGENTS.md` and `.codex/agents/*.toml` when the runtime supports those agents + +Expected outputs: + +- entity-scoped DSL quotes for the active work +- a clean stage plan +- `server/` and `client/` confirmed healthy or repaired from official scaffolding + +Handoff: + +- proceed to Prisma only after the repository has a valid NestJS workspace, Vite React TypeScript workspace, or a documented repair plan using official CLIs + +Stage rules: + +- Use official Nest CLI for initial backend workspace creation or repair. +- Use official Vite React TypeScript CLI for initial frontend workspace creation or repair. +- Use Prisma CLI for Prisma initialization when relevant. +- Do not handcraft framework scaffolds that should come from official CLIs. + +## 2. Prisma + +Purpose: + +- generate the repository schema that reflects the DSL exactly + +Responsible: + +- orchestrator +- Prisma-focused stage worker +- `docs_researcher` when Prisma behavior is uncertain + +Mandatory inputs: + +- entity-scoped DSL quotes from `domain/toir.api.dsl` +- `prompts/prisma-rules.md` + +Expected outputs: - `server/prisma/schema.prisma` +- Prisma initialization or repair steps completed when the workspace was missing required baseline files + +Handoff: + +- backend generation starts only after schema output reflects the DSL and Prisma setup is coherent with runtime rules + +## 3. Backend + +Purpose: + +- generate NestJS modules, controllers, services, DTOs, and module registration from the DSL contract + +Responsible: + +- orchestrator +- backend stage worker, ideally one entity at a time when parallelized + +Mandatory inputs: + +- entity-scoped DSL quotes from `domain/toir.api.dsl` +- `prompts/backend-rules.md` + +Expected outputs: + +- `server/src/modules//...` +- `server/src/app.module.ts` + +Handoff: + +- frontend generation starts only after backend contracts, guards, DTOs, and natural-key behavior align with backend rules + +## 4. Frontend + +Purpose: + +- generate React Admin resources and resource registration that match backend and DSL contracts + +Responsible: + +- orchestrator +- frontend stage worker, ideally one entity at a time when parallelized + +Mandatory inputs: + +- entity-scoped DSL quotes from `domain/toir.api.dsl` +- `prompts/frontend-rules.md` + +Expected outputs: + +- `client/src/resources//...` +- `client/src/App.tsx` + +Handoff: + +- auth/runtime integration starts only after frontend resource contracts align with DTO-derived field sets and type mappings + +## 5. Auth / Runtime / Realm Artifacts + +Purpose: + +- wire authentication, environment defaults, realm import, and runtime topology around the generated CRUD app + +Responsible: + +- orchestrator +- auth/runtime stage worker +- `docs_researcher` when Keycloak or framework integration behavior is uncertain + +Mandatory inputs: + +- `prompts/auth-rules.md` +- `prompts/runtime-rules.md` + +Expected outputs: + +- `server/src/auth/` +- `client/src/auth/` +- `client/src/dataProvider.ts` - `server/.env.example` - `client/.env.example` -- `client/src/auth/keycloak.ts` -- `client/src/auth/authProvider.ts` -- `client/src/dataProvider.ts` -- `server/src/auth/*` - `docker-compose.yml` -- `domain-summary.json` -- root-level `*-realm.json` +- `toir-realm.json` -The baseline output must also remain a real framework workspace, not a prompt-only file collection: +Handoff: -- `server/tsconfig.json` -- `server/tsconfig.build.json` -- `server/nest-cli.json` -- `client/index.html` -- `client/tsconfig.json` -- `client/vite.config.*` +- verification starts only after auth seams, runtime artifacts, and realm output are aligned with backend/frontend expectations -NON-GOALS +## 6. Verification / Success Gate -- No new generator engine -- No compiler/IR platform -- No heavy codegen redesign -- No replacement of the old LLM-first architecture +Purpose: -COMPLETION INVARIANTS +- prove that the generation run is complete and not just plausible -- Generation is incomplete if `server/` is not a valid NestJS workspace. -- Generation is incomplete if `client/` is not a valid Vite React TypeScript workspace. -- Generation is incomplete if auth rules, runtime rules, and validation rules describe different truth paths. -- Generation is incomplete if buildability is broken. -- If buildability cannot be checked because dependencies are missing, report that state explicitly; do not report a green result for buildability. +Responsible: -VALIDATION +- orchestrator +- `reviewer` before completion -Before considering the output complete, satisfy `prompts/validation-rules.md`. +Mandatory inputs: + +- `prompts/validation-rules.md` +- validation command output +- reviewer findings + +Expected outputs: + +- refreshed auxiliary artifacts if the validator/tooling requires them, including `api-summary.json` +- passing validation gates +- successful backend and frontend builds + +Handoff: + +- there is no next stage; report complete only when every success criterion below is satisfied + +# Success Criteria + +Generation is successful only if all of the following are true: + +- `server/` exists in the project root +- `client/` exists in the project root +- the backend builds successfully +- the frontend builds successfully +- `node tools/validate-generation.mjs --artifacts-only` passes +- `npm run eval:generation` passes +- required auth/runtime/realm artifacts exist and match their companion rules +- module/resource registrations are complete +- any validator-required auxiliary artifacts, including `api-summary.json`, are refreshed and consistent +- the reviewer has not identified unresolved contract violations + +# Non-Goals / Constraints + +- Do not edit `domain/toir.api.dsl` during generation. +- Do not treat `api-summary.json` as the source of truth or default starting point. +- Do not inline large backend/frontend/prisma/auth/runtime/validation rule sets into this master prompt; load the companion docs instead. +- Do not generate domain artifacts on top of a broken scaffold when official CLI repair is required. +- Do not claim success from prompt reasoning alone; use builds and repository gates. +- Do not load the full DSL blob when entity-scoped context is enough. + +# Companion Rule Documents + +These documents are mandatory when their stage is active: + +- Prisma stage: `prompts/prisma-rules.md` +- Backend stage: `prompts/backend-rules.md` +- Frontend stage: `prompts/frontend-rules.md` +- Auth / realm stage: `prompts/auth-rules.md` +- Runtime / bootstrap stage: `prompts/runtime-rules.md` +- Verification stage: `prompts/validation-rules.md` + +The master prompt owns orchestration. Companion docs own artifact-specific detail. diff --git a/prompts/prisma-rules.md b/prompts/prisma-rules.md new file mode 100644 index 0000000..02758ba --- /dev/null +++ b/prompts/prisma-rules.md @@ -0,0 +1,119 @@ +# Prisma Rules + + + + + + + +Use this document during the **Prisma** stage defined in `prompts/general-prompt.md`. + +## Purpose + +Generate `server/prisma/schema.prisma` as a faithful reflection of `domain/toir.api.dsl`. + +## Mandatory Inputs + +- `prompts/general-prompt.md` +- the relevant entity/enum definitions from `domain/toir.api.dsl` +- the existing Prisma header if `server/prisma/schema.prisma` already exists + +`api-summary.json` may be used only as an auxiliary validator/inventory artifact. It is not part of the authoritative Prisma source hierarchy. + +## Expected Output + +- `server/prisma/schema.prisma` + +Never edit the schema manually during normal generation. Change the DSL and regenerate instead. + +## Source Of Truth + +Entity definitions, field types, PKs, FKs, enums, optionality, uniqueness, and defaults come from `domain/toir.api.dsl`. + +## Scalar Type Mapping + +| DSL type | Prisma scalar type | +| --------- | ------------------ | +| `uuid` | `String` | +| `string` | `String` | +| `text` | `String` | +| `integer` | `Int` | +| `number` | `Float` | +| `decimal` | `Decimal` | +| `date` | `DateTime` | +| `boolean` | `Boolean` | +| enum name | enum name as-is | + +Unknown DSL types pass through as-is for forward compatibility. + +## Primary Key Rules + +- a field marked `key primary` becomes `@id` +- if the primary key type is `uuid` or the field name is `id`, use `@id @default(uuid())` +- non-uuid natural keys keep plain `@id` +- every entity must resolve to exactly one primary key + +## Optionality And Defaults + +- primary keys are required +- fields marked `is required` are required +- all other fields are optional with Prisma `?` +- non-primary unique fields get `@unique` +- `default ` maps to Prisma `@default(...)` + +## Foreign Key And Relation Rules + +For a DSL field declared `key foreign { relates Entity.field }`: + +1. emit the FK scalar field first +2. add a relation field named `lowerFirst(relatedEntity)` +3. if that relation name collides, append `Ref` +4. annotate with `@relation(fields: [], references: [])` + +Inverse array relations: + +- add inverse array fields for referencing entities automatically +- pluralization rules: + - `equipment` stays `equipment` + - names ending in `s` add `es` + - all others add `s` +- if the inverse name collides, append `List` + +## Enum Rules + +- every DSL enum becomes a Prisma enum +- preserve declaration order +- preserve the enum name exactly + +## Header Preservation + +If `server/prisma/schema.prisma` already contains a `generator client { ... }` block, preserve everything before the first `enum` or `model` keyword. + +If no valid header exists, emit: + +```prisma +generator client { + provider = "prisma-client-js" +} + +datasource db { + provider = "postgresql" + url = env("DATABASE_URL") +} +``` + +## Forbidden Patterns + +- do not add fields not declared in the DSL +- do not add `@@index`, `@@map`, or schema-level directives not declared by the DSL +- do not add `@db.*` modifiers +- do not change the datasource provider away from `postgresql` + +## Completion Expectations + +Prisma generation is incomplete if any of the following is true: + +- `server/prisma/schema.prisma` does not exist +- the schema no longer reflects the DSL +- required relation fields or inverse arrays are missing +- header generation or preservation breaks Prisma baseline behavior diff --git a/prompts/runtime-rules.md b/prompts/runtime-rules.md index 6ff930e..6c5bbf8 100644 --- a/prompts/runtime-rules.md +++ b/prompts/runtime-rules.md @@ -1,96 +1,86 @@ # Runtime Rules -This repository keeps the current LLM-first CRUD generation architecture as the primary working baseline and strengthens the existing pipeline instead of replacing it. + + + -## Baseline runtime topology +Use this document during the **Preparation / Discovery** and **Auth / Runtime / Realm** stages defined in `prompts/general-prompt.md`. -- `server/` is the active backend target output path. -- `client/` is the active frontend target output path. -- Docker scope remains PostgreSQL only. -- Keycloak remains external to the repository runtime. -- The project remains LLM-first: markdown knowledge blocks in `prompts/` orchestrate generation, while active generated/maintained code lives in `server/` and `client/`. +## Purpose -## Required input and derived artifacts +Define the runtime topology, environment defaults, scaffold expectations, and bootstrap sequence for a buildable generated workspace. -- Source of truth: - - `domain/*.dsl` -- Required derived artifacts: - - `domain-summary.json` - - root-level `*-realm.json` +## Mandatory Inputs -`domain-summary.json` exists to stabilize generation and validation; it must be regenerated from the DSL and treated as non-authoritative. +- `prompts/general-prompt.md` +- `prompts/auth-rules.md` when runtime changes affect auth defaults or seams +- current repository runtime/auth defaults -## Output contract +`api-summary.json` is an auxiliary artifact only. Refresh it when validator/tooling requires freshness checks or when a compact inventory helps discovery. Do not treat it as the runtime source of truth. -The strengthened baseline must produce and keep aligned: +## Expected Outputs -- `server/prisma/schema.prisma` -- backend/frontend env examples -- backend/frontend auth seams -- root `.gitignore`, `server/.gitignore`, `client/.gitignore` - `docker-compose.yml` -- `domain-summary.json` -- root-level realm import artifact +- `server/.env.example` +- `client/.env.example` +- a buildable NestJS workspace under `server/` +- a buildable Vite React TypeScript workspace under `client/` +- any validator-required auxiliary artifacts such as `api-summary.json` -## Concrete runtime examples +## Baseline Runtime Topology -Use these as the baseline examples for this project unless the prompt explicitly overrides them: +- `server/` is the backend output path +- `client/` is the frontend output path +- Docker scope stays PostgreSQL-only +- Keycloak remains external to repository runtime +- the project remains LLM-first and prompt-driven -- Backend: - - `PORT=3000` - - `DATABASE_URL="postgresql://postgres:postgres@localhost:5432/toir"` - - `CORS_ALLOWED_ORIGINS="http://localhost:5173,https://toir-frontend.greact.ru"` - - `KEYCLOAK_ISSUER_URL="https://sso.greact.ru/realms/toir"` - - `KEYCLOAK_AUDIENCE="toir-backend"` -- Frontend: - - `VITE_API_URL=http://localhost:3000` - - `VITE_KEYCLOAK_URL=https://sso.greact.ru` - - `VITE_KEYCLOAK_REALM=toir` - - `VITE_KEYCLOAK_CLIENT_ID=toir-frontend` +## Concrete Runtime Defaults -These example values come from the already working runtime shape and are preferred over local-only Keycloak placeholders. +Backend: -## Runtime bootstrap +- `PORT=3000` +- `DATABASE_URL="postgresql://postgres:postgres@localhost:5432/toir"` +- `CORS_ALLOWED_ORIGINS="http://localhost:5173,https://toir-frontend.greact.ru"` +- `KEYCLOAK_ISSUER_URL="https://sso.greact.ru/realms/toir"` +- `KEYCLOAK_AUDIENCE="toir-backend"` -1. Import the root-level realm artifact into Keycloak. -2. Start PostgreSQL with `docker compose up -d`. -3. From `server/` run: - - initialize or repair the workspace with official Nest CLI scaffolding if required before generating domain code - - `npm install` - - `npx prisma generate` - - `npx prisma migrate dev` - - `npx prisma db seed` - - `npm run build` - - `npm run start` -4. From `client/` run: - - initialize or repair the workspace with official Vite React TypeScript scaffolding if required before generating app code - - `npm install` - - `npm run build` - - `npm run dev` +Frontend: -## Recovery and completion rules +- `VITE_API_URL=http://localhost:3000` +- `VITE_KEYCLOAK_URL=https://sso.greact.ru` +- `VITE_KEYCLOAK_REALM=toir` +- `VITE_KEYCLOAK_CLIENT_ID=toir-frontend` -- Repair degraded framework workspaces before applying any new domain-derived generation changes. -- Do not mark generation complete while `server/` or `client/` remains non-buildable. -- If dependency installation has not happened yet, buildability may be reported as skipped, but it must never be reported as green without verification. -- Runtime/bootstrap instructions are reusable project baseline rules; TOiR names remain examples, not the only supported domain project. +## Scaffold Expectations -## Scaffold expectations +- new or repaired backend workspaces start from the official Nest CLI +- new or repaired frontend workspaces start from the official Vite React TypeScript CLI +- Prisma initialization uses the official Prisma CLI when relevant +- the LLM may customize generated code after scaffold creation, but must not replace official initialization with ad hoc file creation -- NestJS workspace creation should follow the official Nest CLI path for new applications and resource scaffolding. -- Vite frontend creation should follow the official Vite `create-vite` path for React TypeScript applications. -- The LLM may customize generated code after scaffold creation, but must not replace official workspace initialization with ad hoc file creation. +## Runtime Bootstrap -## Common generation failures to avoid +1. import `toir-realm.json` into Keycloak +2. start PostgreSQL with `docker compose up -d` +3. from `server/`: + - repair or create the workspace with official Nest CLI if needed + - install dependencies + - run Prisma commands required by the schema stage + - run `npm run build` + - run `npm run start` +4. from `client/`: + - repair or create the workspace with official Vite CLI if needed + - install dependencies + - run `npm run build` + - run `npm run dev` -- starting feature generation before scaffold repair -- leaving deleted framework config files unrepaired because the current diff looks smaller -- accepting a form-only validation pass while buildability is unknown -- binding runtime rules to one project-specific DSL filename instead of `domain/*.dsl` +## Completion Expectations -## Baseline intent +Runtime preparation is incomplete if any of the following is true: -- No new generator engine -- No compiler platform -- No planner/emitter/runtime redesign -- Only the current LLM-first pipeline, strengthened by summary, realm, and validation artifacts +- `server/` is missing or not buildable as a NestJS workspace +- `client/` is missing or not buildable as a Vite React TypeScript workspace +- framework scaffolding was hand-built instead of created or repaired from official CLIs +- shared env defaults drift from the repository auth/runtime contract +- runtime success is claimed without actual build verification diff --git a/prompts/validation-rules.md b/prompts/validation-rules.md index a071be5..a20db15 100644 --- a/prompts/validation-rules.md +++ b/prompts/validation-rules.md @@ -1,98 +1,101 @@ # Validation Rules -Validation is now a lightweight automated gate instead of a prose-only checklist. + + + -## Commands +Use this document during the **Verification / Success Gate** stage defined in `prompts/general-prompt.md`. -- `npm run generate:domain-summary` -- `npm run validate:generation` -- `npm run validate:generation:runtime` +## Purpose + +Define the repository gates that convert a plausible generation run into a verified one. + +## Primary Gates + +- `node tools/validate-generation.mjs --artifacts-only` +- `npm run eval:generation` + +## Auxiliary Freshness Prep + +- `npm run generate:api-summary` + +Run the freshness prep when the repository validator or supporting tooling expects `api-summary.json` to exist and match the current DSL. This artifact is auxiliary to validation and inventory, not the generation source of truth. ## Prompt-Gate Alignment Rule -- Every invariant described as required in the active prompt corpus must either be enforced by this gate or be called out explicitly as a manual/runtime-only check. -- Validation must not stay silent about a violation that the prompts describe as forbidden. -- Validation must not report green buildability when build verification was skipped. +- every invariant marked required in the active prompt corpus must either be enforced by a gate or called out as manual/runtime-only +- validation must not silently ignore a forbidden pattern +- build verification must not be reported as green when it was skipped -## Gate groups +## Gate Groups -### Build checks +### Build Checks -- at least one `domain/*.dsl` file exists -- required artifacts exist -- Prisma schema exists -- frontend/backend env contracts exist -- frontend/backend framework workspace files exist -- `domain-summary.json` matches the current DSL -- project `.env.example` files keep the working domain-based Keycloak examples unless explicitly overridden +- at least one `domain/*.api.dsl` file exists +- required artifacts exist: + - `server/prisma/schema.prisma` + - env examples + - required scaffold files + - auth/runtime/realm artifacts +- if the current validator policy checks `api-summary.json`, it exists and is fresh relative to the DSL - `server/` remains a valid Nest workspace - `client/` remains a valid Vite workspace -- generation must not pass validation if framework scaffolding files were deleted and replaced by a hand-written minimal skeleton -- if dependencies are installed, build verification runs for `server/` and `client/` +- if dependencies are installed, backend and frontend build verification runs - if dependencies are missing, build verification is reported as skipped with reason instead of green -### Auth checks +### Auth Checks - frontend auth seam files exist - backend auth seam files exist -- `401` and `403` semantics stay split +- `401` and `403` semantics remain split - auth code keeps the required Keycloak/JWT contracts -- JWKS resolution chain matches the contract: +- JWKS resolution order remains: 1. explicit `KEYCLOAK_JWKS_URL` 2. OIDC discovery 3. certs fallback -### Filter checks +### Filter And UI Checks -- list resources expose filter UI (including `FilterButton`) +- list resources expose filter UI including `FilterButton` - reference filters use `ReferenceInput` + `AutocompleteInput` with `filterToQuery` -- data provider preserves repeated query params for array filters -- backend FK filters keep exact-match semantics -- enum repeated params are mapped to Prisma `in` -- typed form mapping is preserved: - - `integer` / `decimal` -> `NumberInput` - - `date` -> `DateInput` -- reference fields intended for navigation keep `ReferenceField link="show"` +- `dataProvider` preserves repeated query params for array filters +- backend FK filters remain exact-match +- repeated enum params map to Prisma `in` +- Create/Edit forms keep type-correct inputs +- navigable references keep `ReferenceField link="show"` - resources keep `show={...}` registration in `App.tsx` -### Natural-key checks +### Natural-Key Checks - response records expose `id` - route/update contracts use the real primary key - natural-key sort/update paths do not regress to a fake physical `id` -### Realm checks +### Realm Checks - a root `*-realm.json` artifact exists -- realm roles exist -- audience delivery exists -- required claims are explicit -- SPA/backend client structure is explicit +- required roles, audience delivery, and claims remain explicit +- SPA and backend client structure remains explicit -### Runtime checks +### Runtime Checks -- compose topology stays PostgreSQL-only -- Prisma lifecycle scripts remain in place -- `/health` stays public -- backend can execute `npm run build` inside `server/` -- frontend can execute `npm run build` inside `client/` after dependencies are installed -- client/server `.env.example` stay aligned with the working runtime defaults: - - `https://sso.greact.ru` - - `toir` - - `toir-frontend` - - `toir-backend` - - `https://toir-frontend.greact.ru` -- optional runtime execution mode runs: - - `npx prisma generate` - - `npx prisma migrate dev` - - `npx prisma db seed` +- Docker topology remains PostgreSQL-only +- Prisma lifecycle commands remain available where required +- `/health` remains public +- backend build runs inside `server/` +- frontend build runs inside `client/` +- client/server `.env.example` stay aligned with repository defaults -### Scaffold checks +### Output Contract Checks -- backend initialization starts from official Nest CLI scaffolding -- frontend initialization starts from official Vite React TypeScript scaffolding -- feature generation happens after scaffold creation, not instead of scaffold creation -- repair happens before generation when workspace is degraded -- required framework configs and entry files must survive subsequent LLM edits +- every generated Create/Update DTO imports from `'class-validator'` +- DTO fields have type-correct decorators +- optional/nullable fields carry `@IsOptional()` before the type decorator +- controllers carry the required guards and roles +- React Admin components use correct input/field types -The automated gate is intentionally small. It enforces the critical reproducibility contract without turning the repository into a test platform or a generator engine. +### Eval Harness + +- `npm run eval:generation` runs fixture-based semantic checks +- eval failures block completion +- prompt changes that break evals are regressions, not acceptable simplifications diff --git a/server/.dockerignore b/server/.dockerignore deleted file mode 100644 index 661172a..0000000 --- a/server/.dockerignore +++ /dev/null @@ -1,8 +0,0 @@ -node_modules -dist -coverage -.git -.env -.env.local -.env.*.local -npm-debug.log* diff --git a/server/.env.example b/server/.env.example index cd9c83f..1dec850 100644 --- a/server/.env.example +++ b/server/.env.example @@ -3,5 +3,4 @@ DATABASE_URL="postgresql://postgres:postgres@localhost:5432/toir" CORS_ALLOWED_ORIGINS="http://localhost:5173,https://toir-frontend.greact.ru" KEYCLOAK_ISSUER_URL="https://sso.greact.ru/realms/toir" KEYCLOAK_AUDIENCE="toir-backend" -# Optional: if omitted, backend uses OIDC discovery and then falls back to issuer + /protocol/openid-connect/certs -# KEYCLOAK_JWKS_URL="https://sso.greact.ru/realms/toir/protocol/openid-connect/certs" +KEYCLOAK_JWKS_URL="" diff --git a/server/.eslintrc.js b/server/.eslintrc.js deleted file mode 100644 index 259de13..0000000 --- a/server/.eslintrc.js +++ /dev/null @@ -1,25 +0,0 @@ -module.exports = { - parser: '@typescript-eslint/parser', - parserOptions: { - project: 'tsconfig.json', - tsconfigRootDir: __dirname, - sourceType: 'module', - }, - plugins: ['@typescript-eslint/eslint-plugin'], - extends: [ - 'plugin:@typescript-eslint/recommended', - 'plugin:prettier/recommended', - ], - root: true, - env: { - node: true, - jest: true, - }, - ignorePatterns: ['.eslintrc.js'], - rules: { - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off', - '@typescript-eslint/no-explicit-any': 'off', - }, -}; diff --git a/server/.gitignore b/server/.gitignore index 3f7fb8e..9f62ec0 100644 --- a/server/.gitignore +++ b/server/.gitignore @@ -1,32 +1,5 @@ -# Dependencies -node_modules/ - -# Build -dist/ - -# Environment +node_modules +# Keep environment variables out of version control .env -.env.local -.env.*.local -# Prisma -prisma/migrations/**/migration_lock.toml - -# Logs -logs/ -*.log -npm-debug.log* - -# OS files -.DS_Store -Thumbs.db - -# Editor / IDE -.vscode/* -!.vscode/extensions.json -.idea/ -*.suo -*.ntvs* -*.njsproj -*.sln -*.sw? +/generated/prisma diff --git a/server/.prettierrc b/server/.prettierrc index dcb7279..a20502b 100644 --- a/server/.prettierrc +++ b/server/.prettierrc @@ -1,4 +1,4 @@ { "singleQuote": true, "trailingComma": "all" -} \ No newline at end of file +} diff --git a/server/Dockerfile b/server/Dockerfile deleted file mode 100644 index 80780c9..0000000 --- a/server/Dockerfile +++ /dev/null @@ -1,37 +0,0 @@ -FROM node:20-bookworm-slim AS build - -WORKDIR /app - -RUN apt-get update \ - && apt-get install -y --no-install-recommends openssl \ - && rm -rf /var/lib/apt/lists/* - -COPY package*.json ./ - -COPY prisma ./prisma - -RUN npm ci - -COPY nest-cli.json tsconfig*.json ./ -COPY src ./src - -RUN npm run build - -FROM node:20-bookworm-slim AS runtime - -WORKDIR /app - -ENV NODE_ENV=production - -RUN apt-get update \ - && apt-get install -y --no-install-recommends openssl \ - && rm -rf /var/lib/apt/lists/* - -COPY --from=build /app/package*.json ./ -COPY --from=build /app/node_modules ./node_modules -COPY --from=build /app/prisma ./prisma -COPY --from=build /app/dist ./dist - -EXPOSE 3000 - -CMD ["sh", "-c", "npx prisma migrate deploy && node dist/src/main.js"] diff --git a/server/README.md b/server/README.md index c17103c..8f0f65f 100644 --- a/server/README.md +++ b/server/README.md @@ -11,7 +11,6 @@ Package License NPM Downloads CircleCI -Coverage Discord Backers on Open Collective Sponsors on Open Collective @@ -65,7 +64,7 @@ When you're ready to deploy your NestJS application to production, there are som If you are looking for a cloud-based platform to deploy your NestJS application, check out [Mau](https://mau.nestjs.com), our official platform for deploying NestJS applications on AWS. Mau makes deployment straightforward and fast, requiring just a few simple steps: ```bash -$ npm install -g mau +$ npm install -g @nestjs/mau $ mau deploy ``` diff --git a/server/eslint.config.mjs b/server/eslint.config.mjs new file mode 100644 index 0000000..4e9f827 --- /dev/null +++ b/server/eslint.config.mjs @@ -0,0 +1,35 @@ +// @ts-check +import eslint from '@eslint/js'; +import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'; +import globals from 'globals'; +import tseslint from 'typescript-eslint'; + +export default tseslint.config( + { + ignores: ['eslint.config.mjs'], + }, + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + eslintPluginPrettierRecommended, + { + languageOptions: { + globals: { + ...globals.node, + ...globals.jest, + }, + sourceType: 'commonjs', + parserOptions: { + projectService: true, + tsconfigRootDir: import.meta.dirname, + }, + }, + }, + { + rules: { + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-floating-promises': 'warn', + '@typescript-eslint/no-unsafe-argument': 'warn', + "prettier/prettier": ["error", { endOfLine: "auto" }], + }, + }, +); diff --git a/server/package-lock.json b/server/package-lock.json index 16c0ade..215b232 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -7,64 +7,67 @@ "": { "name": "server", "version": "0.0.1", - "hasInstallScript": true, "license": "UNLICENSED", "dependencies": { - "@nestjs/common": "^10.0.0", - "@nestjs/config": "^4.0.3", - "@nestjs/core": "^10.0.0", - "@nestjs/platform-express": "^10.0.0", - "@prisma/client": "^5.22.0", - "jose": "^6.2.2", - "reflect-metadata": "^0.2.0", + "@nestjs/common": "^11.0.1", + "@nestjs/core": "^11.0.1", + "@nestjs/platform-express": "^11.0.1", + "@prisma/client": "^6.19.0", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.2", + "jose": "^6.1.2", + "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1" }, "devDependencies": { - "@nestjs/cli": "^10.0.0", - "@nestjs/schematics": "^10.0.0", - "@nestjs/testing": "^10.0.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.18.0", + "@nestjs/cli": "^11.0.0", + "@nestjs/schematics": "^11.0.0", + "@nestjs/testing": "^11.0.1", "@types/express": "^5.0.0", - "@types/jest": "^29.5.2", - "@types/node": "^20.3.1", - "@types/supertest": "^6.0.0", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", - "eslint": "^8.0.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-prettier": "^5.0.0", - "jest": "^29.5.0", - "prettier": "^3.0.0", - "prisma": "^5.22.0", + "@types/jest": "^30.0.0", + "@types/node": "^24.0.0", + "@types/supertest": "^7.0.0", + "dotenv": "^17.2.3", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.2", + "globals": "^17.0.0", + "jest": "^30.0.0", + "prettier": "^3.4.2", + "prisma": "^6.19.0", "source-map-support": "^0.5.21", "supertest": "^7.0.0", - "ts-jest": "^29.1.0", - "ts-loader": "^9.4.3", + "ts-jest": "^29.2.5", + "ts-loader": "^9.5.2", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3" + "typescript": "^5.7.3", + "typescript-eslint": "^8.20.0" } }, "node_modules/@angular-devkit/core": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-17.3.11.tgz", - "integrity": "sha512-vTNDYNsLIWpYk2I969LMQFH29GTsLzxNk/0cLw5q56ARF0v5sIWfHYwGTS88jdDqIpuuettcSczbxeA7EuAmqQ==", + "version": "19.2.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.22.tgz", + "integrity": "sha512-OqN/Ded+ZKypPZN5+qUFwtnKGl7FKpxJXYO2Vts5vLBojY5goCZd9SGW1CyXeuPnisRUW+vjqBQbWYuEUh36Tw==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "8.12.0", - "ajv-formats": "2.1.1", - "jsonc-parser": "3.2.1", - "picomatch": "4.0.1", + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.2", "rxjs": "7.8.1", "source-map": "0.7.4" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" }, "peerDependencies": { - "chokidar": "^3.5.2" + "chokidar": "^4.0.0" }, "peerDependenciesMeta": { "chokidar": { @@ -72,6 +75,30 @@ } } }, + "node_modules/@angular-devkit/core/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@angular-devkit/core/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/@angular-devkit/core/node_modules/rxjs": { "version": "7.8.1", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", @@ -83,35 +110,35 @@ } }, "node_modules/@angular-devkit/schematics": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-17.3.11.tgz", - "integrity": "sha512-I5wviiIqiFwar9Pdk30Lujk8FczEEc18i22A5c6Z9lbmhPQdTroDnEQdsfXjy404wPe8H62s0I15o4pmMGfTYQ==", + "version": "19.2.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.22.tgz", + "integrity": "sha512-tvfu5jhem1o8qidVxvXe5KfCij65ioMLCOFA947DD+zb3yTl5pJyDm2dqzbOehuQw0fmH4XPQukRJsCUy+UwaA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "17.3.11", - "jsonc-parser": "3.2.1", - "magic-string": "0.30.8", + "@angular-devkit/core": "19.2.22", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", "ora": "5.4.1", "rxjs": "7.8.1" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, "node_modules/@angular-devkit/schematics-cli": { - "version": "17.3.11", - "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-17.3.11.tgz", - "integrity": "sha512-kcOMqp+PHAKkqRad7Zd7PbpqJ0LqLaNZdY1+k66lLWmkEBozgq8v4ASn/puPWf9Bo0HpCiK+EzLf0VHE8Z/y6Q==", + "version": "19.2.22", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics-cli/-/schematics-cli-19.2.22.tgz", + "integrity": "sha512-6BvkxDz4nV8B6Ha4n/pYZ503vXgLxMaEpcKsFDao1sl0iSwrIOphlIS1yWprlGdCThIM3aJref1JU13ZvEcBCA==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "17.3.11", - "@angular-devkit/schematics": "17.3.11", + "@angular-devkit/core": "19.2.22", + "@angular-devkit/schematics": "19.2.22", + "@inquirer/prompts": "7.3.2", "ansi-colors": "4.1.3", - "inquirer": "9.2.15", "symbol-observable": "4.0.0", "yargs-parser": "21.1.1" }, @@ -119,79 +146,39 @@ "schematics": "bin/schematics.js" }, "engines": { - "node": "^18.13.0 || >=20.9.0", + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", "yarn": ">= 1.13.0" } }, - "node_modules/@angular-devkit/schematics-cli/node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@angular-devkit/schematics-cli/node_modules/cli-width": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", - "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", - "dev": true, - "license": "ISC", - "engines": { - "node": ">= 12" - } - }, - "node_modules/@angular-devkit/schematics-cli/node_modules/inquirer": { - "version": "9.2.15", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.2.15.tgz", - "integrity": "sha512-vI2w4zl/mDluHt9YEQ/543VTCwPKWiHzKtm9dM2V0NdFcqEexDAjUHzO1oA60HRNaVifGXXM1tRRNluLVHa0Kg==", + "node_modules/@angular-devkit/schematics-cli/node_modules/@inquirer/prompts": { + "version": "7.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.3.2.tgz", + "integrity": "sha512-G1ytyOoHh5BphmEBxSwALin3n1KGNYB6yImbICcRQdzXfOGbuJ9Jske/Of5Sebk339NSGGNfUshnzK8YWkTPsQ==", "dev": true, "license": "MIT", "dependencies": { - "@ljharb/through": "^2.3.12", - "ansi-escapes": "^4.3.2", - "chalk": "^5.3.0", - "cli-cursor": "^3.1.0", - "cli-width": "^4.1.0", - "external-editor": "^3.1.0", - "figures": "^3.2.0", - "lodash": "^4.17.21", - "mute-stream": "1.0.0", - "ora": "^5.4.1", - "run-async": "^3.0.0", - "rxjs": "^7.8.1", - "string-width": "^4.2.3", - "strip-ansi": "^6.0.1", - "wrap-ansi": "^6.2.0" + "@inquirer/checkbox": "^4.1.2", + "@inquirer/confirm": "^5.1.6", + "@inquirer/editor": "^4.2.7", + "@inquirer/expand": "^4.0.9", + "@inquirer/input": "^4.1.6", + "@inquirer/number": "^3.0.9", + "@inquirer/password": "^4.0.9", + "@inquirer/rawlist": "^4.0.9", + "@inquirer/search": "^3.0.9", + "@inquirer/select": "^4.0.9" }, "engines": { "node": ">=18" - } - }, - "node_modules/@angular-devkit/schematics-cli/node_modules/mute-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", - "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", - "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } - }, - "node_modules/@angular-devkit/schematics-cli/node_modules/run-async": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", - "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } } }, "node_modules/@angular-devkit/schematics/node_modules/rxjs": { @@ -397,23 +384,23 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", "dev": true, "license": "MIT", "dependencies": { "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.0" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz", - "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==", + "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": { @@ -765,6 +752,40 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@emnapi/core": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.9.2.tgz", + "integrity": "sha512-UC+ZhH3XtczQYfOlu3lNEkdW/p4dsJ1r/bP7H8+rhao3TTTMO1ATq/4DdIi23XuGoFY+Cz0JmCbdVl0hz9jZcA==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.1", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.9.2.tgz", + "integrity": "sha512-3U4+MIWHImeyu1wnmVygh5WlgfYDtyf0k8AbLhMFxOipihf6nrWC4syIm/SwEeec0mNSafiiNnMJwbza/Is6Lw==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", + "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -784,6 +805,19 @@ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, + "node_modules/@eslint-community/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/@eslint-community/regexpp": { "version": "4.12.2", "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz", @@ -794,150 +828,143 @@ "node": "^12.0.0 || ^14.0.0 || >=16.0.0" } }, + "node_modules/@eslint/config-array": { + "version": "0.21.2", + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.2.tgz", + "integrity": "sha512-nJl2KGTlrf9GjLimgIru+V/mzgSK0ABCDQRvxw5BjURL7WfH5uoWmizbH7QB6MmnMBd8cIC9uceWnezL1VZWWw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/object-schema": "^2.1.7", + "debug": "^4.3.1", + "minimatch": "^3.1.5" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/config-helpers": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@eslint/config-helpers/-/config-helpers-0.4.2.tgz", + "integrity": "sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^0.17.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/core": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz", + "integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.15" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, "node_modules/@eslint/eslintrc": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", - "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "version": "3.3.5", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.5.tgz", + "integrity": "sha512-4IlJx0X0qftVsN5E+/vGujTRIFtwuLbNsVUe7TO6zYPDR1O6nFwvwhIKEKSrl6dZchmYBITazxKoUYOjdtjlRg==", "dev": true, "license": "MIT", "dependencies": { - "ajv": "^6.12.4", + "ajv": "^6.14.0", "debug": "^4.3.2", - "espree": "^9.6.0", - "globals": "^13.19.0", + "espree": "^10.0.1", + "globals": "^14.0.0", "ignore": "^5.2.0", "import-fresh": "^3.2.1", - "js-yaml": "^4.1.0", - "minimatch": "^3.1.2", + "js-yaml": "^4.1.1", + "minimatch": "^3.1.5", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/@eslint/eslintrc/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", + "node_modules/@eslint/eslintrc/node_modules/globals": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", "dev": true, "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" + "engines": { + "node": ">=18" }, "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@eslint/eslintrc/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/eslintrc/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/@eslint/eslintrc/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/@eslint/eslintrc/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/@eslint/eslintrc/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" + "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/@eslint/js": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", - "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.4.tgz", + "integrity": "sha512-nE7DEIchvtiFTwBw4Lfbu59PG+kCofhjsKaCWzxTpt4lfRjRMqG6uMBzKXuEcyXhOHoUp9riAm7/aWYGhXZ9cw==", "dev": true, "license": "MIT", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "url": "https://eslint.org/donate" } }, - "node_modules/@humanwhocodes/config-array": { - "version": "0.13.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", - "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", - "deprecated": "Use @eslint/config-array instead", + "node_modules/@eslint/object-schema": { + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.7.tgz", + "integrity": "sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + } + }, + "node_modules/@eslint/plugin-kit": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.4.1.tgz", + "integrity": "sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@humanwhocodes/object-schema": "^2.0.3", - "debug": "^4.3.1", - "minimatch": "^3.0.5" + "@eslint/core": "^0.17.0", + "levn": "^0.4.1" }, "engines": { - "node": ">=10.10.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "node_modules/@humanfs/core": { + "version": "0.19.1", + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", "dev": true, - "license": "MIT" - }, - "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18.0" } }, - "node_modules/@humanwhocodes/config-array/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "node_modules/@humanfs/node": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.7.tgz", + "integrity": "sha512-/zUx+yOsIrG4Y43Eh2peDeKCxlRt/gET6aHfaKpuq267qXdYDFViVHfMaLyygZOnl0kGWxFIgsBy8QFuTLUXEQ==", "dev": true, - "license": "ISC", + "license": "Apache-2.0", "dependencies": { - "brace-expansion": "^1.1.7" + "@humanfs/core": "^0.19.1", + "@humanwhocodes/retry": "^0.4.0" }, "engines": { - "node": "*" + "node": ">=18.18.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -954,13 +981,369 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/object-schema": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", - "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", - "deprecated": "Use @eslint/object-schema instead", + "node_modules/@humanwhocodes/retry": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", + "integrity": "sha512-bV0Tgo9K4hfPCek+aMAn81RppFKv2ySDQeMoSZuvTASywNTnVJCArCZE2FWqpvIatKu7VMRLWlR1EazvVhDyhQ==", "dev": true, - "license": "BSD-3-Clause" + "license": "Apache-2.0", + "engines": { + "node": ">=18.18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@inquirer/ansi": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@inquirer/ansi/-/ansi-1.0.2.tgz", + "integrity": "sha512-S8qNSZiYzFd0wAcyG5AXCvUHC5Sr7xpZ9wZ2py9XR88jUz8wooStVx5M6dRzczbBWjic9NP7+rY0Xi7qqK/aMQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/checkbox": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/checkbox/-/checkbox-4.3.2.tgz", + "integrity": "sha512-VXukHf0RR1doGe6Sm4F0Em7SWYLTHSsbGfJdS9Ja2bX5/D5uwVOEjr07cncLROdBvmnvCATYEWlHqYmXv2IlQA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/confirm": { + "version": "5.1.21", + "resolved": "https://registry.npmjs.org/@inquirer/confirm/-/confirm-5.1.21.tgz", + "integrity": "sha512-KR8edRkIsUayMXV+o3Gv+q4jlhENF9nMYUZs9PA2HzrXeHI8M5uDag70U7RJn9yyiMZSbtF5/UexBtAVtZGSbQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/core": { + "version": "10.3.2", + "resolved": "https://registry.npmjs.org/@inquirer/core/-/core-10.3.2.tgz", + "integrity": "sha512-43RTuEbfP8MbKzedNqBrlhhNKVwoK//vUFNW3Q3vZ88BLcrs4kYpGg+B2mm5p2K/HfygoCxuKwJJiv8PbGmE0A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "cli-width": "^4.1.0", + "mute-stream": "^2.0.0", + "signal-exit": "^4.1.0", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/editor": { + "version": "4.2.23", + "resolved": "https://registry.npmjs.org/@inquirer/editor/-/editor-4.2.23.tgz", + "integrity": "sha512-aLSROkEwirotxZ1pBaP8tugXRFCxW94gwrQLxXfrZsKkfjOYC1aRvAZuhpJOb5cu4IBTJdsCigUlf2iCOu4ZDQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/external-editor": "^1.0.3", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/expand": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/expand/-/expand-4.0.23.tgz", + "integrity": "sha512-nRzdOyFYnpeYTTR2qFwEVmIWypzdAx/sIkCMeTNTcflFOovfqUk+HcFhQQVBftAh9gmGrpFj6QcGEqrDMDOiew==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "dev": true, + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/@inquirer/input": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/@inquirer/input/-/input-4.3.1.tgz", + "integrity": "sha512-kN0pAM4yPrLjJ1XJBjDxyfDduXOuQHrBB8aLDMueuwUGn+vNpF7Gq7TvyVxx8u4SHlFFj4trmj+a2cbpG4Jn1g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/number": { + "version": "3.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/number/-/number-3.0.23.tgz", + "integrity": "sha512-5Smv0OK7K0KUzUfYUXDXQc9jrf8OHo4ktlEayFlelCjwMXz0299Y8OrI+lj7i4gCBY15UObk76q0QtxjzFcFcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/password": { + "version": "4.0.23", + "resolved": "https://registry.npmjs.org/@inquirer/password/-/password-4.0.23.tgz", + "integrity": "sha512-zREJHjhT5vJBMZX/IUbyI9zVtVfOLiTO66MrF/3GFZYZ7T4YILW5MSkEYHceSii/KtRk+4i3RE7E1CUXA2jHcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/prompts": { + "version": "7.10.1", + "resolved": "https://registry.npmjs.org/@inquirer/prompts/-/prompts-7.10.1.tgz", + "integrity": "sha512-Dx/y9bCQcXLI5ooQ5KyvA4FTgeo2jYj/7plWfV5Ak5wDPKQZgudKez2ixyfz7tKXzcJciTxqLeK7R9HItwiByg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/checkbox": "^4.3.2", + "@inquirer/confirm": "^5.1.21", + "@inquirer/editor": "^4.2.23", + "@inquirer/expand": "^4.0.23", + "@inquirer/input": "^4.3.1", + "@inquirer/number": "^3.0.23", + "@inquirer/password": "^4.0.23", + "@inquirer/rawlist": "^4.1.11", + "@inquirer/search": "^3.2.2", + "@inquirer/select": "^4.4.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/rawlist": { + "version": "4.1.11", + "resolved": "https://registry.npmjs.org/@inquirer/rawlist/-/rawlist-4.1.11.tgz", + "integrity": "sha512-+LLQB8XGr3I5LZN/GuAHo+GpDJegQwuPARLChlMICNdwW7OwV2izlCSCxN6cqpL0sMXmbKbFcItJgdQq5EBXTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/search": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@inquirer/search/-/search-3.2.2.tgz", + "integrity": "sha512-p2bvRfENXCZdWF/U2BXvnSI9h+tuA8iNqtUKb9UWbmLYCRQxd8WkvwWvYn+3NgYaNwdUkHytJMGG4MMLucI1kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/select": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/@inquirer/select/-/select-4.4.2.tgz", + "integrity": "sha512-l4xMuJo55MAe+N7Qr4rX90vypFwCajSakx59qe/tMaC1aEHWLyw68wF4o0A4SLAY4E0nd+Vt+EyskeDIqu1M6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@inquirer/ansi": "^1.0.2", + "@inquirer/core": "^10.3.2", + "@inquirer/figures": "^1.0.15", + "@inquirer/type": "^3.0.10", + "yoctocolors-cjs": "^2.1.3" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/type": { + "version": "3.0.10", + "resolved": "https://registry.npmjs.org/@inquirer/type/-/type-3.0.10.tgz", + "integrity": "sha512-BvziSRxfz5Ov8ch0z/n3oijRSEcEsHnhggm4xFZe93DHcUCTlutlq9Ox4SVENAfcRD22UQq7T/atg9Wr3k09eA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } }, "node_modules/@isaacs/cliui": { "version": "8.0.2", @@ -1183,61 +1566,60 @@ } }, "node_modules/@jest/console": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/console/-/console-29.7.0.tgz", - "integrity": "sha512-5Ni4CU7XHQi32IJ398EEP4RrB8eV09sXP2ROqD4bksHrnTree52PsxvX8tpL8LvTZ3pFzXyPbNQReSN41CAhOg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/console/-/console-30.3.0.tgz", + "integrity": "sha512-PAwCvFJ4696XP2qZj+LAn1BWjZaJ6RjG6c7/lkMaUJnkyMS34ucuIsfqYvfskVNvUI27R/u4P1HMYFnlVXG/Ww==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.3.0", "@types/node": "*", - "chalk": "^4.0.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/core": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/core/-/core-29.7.0.tgz", - "integrity": "sha512-n7aeXWKMnGtDA48y8TLWJPJmLmmZ642Ceo78cYWEpiD7FzDgmNDV/GCVRorPABdXLJZ/9wzzgZAlHjXjxDHGsg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/core/-/core-30.3.0.tgz", + "integrity": "sha512-U5mVPsBxLSO6xYbf+tgkymLx+iAhvZX43/xI1+ej2ZOPnPdkdO1CzDmFKh2mZBn2s4XZixszHeQnzp1gm/DIxw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/reporters": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/console": "30.3.0", + "@jest/pattern": "30.0.1", + "@jest/reporters": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-changed-files": "^29.7.0", - "jest-config": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-resolve-dependencies": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "jest-watcher": "^29.7.0", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", - "slash": "^3.0.0", - "strip-ansi": "^6.0.0" + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-changed-files": "30.3.0", + "jest-config": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-resolve-dependencies": "30.3.0", + "jest-runner": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "jest-watcher": "30.3.0", + "pretty-format": "30.3.0", + "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1248,117 +1630,150 @@ } } }, + "node_modules/@jest/diff-sequences": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/diff-sequences/-/diff-sequences-30.3.0.tgz", + "integrity": "sha512-cG51MVnLq1ecVUaQ3fr6YuuAOitHK1S4WUJHnsPFE/quQr33ADUx1FfrTCpMCRxvy0Yr9BThKpDjSlcTi91tMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, "node_modules/@jest/environment": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-29.7.0.tgz", - "integrity": "sha512-aQIfHDq33ExsN4jP1NWGXhxgQ/wixs60gDiKO+XVMd8Mn0NWPWgc34ZQDTb2jKaUWQ7MuwoitXAsN2XVXNMpAw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/environment/-/environment-30.3.0.tgz", + "integrity": "sha512-SlLSF4Be735yQXyh2+mctBOzNDx5s5uLv88/j8Qn1wH679PDcwy67+YdADn8NJnGjzlXtN62asGH/T4vWOkfaw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-mock": "^29.7.0" + "jest-mock": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-8uMeAMycttpva3P1lBHB8VciS9V0XAr3GymPpipdyQXbBcuhkLQOSe8E/p92RyAdToS6ZD1tFkX+CkhoECE0dQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-76Nlh4xJxk2D/9URCn3wFi98d2hb19uWE1idLsTt2ywhvdOldbw3S570hBgn25P4ICUZ/cBjybrBex2g17IDbg==", "dev": true, "license": "MIT", "dependencies": { - "expect": "^29.7.0", - "jest-snapshot": "^29.7.0" + "expect": "30.3.0", + "jest-snapshot": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/expect-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-29.7.0.tgz", - "integrity": "sha512-GlsNBWiFQFCVi9QVSx7f5AgMeLxe9YCCs5PuP2O2LdjDAA8Jh9eX7lA1Jq/xdXw3Wb3hyvlFNfZIfcRetSzYcA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-30.3.0.tgz", + "integrity": "sha512-j0+W5iQQ8hBh7tHZkTQv3q2Fh/M7Je72cIsYqC4OaktgtO7v1So9UTjp6uPBHIaB6beoF/RRsCgMJKvti0wADA==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3" + "@jest/get-type": "30.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/fake-timers": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-29.7.0.tgz", - "integrity": "sha512-q4DH1Ha4TTFPdxLsqDXK1d3+ioSL7yL5oCMJZgDYm6i+6CygW5E5xVr/D1HdsGxjt1ZWSfUAs9OxSB/BNelWrQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-30.3.0.tgz", + "integrity": "sha512-WUQDs8SOP9URStX1DzhD425CqbN/HxUYCTwVrT8sTVBfMvFqYt/s61EK5T05qnHu0po6RitXIvP9otZxYDzTGQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@sinonjs/fake-timers": "^10.0.2", + "@jest/types": "30.3.0", + "@sinonjs/fake-timers": "^15.0.0", "@types/node": "*", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/get-type": { + "version": "30.1.0", + "resolved": "https://registry.npmjs.org/@jest/get-type/-/get-type-30.1.0.tgz", + "integrity": "sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/globals": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-29.7.0.tgz", - "integrity": "sha512-mpiz3dutLbkW2MNFubUGUEVLkTGiqW6yLVTA+JbP6fI6J5iL9Y0Nlg8k95pcF8ctKwCS7WVxteBs29hhfAotzQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/globals/-/globals-30.3.0.tgz", + "integrity": "sha512-+owLCBBdfpgL3HU+BD5etr1SvbXpSitJK0is1kiYjJxAAJggYMRQz5hSdd5pq1sSggfxPbw2ld71pt4x5wwViA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/types": "^29.6.3", - "jest-mock": "^29.7.0" + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/types": "30.3.0", + "jest-mock": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/pattern": { + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/pattern/-/pattern-30.0.1.tgz", + "integrity": "sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "jest-regex-util": "30.0.1" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/reporters": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-29.7.0.tgz", - "integrity": "sha512-DApq0KJbJOEzAFYjHADNNxAE3KbhxQB1y5Kplb5Waqw6zVbuWatSnMjE5gs8FUgEPmNsnZA3NCWl9NG0ia04Pg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/reporters/-/reporters-30.3.0.tgz", + "integrity": "sha512-a09z89S+PkQnL055bVj8+pe2Caed2PBOaczHcXCykW5ngxX9EWx/1uAwncxc/HiU0oZqfwseMjyhxgRjS49qPw==", "dev": true, "license": "MIT", "dependencies": { "@bcoe/v8-coverage": "^0.2.3", - "@jest/console": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", + "@jest/console": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", "@types/node": "*", - "chalk": "^4.0.0", - "collect-v8-coverage": "^1.0.0", - "exit": "^0.1.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", + "chalk": "^4.1.2", + "collect-v8-coverage": "^1.0.2", + "exit-x": "^0.2.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", "istanbul-lib-coverage": "^3.0.0", "istanbul-lib-instrument": "^6.0.0", "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", + "istanbul-lib-source-maps": "^5.0.0", "istanbul-reports": "^3.1.3", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", "slash": "^3.0.0", - "string-length": "^4.0.1", - "strip-ansi": "^6.0.0", + "string-length": "^4.0.2", "v8-to-istanbul": "^9.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -1369,162 +1784,197 @@ } } }, - "node_modules/@jest/reporters/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/@jest/reporters/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/@jest/reporters/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/@jest/reporters/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/@jest/reporters/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.2" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@jest/reporters/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@jest/schemas": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", - "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "version": "30.0.5", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-30.0.5.tgz", + "integrity": "sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==", "dev": true, "license": "MIT", "dependencies": { - "@sinclair/typebox": "^0.27.8" + "@sinclair/typebox": "^0.34.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/@jest/snapshot-utils": { + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/snapshot-utils/-/snapshot-utils-30.3.0.tgz", + "integrity": "sha512-ORbRN9sf5PP82v3FXNSwmO1OTDR2vzR2YTaR+E3VkSBZ8zadQE6IqYdYEeFH1NIkeB2HIGdF02dapb6K0Mj05g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/source-map": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-29.6.3.tgz", - "integrity": "sha512-MHjT95QuipcPrpLM+8JMSzFx6eHp5Bm+4XeFDJlwsvVBjmKNiIAvasGK2fxz2WbGRlnvqehFbh07MMa7n3YJnw==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/@jest/source-map/-/source-map-30.0.1.tgz", + "integrity": "sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/trace-mapping": "^0.3.18", - "callsites": "^3.0.0", - "graceful-fs": "^4.2.9" + "@jridgewell/trace-mapping": "^0.3.25", + "callsites": "^3.1.0", + "graceful-fs": "^4.2.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-result": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-29.7.0.tgz", - "integrity": "sha512-Fdx+tv6x1zlkJPcWXmMDAG2HBnaR9XPSd5aDWQVsfrZmLVT3lU1cwyxLgRmXR9yrq4NBoEm9BMsfgFzTQAbJYA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-result/-/test-result-30.3.0.tgz", + "integrity": "sha512-e/52nJGuD74AKTSe0P4y5wFRlaXP0qmrS17rqOMHeSwm278VyNyXE3gFO/4DTGF9w+65ra3lo3VKj0LBrzmgdQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/types": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "collect-v8-coverage": "^1.0.0" + "@jest/console": "30.3.0", + "@jest/types": "30.3.0", + "@types/istanbul-lib-coverage": "^2.0.6", + "collect-v8-coverage": "^1.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/test-sequencer": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-29.7.0.tgz", - "integrity": "sha512-GQwJ5WZVrKnOJuiYiAF52UNUJXgTZx1NHjFSEB0qEMmSZKAkdMoIzw/Cj6x6NF4AvV23AUqDpFzQkN/eYCYTxw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-30.3.0.tgz", + "integrity": "sha512-dgbWy9b8QDlQeRZcv7LNF+/jFiiYHTKho1xirauZ7kVwY7avjFF6uTT0RqlgudB5OuIPagFdVtfFMosjVbk1eA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", + "@jest/test-result": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/transform": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-29.7.0.tgz", - "integrity": "sha512-ok/BTPFzFKVMwO5eOHRrvnBVHdRy9IrsrW1GpMaQ9MCnilNLXQKmAX8s1YXDFaai9xJpac2ySzV0YeRRECr2Vw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/transform/-/transform-30.3.0.tgz", + "integrity": "sha512-TLKY33fSLVd/lKB2YI1pH69ijyUblO/BQvCj566YvnwuzoTNr648iE0j22vRvVNk2HsPwByPxATg3MleS3gf5A==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/types": "^29.6.3", - "@jridgewell/trace-mapping": "^0.3.18", - "babel-plugin-istanbul": "^6.1.1", - "chalk": "^4.0.0", + "@babel/core": "^7.27.4", + "@jest/types": "30.3.0", + "@jridgewell/trace-mapping": "^0.3.25", + "babel-plugin-istanbul": "^7.0.1", + "chalk": "^4.1.2", "convert-source-map": "^2.0.0", "fast-json-stable-stringify": "^2.1.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "micromatch": "^4.0.4", - "pirates": "^4.0.4", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "pirates": "^4.0.7", "slash": "^3.0.0", - "write-file-atomic": "^4.0.2" + "write-file-atomic": "^5.0.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jest/types": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/@jest/types/-/types-29.6.3.tgz", - "integrity": "sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/@jest/types/-/types-30.3.0.tgz", + "integrity": "sha512-JHm87k7bA33hpBngtU8h6UBub/fqqA9uXfw+21j5Hmk7ooPHlboRNxHq0JcMtC+n8VJGP1mcfnD3Mk+XKe1oSw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "@types/istanbul-lib-coverage": "^2.0.0", - "@types/istanbul-reports": "^3.0.0", + "@jest/pattern": "30.0.1", + "@jest/schemas": "30.0.5", + "@types/istanbul-lib-coverage": "^2.0.6", + "@types/istanbul-reports": "^3.0.4", "@types/node": "*", - "@types/yargs": "^17.0.8", - "chalk": "^4.0.0" + "@types/yargs": "^17.0.33", + "chalk": "^4.1.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/@jridgewell/gen-mapping": { @@ -1588,19 +2038,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@ljharb/through": { - "version": "2.3.14", - "resolved": "https://registry.npmjs.org/@ljharb/through/-/through-2.3.14.tgz", - "integrity": "sha512-ajBvlKpWucBB17FuQYUShqpqy8GRgYEpJW0vWJbUu1CV9lWyrDCapy0lScU8T8Z6qn49sSwJB3+M+evYIdGg+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "call-bind": "^1.0.8" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/@lukeed/csprng": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@lukeed/csprng/-/csprng-1.1.0.tgz", @@ -1610,41 +2047,53 @@ "node": ">=8" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, "node_modules/@nestjs/cli": { - "version": "10.4.9", - "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-10.4.9.tgz", - "integrity": "sha512-s8qYd97bggqeK7Op3iD49X2MpFtW4LVNLAwXFkfbRxKME6IYT7X0muNTJ2+QfI8hpbNx9isWkrLWIp+g5FOhiA==", + "version": "11.0.17", + "resolved": "https://registry.npmjs.org/@nestjs/cli/-/cli-11.0.17.tgz", + "integrity": "sha512-tOMgoB9k+Zb2WdKYPhbhceROLcDR1BFQZWfkBOGMRgBTo8rnC125E65UvThEA77vp4w+zKjqiSIv0leT+wdpHg==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "17.3.11", - "@angular-devkit/schematics": "17.3.11", - "@angular-devkit/schematics-cli": "17.3.11", - "@nestjs/schematics": "^10.0.1", - "chalk": "4.1.2", - "chokidar": "3.6.0", + "@angular-devkit/core": "19.2.22", + "@angular-devkit/schematics": "19.2.22", + "@angular-devkit/schematics-cli": "19.2.22", + "@inquirer/prompts": "7.10.1", + "@nestjs/schematics": "^11.0.1", + "ansis": "4.2.0", + "chokidar": "4.0.3", "cli-table3": "0.6.5", "commander": "4.1.1", - "fork-ts-checker-webpack-plugin": "9.0.2", - "glob": "10.4.5", - "inquirer": "8.2.6", + "fork-ts-checker-webpack-plugin": "9.1.0", + "glob": "13.0.6", "node-emoji": "1.11.0", "ora": "5.4.1", - "tree-kill": "1.2.2", "tsconfig-paths": "4.2.0", "tsconfig-paths-webpack-plugin": "4.2.0", - "typescript": "5.7.2", - "webpack": "5.97.1", + "typescript": "5.9.3", + "webpack": "5.105.4", "webpack-node-externals": "3.0.0" }, "bin": { "nest": "bin/nest.js" }, "engines": { - "node": ">= 16.14" + "node": ">= 20.11" }, "peerDependencies": { - "@swc/cli": "^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0", + "@swc/cli": "^0.1.62 || ^0.3.0 || ^0.4.0 || ^0.5.0 || ^0.6.0 || ^0.7.0", "@swc/core": "^1.3.62" }, "peerDependenciesMeta": { @@ -1656,106 +2105,15 @@ } } }, - "node_modules/@nestjs/cli/node_modules/es-module-lexer": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", - "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", - "dev": true, - "license": "MIT" - }, - "node_modules/@nestjs/cli/node_modules/eslint-scope": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", - "dev": true, - "license": "BSD-2-Clause", - "dependencies": { - "esrecurse": "^4.3.0", - "estraverse": "^4.1.1" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/@nestjs/cli/node_modules/estraverse": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", - "dev": true, - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, - "node_modules/@nestjs/cli/node_modules/typescript": { - "version": "5.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", - "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, - "node_modules/@nestjs/cli/node_modules/webpack": { - "version": "5.97.1", - "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.97.1.tgz", - "integrity": "sha512-EksG6gFY3L1eFMROS/7Wzgrii5mBAFe4rIr3r2BTfo7bcc+DWwFZ4OJ/miOuHJO/A85HwyI4eQ0F6IKXesO7Fg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/eslint-scope": "^3.7.7", - "@types/estree": "^1.0.6", - "@webassemblyjs/ast": "^1.14.1", - "@webassemblyjs/wasm-edit": "^1.14.1", - "@webassemblyjs/wasm-parser": "^1.14.1", - "acorn": "^8.14.0", - "browserslist": "^4.24.0", - "chrome-trace-event": "^1.0.2", - "enhanced-resolve": "^5.17.1", - "es-module-lexer": "^1.2.1", - "eslint-scope": "5.1.1", - "events": "^3.2.0", - "glob-to-regexp": "^0.4.1", - "graceful-fs": "^4.2.11", - "json-parse-even-better-errors": "^2.3.1", - "loader-runner": "^4.2.0", - "mime-types": "^2.1.27", - "neo-async": "^2.6.2", - "schema-utils": "^3.2.0", - "tapable": "^2.1.1", - "terser-webpack-plugin": "^5.3.10", - "watchpack": "^2.4.1", - "webpack-sources": "^3.2.3" - }, - "bin": { - "webpack": "bin/webpack.js" - }, - "engines": { - "node": ">=10.13.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/webpack" - }, - "peerDependenciesMeta": { - "webpack-cli": { - "optional": true - } - } - }, "node_modules/@nestjs/common": { - "version": "10.4.22", - "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-10.4.22.tgz", - "integrity": "sha512-fxJ4v85nDHaqT1PmfNCQ37b/jcv2OojtXTaK1P2uAXhzLf9qq6WNUOFvxBrV4fhQek1EQoT1o9oj5xAZmv3NRw==", + "version": "11.1.18", + "resolved": "https://registry.npmjs.org/@nestjs/common/-/common-11.1.18.tgz", + "integrity": "sha512-0sLq8Z+TIjLnz1Tqp0C/x9BpLbqpt1qEu0VcH4/fkE0y3F5JxhfK1AdKQ/SPbKhKgwqVDoY4gS8GQr2G6ujaWg==", "license": "MIT", "dependencies": { - "file-type": "20.4.1", + "file-type": "21.3.4", "iterare": "1.2.1", + "load-esm": "1.0.3", "tslib": "2.8.1", "uid": "2.0.2" }, @@ -1764,8 +2122,8 @@ "url": "https://opencollective.com/nest" }, "peerDependencies": { - "class-transformer": "*", - "class-validator": "*", + "class-transformer": ">=0.4.1", + "class-validator": ">=0.13.2", "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, @@ -1778,44 +2136,32 @@ } } }, - "node_modules/@nestjs/config": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@nestjs/config/-/config-4.0.3.tgz", - "integrity": "sha512-FQ3M3Ohqfl+nHAn5tp7++wUQw0f2nAk+SFKe8EpNRnIifPqvfJP6JQxPKtFLMOHbyer4X646prFG4zSRYEssQQ==", - "license": "MIT", - "dependencies": { - "dotenv": "17.2.3", - "dotenv-expand": "12.0.3", - "lodash": "4.17.23" - }, - "peerDependencies": { - "@nestjs/common": "^10.0.0 || ^11.0.0", - "rxjs": "^7.1.0" - } - }, "node_modules/@nestjs/core": { - "version": "10.4.22", - "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-10.4.22.tgz", - "integrity": "sha512-6IX9+VwjiKtCjx+mXVPncpkQ5ZjKfmssOZPFexmT+6T9H9wZ3svpYACAo7+9e7Nr9DZSoRZw3pffkJP7Z0UjaA==", + "version": "11.1.18", + "resolved": "https://registry.npmjs.org/@nestjs/core/-/core-11.1.18.tgz", + "integrity": "sha512-wR3DtGyk/LUAiPtbXDuWJJwVkWElKBY0sqnTzf9d4uM3+X18FRZhK7WFc47czsIGOdWuRsMeLYV+1Z9dO4zDEQ==", "hasInstallScript": true, "license": "MIT", "dependencies": { - "@nuxtjs/opencollective": "0.3.2", + "@nuxt/opencollective": "0.4.1", "fast-safe-stringify": "2.1.1", "iterare": "1.2.1", - "path-to-regexp": "3.3.0", + "path-to-regexp": "8.4.2", "tslib": "2.8.1", "uid": "2.0.2" }, + "engines": { + "node": ">= 20" + }, "funding": { "type": "opencollective", "url": "https://opencollective.com/nest" }, "peerDependencies": { - "@nestjs/common": "^10.0.0", - "@nestjs/microservices": "^10.0.0", - "@nestjs/platform-express": "^10.0.0", - "@nestjs/websockets": "^10.0.0", + "@nestjs/common": "^11.0.0", + "@nestjs/microservices": "^11.0.0", + "@nestjs/platform-express": "^11.0.0", + "@nestjs/websockets": "^11.0.0", "reflect-metadata": "^0.1.12 || ^0.2.0", "rxjs": "^7.1.0" }, @@ -1832,15 +2178,15 @@ } }, "node_modules/@nestjs/platform-express": { - "version": "10.4.22", - "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-10.4.22.tgz", - "integrity": "sha512-ySSq7Py/DFozzZdNDH67m/vHoeVdphDniWBnl6q5QVoXldDdrZIHLXLRMPayTDh5A95nt7jjJzmD4qpTbNQ6tA==", + "version": "11.1.18", + "resolved": "https://registry.npmjs.org/@nestjs/platform-express/-/platform-express-11.1.18.tgz", + "integrity": "sha512-s6GdHMTa3qx0fJewR74Xa30ysPHfBEqxIwZ7BGSTLoAEQ1vTP24urNl+b6+s49NFLEIOyeNho5fN/9/I17QlOw==", "license": "MIT", "dependencies": { - "body-parser": "1.20.4", - "cors": "2.8.5", - "express": "4.22.1", - "multer": "2.0.2", + "cors": "2.8.6", + "express": "5.2.1", + "multer": "2.1.1", + "path-to-regexp": "8.4.2", "tslib": "2.8.1" }, "funding": { @@ -1848,20 +2194,20 @@ "url": "https://opencollective.com/nest" }, "peerDependencies": { - "@nestjs/common": "^10.0.0", - "@nestjs/core": "^10.0.0" + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0" } }, "node_modules/@nestjs/schematics": { - "version": "10.2.3", - "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-10.2.3.tgz", - "integrity": "sha512-4e8gxaCk7DhBxVUly2PjYL4xC2ifDFexCqq1/u4TtivLGXotVk0wHdYuPYe1tHTHuR1lsOkRbfOCpkdTnigLVg==", + "version": "11.0.10", + "resolved": "https://registry.npmjs.org/@nestjs/schematics/-/schematics-11.0.10.tgz", + "integrity": "sha512-q9lr0wGwgBHLarD4uno3XiW4JX60WPlg2VTgbqPHl/6bT4u1IEEzj+q9Tad3bVnqL5mlDF3vrZ2tj+x13CJpmw==", "dev": true, "license": "MIT", "dependencies": { - "@angular-devkit/core": "17.3.11", - "@angular-devkit/schematics": "17.3.11", - "comment-json": "4.2.5", + "@angular-devkit/core": "19.2.23", + "@angular-devkit/schematics": "19.2.23", + "comment-json": "4.6.2", "jsonc-parser": "3.3.1", "pluralize": "8.0.0" }, @@ -1869,17 +2215,104 @@ "typescript": ">=4.8.2" } }, - "node_modules/@nestjs/schematics/node_modules/jsonc-parser": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", - "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/core": { + "version": "19.2.23", + "resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-19.2.23.tgz", + "integrity": "sha512-RazHPQkUEsNU/OZ75w9UeHxGFMthRiuAW2B/uA7eXExBj/1meHrrBfoCA56ujW2GUxVjRtSrMjylKh4R4meiYA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "8.18.0", + "ajv-formats": "3.0.1", + "jsonc-parser": "3.3.1", + "picomatch": "4.0.4", + "rxjs": "7.8.1", + "source-map": "0.7.4" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + }, + "peerDependencies": { + "chokidar": "^4.0.0" + }, + "peerDependenciesMeta": { + "chokidar": { + "optional": true + } + } + }, + "node_modules/@nestjs/schematics/node_modules/@angular-devkit/schematics": { + "version": "19.2.23", + "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-19.2.23.tgz", + "integrity": "sha512-Jzs7YM4X6azmHU7Mw5tQSPMuvaqYS8SLnZOJbtiXCy1JyuW9bm/WBBecNHMiuZ8LHXKhvQ6AVX1tKrzF6uiDmw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@angular-devkit/core": "19.2.23", + "jsonc-parser": "3.3.1", + "magic-string": "0.30.17", + "ora": "5.4.1", + "rxjs": "7.8.1" + }, + "engines": { + "node": "^18.19.1 || ^20.11.1 || >=22.0.0", + "npm": "^6.11.0 || ^7.5.6 || >=8.0.0", + "yarn": ">= 1.13.0" + } + }, + "node_modules/@nestjs/schematics/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@nestjs/schematics/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, "license": "MIT" }, + "node_modules/@nestjs/schematics/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/@nestjs/schematics/node_modules/rxjs": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.1.tgz", + "integrity": "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, "node_modules/@nestjs/testing": { - "version": "10.4.22", - "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-10.4.22.tgz", - "integrity": "sha512-HO9aPus3bAedAC+jKVAA8jTdaj4fs5M9fing4giHrcYV2txe9CvC1l1WAjwQ9RDhEHdugjY4y+FZA/U/YqPZrA==", + "version": "11.1.18", + "resolved": "https://registry.npmjs.org/@nestjs/testing/-/testing-11.1.18.tgz", + "integrity": "sha512-frzwNlpBgtAzI3hp/qo57DZoRO4RMTH1wST3QUYEhRTHyfPkLpzkWz3jV/mhApXjD0yT56Ptlzn6zuYPLh87Lw==", "dev": true, "license": "MIT", "dependencies": { @@ -1890,10 +2323,10 @@ "url": "https://opencollective.com/nest" }, "peerDependencies": { - "@nestjs/common": "^10.0.0", - "@nestjs/core": "^10.0.0", - "@nestjs/microservices": "^10.0.0", - "@nestjs/platform-express": "^10.0.0" + "@nestjs/common": "^11.0.0", + "@nestjs/core": "^11.0.0", + "@nestjs/microservices": "^11.0.0", + "@nestjs/platform-express": "^11.0.0" }, "peerDependenciesMeta": { "@nestjs/microservices": { @@ -1917,60 +2350,20 @@ "url": "https://paulmillr.com/funding/" } }, - "node_modules/@nodelib/fs.scandir": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", - "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", - "dev": true, + "node_modules/@nuxt/opencollective": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@nuxt/opencollective/-/opencollective-0.4.1.tgz", + "integrity": "sha512-GXD3wy50qYbxCJ652bDrDzgMr3NFEkIS374+IgFQKkCvk9yiYcLvX2XDYr7UyQxf4wK0e+yqDYRubZ0DtOxnmQ==", "license": "MIT", "dependencies": { - "@nodelib/fs.stat": "2.0.5", - "run-parallel": "^1.1.9" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.stat": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", - "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nodelib/fs.walk": { - "version": "1.2.8", - "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", - "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "@nodelib/fs.scandir": "2.1.5", - "fastq": "^1.6.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/@nuxtjs/opencollective": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@nuxtjs/opencollective/-/opencollective-0.3.2.tgz", - "integrity": "sha512-um0xL3fO7Mf4fDxcqx9KryrB7zgRM5JSlvGN5AGkP6JLM5XEKyjeAiPbNxdXVXQ16isuAhYpvP88NgL2BGd6aA==", - "license": "MIT", - "dependencies": { - "chalk": "^4.1.0", - "consola": "^2.15.0", - "node-fetch": "^2.6.1" + "consola": "^3.2.3" }, "bin": { "opencollective": "bin/opencollective.js" }, "engines": { - "node": ">=8.0.0", - "npm": ">=5.0.0" + "node": "^14.18.0 || >=16.10.0", + "npm": ">=5.10.0" } }, "node_modules/@paralleldrive/cuid2": { @@ -2008,77 +2401,94 @@ } }, "node_modules/@prisma/client": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/client/-/client-5.22.0.tgz", - "integrity": "sha512-M0SVXfyHnQREBKxCgyo7sffrKttwE6R8PMq330MIUF0pTwjUhLbW84pFDlf06B27XyCR++VtjugEnIHdr07SVA==", + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/client/-/client-6.19.3.tgz", + "integrity": "sha512-mKq3jQFhjvko5LTJFHGilsuQs+W+T3Gm451NzuTDGQxwCzwXHYnIu2zGkRoW+Exq3Rob7yp2MfzSrdIiZVhrBg==", "hasInstallScript": true, "license": "Apache-2.0", "engines": { - "node": ">=16.13" + "node": ">=18.18" }, "peerDependencies": { - "prisma": "*" + "prisma": "*", + "typescript": ">=5.1.0" }, "peerDependenciesMeta": { "prisma": { "optional": true + }, + "typescript": { + "optional": true } } }, + "node_modules/@prisma/config": { + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/config/-/config-6.19.3.tgz", + "integrity": "sha512-CBPT44BjlQxEt8kiMEauji2WHTDoVBOKl7UlewXmUgBPnr/oPRZC3psci5chJnYmH0ivEIog2OU9PGWoki3DLQ==", + "devOptional": true, + "license": "Apache-2.0", + "dependencies": { + "c12": "3.1.0", + "deepmerge-ts": "7.1.5", + "effect": "3.21.0", + "empathic": "2.0.0" + } + }, "node_modules/@prisma/debug": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.22.0.tgz", - "integrity": "sha512-AUt44v3YJeggO2ZU5BkXI7M4hu9BF2zzH2iF2V5pyXT/lRTyWiElZ7It+bRH1EshoMRxHgpYg4VB6rCM+mG5jQ==", + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-6.19.3.tgz", + "integrity": "sha512-ljkJ+SgpXNktLG0Q/n4JGYCkKf0f8oYLyjImS2I8e2q2WCfdRRtWER062ZV/ixaNP2M2VKlWXVJiGzZaUgbKZw==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/engines": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.22.0.tgz", - "integrity": "sha512-UNjfslWhAt06kVL3CjkuYpHAWSO6L4kDCVPegV6itt7nD1kSJavd3vhgAEhjglLJJKEdJ7oIqDJ+yHk6qO8gPA==", + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-6.19.3.tgz", + "integrity": "sha512-RSYxtlYFl5pJ8ZePgMv0lZ9IzVCOdTPOegrs2qcbAEFrBI1G33h6wyC9kjQvo0DnYEhEVY0X4LsuFHXLKQk88g==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.22.0", - "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "@prisma/fetch-engine": "5.22.0", - "@prisma/get-platform": "5.22.0" + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/fetch-engine": "6.19.3", + "@prisma/get-platform": "6.19.3" } }, "node_modules/@prisma/engines-version": { - "version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2.tgz", - "integrity": "sha512-2PTmxFR2yHW/eB3uqWtcgRcgAbG1rwG9ZriSvQw+nnb7c4uCr3RAcGMb6/zfE88SKlC1Nj2ziUvc96Z379mHgQ==", + "version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7.tgz", + "integrity": "sha512-03bgb1VD5gvuumNf+7fVGBzfpJPjmqV423l/WxsWk2cNQ42JD0/SsFBPhN6z8iAvdHs07/7ei77SKu7aZfq8bA==", "devOptional": true, "license": "Apache-2.0" }, "node_modules/@prisma/fetch-engine": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.22.0.tgz", - "integrity": "sha512-bkrD/Mc2fSvkQBV5EpoFcZ87AvOgDxbG99488a5cexp5Ccny+UM6MAe/UFkUC0wLYD9+9befNOqGiIJhhq+HbA==", + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-6.19.3.tgz", + "integrity": "sha512-tKtl/qco9Nt7LU5iKhpultD8O4vMCZcU2CHjNTnRrL1QvSUr5W/GcyFPjNL87GtRrwBc7ubXXD9xy4EvLvt8JA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.22.0", - "@prisma/engines-version": "5.22.0-44.605197351a3c8bdd595af2d2a9bc3025bca48ea2", - "@prisma/get-platform": "5.22.0" + "@prisma/debug": "6.19.3", + "@prisma/engines-version": "7.1.1-3.c2990dca591cba766e3b7ef5d9e8a84796e47ab7", + "@prisma/get-platform": "6.19.3" } }, "node_modules/@prisma/get-platform": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.22.0.tgz", - "integrity": "sha512-pHhpQdr1UPFpt+zFfnPazhulaZYCUqeIcPpJViYoq9R+D/yw4fjE+CtnsnKzPYm0ddUbeXUzjGVGIRVgPDCk4Q==", + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-6.19.3.tgz", + "integrity": "sha512-xFj1VcJ1N3MKooOQAGO0W5tsd0W2QzIvW7DD7c/8H14Zmp4jseeWAITm+w2LLoLrlhoHdPPh0NMZ8mfL6puoHA==", "devOptional": true, "license": "Apache-2.0", "dependencies": { - "@prisma/debug": "5.22.0" + "@prisma/debug": "6.19.3" } }, "node_modules/@sinclair/typebox": { - "version": "0.27.10", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", - "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "version": "0.34.49", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.49.tgz", + "integrity": "sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==", "dev": true, "license": "MIT" }, @@ -2093,24 +2503,30 @@ } }, "node_modules/@sinonjs/fake-timers": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", - "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.3.0.tgz", + "integrity": "sha512-m2xozxSfCIxjDdvbhIWazlP2i2aha/iUmbl94alpsIbd3iLTfeXgfBVbwyWogB6l++istyGZqamgA/EcqYf+Bg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { - "@sinonjs/commons": "^3.0.0" + "@sinonjs/commons": "^3.0.1" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "devOptional": true, + "license": "MIT" + }, "node_modules/@tokenizer/inflate": { - "version": "0.2.7", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", - "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", + "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", "license": "MIT", "dependencies": { - "debug": "^4.4.0", - "fflate": "^0.8.2", - "token-types": "^6.0.0" + "debug": "^4.4.3", + "token-types": "^6.1.1" }, "engines": { "node": ">=18" @@ -2154,6 +2570,17 @@ "dev": true, "license": "MIT" }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.1", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", + "integrity": "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==", + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2281,16 +2708,6 @@ "@types/send": "*" } }, - "node_modules/@types/graceful-fs": { - "version": "4.1.9", - "resolved": "https://registry.npmjs.org/@types/graceful-fs/-/graceful-fs-4.1.9.tgz", - "integrity": "sha512-olP3sd1qOEe5dXTSaFvQG+02VdRXcdytWLAZsAq1PecU8uqQAhkrnbli7DagjtXKW/Bl7YJbUsa8MPcuc8LHEQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/http-errors": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/@types/http-errors/-/http-errors-2.0.5.tgz", @@ -2326,14 +2743,14 @@ } }, "node_modules/@types/jest": { - "version": "29.5.14", - "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.14.tgz", - "integrity": "sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==", + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jest/-/jest-30.0.0.tgz", + "integrity": "sha512-XTYugzhuwqWjws0CVz8QpM36+T+Dz5mTEBKhNs/esGLnCIlGdRy+Dq78NRjd7ls7r8BC8ZRMOrKlkO1hU0JOwA==", "dev": true, "license": "MIT", "dependencies": { - "expect": "^29.0.0", - "pretty-format": "^29.0.0" + "expect": "^30.0.0", + "pretty-format": "^30.0.0" } }, "node_modules/@types/json-schema": { @@ -2351,13 +2768,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.19.37", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.37.tgz", - "integrity": "sha512-8kzdPJ3FsNsVIurqBs7oodNnCEVbni9yUEkaHbgptDACOPW04jimGagZ51E6+lXUwJjgnBw+hyko/lkFWCldqw==", + "version": "24.12.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.12.1.tgz", + "integrity": "sha512-v6Ct1W1Fdz7xg5jYCg4FTrbNcIqzds2jv/HL6+5Rs/Cyjf0oljAgW59zvDZXyYG7nt9MLrAFJv9erP/fLjwt+g==", "dev": true, "license": "MIT", "dependencies": { - "undici-types": "~6.21.0" + "undici-types": "~7.16.0" } }, "node_modules/@types/qs": { @@ -2416,9 +2833,9 @@ } }, "node_modules/@types/supertest": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-6.0.3.tgz", - "integrity": "sha512-8WzXq62EXFhJ7QsH3Ocb/iKQ/Ty9ZVWnVzoTKc9tyyFRRF3a74Tk2+TLFgaFFw364Ere+npzHKEJ6ga2LzIL7w==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@types/supertest/-/supertest-7.2.0.tgz", + "integrity": "sha512-uh2Lv57xvggst6lCqNdFAmDSvoMG7M/HDtX4iUCquxQ5EGPtaPM5PL5Hmi7LCvOG8db7YaCPNJEeoI8s/WzIQw==", "dev": true, "license": "MIT", "dependencies": { @@ -2426,6 +2843,12 @@ "@types/superagent": "^8.1.0" } }, + "node_modules/@types/validator": { + "version": "13.15.10", + "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.15.10.tgz", + "integrity": "sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==", + "license": "MIT" + }, "node_modules/@types/yargs": { "version": "17.0.35", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.35.tgz", @@ -2444,20 +2867,20 @@ "license": "MIT" }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.57.0.tgz", - "integrity": "sha512-qeu4rTHR3/IaFORbD16gmjq9+rEs9fGKdX0kF6BKSfi+gCuG3RCKLlSBYzn/bGsY9Tj7KE/DAQStbp8AHJGHEQ==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.58.0.tgz", + "integrity": "sha512-RLkVSiNuUP1C2ROIWfqX+YcUfLaSnxGE/8M+Y57lopVwg9VTYYfhuz15Yf1IzCKgZj6/rIbYTmJCUSqr76r0Wg==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/type-utils": "8.57.0", - "@typescript-eslint/utils": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/type-utils": "8.58.0", + "@typescript-eslint/utils": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2467,22 +2890,32 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.57.0", + "@typescript-eslint/parser": "^8.58.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", + "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 4" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.57.0.tgz", - "integrity": "sha512-XZzOmihLIr8AD1b9hL9ccNMzEMWt/dE2u7NyTY9jJG6YNiNthaD5XtUHVF2uCXZ15ng+z2hT3MVuxnUYhq6k1g==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.58.0.tgz", + "integrity": "sha512-rLoGZIf9afaRBYsPUMtvkDWykwXwUPL60HebR4JgTI8mxfFe2cQTu3AGitANp4b9B2QlVru6WzjgB2IzJKiCSA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3" }, "engines": { @@ -2494,18 +2927,18 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.57.0.tgz", - "integrity": "sha512-pR+dK0BlxCLxtWfaKQWtYr7MhKmzqZxuii+ZjuFlZlIGRZm22HnXFqa2eY+90MUz8/i80YJmzFGDUsi8dMOV5w==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.58.0.tgz", + "integrity": "sha512-8Q/wBPWLQP1j16NxoPNIKpDZFMaxl7yWIoqXWYeWO+Bbd2mjgvoF0dxP2jKZg5+x49rgKdf7Ck473M8PC3V9lg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.57.0", - "@typescript-eslint/types": "^8.57.0", + "@typescript-eslint/tsconfig-utils": "^8.58.0", + "@typescript-eslint/types": "^8.58.0", "debug": "^4.4.3" }, "engines": { @@ -2516,18 +2949,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.57.0.tgz", - "integrity": "sha512-nvExQqAHF01lUM66MskSaZulpPL5pgy5hI5RfrxviLgzZVffB5yYzw27uK/ft8QnKXI2X0LBrHJFr1TaZtAibw==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.58.0.tgz", + "integrity": "sha512-W1Lur1oF50FxSnNdGp3Vs6P+yBRSmZiw4IIjEeYxd8UQJwhUF0gDgDD/W/Tgmh73mxgEU3qX0Bzdl/NGuSPEpQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0" + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2538,9 +2971,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.57.0.tgz", - "integrity": "sha512-LtXRihc5ytjJIQEH+xqjB0+YgsV4/tW35XKX3GTZHpWtcC8SPkT/d4tqdf1cKtesryHm2bgp6l555NYcT2NLvA==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.58.0.tgz", + "integrity": "sha512-doNSZEVJsWEu4htiVC+PR6NpM+pa+a4ClH9INRWOWCUzMst/VA9c4gXq92F8GUD1rwhNvRLkgjfYtFXegXQF7A==", "dev": true, "license": "MIT", "engines": { @@ -2551,21 +2984,21 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.57.0.tgz", - "integrity": "sha512-yjgh7gmDcJ1+TcEg8x3uWQmn8ifvSupnPfjP21twPKrDP/pTHlEQgmKcitzF/rzPSmv7QjJ90vRpN4U+zoUjwQ==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.58.0.tgz", + "integrity": "sha512-aGsCQImkDIqMyx1u4PrVlbi/krmDsQUs4zAcCV6M7yPcPev+RqVlndsJy9kJ8TLihW9TZ0kbDAzctpLn5o+lOg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0", - "@typescript-eslint/utils": "8.57.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0", "debug": "^4.4.3", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2576,13 +3009,13 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.57.0.tgz", - "integrity": "sha512-dTLI8PEXhjUC7B9Kre+u0XznO696BhXcTlOn0/6kf1fHaQW8+VjJAVHJ3eTI14ZapTxdkOmc80HblPQLaEeJdg==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.58.0.tgz", + "integrity": "sha512-O9CjxypDT89fbHxRfETNoAnHj/i6IpRK0CvbVN3qibxlLdo5p5hcLmUuCCrHMpxiWSwKyI8mCP7qRNYuOJ0Uww==", "dev": true, "license": "MIT", "engines": { @@ -2594,21 +3027,21 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.57.0.tgz", - "integrity": "sha512-m7faHcyVg0BT3VdYTlX8GdJEM7COexXxS6KqGopxdtkQRvBanK377QDHr4W/vIPAR+ah9+B/RclSW5ldVniO1Q==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.58.0.tgz", + "integrity": "sha512-7vv5UWbHqew/dvs+D3e1RvLv1v2eeZ9txRHPnEEBUgSNLx5ghdzjHa0sgLWYVKssH+lYmV0JaWdoubo0ncGYLA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.57.0", - "@typescript-eslint/tsconfig-utils": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/visitor-keys": "8.57.0", + "@typescript-eslint/project-service": "8.58.0", + "@typescript-eslint/tsconfig-utils": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/visitor-keys": "8.58.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", - "ts-api-utils": "^2.4.0" + "ts-api-utils": "^2.5.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2618,20 +3051,59 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.5" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.57.0.tgz", - "integrity": "sha512-5iIHvpD3CZe06riAsbNxxreP+MuYgVUsV0n4bwLH//VJmgtt54sQeY2GszntJ4BjYCpMzrfVh2SBnUQTtys2lQ==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.58.0.tgz", + "integrity": "sha512-RfeSqcFeHMHlAWzt4TBjWOAtoW9lnsAGiP3GbaX9uVgTYYrMbVnGONEfUCiSss+xMHFl+eHZiipmA8WkQ7FuNA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.57.0", - "@typescript-eslint/types": "8.57.0", - "@typescript-eslint/typescript-estree": "8.57.0" + "@typescript-eslint/scope-manager": "8.58.0", + "@typescript-eslint/types": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2642,17 +3114,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", - "typescript": ">=4.8.4 <6.0.0" + "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.57.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.57.0.tgz", - "integrity": "sha512-zm6xx8UT/Xy2oSr2ZXD0pZo7Jx2XsCoID2IUh9YSTFRu7z+WdwYTRk6LhUftm1crwqbuoF6I8zAFeCMw0YjwDg==", + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.58.0.tgz", + "integrity": "sha512-XJ9UD9+bbDo4a4epraTwG3TsNPeiB9aShrUneAVXy8q4LuwowN+qu89/6ByLMINqvIMeI9H9hOHQtg/ijrYXzQ==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.57.0", + "@typescript-eslint/types": "8.58.0", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2683,6 +3155,299 @@ "dev": true, "license": "ISC" }, + "node_modules/@unrs/resolver-binding-android-arm-eabi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm-eabi/-/resolver-binding-android-arm-eabi-1.11.1.tgz", + "integrity": "sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-android-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-android-arm64/-/resolver-binding-android-arm64-1.11.1.tgz", + "integrity": "sha512-lCxkVtb4wp1v+EoN+HjIG9cIIzPkX5OtM03pQYkG+U5O/wL53LC4QbIeazgiKqluGeVEeBlZahHalCaBvU1a2g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-arm64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-arm64/-/resolver-binding-darwin-arm64-1.11.1.tgz", + "integrity": "sha512-gPVA1UjRu1Y/IsB/dQEsp2V1pm44Of6+LWvbLc9SDk1c2KhhDRDBUkQCYVWe6f26uJb3fOK8saWMgtX8IrMk3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-darwin-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-darwin-x64/-/resolver-binding-darwin-x64-1.11.1.tgz", + "integrity": "sha512-cFzP7rWKd3lZaCsDze07QX1SC24lO8mPty9vdP+YVa3MGdVgPmFc59317b2ioXtgCMKGiCLxJ4HQs62oz6GfRQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@unrs/resolver-binding-freebsd-x64": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-freebsd-x64/-/resolver-binding-freebsd-x64-1.11.1.tgz", + "integrity": "sha512-fqtGgak3zX4DCB6PFpsH5+Kmt/8CIi4Bry4rb1ho6Av2QHTREM+47y282Uqiu3ZRF5IQioJQ5qWRV6jduA+iGw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-gnueabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-gnueabihf/-/resolver-binding-linux-arm-gnueabihf-1.11.1.tgz", + "integrity": "sha512-u92mvlcYtp9MRKmP+ZvMmtPN34+/3lMHlyMj7wXJDeXxuM0Vgzz0+PPJNsro1m3IZPYChIkn944wW8TYgGKFHw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm-musleabihf": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm-musleabihf/-/resolver-binding-linux-arm-musleabihf-1.11.1.tgz", + "integrity": "sha512-cINaoY2z7LVCrfHkIcmvj7osTOtm6VVT16b5oQdS4beibX2SYBwgYLmqhBjA1t51CarSaBuX5YNsWLjsqfW5Cw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-gnu/-/resolver-binding-linux-arm64-gnu-1.11.1.tgz", + "integrity": "sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-arm64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-arm64-musl/-/resolver-binding-linux-arm64-musl-1.11.1.tgz", + "integrity": "sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-ppc64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-ppc64-gnu/-/resolver-binding-linux-ppc64-gnu-1.11.1.tgz", + "integrity": "sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-gnu/-/resolver-binding-linux-riscv64-gnu-1.11.1.tgz", + "integrity": "sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-riscv64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-riscv64-musl/-/resolver-binding-linux-riscv64-musl-1.11.1.tgz", + "integrity": "sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-s390x-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-s390x-gnu/-/resolver-binding-linux-s390x-gnu-1.11.1.tgz", + "integrity": "sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-gnu": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-gnu/-/resolver-binding-linux-x64-gnu-1.11.1.tgz", + "integrity": "sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-linux-x64-musl": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-linux-x64-musl/-/resolver-binding-linux-x64-musl-1.11.1.tgz", + "integrity": "sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@unrs/resolver-binding-wasm32-wasi": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-wasm32-wasi/-/resolver-binding-wasm32-wasi-1.11.1.tgz", + "integrity": "sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==", + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.11" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@unrs/resolver-binding-win32-arm64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-arm64-msvc/-/resolver-binding-win32-arm64-msvc-1.11.1.tgz", + "integrity": "sha512-nRcz5Il4ln0kMhfL8S3hLkxI85BXs3o8EYoattsJNdsX4YUU89iOkVn7g0VHSRxFuVMdM4Q1jEpIId1Ihim/Uw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-ia32-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-ia32-msvc/-/resolver-binding-win32-ia32-msvc-1.11.1.tgz", + "integrity": "sha512-DCEI6t5i1NmAZp6pFonpD5m7i6aFrpofcp4LA2i8IIq60Jyo28hamKBxNrZcyOwVOZkgsRp9O2sXWBWP8MnvIQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@unrs/resolver-binding-win32-x64-msvc": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.11.1.tgz", + "integrity": "sha512-lrW200hZdbfRtztbygyaq/6jP6AKE8qQN2KvPcJ+x7wiD038YtnYtZ82IMNJ69GJibV7bwL3y9FgK+5w/pYt6g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -2859,13 +3624,13 @@ "license": "Apache-2.0" }, "node_modules/accepts": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", - "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { - "mime-types": "~2.1.34", - "negotiator": "0.6.3" + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" }, "engines": { "node": ">= 0.6" @@ -2890,7 +3655,6 @@ "integrity": "sha512-wKmbr/DDiIXzEOiWrTTUcDm24kQ2vGfZQvM2fwg2vXqR5uW6aapr7ObPtj1th32b9u90/Pf4AItvdTh42fBmVQ==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10.13.0" }, @@ -2922,15 +3686,15 @@ } }, "node_modules/ajv": { - "version": "8.12.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", - "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", + "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", "dev": true, "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" }, "funding": { @@ -2939,9 +3703,9 @@ } }, "node_modules/ajv-formats": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", - "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", + "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "dev": true, "license": "MIT", "dependencies": { @@ -2956,17 +3720,38 @@ } } }, - "node_modules/ajv-keywords": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", - "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "node_modules/ajv-formats/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { - "fast-deep-equal": "^3.1.3" + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "license": "MIT", "peerDependencies": { - "ajv": "^8.8.2" + "ajv": "^6.9.1" } }, "node_modules/ansi-colors": { @@ -2995,19 +3780,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/ansi-escapes/node_modules/type-fest": { - "version": "0.21.3", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", - "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true, - "license": "(MIT OR CC0-1.0)", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -3022,6 +3794,7 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, "license": "MIT", "dependencies": { "color-convert": "^2.0.1" @@ -3033,6 +3806,16 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/ansis": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/ansis/-/ansis-4.2.0.tgz", + "integrity": "sha512-HqZ5rWlFjGiV0tDm3UxxgNRqsOTniqoKZu0pIAfh7TZQMGuZK+hH0drySty0si0QXj1ieop4+SkSfPZBPPkHig==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + } + }, "node_modules/anymatch": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", @@ -3048,9 +3831,9 @@ } }, "node_modules/anymatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -3080,12 +3863,6 @@ "dev": true, "license": "Python-2.0" }, - "node_modules/array-flatten": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", - "license": "MIT" - }, "node_modules/array-timsort": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-timsort/-/array-timsort-1.0.3.tgz", @@ -3108,85 +3885,58 @@ "license": "MIT" }, "node_modules/babel-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-29.7.0.tgz", - "integrity": "sha512-BrvGY3xZSwEcCzKvKsCi2GgHqDqsYkOP4/by5xCgIwGXQxIEh+8ew3gmrE1y7XRR6LHZIj6yLYnUi/mm2KXKBg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-jest/-/babel-jest-30.3.0.tgz", + "integrity": "sha512-gRpauEU2KRrCox5Z296aeVHR4jQ98BCnu0IO332D/xpHNOsIH/bgSRk9k6GbKIbBw8vFeN6ctuu6tV8WOyVfYQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/transform": "^29.7.0", - "@types/babel__core": "^7.1.14", - "babel-plugin-istanbul": "^6.1.1", - "babel-preset-jest": "^29.6.3", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", + "@jest/transform": "30.3.0", + "@types/babel__core": "^7.20.5", + "babel-plugin-istanbul": "^7.0.1", + "babel-preset-jest": "30.3.0", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", "slash": "^3.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.8.0" + "@babel/core": "^7.11.0 || ^8.0.0-0" } }, "node_modules/babel-plugin-istanbul": { - "version": "6.1.1", - "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz", - "integrity": "sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/babel-plugin-istanbul/-/babel-plugin-istanbul-7.0.1.tgz", + "integrity": "sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==", "dev": true, "license": "BSD-3-Clause", + "workspaces": [ + "test/babel-8" + ], "dependencies": { "@babel/helper-plugin-utils": "^7.0.0", "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-instrument": "^5.0.4", + "@istanbuljs/schema": "^0.1.3", + "istanbul-lib-instrument": "^6.0.2", "test-exclude": "^6.0.0" }, "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/istanbul-lib-instrument": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz", - "integrity": "sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==", - "dev": true, - "license": "BSD-3-Clause", - "dependencies": { - "@babel/core": "^7.12.3", - "@babel/parser": "^7.14.7", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.2.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/babel-plugin-istanbul/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "dev": true, - "license": "ISC", - "bin": { - "semver": "bin/semver.js" + "node": ">=12" } }, "node_modules/babel-plugin-jest-hoist": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.6.3.tgz", - "integrity": "sha512-ESAc/RJvGTFEzRwOTT4+lNDk/GNHMkKbNzsvT0qKRfDyyYTskxB5rnU2njIDYVxXCBHHEI1c0YwHob3WaYujOg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-30.3.0.tgz", + "integrity": "sha512-+TRkByhsws6sfPjVaitzadk1I0F5sPvOVUH5tyTSzhePpsGIVrdeunHSw/C36QeocS95OOk8lunc4rlu5Anwsg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.3.3", - "@babel/types": "^7.3.3", - "@types/babel__core": "^7.1.14", - "@types/babel__traverse": "^7.0.6" + "@types/babel__core": "^7.20.5" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/babel-preset-current-node-syntax": { @@ -3217,31 +3967,28 @@ } }, "node_modules/babel-preset-jest": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-29.6.3.tgz", - "integrity": "sha512-0B3bhxR6snWXJZtR/RliHTDPRgn1sNHOR0yVtq/IiQFyuOVjFS+wuio/R4gSNkyYmKmJB4wGZv2NZanmKmTnNA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-30.3.0.tgz", + "integrity": "sha512-6ZcUbWHC+dMz2vfzdNwi87Z1gQsLNK2uLuK1Q89R11xdvejcivlYYwDlEv0FHX3VwEXpbBQ9uufB/MUNpZGfhQ==", "dev": true, "license": "MIT", "dependencies": { - "babel-plugin-jest-hoist": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0" + "babel-plugin-jest-hoist": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { - "@babel/core": "^7.0.0" + "@babel/core": "^7.11.0 || ^8.0.0-beta.1" } }, "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } + "license": "MIT" }, "node_modules/base64-js": { "version": "1.5.1", @@ -3265,9 +4012,9 @@ "license": "MIT" }, "node_modules/baseline-browser-mapping": { - "version": "2.10.8", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.8.tgz", - "integrity": "sha512-PCLz/LXGBsNTErbtB6i5u4eLpHeMfi93aUv5duMmj6caNu6IphS4q6UevDnL36sZQv9lrP11dbPKGMaXPwMKfQ==", + "version": "2.10.13", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.13.tgz", + "integrity": "sha512-BL2sTuHOdy0YT1lYieUxTw/QMtPBC3pmlJC6xk8BBYVv6vcw3SGdKemQ+Xsx9ik2F/lYDO9tqsFQH1r9PFuHKw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -3277,19 +4024,6 @@ "node": ">=6.0.0" } }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -3303,55 +4037,38 @@ } }, "node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", + "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", "dependencies": { - "bytes": "~3.1.2", - "content-type": "~1.0.5", - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "~1.2.0", - "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", - "on-finished": "~2.4.1", - "qs": "~6.14.0", - "raw-body": "~2.5.3", - "type-is": "~1.6.18", - "unpipe": "~1.0.0" + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.3", + "http-errors": "^2.0.0", + "iconv-lite": "^0.7.0", + "on-finished": "^2.4.1", + "qs": "^6.14.1", + "raw-body": "^3.0.1", + "type-is": "^2.0.1" }, "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/body-parser/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/body-parser/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/brace-expansion": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.4.tgz", - "integrity": "sha512-h+DEnpVvxmfVefa4jFbCf5HdH5YMDXRsmKflpf1pILZWRFlTbJpxeU55nJl4Smt5HQaGzg1o6RHFPJaOqnmBDg==", + "version": "1.1.13", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", + "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" } }, "node_modules/braces": { @@ -3368,9 +4085,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.1", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", - "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "version": "4.28.2", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", + "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", "dev": true, "funding": [ { @@ -3388,11 +4105,11 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.9.0", - "caniuse-lite": "^1.0.30001759", - "electron-to-chromium": "^1.5.263", - "node-releases": "^2.0.27", - "update-browserslist-db": "^1.2.0" + "baseline-browser-mapping": "^2.10.12", + "caniuse-lite": "^1.0.30001782", + "electron-to-chromium": "^1.5.328", + "node-releases": "^2.0.36", + "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" @@ -3475,23 +4192,46 @@ "node": ">= 0.8" } }, - "node_modules/call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, + "node_modules/c12": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/c12/-/c12-3.1.0.tgz", + "integrity": "sha512-uWoS8OU1MEIsOv8p/5a82c3H31LsWVR5qiyXVfBNOzfffjUWtPnhAb4BYI2uG2HfGmZmFjCtui5XNWaps+iFuw==", + "devOptional": true, "license": "MIT", "dependencies": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" + "chokidar": "^4.0.3", + "confbox": "^0.2.2", + "defu": "^6.1.4", + "dotenv": "^16.6.1", + "exsolve": "^1.0.7", + "giget": "^2.0.0", + "jiti": "^2.4.2", + "ohash": "^2.0.11", + "pathe": "^2.0.3", + "perfect-debounce": "^1.0.0", + "pkg-types": "^2.2.0", + "rc9": "^2.1.2" }, + "peerDependencies": { + "magicast": "^0.3.5" + }, + "peerDependenciesMeta": { + "magicast": { + "optional": true + } + } + }, + "node_modules/c12/node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "devOptional": true, + "license": "BSD-2-Clause", "engines": { - "node": ">= 0.4" + "node": ">=12" }, "funding": { - "url": "https://github.com/sponsors/ljharb" + "url": "https://dotenvx.com" } }, "node_modules/call-bind-apply-helpers": { @@ -3544,9 +4284,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001779", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001779.tgz", - "integrity": "sha512-U5og2PN7V4DMgF50YPNtnZJGWVLFjjsN3zb6uMT5VGYIewieDj1upwfuVNXf4Kor+89c3iCRJnSzMD5LmTvsfA==", + "version": "1.0.30001784", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001784.tgz", + "integrity": "sha512-WU346nBTklUV9YfUl60fqRbU5ZqyXlqvo1SgigE1OAXK5bFL8LL9q1K7aap3N739l4BvNqnkm3YrGHiY9sfUQw==", "dev": true, "funding": [ { @@ -3568,6 +4308,7 @@ "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, "license": "MIT", "dependencies": { "ansi-styles": "^4.1.0", @@ -3591,35 +4332,26 @@ } }, "node_modules/chardet": { - "version": "0.7.0", - "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz", - "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", "dev": true, "license": "MIT" }, "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "devOptional": true, "license": "MIT", "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" + "readdirp": "^4.0.1" }, "engines": { - "node": ">= 8.10.0" + "node": ">= 14.16.0" }, "funding": { "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" } }, "node_modules/chrome-trace-event": { @@ -3633,9 +4365,9 @@ } }, "node_modules/ci-info": { - "version": "3.9.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz", - "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==", + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.4.0.tgz", + "integrity": "sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==", "dev": true, "funding": [ { @@ -3648,13 +4380,40 @@ "node": ">=8" } }, + "node_modules/citty": { + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", + "integrity": "sha512-tskPPKEs8D2KPafUypv2gxwJP8h/OaJmC82QQGGDQcHvXX43xF2VDACcJVmZ0EuSxkpO9Kc4MlrA3q0+FG58AQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "consola": "^3.2.3" + } + }, "node_modules/cjs-module-lexer": { - "version": "1.4.3", - "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-1.4.3.tgz", - "integrity": "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q==", + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/cjs-module-lexer/-/cjs-module-lexer-2.2.0.tgz", + "integrity": "sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==", "dev": true, "license": "MIT" }, + "node_modules/class-transformer": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/class-transformer/-/class-transformer-0.5.1.tgz", + "integrity": "sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==", + "license": "MIT" + }, + "node_modules/class-validator": { + "version": "0.14.4", + "resolved": "https://registry.npmjs.org/class-validator/-/class-validator-0.14.4.tgz", + "integrity": "sha512-AwNusCCam51q703dW82x95tOqQp6oC9HNUl724KxJJOfnKscI8dOloXFgyez7LbTTKWuRBA37FScqVbJEoq8Yw==", + "license": "MIT", + "dependencies": { + "@types/validator": "^13.15.3", + "libphonenumber-js": "^1.11.1", + "validator": "^13.15.22" + } + }, "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", @@ -3698,13 +4457,13 @@ } }, "node_modules/cli-width": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-3.0.0.tgz", - "integrity": "sha512-FxqpkPPwu1HjuN93Omfm4h8uIanXofW0RxVEW3k5RKx+mJJYSthzNhp32Kzxxy3YAEZ/Dc/EWN1vZRY0+kOhbw==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", "dev": true, "license": "ISC", "engines": { - "node": ">= 10" + "node": ">= 12" } }, "node_modules/cliui": { @@ -3772,6 +4531,7 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, "license": "MIT", "dependencies": { "color-name": "~1.1.4" @@ -3784,6 +4544,7 @@ "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, "license": "MIT" }, "node_modules/combined-stream": { @@ -3810,17 +4571,14 @@ } }, "node_modules/comment-json": { - "version": "4.2.5", - "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.2.5.tgz", - "integrity": "sha512-bKw/r35jR3HGt5PEPm1ljsQQGyCrR8sFGNiN5L+ykDHdpO8Smxkrkla9Yi6NkQyUrb8V54PGhfMs6NrIwtxtdw==", + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/comment-json/-/comment-json-4.6.2.tgz", + "integrity": "sha512-R2rze/hDX30uul4NZoIZ76ImSJLFxn/1/ZxtKC1L77y2X1k+yYu1joKbAtMA2Fg3hZrTOiw0I5mwVMo0cf250w==", "dev": true, "license": "MIT", "dependencies": { "array-timsort": "^1.0.3", - "core-util-is": "^1.0.3", - "esprima": "^4.0.1", - "has-own-prop": "^2.0.0", - "repeat-string": "^1.6.1" + "esprima": "^4.0.1" }, "engines": { "node": ">= 6" @@ -3858,22 +4616,33 @@ "typedarray": "^0.0.6" } }, - "node_modules/consola": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/consola/-/consola-2.15.3.tgz", - "integrity": "sha512-9vAdYbHj6x2fLKC4+oPH0kFzY/orMZyG2Aj+kNylHxKGJ/Ed4dpNyAQYwJOdqO4zdM7XpVHmyejQDcQHrnuXbw==", + "node_modules/confbox": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.2.4.tgz", + "integrity": "sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==", + "devOptional": true, "license": "MIT" }, - "node_modules/content-disposition": { - "version": "0.5.4", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", - "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "node_modules/consola": { + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", + "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==", "license": "MIT", - "dependencies": { - "safe-buffer": "5.2.1" - }, "engines": { - "node": ">= 0.6" + "node": "^14.18.0 || >=16.10.0" + } + }, + "node_modules/content-disposition": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", + "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/content-type": { @@ -3902,10 +4671,13 @@ } }, "node_modules/cookie-signature": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", - "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", - "license": "MIT" + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } }, "node_modules/cookiejar": { "version": "2.1.4", @@ -3914,17 +4686,10 @@ "dev": true, "license": "MIT" }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "dev": true, - "license": "MIT" - }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", "dependencies": { "object-assign": "^4", @@ -3932,6 +4697,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cosmiconfig": { @@ -3961,28 +4730,6 @@ } } }, - "node_modules/create-jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/create-jest/-/create-jest-29.7.0.tgz", - "integrity": "sha512-Adz2bdH0Vq3F53KEMJOoftQFutWCukm6J24wbPWRO4k1kMY7gS7ds/uoJkNuV8wDCtWWnuwGcJwpWcih+zEW1Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "exit": "^0.1.2", - "graceful-fs": "^4.2.9", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "prompts": "^2.0.1" - }, - "bin": { - "create-jest": "bin/create-jest.js" - }, - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", @@ -4054,6 +4801,16 @@ "node": ">=0.10.0" } }, + "node_modules/deepmerge-ts": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/deepmerge-ts/-/deepmerge-ts-7.1.5.tgz", + "integrity": "sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==", + "devOptional": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=16.0.0" + } + }, "node_modules/defaults": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", @@ -4067,23 +4824,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "node_modules/defu": { + "version": "6.1.6", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.6.tgz", + "integrity": "sha512-f8mefEW4WIVg4LckePx3mALjQSPQgFlg9U8yaPdlsbdYcHQyj9n2zL2LJEA52smeYxOvmd/nB7TpMtHGMTHcug==", + "devOptional": true, + "license": "MIT" }, "node_modules/delayed-stream": { "version": "1.0.0", @@ -4104,15 +4850,12 @@ "node": ">= 0.8" } }, - "node_modules/destroy": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", - "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", - "license": "MIT", - "engines": { - "node": ">= 0.8", - "npm": "1.2.8000 || >= 1.4.16" - } + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "devOptional": true, + "license": "MIT" }, "node_modules/detect-newline": { "version": "3.1.0", @@ -4145,60 +4888,11 @@ "node": ">=0.3.1" } }, - "node_modules/diff-sequences": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", - "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/doctrine": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", - "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "esutils": "^2.0.2" - }, - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/dotenv": { - "version": "17.2.3", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.3.tgz", - "integrity": "sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dotenv-expand": { - "version": "12.0.3", - "resolved": "https://registry.npmjs.org/dotenv-expand/-/dotenv-expand-12.0.3.tgz", - "integrity": "sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==", - "license": "BSD-2-Clause", - "dependencies": { - "dotenv": "^16.4.5" - }, - "engines": { - "node": ">=12" - }, - "funding": { - "url": "https://dotenvx.com" - } - }, - "node_modules/dotenv-expand/node_modules/dotenv": { - "version": "16.6.1", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", - "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.4.0.tgz", + "integrity": "sha512-kCKF62fwtzwYm0IGBNjRUjtJgMfGapII+FslMHIjMR5KTnwEmBmWLDRSnc3XSNP8bNy34tekgQyDT0hr7pERRQ==", + "dev": true, "license": "BSD-2-Clause", "engines": { "node": ">=12" @@ -4234,10 +4928,21 @@ "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "license": "MIT" }, + "node_modules/effect": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/effect/-/effect-3.21.0.tgz", + "integrity": "sha512-PPN80qRokCd1f015IANNhrwOnLO7GrrMQfk4/lnZRE/8j7UPWrNNjPV0uBrZutI/nHzernbW+J0hdqQysHiSnQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "fast-check": "^3.23.1" + } + }, "node_modules/electron-to-chromium": { - "version": "1.5.313", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.313.tgz", - "integrity": "sha512-QBMrTWEf00GXZmJyx2lbYD45jpI3TUFnNIzJ5BBc8piGUDwMPa1GV6HJWTZVvY/eiN3fSopl7NRbgGp9sZ9LTA==", + "version": "1.5.331", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.331.tgz", + "integrity": "sha512-IbxXrsTlD3hRodkLnbxAPP4OuJYdWCeM3IOdT+CpcMoIwIoDfCmRpEtSPfwBXxVkg9xmBeY7Lz2Eo2TDn/HC3Q==", "dev": true, "license": "ISC" }, @@ -4261,6 +4966,16 @@ "dev": true, "license": "MIT" }, + "node_modules/empathic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/empathic/-/empathic-2.0.0.tgz", + "integrity": "sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==", + "devOptional": true, + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", @@ -4271,9 +4986,9 @@ } }, "node_modules/enhanced-resolve": { - "version": "5.20.0", - "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.0.tgz", - "integrity": "sha512-/ce7+jQ1PQ6rVXwe+jKEg5hW5ciicHwIQUagZkp6IufBoY3YDgdTTY1azVs0qoRgVmvsNB+rbjLJxDAeHHtwsQ==", + "version": "5.20.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.20.1.tgz", + "integrity": "sha512-Qohcme7V1inbAfvjItgw0EaxVX5q2rdVEZHRBrEQdRZTssLDGsL8Lwrznl8oQ/6kuTJONLaDcGjkNP247XEhcA==", "dev": true, "license": "MIT", "dependencies": { @@ -4317,8 +5032,7 @@ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-2.0.0.tgz", "integrity": "sha512-5POEcUuZybH7IdmGsD8wlf0AI55wMecM9rVBTI/qEAy2c1kTOm3DjFYjrBdI2K3BaJjJYfYFeRtM0t9ssnRuxw==", "dev": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/es-object-atoms": { "version": "1.1.1", @@ -4378,71 +5092,77 @@ } }, "node_modules/eslint": { - "version": "8.57.1", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", - "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", - "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", + "version": "9.39.4", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.39.4.tgz", + "integrity": "sha512-XoMjdBOwe/esVgEvLmNsD3IRHkm7fbKIUGvrleloJXUZgDHig2IPWNniv+GwjyJXzuNqVjlr5+4yVUZjycJwfQ==", "dev": true, "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.2.0", - "@eslint-community/regexpp": "^4.6.1", - "@eslint/eslintrc": "^2.1.4", - "@eslint/js": "8.57.1", - "@humanwhocodes/config-array": "^0.13.0", + "@eslint-community/eslint-utils": "^4.8.0", + "@eslint-community/regexpp": "^4.12.1", + "@eslint/config-array": "^0.21.2", + "@eslint/config-helpers": "^0.4.2", + "@eslint/core": "^0.17.0", + "@eslint/eslintrc": "^3.3.5", + "@eslint/js": "9.39.4", + "@eslint/plugin-kit": "^0.4.1", + "@humanfs/node": "^0.16.6", "@humanwhocodes/module-importer": "^1.0.1", - "@nodelib/fs.walk": "^1.2.8", - "@ungap/structured-clone": "^1.2.0", - "ajv": "^6.12.4", + "@humanwhocodes/retry": "^0.4.2", + "@types/estree": "^1.0.6", + "ajv": "^6.14.0", "chalk": "^4.0.0", - "cross-spawn": "^7.0.2", + "cross-spawn": "^7.0.6", "debug": "^4.3.2", - "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^7.2.2", - "eslint-visitor-keys": "^3.4.3", - "espree": "^9.6.1", - "esquery": "^1.4.2", + "eslint-scope": "^8.4.0", + "eslint-visitor-keys": "^4.2.1", + "espree": "^10.4.0", + "esquery": "^1.5.0", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^6.0.1", + "file-entry-cache": "^8.0.0", "find-up": "^5.0.0", "glob-parent": "^6.0.2", - "globals": "^13.19.0", - "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", - "is-path-inside": "^3.0.3", - "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", - "levn": "^0.4.1", "lodash.merge": "^4.6.2", - "minimatch": "^3.1.2", + "minimatch": "^3.1.5", "natural-compare": "^1.4.0", - "optionator": "^0.9.3", - "strip-ansi": "^6.0.1", - "text-table": "^0.2.0" + "optionator": "^0.9.3" }, "bin": { "eslint": "bin/eslint.js" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { - "url": "https://opencollective.com/eslint" + "url": "https://eslint.org/donate" + }, + "peerDependencies": { + "jiti": "*" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + } } }, "node_modules/eslint-config-prettier": { - "version": "9.1.2", - "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-9.1.2.tgz", - "integrity": "sha512-iI1f+D2ViGn+uvv5HuHVUamg8ll4tN+JRHGc6IJi4TP9Kl976C57fzPXgseXNs8v0iA8aSJpHsTWjDb9QJamGQ==", + "version": "10.1.8", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz", + "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==", "dev": true, "license": "MIT", "bin": { "eslint-config-prettier": "bin/cli.js" }, + "funding": { + "url": "https://opencollective.com/eslint-config-prettier" + }, "peerDependencies": { "eslint": ">=7.0.0" } @@ -4479,9 +5199,9 @@ } }, "node_modules/eslint-scope": { - "version": "7.2.2", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", - "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.4.0.tgz", + "integrity": "sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -4489,116 +5209,38 @@ "estraverse": "^5.2.0" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-visitor-keys": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", - "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", + "integrity": "sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==", "dev": true, "license": "Apache-2.0", "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" } }, - "node_modules/eslint/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/eslint/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/eslint/node_modules/glob-parent": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", - "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.3" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/eslint/node_modules/ignore": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", - "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 4" - } - }, - "node_modules/eslint/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, - "node_modules/eslint/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/espree": { - "version": "9.6.1", - "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", - "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "version": "10.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-10.4.0.tgz", + "integrity": "sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { - "acorn": "^8.9.0", + "acorn": "^8.15.0", "acorn-jsx": "^5.3.2", - "eslint-visitor-keys": "^3.4.1" + "eslint-visitor-keys": "^4.2.1" }, "engines": { - "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -4714,114 +5356,124 @@ "dev": true, "license": "ISC" }, - "node_modules/exit": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/exit/-/exit-0.1.2.tgz", - "integrity": "sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==", + "node_modules/exit-x": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/exit-x/-/exit-x-0.2.2.tgz", + "integrity": "sha512-+I6B/IkJc1o/2tiURyz/ivu/O0nKNEArIUB5O7zBrlDVJr22SCLH3xTeEry428LvFhRzIA1g8izguxJ/gbNcVQ==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.8.0" } }, "node_modules/expect": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/expect/-/expect-29.7.0.tgz", - "integrity": "sha512-2Zks0hf1VLFYI1kbh0I5jP3KHHyCHpkfyHBzsSXRFgl/Bg9mWYfMW8oD+PdMPlEwy5HNsR9JutYy6pMeOh61nw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/expect/-/expect-30.3.0.tgz", + "integrity": "sha512-1zQrciTiQfRdo7qJM1uG4navm8DayFa2TgCSRlzUyNkhcJ6XUZF3hjnpkyr3VhAqPH7i/9GkG7Tv5abz6fqz0Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/expect-utils": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0" + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-util": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", + "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.3", - "content-disposition": "~0.5.4", - "content-type": "~1.0.4", - "cookie": "~0.7.1", - "cookie-signature": "~1.0.6", - "debug": "2.6.9", - "depd": "2.0.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "finalhandler": "~1.3.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "~0.1.12", - "proxy-addr": "~2.0.7", - "qs": "~6.14.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "~0.19.0", - "serve-static": "~1.16.2", - "setprototypeof": "1.2.0", - "statuses": "~2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" } }, - "node_modules/express/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/express/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "node_modules/exsolve": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/exsolve/-/exsolve-1.0.8.tgz", + "integrity": "sha512-LmDxfWXwcTArk8fUEnOfSZpHOJ6zOMUJKOtFLFqJLoKJetuQG874Uc7/Kki7zFLzYybmZhp1M7+98pfMqeX8yA==", + "devOptional": true, "license": "MIT" }, - "node_modules/express/node_modules/path-to-regexp": { - "version": "0.1.12", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", - "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", - "license": "MIT" - }, - "node_modules/external-editor": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz", - "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==", - "dev": true, + "node_modules/fast-check": { + "version": "3.23.2", + "resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.23.2.tgz", + "integrity": "sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], "license": "MIT", "dependencies": { - "chardet": "^0.7.0", - "iconv-lite": "^0.4.24", - "tmp": "^0.0.33" + "pure-rand": "^6.1.0" }, "engines": { - "node": ">=4" + "node": ">=8.0.0" } }, + "node_modules/fast-check/node_modules/pure-rand": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", + "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "devOptional": true, + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/dubzzz" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fast-check" + } + ], + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4856,15 +5508,22 @@ "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", "license": "MIT" }, - "node_modules/fastq": { - "version": "1.20.1", - "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", - "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", + "node_modules/fast-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", + "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==", "dev": true, - "license": "ISC", - "dependencies": { - "reusify": "^1.0.4" - } + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" }, "node_modules/fb-watchman": { "version": "2.0.2", @@ -4894,64 +5553,32 @@ } } }, - "node_modules/fflate": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", - "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", - "license": "MIT" - }, - "node_modules/figures": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", - "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", - "dev": true, - "license": "MIT", - "dependencies": { - "escape-string-regexp": "^1.0.5" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/figures/node_modules/escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.8.0" - } - }, "node_modules/file-entry-cache": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", - "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", "dev": true, "license": "MIT", "dependencies": { - "flat-cache": "^3.0.4" + "flat-cache": "^4.0.0" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16.0.0" } }, "node_modules/file-type": { - "version": "20.4.1", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.4.1.tgz", - "integrity": "sha512-hw9gNZXUfZ02Jo0uafWLaFVPter5/k2rfcrjFJJHX/77xtSDOfJuEFb6oKlFV86FLP1SuyHMW1PSk0U9M5tKkQ==", + "version": "21.3.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.4.tgz", + "integrity": "sha512-Ievi/yy8DS3ygGvT47PjSfdFoX+2isQueoYP1cntFW1JLYAuS4GD7NUPGg4zv2iZfV52uDyk5w5Z0TdpRS6Q1g==", "license": "MIT", "dependencies": { - "@tokenizer/inflate": "^0.2.6", - "strtok3": "^10.2.0", - "token-types": "^6.0.0", + "@tokenizer/inflate": "^0.4.1", + "strtok3": "^10.3.4", + "token-types": "^6.1.1", "uint8array-extras": "^1.4.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sindresorhus/file-type?sponsor=1" @@ -4971,38 +5598,26 @@ } }, "node_modules/finalhandler": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", - "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", + "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "on-finished": "~2.4.1", - "parseurl": "~1.3.3", - "statuses": "~2.0.2", - "unpipe": "~1.0.0" + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" }, "engines": { - "node": ">= 0.8" + "node": ">= 18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/finalhandler/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/finalhandler/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -5021,24 +5636,23 @@ } }, "node_modules/flat-cache": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", - "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", "dev": true, "license": "MIT", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.3", - "rimraf": "^3.0.2" + "keyv": "^4.5.4" }, "engines": { - "node": "^10.12.0 || >=12.0.0" + "node": ">=16" } }, "node_modules/flatted": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.1.tgz", - "integrity": "sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", + "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==", "dev": true, "license": "ISC" }, @@ -5060,15 +5674,15 @@ } }, "node_modules/fork-ts-checker-webpack-plugin": { - "version": "9.0.2", - "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.0.2.tgz", - "integrity": "sha512-Uochze2R8peoN1XqlSi/rGUkDQpRogtLFocP9+PGu68zk1BDAKXfdeCdyVZpgTk8V8WFVQXdEz426VKjXLO1Gg==", + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/fork-ts-checker-webpack-plugin/-/fork-ts-checker-webpack-plugin-9.1.0.tgz", + "integrity": "sha512-mpafl89VFPJmhnJ1ssH+8wmM2b50n+Rew5x42NeI2U78aRWgtkEtGmctp7iT16UjquJTjorEmIfESj3DxdW84Q==", "dev": true, "license": "MIT", "dependencies": { "@babel/code-frame": "^7.16.7", "chalk": "^4.1.2", - "chokidar": "^3.5.3", + "chokidar": "^4.0.1", "cosmiconfig": "^8.2.0", "deepmerge": "^4.2.2", "fs-extra": "^10.0.0", @@ -5080,45 +5694,13 @@ "tapable": "^2.2.1" }, "engines": { - "node": ">=12.13.0", - "yarn": ">=1.0.0" + "node": ">=14.21.3" }, "peerDependencies": { "typescript": ">3.6.0", "webpack": "^5.11.0" } }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/fork-ts-checker-webpack-plugin/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/form-data": { "version": "4.0.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", @@ -5136,6 +5718,29 @@ "node": ">= 6" } }, + "node_modules/form-data/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/form-data/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/formidable": { "version": "3.5.4", "resolved": "https://registry.npmjs.org/formidable/-/formidable-3.5.4.tgz", @@ -5164,12 +5769,12 @@ } }, "node_modules/fresh": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/fs-extra": { @@ -5205,6 +5810,7 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -5304,39 +5910,53 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/glob": { - "version": "10.4.5", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", + "node_modules/giget": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/giget/-/giget-2.0.0.tgz", + "integrity": "sha512-L5bGsVkxJbJgdnwyuheIunkGatUF/zssUoxxjACCseZYAVbaqdh9Tsmmlkl8vYan09H7sbvKt4pS8GqKLBrEzA==", + "devOptional": true, + "license": "MIT", "dependencies": { - "foreground-child": "^3.1.0", - "jackspeak": "^3.1.2", - "minimatch": "^9.0.4", - "minipass": "^7.1.2", - "package-json-from-dist": "^1.0.0", - "path-scurry": "^1.11.1" + "citty": "^0.1.6", + "consola": "^3.4.0", + "defu": "^6.1.4", + "node-fetch-native": "^1.6.6", + "nypm": "^0.6.0", + "pathe": "^2.0.3" }, "bin": { - "glob": "dist/esm/bin.mjs" + "giget": "dist/cli.mjs" + } + }, + "node_modules/glob": { + "version": "13.0.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", + "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "minimatch": "^10.2.2", + "minipass": "^7.1.3", + "path-scurry": "^2.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.3" }, "engines": { - "node": ">= 6" + "node": ">=10.13.0" } }, "node_modules/glob-to-regexp": { @@ -5347,49 +5967,52 @@ "license": "BSD-2-Clause" }, "node_modules/glob/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", + "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0" + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "18 || 20 || >=22" } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", - "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", + "version": "10.2.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", + "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "dev": true, - "license": "ISC", + "license": "BlueOak-1.0.0", "dependencies": { - "brace-expansion": "^2.0.2" + "brace-expansion": "^5.0.5" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "version": "17.4.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-17.4.0.tgz", + "integrity": "sha512-hjrNztw/VajQwOLsMNT1cbJiH2muO3OROCHnbehc8eY5JyD2gqz4AcMHPqgaOR59DjgUjYAYLeH699g/eWi2jw==", "dev": true, "license": "MIT", - "dependencies": { - "type-fest": "^0.20.2" - }, "engines": { - "node": ">=8" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -5414,17 +6037,10 @@ "dev": true, "license": "ISC" }, - "node_modules/graphemer": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", - "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", - "dev": true, - "license": "MIT" - }, "node_modules/handlebars": { - "version": "4.7.8", - "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", - "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", + "version": "4.7.9", + "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.9.tgz", + "integrity": "sha512-4E71E0rpOaQuJR2A3xDZ+GM1HyWYv1clR58tC8emQNeQe3RH7MAzSbat+V0wG78LQBo6m6bzSG/L4pBuCsgnUQ==", "dev": true, "license": "MIT", "dependencies": { @@ -5457,34 +6073,12 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/has-own-prop": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-own-prop/-/has-own-prop-2.0.0.tgz", - "integrity": "sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==", "dev": true, "license": "MIT", "engines": { "node": ">=8" } }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/has-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", @@ -5563,15 +6157,19 @@ } }, "node_modules/iconv-lite": { - "version": "0.4.24", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", - "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", "dependencies": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": ">= 2.1.2 < 3.0.0" }, "engines": { "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/ieee754": { @@ -5595,9 +6193,9 @@ "license": "BSD-3-Clause" }, "node_modules/ignore": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/ignore/-/ignore-7.0.5.tgz", - "integrity": "sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==", + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, "license": "MIT", "engines": { @@ -5669,33 +6267,6 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, - "node_modules/inquirer": { - "version": "8.2.6", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-8.2.6.tgz", - "integrity": "sha512-M1WuAmb7pn9zdFRtQYk26ZBoY043Sse0wVDdk4Bppr+JOXyQYybdtvK+l9wUibhtjdjvtoiNy8tk+EgsYIUqKg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-escapes": "^4.2.1", - "chalk": "^4.1.1", - "cli-cursor": "^3.1.0", - "cli-width": "^3.0.0", - "external-editor": "^3.0.3", - "figures": "^3.0.0", - "lodash": "^4.17.21", - "mute-stream": "0.0.8", - "ora": "^5.4.1", - "run-async": "^2.4.0", - "rxjs": "^7.5.5", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0", - "through": "^2.3.6", - "wrap-ansi": "^6.0.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -5712,35 +6283,6 @@ "dev": true, "license": "MIT" }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-core-module": { - "version": "2.16.1", - "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", - "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", - "dev": true, - "license": "MIT", - "dependencies": { - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", @@ -5804,15 +6346,11 @@ "node": ">=0.12.0" } }, - "node_modules/is-path-inside": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - } + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" }, "node_modules/is-stream": { "version": "2.0.1", @@ -5890,30 +6428,20 @@ } }, "node_modules/istanbul-lib-source-maps": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz", - "integrity": "sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==", + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz", + "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==", "dev": true, "license": "BSD-3-Clause", "dependencies": { + "@jridgewell/trace-mapping": "^0.3.23", "debug": "^4.1.1", - "istanbul-lib-coverage": "^3.0.0", - "source-map": "^0.6.1" + "istanbul-lib-coverage": "^3.0.0" }, "engines": { "node": ">=10" } }, - "node_modules/istanbul-lib-source-maps/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/istanbul-reports": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz", @@ -5954,22 +6482,22 @@ } }, "node_modules/jest": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest/-/jest-29.7.0.tgz", - "integrity": "sha512-NIy3oAFp9shda19hy4HK0HRTWKtPJmGdnvywu01nOqNC2vZg+Z+fvJDxpMQA88eb2I9EcafcdjYgsDthnYTvGw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest/-/jest-30.3.0.tgz", + "integrity": "sha512-AkXIIFcaazymvey2i/+F94XRnM6TsVLZDhBMLsd1Sf/W0wzsvvpjeyUrCZD6HGG4SDYPgDJDBKeiJTBb10WzMg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/types": "^29.6.3", - "import-local": "^3.0.2", - "jest-cli": "^29.7.0" + "@jest/core": "30.3.0", + "@jest/types": "30.3.0", + "import-local": "^3.2.0", + "jest-cli": "30.3.0" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -5981,76 +6509,75 @@ } }, "node_modules/jest-changed-files": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-29.7.0.tgz", - "integrity": "sha512-fEArFiwf1BpQ+4bXSprcDc3/x4HSzL4al2tozwVpDFpsxALjLYdyiIK4e5Vz66GQJIbXJ82+35PtysofptNX2w==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-30.3.0.tgz", + "integrity": "sha512-B/7Cny6cV5At6M25EWDgf9S617lHivamL8vl6KEpJqkStauzcG4e+WPfDgMMF+H4FVH4A2PLRyvgDJan4441QA==", "dev": true, "license": "MIT", "dependencies": { - "execa": "^5.0.0", - "jest-util": "^29.7.0", + "execa": "^5.1.1", + "jest-util": "30.3.0", "p-limit": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-circus": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-29.7.0.tgz", - "integrity": "sha512-3E1nCMgipcTkCocFwM90XXQab9bS+GMsjdpmPrlelaxwD93Ad8iVEjX/vvHPdLPnFf+L40u+5+iutRdA1N9myw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-circus/-/jest-circus-30.3.0.tgz", + "integrity": "sha512-PyXq5szeSfR/4f1lYqCmmQjh0vqDkURUYi9N6whnHjlRz4IUQfMcXkGLeEoiJtxtyPqgUaUUfyQlApXWBSN1RA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/expect": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.3.0", + "@jest/expect": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "co": "^4.6.0", - "dedent": "^1.0.0", - "is-generator-fn": "^2.0.0", - "jest-each": "^29.7.0", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "dedent": "^1.6.0", + "is-generator-fn": "^2.1.0", + "jest-each": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-runtime": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", "p-limit": "^3.1.0", - "pretty-format": "^29.7.0", - "pure-rand": "^6.0.0", + "pretty-format": "30.3.0", + "pure-rand": "^7.0.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-cli": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-29.7.0.tgz", - "integrity": "sha512-OVVobw2IubN/GSYsxETi+gOe7Ka59EFMR/twOU3Jb2GnKKeMGJB5SGUUrEz3SFVmJASUdZUzy83sLNNQ2gZslg==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-cli/-/jest-cli-30.3.0.tgz", + "integrity": "sha512-l6Tqx+j1fDXJEW5bqYykDQQ7mQg+9mhWXtnj+tQZrTWYHyHoi6Be8HPumDSA+UiX2/2buEgjA58iJzdj146uCw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/core": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "create-jest": "^29.7.0", - "exit": "^0.1.2", - "import-local": "^3.0.2", - "jest-config": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "yargs": "^17.3.1" + "@jest/core": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "exit-x": "^0.2.2", + "import-local": "^3.2.0", + "jest-config": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "yargs": "^17.7.2" }, "bin": { "jest": "bin/jest.js" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "node-notifier": "^8.0.1 || ^9.0.0 || ^10.0.0" @@ -6062,268 +6589,308 @@ } }, "node_modules/jest-config": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-29.7.0.tgz", - "integrity": "sha512-uXbpfeQ7R6TZBqI3/TxCU4q4ttk3u0PJeC+E0zbfSoSjq6bJ7buBPxzQPL0ifrkY4DNu4JUdk0ImlBUYi840eQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-config/-/jest-config-30.3.0.tgz", + "integrity": "sha512-WPMAkMAtNDY9P/oKObtsRG/6KTrhtgPJoBTmk20uDn4Uy6/3EJnnaZJre/FMT1KVRx8cve1r7/FlMIOfRVWL4w==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@jest/test-sequencer": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-jest": "^29.7.0", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "deepmerge": "^4.2.2", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-circus": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-runner": "^29.7.0", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "micromatch": "^4.0.4", + "@babel/core": "^7.27.4", + "@jest/get-type": "30.1.0", + "@jest/pattern": "30.0.1", + "@jest/test-sequencer": "30.3.0", + "@jest/types": "30.3.0", + "babel-jest": "30.3.0", + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "deepmerge": "^4.3.1", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-circus": "30.3.0", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-runner": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", "parse-json": "^5.2.0", - "pretty-format": "^29.7.0", + "pretty-format": "30.3.0", "slash": "^3.0.0", "strip-json-comments": "^3.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "peerDependencies": { "@types/node": "*", + "esbuild-register": ">=3.4.0", "ts-node": ">=9.0.0" }, "peerDependenciesMeta": { "@types/node": { "optional": true }, + "esbuild-register": { + "optional": true + }, "ts-node": { "optional": true } } }, - "node_modules/jest-config/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/jest-config/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/jest-config/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jest-config/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/jest-config/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.2" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-config/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/jest-diff": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-29.7.0.tgz", - "integrity": "sha512-LMIgiIrhigmPrs03JHpxUh2yISK3vLFPkAodPeo0+BuF7wA2FoQbkEg1u8gBYBThncu7e1oEDUfIXVuTqLRUjw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-30.3.0.tgz", + "integrity": "sha512-n3q4PDQjS4LrKxfWB3Z5KNk1XjXtZTBwQp71OP0Jo03Z6V60x++K5L8k6ZrW8MY8pOFylZvHM0zsjS1RqlHJZQ==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "diff-sequences": "^29.6.3", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/diff-sequences": "30.3.0", + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "pretty-format": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-docblock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-29.7.0.tgz", - "integrity": "sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g==", + "version": "30.2.0", + "resolved": "https://registry.npmjs.org/jest-docblock/-/jest-docblock-30.2.0.tgz", + "integrity": "sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==", "dev": true, "license": "MIT", "dependencies": { - "detect-newline": "^3.0.0" + "detect-newline": "^3.1.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-each": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-29.7.0.tgz", - "integrity": "sha512-gns+Er14+ZrEoC5fhOfYCY1LOHHr0TI+rQUHZS8Ttw2l7gl+80eHc/gFf2Ktkw0+SIACDTeWvpFcv3B04VembQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-each/-/jest-each-30.3.0.tgz", + "integrity": "sha512-V8eMndg/aZ+3LnCJgSm13IxS5XSBM22QSZc9BtPK8Dek6pm+hfUNfwBdvsB3d342bo1q7wnSkC38zjX259qZNA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", - "jest-util": "^29.7.0", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "chalk": "^4.1.2", + "jest-util": "30.3.0", + "pretty-format": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-environment-node": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-29.7.0.tgz", - "integrity": "sha512-DOSwCRqXirTOyheM+4d5YZOrWcdu0LNZ87ewUoywbcb2XR4wKgqiG8vNeYwhjFMbEkfju7wx2GYH0P2gevGvFw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-30.3.0.tgz", + "integrity": "sha512-4i6HItw/JSiJVsC5q0hnKIe/hbYfZLVG9YJ/0pU9Hz2n/9qZe3Rhn5s5CUZA5ORZlcdT/vmAXRMyONXJwPrmYQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-mock": "^29.7.0", - "jest-util": "^29.7.0" + "jest-mock": "30.3.0", + "jest-util": "30.3.0", + "jest-validate": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" - } - }, - "node_modules/jest-get-type": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-29.6.3.tgz", - "integrity": "sha512-zrteXnqYxfQh7l5FHyL38jL39di8H8rHoecLH3JNxH3BwOrBsNeabdap5e0I23lD4HHI8W5VFBZqG4Eaq5LNcw==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-haste-map": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-29.7.0.tgz", - "integrity": "sha512-fP8u2pyfqx0K1rGn1R9pyE0/KTn+G7PxktWidOBTqFPLYX0b9ksaMFkhK5vrS3DVun09pckLdlx90QthlW7AmA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-30.3.0.tgz", + "integrity": "sha512-mMi2oqG4KRU0R9QEtscl87JzMXfUhbKaFqOxmjb2CKcbHcUGFrJCBWHmnTiUqi6JcnzoBlO4rWfpdl2k/RfLCA==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "@types/graceful-fs": "^4.1.3", + "@jest/types": "30.3.0", "@types/node": "*", - "anymatch": "^3.0.3", - "fb-watchman": "^2.0.0", - "graceful-fs": "^4.2.9", - "jest-regex-util": "^29.6.3", - "jest-util": "^29.7.0", - "jest-worker": "^29.7.0", - "micromatch": "^4.0.4", + "anymatch": "^3.1.3", + "fb-watchman": "^2.0.2", + "graceful-fs": "^4.2.11", + "jest-regex-util": "30.0.1", + "jest-util": "30.3.0", + "jest-worker": "30.3.0", + "picomatch": "^4.0.3", "walker": "^1.0.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" }, "optionalDependencies": { - "fsevents": "^2.3.2" + "fsevents": "^2.3.3" + } + }, + "node_modules/jest-haste-map/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/jest-leak-detector": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-29.7.0.tgz", - "integrity": "sha512-kYA8IJcSYtST2BY9I+SMC32nDpBT3J2NvWJx8+JCuCdl/CR1I4EKUJROiP8XtCcxqgTTBGJNdbB1A8XRKbTetw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-30.3.0.tgz", + "integrity": "sha512-cuKmUUGIjfXZAiGJ7TbEMx0bcqNdPPI6P1V+7aF+m/FUJqFDxkFR4JqkTu8ZOiU5AaX/x0hZ20KaaIPXQzbMGQ==", "dev": true, "license": "MIT", "dependencies": { - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "pretty-format": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-matcher-utils": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-29.7.0.tgz", - "integrity": "sha512-sBkD+Xi9DtcChsI3L3u0+N0opgPYnCRPtGcQYrgXmR+hmt/fYfWAL0xRXYU8eWOdfuLgBe0YCW3AFtnRLagq/g==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-30.3.0.tgz", + "integrity": "sha512-HEtc9uFQgaUHkC7nLSlQL3Tph4Pjxt/yiPvkIrrDCt9jhoLIgxaubo1G+CFOnmHYMxHwwdaSN7mkIFs6ZK8OhA==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "pretty-format": "^29.7.0" + "@jest/get-type": "30.1.0", + "chalk": "^4.1.2", + "jest-diff": "30.3.0", + "pretty-format": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-message-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-29.7.0.tgz", - "integrity": "sha512-GBEV4GRADeP+qtB2+6u61stea8mGcOT4mCtrYISZwfu9/ISHFJ/5zOMXYbpBE9RsS5+Gb63DW4FgmnKJ79Kf6w==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-30.3.0.tgz", + "integrity": "sha512-Z/j4Bo+4ySJ+JPJN3b2Qbl9hDq3VrXmnjjGEWD/x0BCXeOXPTV1iZYYzl2X8c1MaCOL+ewMyNBcm88sboE6YWw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.12.13", - "@jest/types": "^29.6.3", - "@types/stack-utils": "^2.0.0", - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "micromatch": "^4.0.4", - "pretty-format": "^29.7.0", + "@babel/code-frame": "^7.27.1", + "@jest/types": "30.3.0", + "@types/stack-utils": "^2.0.3", + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3", + "pretty-format": "30.3.0", "slash": "^3.0.0", - "stack-utils": "^2.0.3" + "stack-utils": "^2.0.6" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" + } + }, + "node_modules/jest-message-util/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/jest-mock": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-29.7.0.tgz", - "integrity": "sha512-ITOMZn+UkYS4ZFh83xYAOzWStloNzJFO2s8DWrE4lhtGD+AorgnbkiKERe4wQVBydIGPx059g6riW5Btp6Llnw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-mock/-/jest-mock-30.3.0.tgz", + "integrity": "sha512-OTzICK8CpE+t4ndhKrwlIdbM6Pn8j00lvmSmq5ejiO+KxukbLjgOflKWMn3KE34EZdQm5RqTuKj+5RIEniYhog==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.3.0", "@types/node": "*", - "jest-util": "^29.7.0" + "jest-util": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-pnp-resolver": { @@ -6345,81 +6912,81 @@ } }, "node_modules/jest-regex-util": { - "version": "29.6.3", - "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-29.6.3.tgz", - "integrity": "sha512-KJJBsRCyyLNWCNBOvZyRDnAIfUiRJ8v+hOBQYGn8gDyF3UegwiP4gwRR3/SDa42g1YbVycTidUF3rKjyLFDWbg==", + "version": "30.0.1", + "resolved": "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-30.0.1.tgz", + "integrity": "sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==", "dev": true, "license": "MIT", "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-29.7.0.tgz", - "integrity": "sha512-IOVhZSrg+UvVAshDSDtHyFCCBUl/Q3AAJv8iZ6ZjnZ74xzvwuzLXid9IIIPgTnY62SJjfuupMKZsZQRsCvxEgA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve/-/jest-resolve-30.3.0.tgz", + "integrity": "sha512-NRtTAHQlpd15F9rUR36jqwelbrDV/dY4vzNte3S2kxCKUJRYNd5/6nTSbYiak1VX5g8IoFF23Uj5TURkUW8O5g==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^4.0.0", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-pnp-resolver": "^1.2.2", - "jest-util": "^29.7.0", - "jest-validate": "^29.7.0", - "resolve": "^1.20.0", - "resolve.exports": "^2.0.0", - "slash": "^3.0.0" + "chalk": "^4.1.2", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-pnp-resolver": "^1.2.3", + "jest-util": "30.3.0", + "jest-validate": "30.3.0", + "slash": "^3.0.0", + "unrs-resolver": "^1.7.11" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-resolve-dependencies": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-29.7.0.tgz", - "integrity": "sha512-un0zD/6qxJ+S0et7WxeI3H5XSe9lTBBR7bOHCHXkKR6luG5mwDDlIzVQ0V5cZCuoTgEdcdwzTghYkTWfubi+nA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-30.3.0.tgz", + "integrity": "sha512-9ev8s3YN6Hsyz9LV75XUwkCVFlwPbaFn6Wp75qnI0wzAINYWY8Fb3+6y59Rwd3QaS3kKXffHXsZMziMavfz/nw==", "dev": true, "license": "MIT", "dependencies": { - "jest-regex-util": "^29.6.3", - "jest-snapshot": "^29.7.0" + "jest-regex-util": "30.0.1", + "jest-snapshot": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-29.7.0.tgz", - "integrity": "sha512-fsc4N6cPCAahybGBfTRcq5wFR6fpLznMg47sY5aDpsoejOcVYFb07AHuSnR0liMcPTgBsA3ZJL6kFOjPdoNipQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runner/-/jest-runner-30.3.0.tgz", + "integrity": "sha512-gDv6C9LGKWDPLia9TSzZwf4h3kMQCqyTpq+95PODnTRDO0g9os48XIYYkS6D236vjpBir2fF63YmJFtqkS5Duw==", "dev": true, "license": "MIT", "dependencies": { - "@jest/console": "^29.7.0", - "@jest/environment": "^29.7.0", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/console": "30.3.0", + "@jest/environment": "30.3.0", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "chalk": "^4.0.0", + "chalk": "^4.1.2", "emittery": "^0.13.1", - "graceful-fs": "^4.2.9", - "jest-docblock": "^29.7.0", - "jest-environment-node": "^29.7.0", - "jest-haste-map": "^29.7.0", - "jest-leak-detector": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-resolve": "^29.7.0", - "jest-runtime": "^29.7.0", - "jest-util": "^29.7.0", - "jest-watcher": "^29.7.0", - "jest-worker": "^29.7.0", + "exit-x": "^0.2.2", + "graceful-fs": "^4.2.11", + "jest-docblock": "30.2.0", + "jest-environment-node": "30.3.0", + "jest-haste-map": "30.3.0", + "jest-leak-detector": "30.3.0", + "jest-message-util": "30.3.0", + "jest-resolve": "30.3.0", + "jest-runtime": "30.3.0", + "jest-util": "30.3.0", + "jest-watcher": "30.3.0", + "jest-worker": "30.3.0", "p-limit": "^3.1.0", "source-map-support": "0.5.13" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-runner/node_modules/source-map": { @@ -6444,171 +7011,191 @@ } }, "node_modules/jest-runtime": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-29.7.0.tgz", - "integrity": "sha512-gUnLjgwdGqW7B4LvOIkbKs9WGbn+QLqRQQ9juC6HndeDiezIwhDP+mhMwHWCEcfQ5RUXa6OPnFF8BJh5xegwwQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-runtime/-/jest-runtime-30.3.0.tgz", + "integrity": "sha512-CgC+hIBJbuh78HEffkhNKcbXAytQViplcl8xupqeIWyKQF50kCQA8J7GeJCkjisC6hpnC9Muf8jV5RdtdFbGng==", "dev": true, "license": "MIT", "dependencies": { - "@jest/environment": "^29.7.0", - "@jest/fake-timers": "^29.7.0", - "@jest/globals": "^29.7.0", - "@jest/source-map": "^29.6.3", - "@jest/test-result": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/environment": "30.3.0", + "@jest/fake-timers": "30.3.0", + "@jest/globals": "30.3.0", + "@jest/source-map": "30.0.1", + "@jest/test-result": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "chalk": "^4.0.0", - "cjs-module-lexer": "^1.0.0", - "collect-v8-coverage": "^1.0.0", - "glob": "^7.1.3", - "graceful-fs": "^4.2.9", - "jest-haste-map": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-mock": "^29.7.0", - "jest-regex-util": "^29.6.3", - "jest-resolve": "^29.7.0", - "jest-snapshot": "^29.7.0", - "jest-util": "^29.7.0", + "chalk": "^4.1.2", + "cjs-module-lexer": "^2.1.0", + "collect-v8-coverage": "^1.0.2", + "glob": "^10.5.0", + "graceful-fs": "^4.2.11", + "jest-haste-map": "30.3.0", + "jest-message-util": "30.3.0", + "jest-mock": "30.3.0", + "jest-regex-util": "30.0.1", + "jest-resolve": "30.3.0", + "jest-snapshot": "30.3.0", + "jest-util": "30.3.0", "slash": "^3.0.0", "strip-bom": "^4.0.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, - "node_modules/jest-runtime/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, "node_modules/jest-runtime/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.3.tgz", + "integrity": "sha512-MCV/fYJEbqx68aE58kv2cA/kiky1G8vux3OR6/jbS+jIMe/6fJWa0DTzJU7dqijOWYwHi1t29FlfYI9uytqlpA==", "dev": true, "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" + "balanced-match": "^1.0.0" } }, "node_modules/jest-runtime/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "version": "10.5.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, "license": "ISC", "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "foreground-child": "^3.1.0", + "jackspeak": "^3.1.2", + "minimatch": "^9.0.4", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^1.11.1" }, - "engines": { - "node": "*" + "bin": { + "glob": "dist/esm/bin.mjs" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/jest-runtime/node_modules/lru-cache": { + "version": "10.4.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "dev": true, + "license": "ISC" + }, "node_modules/jest-runtime/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "9.0.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz", + "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==", "dev": true, "license": "ISC", "dependencies": { - "brace-expansion": "^1.1.7" + "brace-expansion": "^2.0.2" }, "engines": { - "node": "*" + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/jest-runtime/node_modules/path-scurry": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", + "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.18" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/jest-snapshot": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-29.7.0.tgz", - "integrity": "sha512-Rm0BMWtxBcioHr1/OX5YCP8Uov4riHvKPknOGs804Zg9JGZgmIBkbtlxJC/7Z4msKYVbIJtfU+tKb8xlYNfdkw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-30.3.0.tgz", + "integrity": "sha512-f14c7atpb4O2DeNhwcvS810Y63wEn8O1HqK/luJ4F6M4NjvxmAKQwBUWjbExUtMxWJQ0wVgmCKymeJK6NZMnfQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/core": "^7.11.6", - "@babel/generator": "^7.7.2", - "@babel/plugin-syntax-jsx": "^7.7.2", - "@babel/plugin-syntax-typescript": "^7.7.2", - "@babel/types": "^7.3.3", - "@jest/expect-utils": "^29.7.0", - "@jest/transform": "^29.7.0", - "@jest/types": "^29.6.3", - "babel-preset-current-node-syntax": "^1.0.0", - "chalk": "^4.0.0", - "expect": "^29.7.0", - "graceful-fs": "^4.2.9", - "jest-diff": "^29.7.0", - "jest-get-type": "^29.6.3", - "jest-matcher-utils": "^29.7.0", - "jest-message-util": "^29.7.0", - "jest-util": "^29.7.0", - "natural-compare": "^1.4.0", - "pretty-format": "^29.7.0", - "semver": "^7.5.3" + "@babel/core": "^7.27.4", + "@babel/generator": "^7.27.5", + "@babel/plugin-syntax-jsx": "^7.27.1", + "@babel/plugin-syntax-typescript": "^7.27.1", + "@babel/types": "^7.27.3", + "@jest/expect-utils": "30.3.0", + "@jest/get-type": "30.1.0", + "@jest/snapshot-utils": "30.3.0", + "@jest/transform": "30.3.0", + "@jest/types": "30.3.0", + "babel-preset-current-node-syntax": "^1.2.0", + "chalk": "^4.1.2", + "expect": "30.3.0", + "graceful-fs": "^4.2.11", + "jest-diff": "30.3.0", + "jest-matcher-utils": "30.3.0", + "jest-message-util": "30.3.0", + "jest-util": "30.3.0", + "pretty-format": "30.3.0", + "semver": "^7.7.2", + "synckit": "^0.11.8" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-util": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-29.7.0.tgz", - "integrity": "sha512-z6EbKajIpqGKU56y5KBUgy1dt1ihhQJgWzUlZHArA/+X2ad7Cb5iF+AK1EWVL/Bo7Rz9uurpqw6SiBCefUbCGA==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-30.3.0.tgz", + "integrity": "sha512-/jZDa00a3Sz7rdyu55NLrQCIrbyIkbBxareejQI315f/i8HjYN+ZWsDLLpoQSiUIEIyZF/R8fDg3BmB8AtHttg==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", + "@jest/types": "30.3.0", "@types/node": "*", - "chalk": "^4.0.0", - "ci-info": "^3.2.0", - "graceful-fs": "^4.2.9", - "picomatch": "^2.2.3" + "chalk": "^4.1.2", + "ci-info": "^4.2.0", + "graceful-fs": "^4.2.11", + "picomatch": "^4.0.3" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-util/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">=12" }, "funding": { "url": "https://github.com/sponsors/jonschlinkert" } }, "node_modules/jest-validate": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-29.7.0.tgz", - "integrity": "sha512-ZB7wHqaRGVw/9hST/OuFUReG7M8vKeq0/J2egIGLdvjHCmYqGARhzXmtgi+gVeZ5uXFF219aOc3Ls2yLg27tkw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-validate/-/jest-validate-30.3.0.tgz", + "integrity": "sha512-I/xzC8h5G+SHCb2P2gWkJYrNiTbeL47KvKeW5EzplkyxzBRBw1ssSHlI/jXec0ukH2q7x2zAWQm7015iusg62Q==", "dev": true, "license": "MIT", "dependencies": { - "@jest/types": "^29.6.3", - "camelcase": "^6.2.0", - "chalk": "^4.0.0", - "jest-get-type": "^29.6.3", + "@jest/get-type": "30.1.0", + "@jest/types": "30.3.0", + "camelcase": "^6.3.0", + "chalk": "^4.1.2", "leven": "^3.1.0", - "pretty-format": "^29.7.0" + "pretty-format": "30.3.0" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-validate/node_modules/camelcase": { @@ -6625,39 +7212,40 @@ } }, "node_modules/jest-watcher": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-29.7.0.tgz", - "integrity": "sha512-49Fg7WXkU3Vl2h6LbLtMQ/HyB6rXSIX7SqvBLQmssRBGN9I0PNvPmAmCWSOY6SOvrjhI/F7/bGAv9RtnsPA03g==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-watcher/-/jest-watcher-30.3.0.tgz", + "integrity": "sha512-PJ1d9ThtTR8aMiBWUdcownq9mDdLXsQzJayTk4kmaBRHKvwNQn+ANveuhEBUyNI2hR1TVhvQ8D5kHubbzBHR/w==", "dev": true, "license": "MIT", "dependencies": { - "@jest/test-result": "^29.7.0", - "@jest/types": "^29.6.3", + "@jest/test-result": "30.3.0", + "@jest/types": "30.3.0", "@types/node": "*", - "ansi-escapes": "^4.2.1", - "chalk": "^4.0.0", + "ansi-escapes": "^4.3.2", + "chalk": "^4.1.2", "emittery": "^0.13.1", - "jest-util": "^29.7.0", - "string-length": "^4.0.1" + "jest-util": "30.3.0", + "string-length": "^4.0.2" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-29.7.0.tgz", - "integrity": "sha512-eIz2msL/EzL9UFTFFx7jBTkeZfku0yUAyZZZmJ93H2TYEiroIx2PQjEXcwYtYl8zXCxb+PAmA2hLIt/6ZEkPHw==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-30.3.0.tgz", + "integrity": "sha512-DrCKkaQwHexjRUFTmPzs7sHQe0TSj9nvDALKGdwmK5mW9v7j90BudWirKAJHt3QQ9Dhrg1F7DogPzhChppkJpQ==", "dev": true, "license": "MIT", "dependencies": { "@types/node": "*", - "jest-util": "^29.7.0", + "@ungap/structured-clone": "^1.3.0", + "jest-util": "30.3.0", "merge-stream": "^2.0.0", - "supports-color": "^8.0.0" + "supports-color": "^8.1.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/jest-worker/node_modules/supports-color": { @@ -6676,6 +7264,16 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "devOptional": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/jose": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", @@ -6733,9 +7331,9 @@ "license": "MIT" }, "node_modules/json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true, "license": "MIT" }, @@ -6760,9 +7358,9 @@ } }, "node_modules/jsonc-parser": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.2.1.tgz", - "integrity": "sha512-AilxAyFOAcK5wA1+LeaySVBrHsGQvUFCDWXKpZjzaL0PqW+xfBOttn8GNtWKFWqneyMZj41MWF9Kl6iPWLwgOA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/jsonc-parser/-/jsonc-parser-3.3.1.tgz", + "integrity": "sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ==", "dev": true, "license": "MIT" }, @@ -6789,16 +7387,6 @@ "json-buffer": "3.0.1" } }, - "node_modules/kleur": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", - "integrity": "sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/leven": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/leven/-/leven-3.1.0.tgz", @@ -6823,6 +7411,12 @@ "node": ">= 0.8.0" } }, + "node_modules/libphonenumber-js": { + "version": "1.12.41", + "resolved": "https://registry.npmjs.org/libphonenumber-js/-/libphonenumber-js-1.12.41.tgz", + "integrity": "sha512-lsmMmGXBxXIK/VMLEj0kL6MtUs1kBGj1nTCzi6zgQoG1DEwqwt2DQyHxcLykceIxAnfE3hya7NuIh6PpC6S3fA==", + "license": "MIT" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -6830,6 +7424,25 @@ "dev": true, "license": "MIT" }, + "node_modules/load-esm": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/load-esm/-/load-esm-1.0.3.tgz", + "integrity": "sha512-v5xlu8eHD1+6r8EHTg6hfmO97LN8ugKtiXcy5e6oN72iD2r6u0RPfLl6fxM+7Wnh2ZRq15o0russMst44WauPA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + }, + { + "type": "buymeacoffee", + "url": "https://buymeacoffee.com/borewit" + } + ], + "license": "MIT", + "engines": { + "node": ">=13.2.0" + } + }, "node_modules/loader-runner": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.1.tgz", @@ -6861,9 +7474,10 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", + "dev": true, "license": "MIT" }, "node_modules/lodash.memoize": { @@ -6908,16 +7522,13 @@ } }, "node_modules/magic-string": { - "version": "0.30.8", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.8.tgz", - "integrity": "sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ==", + "version": "0.30.17", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz", + "integrity": "sha512-sNPKHvyjVf7gyjwS4xGTaW/mCnF8wnjtifKBEhxfZ7E/S8tQ0rssrwGNn6q8JH/ohItJfSQp9mBtQYuTlH5QnA==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.4.15" - }, - "engines": { - "node": ">=12" + "@jridgewell/sourcemap-codec": "^1.5.0" } }, "node_modules/make-dir": { @@ -6963,12 +7574,12 @@ } }, "node_modules/media-typer": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", "engines": { - "node": ">= 0.6" + "node": ">= 0.8" } }, "node_modules/memfs": { @@ -6985,10 +7596,13 @@ } }, "node_modules/merge-descriptors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", - "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", + "engines": { + "node": ">=18" + }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } @@ -7004,6 +7618,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "dev": true, "license": "MIT", "engines": { "node": ">= 0.6" @@ -7024,9 +7639,9 @@ } }, "node_modules/micromatch/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -7037,36 +7652,41 @@ } }, "node_modules/mime": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", - "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", + "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", + "dev": true, "license": "MIT", "bin": { "mime": "cli.js" }, "engines": { - "node": ">=4" + "node": ">=4.0.0" } }, "node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", "engines": { "node": ">= 0.6" } }, "node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", + "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { - "mime-db": "1.52.0" + "mime-db": "^1.54.0" }, "engines": { - "node": ">= 0.6" + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/mimic-fn": { @@ -7080,25 +7700,23 @@ } }, "node_modules/minimatch": { - "version": "10.2.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.4.tgz", - "integrity": "sha512-oRjTw/97aTBN0RHbYCdtF1MQfvusSIBQM0IZEgzl6426+8jSC0nF1a/GmnVLpfB9yyr6g6FTqWqiZVbxrtaCIg==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, - "license": "BlueOak-1.0.0", + "license": "ISC", "dependencies": { - "brace-expansion": "^5.0.2" + "brace-expansion": "^1.1.7" }, "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "node": "*" } }, "node_modules/minimist": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -7114,18 +7732,6 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "license": "MIT", - "dependencies": { - "minimist": "^1.2.6" - }, - "bin": { - "mkdirp": "bin/cmd.js" - } - }, "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", @@ -7133,29 +7739,92 @@ "license": "MIT" }, "node_modules/multer": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/multer/-/multer-2.0.2.tgz", - "integrity": "sha512-u7f2xaZ/UG8oLXHvtF/oWTRvT44p9ecwBBqTwgJVq0+4BW1g8OW01TyMEGWBHbyMOYVHXslaut7qEQ1meATXgw==", + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/multer/-/multer-2.1.1.tgz", + "integrity": "sha512-mo+QTzKlx8R7E5ylSXxWzGoXoZbOsRMpyitcht8By2KHvMbf3tjwosZ/Mu/XYU6UuJ3VZnODIrak5ZrPiPyB6A==", "license": "MIT", "dependencies": { "append-field": "^1.0.0", "busboy": "^1.6.0", "concat-stream": "^2.0.0", - "mkdirp": "^0.5.6", - "object-assign": "^4.1.1", - "type-is": "^1.6.18", - "xtend": "^4.0.2" + "type-is": "^1.6.18" }, "engines": { "node": ">= 10.16.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/multer/node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/multer/node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" } }, "node_modules/mute-stream": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.8.tgz", - "integrity": "sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-2.0.0.tgz", + "integrity": "sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==", "dev": true, - "license": "ISC" + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/napi-postinstall": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", + "integrity": "sha512-PHI5f1O0EP5xJ9gQmFGMS6IZcrVvTjpXjz7Na41gTE7eE2hK11lg04CECCYEEjdc17EV4DO+fkGEtt7TpTaTiQ==", + "dev": true, + "license": "MIT", + "bin": { + "napi-postinstall": "lib/cli.js" + }, + "engines": { + "node": "^12.20.0 || ^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/napi-postinstall" + } }, "node_modules/natural-compare": { "version": "1.4.0", @@ -7165,9 +7834,9 @@ "license": "MIT" }, "node_modules/negotiator": { - "version": "0.6.3", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", - "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -7197,25 +7866,12 @@ "lodash": "^4.17.21" } }, - "node_modules/node-fetch": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", - "license": "MIT", - "dependencies": { - "whatwg-url": "^5.0.0" - }, - "engines": { - "node": "4.x || >=6.0.0" - }, - "peerDependencies": { - "encoding": "^0.1.0" - }, - "peerDependenciesMeta": { - "encoding": { - "optional": true - } - } + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "devOptional": true, + "license": "MIT" }, "node_modules/node-int64": { "version": "0.4.0", @@ -7225,9 +7881,9 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.36", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz", - "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==", + "version": "2.0.37", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.37.tgz", + "integrity": "sha512-1h5gKZCF+pO/o3Iqt5Jp7wc9rH3eJJ0+nh/CIoiRwjRxde/hAHyLPXYN4V3CqKAbiZPSeJFSWHmJsbkicta0Eg==", "dev": true, "license": "MIT" }, @@ -7254,6 +7910,31 @@ "node": ">=8" } }, + "node_modules/nypm": { + "version": "0.6.5", + "resolved": "https://registry.npmjs.org/nypm/-/nypm-0.6.5.tgz", + "integrity": "sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "citty": "^0.2.0", + "pathe": "^2.0.3", + "tinyexec": "^1.0.2" + }, + "bin": { + "nypm": "dist/cli.mjs" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/nypm/node_modules/citty": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/citty/-/citty-0.2.2.tgz", + "integrity": "sha512-+6vJA3L98yv+IdfKGZHBNiGW5KHn22e/JwID0Strsz8h4S/csAu/OuICwxrg44k5MRiZHWIo8XXuJgQTriRP4w==", + "devOptional": true, + "license": "MIT" + }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", @@ -7275,6 +7956,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ohash": { + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/ohash/-/ohash-2.0.11.tgz", + "integrity": "sha512-RdR9FQrFwNBNXAr4GixM8YaRZRJ5PUWbKYbE5eOsrwAjJW0q2REGcf79oYPsLyskQCZG1PLN+S/K1V00joZAoQ==", + "devOptional": true, + "license": "MIT" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -7291,7 +7979,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "license": "ISC", "dependencies": { "wrappy": "1" @@ -7355,16 +8042,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -7485,42 +8162,42 @@ "node": ">=8" } }, - "node_modules/path-parse": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", - "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", - "dev": true, - "license": "MIT" - }, "node_modules/path-scurry": { - "version": "1.11.1", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz", - "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", + "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "dev": true, "license": "BlueOak-1.0.0", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.18" + "node": "18 || 20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.4.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", + "version": "11.2.7", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", + "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "dev": true, - "license": "ISC" + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } }, "node_modules/path-to-regexp": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.3.0.tgz", - "integrity": "sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==", - "license": "MIT" + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } }, "node_modules/path-type": { "version": "4.0.0", @@ -7532,6 +8209,20 @@ "node": ">=8" } }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "devOptional": true, + "license": "MIT" + }, + "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==", + "devOptional": true, + "license": "MIT" + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -7540,9 +8231,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.1.tgz", - "integrity": "sha512-xUXwsxNjwTQ8K3GnT4pCJm+xq3RUPQbmkYJTP5aFIfNIvbcc/4MUxgBaaRSZJ6yGJZiGSyYlM6MzwTsRk8SYCg==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.2.tgz", + "integrity": "sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==", "dev": true, "license": "MIT", "engines": { @@ -7631,6 +8322,18 @@ "node": ">=8" } }, + "node_modules/pkg-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-2.3.0.tgz", + "integrity": "sha512-SIqCzDRg0s9npO5XQ3tNZioRY1uK06lA41ynBC1YmFTmnY6FjUjVt6s4LoADmwoig1qqD0oK8h1p/8mlMx8Oig==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.2.2", + "exsolve": "^1.0.7", + "pathe": "^2.0.3" + } + }, "node_modules/pluralize": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-8.0.0.tgz", @@ -7681,18 +8384,18 @@ } }, "node_modules/pretty-format": { - "version": "29.7.0", - "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", - "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "version": "30.3.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-30.3.0.tgz", + "integrity": "sha512-oG4T3wCbfeuvljnyAzhBvpN45E8iOTXCU/TD3zXW80HA3dQ4ahdqMkWGiPWZvjpQwlbyHrPTWUAqUzGzv4l1JQ==", "dev": true, "license": "MIT", "dependencies": { - "@jest/schemas": "^29.6.3", - "ansi-styles": "^5.0.0", - "react-is": "^18.0.0" + "@jest/schemas": "30.0.5", + "ansi-styles": "^5.2.0", + "react-is": "^18.3.1" }, "engines": { - "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + "node": "^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0" } }, "node_modules/pretty-format/node_modules/ansi-styles": { @@ -7709,37 +8412,29 @@ } }, "node_modules/prisma": { - "version": "5.22.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.22.0.tgz", - "integrity": "sha512-vtpjW3XuYCSnMsNVBjLMNkTj6OZbudcPPTPYHqX0CJfpcdWciI1dM8uHETwmDxxiqEwCIE6WvXucWUetJgfu/A==", + "version": "6.19.3", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-6.19.3.tgz", + "integrity": "sha512-++ZJ0ijLrDJF6hNB4t4uxg2br3fC4H9Yc9tcbjr2fcNFP3rh/SBNrAgjhsqBU4Ght8JPrVofG/ZkXfnSfnYsFg==", "devOptional": true, "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@prisma/engines": "5.22.0" + "@prisma/config": "6.19.3", + "@prisma/engines": "6.19.3" }, "bin": { "prisma": "build/index.js" }, "engines": { - "node": ">=16.13" + "node": ">=18.18" }, - "optionalDependencies": { - "fsevents": "2.3.3" - } - }, - "node_modules/prompts": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", - "integrity": "sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==", - "dev": true, - "license": "MIT", - "dependencies": { - "kleur": "^3.0.3", - "sisteransi": "^1.0.5" + "peerDependencies": { + "typescript": ">=5.1.0" }, - "engines": { - "node": ">= 6" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, "node_modules/proxy-addr": { @@ -7766,9 +8461,9 @@ } }, "node_modules/pure-rand": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", - "integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==", + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-7.0.1.tgz", + "integrity": "sha512-oTUZM/NAZS8p7ANR3SHh30kXB+zK2r2BPcEn/awJIbOvq82WoMN4p62AWWp3Hhw50G0xMsw1mhIBLqHw64EcNQ==", "dev": true, "funding": [ { @@ -7783,9 +8478,9 @@ "license": "MIT" }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.15.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", + "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -7797,27 +8492,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/queue-microtask": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", - "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -7828,18 +8502,29 @@ } }, "node_modules/raw-body": { - "version": "2.5.3", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", - "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", + "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", - "iconv-lite": "~0.4.24", + "iconv-lite": "~0.7.0", "unpipe": "~1.0.0" }, "engines": { - "node": ">= 0.8" + "node": ">= 0.10" + } + }, + "node_modules/rc9": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/rc9/-/rc9-2.1.2.tgz", + "integrity": "sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "defu": "^6.1.4", + "destr": "^2.0.3" } }, "node_modules/react-is": { @@ -7864,29 +8549,17 @@ } }, "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/readdirp/node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "devOptional": true, "license": "MIT", "engines": { - "node": ">=8.6" + "node": ">= 14.18.0" }, "funding": { - "url": "https://github.com/sponsors/jonschlinkert" + "type": "individual", + "url": "https://paulmillr.com/funding/" } }, "node_modules/reflect-metadata": { @@ -7895,16 +8568,6 @@ "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", "license": "Apache-2.0" }, - "node_modules/repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7925,27 +8588,6 @@ "node": ">=0.10.0" } }, - "node_modules/resolve": { - "version": "1.22.11", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", - "integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-core-module": "^2.16.1", - "path-parse": "^1.0.7", - "supports-preserve-symlinks-flag": "^1.0.0" - }, - "bin": { - "resolve": "bin/resolve" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/resolve-cwd": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", @@ -7979,16 +8621,6 @@ "node": ">=4" } }, - "node_modules/resolve.exports": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/resolve.exports/-/resolve.exports-2.0.3.tgz", - "integrity": "sha512-OcXjMsGdhL4XnbShKpAcSqPMzQoYkYyhbEaeSko47MjRP9NfEQMhZkXL1DoFlt9LWQn4YttrdnV6X2OiyzBi+A==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=10" - } - }, "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", @@ -8010,119 +8642,20 @@ "dev": true, "license": "ISC" }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", - "dev": true, - "license": "MIT", - "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" - } - }, - "node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" }, "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "node_modules/run-async": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.4.1.tgz", - "integrity": "sha512-tvVnVv01b8c1RrA6Ep7JkStj85Guv/YrMcwqYQnwjsAS2cTmmPGBBjAjpCW7RrSodNSoE2/qg9O4bceNvUuDgQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/run-parallel": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", - "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "queue-microtask": "^1.2.2" + "node": ">= 18" } }, "node_modules/rxjs": { @@ -8179,40 +8712,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/schema-utils/node_modules/ajv": { - "version": "6.14.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.14.0.tgz", - "integrity": "sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==", - "dev": true, - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.1", - "fast-json-stable-stringify": "^2.0.0", - "json-schema-traverse": "^0.4.1", - "uri-js": "^4.2.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/schema-utils/node_modules/ajv-keywords": { - "version": "3.5.2", - "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", - "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", - "dev": true, - "license": "MIT", - "peerDependencies": { - "ajv": "^6.9.1" - } - }, - "node_modules/schema-utils/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", - "dev": true, - "license": "MIT" - }, "node_modules/semver": { "version": "7.7.4", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", @@ -8227,75 +8726,48 @@ } }, "node_modules/send": { - "version": "0.19.2", - "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", - "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", + "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", "dependencies": { - "debug": "2.6.9", - "depd": "2.0.0", - "destroy": "1.2.0", - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "etag": "~1.8.1", - "fresh": "~0.5.2", - "http-errors": "~2.0.1", - "mime": "1.6.0", - "ms": "2.1.3", - "on-finished": "~2.4.1", - "range-parser": "~1.2.1", - "statuses": "~2.0.2" + "debug": "^4.4.3", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.1", + "mime-types": "^3.0.2", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.2" }, "engines": { - "node": ">= 0.8.0" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, - "node_modules/send/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "license": "MIT", - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/send/node_modules/debug/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "license": "MIT" - }, "node_modules/serve-static": { - "version": "1.16.3", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", - "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", + "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", "dependencies": { - "encodeurl": "~2.0.0", - "escape-html": "~1.0.3", - "parseurl": "~1.3.3", - "send": "~0.19.1" + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" }, "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "node": ">= 18" }, - "engines": { - "node": ">= 0.4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/setprototypeof": { @@ -8412,13 +8884,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/sisteransi": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", - "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", - "dev": true, - "license": "MIT" - }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -8622,9 +9087,9 @@ } }, "node_modules/strtok3": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", - "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", + "version": "10.3.5", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.5.tgz", + "integrity": "sha512-ki4hZQfh5rX0QDLLkOCj+h+CVNkqmp/CMf8v8kZpkNVK6jGQooMytqzLZYUVYIZcFZ6yDB70EfD8POcFXiF5oA==", "license": "MIT", "dependencies": { "@tokenizer/token": "^0.3.0" @@ -8658,19 +9123,6 @@ "node": ">=14.18.0" } }, - "node_modules/superagent/node_modules/mime": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz", - "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==", - "dev": true, - "license": "MIT", - "bin": { - "mime": "cli.js" - }, - "engines": { - "node": ">=4.0.0" - } - }, "node_modules/supertest": { "version": "7.2.2", "resolved": "https://registry.npmjs.org/supertest/-/supertest-7.2.2.tgz", @@ -8686,20 +9138,11 @@ "node": ">=14.18.0" } }, - "node_modules/supertest/node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "license": "MIT", "dependencies": { "has-flag": "^4.0.0" @@ -8708,19 +9151,6 @@ "node": ">=8" } }, - "node_modules/supports-preserve-symlinks-flag": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", - "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/symbol-observable": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/symbol-observable/-/symbol-observable-4.0.0.tgz", @@ -8748,9 +9178,9 @@ } }, "node_modules/tapable": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", - "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.2.tgz", + "integrity": "sha512-1MOpMXuhGzGL5TTCZFItxCc0AARf1EZFQkGqMm7ERKj8+Hgr5oLvJOVFcC+lRmR8hCe2S3jC4T5D7Vg/d7/fhA==", "dev": true, "license": "MIT", "engines": { @@ -8762,9 +9192,9 @@ } }, "node_modules/terser": { - "version": "5.46.0", - "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.0.tgz", - "integrity": "sha512-jTwoImyr/QbOWFFso3YoU3ik0jBBDJ6JTOQiy/J2YxVJdZCc+5u7skhNwiOR3FQIygFqVUPHl7qbbxtjW2K3Qg==", + "version": "5.46.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.46.1.tgz", + "integrity": "sha512-vzCjQO/rgUuK9sf8VJZvjqiqiHFaZLnOiimmUuOKODxWL8mm/xua7viT7aqX7dgPY60otQjUotzFMmCB4VdmqQ==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -8814,6 +9244,54 @@ } } }, + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, "node_modules/terser-webpack-plugin/node_modules/jest-worker": { "version": "27.5.1", "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", @@ -8829,6 +9307,13 @@ "node": ">= 10.13.0" } }, + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, "node_modules/terser-webpack-plugin/node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", @@ -8887,24 +9372,6 @@ "node": ">=8" } }, - "node_modules/test-exclude/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "node_modules/test-exclude/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, "node_modules/test-exclude/node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -8927,33 +9394,16 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/test-exclude/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, + "node_modules/tinyexec": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.0.4.tgz", + "integrity": "sha512-u9r3uZC0bdpGOXtlxUIdwf9pkmvhqJdrVCH9fapQtgy/OeTTMZ1nqH7agtvEfmGui6e1XxjcdrlxvxJvc3sMqw==", + "devOptional": true, + "license": "MIT", "engines": { - "node": "*" + "node": ">=18" } }, - "node_modules/text-table": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", - "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", - "dev": true, - "license": "MIT" - }, - "node_modules/through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", - "dev": true, - "license": "MIT" - }, "node_modules/tinyglobby": { "version": "0.2.15", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", @@ -8972,9 +9422,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "dev": true, "license": "MIT", "engines": { @@ -8984,19 +9434,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/tmp": { - "version": "0.0.33", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", - "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==", - "dev": true, - "license": "MIT", - "dependencies": { - "os-tmpdir": "~1.0.2" - }, - "engines": { - "node": ">=0.6.0" - } - }, "node_modules/tmpl": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/tmpl/-/tmpl-1.0.5.tgz", @@ -9044,26 +9481,10 @@ "url": "https://github.com/sponsors/Borewit" } }, - "node_modules/tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", - "license": "MIT" - }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "license": "MIT", - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/ts-api-utils": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.4.0.tgz", - "integrity": "sha512-3TaVTaAv2gTiMB35i3FiGJaRfwb3Pyn/j3m/bfAvGe8FB7CF6u+LMYqYlDh7reQf7UNvoTvdfAqHGmPGOSsPmA==", + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.5.0.tgz", + "integrity": "sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==", "dev": true, "license": "MIT", "engines": { @@ -9074,19 +9495,19 @@ } }, "node_modules/ts-jest": { - "version": "29.4.6", - "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.6.tgz", - "integrity": "sha512-fSpWtOO/1AjSNQguk43hb/JCo16oJDnMJf3CdEGNkqsEX3t0KX96xvyX1D7PfLCpVoKu4MfVrqUkFyblYoY4lA==", + "version": "29.4.9", + "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.4.9.tgz", + "integrity": "sha512-LTb9496gYPMCqjeDLdPrKuXtncudeV1yRZnF4Wo5l3SFi0RYEnYRNgMrFIdg+FHvfzjCyQk1cLncWVqiSX+EvQ==", "dev": true, "license": "MIT", "dependencies": { "bs-logger": "^0.2.6", "fast-json-stable-stringify": "^2.1.0", - "handlebars": "^4.7.8", + "handlebars": "^4.7.9", "json5": "^2.2.3", "lodash.memoize": "^4.1.2", "make-error": "^1.3.6", - "semver": "^7.7.3", + "semver": "^7.7.4", "type-fest": "^4.41.0", "yargs-parser": "^21.1.1" }, @@ -9103,7 +9524,7 @@ "babel-jest": "^29.0.0 || ^30.0.0", "jest": "^29.0.0 || ^30.0.0", "jest-util": "^29.0.0 || ^30.0.0", - "typescript": ">=4.3 <6" + "typescript": ">=4.3 <7" }, "peerDependenciesMeta": { "@babel/core": { @@ -9140,9 +9561,9 @@ } }, "node_modules/ts-loader": { - "version": "9.5.4", - "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.4.tgz", - "integrity": "sha512-nCz0rEwunlTZiy6rXFByQU1kVVpCIgUpc/psFiKVrUwrizdnIbRFu8w7bxhUF0X613DYwT4XzrZHpVyMe758hQ==", + "version": "9.5.7", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.5.7.tgz", + "integrity": "sha512-/ZNrKgA3K3PtpMYOC71EeMWIloGw3IYEa5/t1cyz2r5/PyUwTXGzYJvcD3kfUvmhlfpz1rhV8B2O6IVTQ0avsg==", "dev": true, "license": "MIT", "dependencies": { @@ -9275,9 +9696,9 @@ } }, "node_modules/type-fest": { - "version": "0.20.2", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", - "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", "dev": true, "license": "(MIT OR CC0-1.0)", "engines": { @@ -9288,13 +9709,14 @@ } }, "node_modules/type-is": { - "version": "1.6.18", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", - "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", "license": "MIT", "dependencies": { - "media-typer": "0.3.0", - "mime-types": "~2.1.24" + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" }, "engines": { "node": ">= 0.6" @@ -9310,7 +9732,7 @@ "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, + "devOptional": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -9320,6 +9742,30 @@ "node": ">=14.17" } }, + "node_modules/typescript-eslint": { + "version": "8.58.0", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.58.0.tgz", + "integrity": "sha512-e2TQzKfaI85fO+F3QywtX+tCTsu/D3WW5LVU6nz8hTFKFZ8yBJ6mSYRpXqdR3mFjPWmO0eWsTa5f+UpAOe/FMA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/eslint-plugin": "8.58.0", + "@typescript-eslint/parser": "8.58.0", + "@typescript-eslint/typescript-estree": "8.58.0", + "@typescript-eslint/utils": "8.58.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, "node_modules/uglify-js": { "version": "3.19.3", "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", @@ -9359,9 +9805,9 @@ } }, "node_modules/undici-types": { - "version": "6.21.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", - "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", "dev": true, "license": "MIT" }, @@ -9384,6 +9830,41 @@ "node": ">= 0.8" } }, + "node_modules/unrs-resolver": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/unrs-resolver/-/unrs-resolver-1.11.1.tgz", + "integrity": "sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "napi-postinstall": "^0.3.0" + }, + "funding": { + "url": "https://opencollective.com/unrs-resolver" + }, + "optionalDependencies": { + "@unrs/resolver-binding-android-arm-eabi": "1.11.1", + "@unrs/resolver-binding-android-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-arm64": "1.11.1", + "@unrs/resolver-binding-darwin-x64": "1.11.1", + "@unrs/resolver-binding-freebsd-x64": "1.11.1", + "@unrs/resolver-binding-linux-arm-gnueabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm-musleabihf": "1.11.1", + "@unrs/resolver-binding-linux-arm64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-arm64-musl": "1.11.1", + "@unrs/resolver-binding-linux-ppc64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-riscv64-musl": "1.11.1", + "@unrs/resolver-binding-linux-s390x-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-gnu": "1.11.1", + "@unrs/resolver-binding-linux-x64-musl": "1.11.1", + "@unrs/resolver-binding-wasm32-wasi": "1.11.1", + "@unrs/resolver-binding-win32-arm64-msvc": "1.11.1", + "@unrs/resolver-binding-win32-ia32-msvc": "1.11.1", + "@unrs/resolver-binding-win32-x64-msvc": "1.11.1" + } + }, "node_modules/update-browserslist-db": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", @@ -9431,15 +9912,6 @@ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, - "node_modules/utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", @@ -9462,6 +9934,15 @@ "node": ">=10.12.0" } }, + "node_modules/validator": { + "version": "13.15.35", + "resolved": "https://registry.npmjs.org/validator/-/validator-13.15.35.tgz", + "integrity": "sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -9505,19 +9986,12 @@ "defaults": "^1.0.3" } }, - "node_modules/webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", - "license": "BSD-2-Clause" - }, "node_modules/webpack": { "version": "5.105.4", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.105.4.tgz", "integrity": "sha512-jTywjboN9aHxFlToqb0K0Zs9SbBoW4zRUlGzI2tYNxVYcEi/IPpn+Xi4ye5jTLvX2YeLuic/IvxNot+Q1jMoOw==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/eslint-scope": "^3.7.7", "@types/estree": "^1.0.8", @@ -9581,13 +10055,60 @@ "node": ">=10.13.0" } }, + "node_modules/webpack/node_modules/ajv": { + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, "node_modules/webpack/node_modules/eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^4.1.1" @@ -9602,18 +10123,46 @@ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", - "peer": true, "engines": { "node": ">=4.0" } }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/webpack/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/webpack/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/webpack/node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", "integrity": "sha512-eflK8wEtyOE6+hsaRVPxvUKYCpRgzLqDTb8krvAsRIwOGlHoSgYLgBXoubGgLd2fT41/OUYdb48v4k4WWHQurA==", "dev": true, "license": "MIT", - "peer": true, "dependencies": { "@types/json-schema": "^7.0.9", "ajv": "^8.9.0", @@ -9628,16 +10177,6 @@ "url": "https://opencollective.com/webpack" } }, - "node_modules/whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "license": "MIT", - "dependencies": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -9709,37 +10248,20 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true, "license": "ISC" }, "node_modules/write-file-atomic": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-4.0.2.tgz", - "integrity": "sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-5.0.1.tgz", + "integrity": "sha512-+QU2zd6OTD8XWIJCbffaiQeH9U73qIqafo1x6V1snCWYGJf6cVE0cDR4D8xRzcEnfI21IFrUPzPGtcPf8AC+Rw==", "dev": true, "license": "ISC", "dependencies": { "imurmurhash": "^0.1.4", - "signal-exit": "^3.0.7" + "signal-exit": "^4.0.1" }, "engines": { - "node": "^12.13.0 || ^14.15.0 || >=16.0.0" - } - }, - "node_modules/write-file-atomic/node_modules/signal-exit": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", - "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", - "dev": true, - "license": "ISC" - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" } }, "node_modules/y18n": { @@ -9810,6 +10332,19 @@ "funding": { "url": "https://github.com/sponsors/sindresorhus" } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/server/package.json b/server/package.json index b7206a3..4f1a8d1 100644 --- a/server/package.json +++ b/server/package.json @@ -11,53 +11,51 @@ "start": "nest start", "start:dev": "nest start --watch", "start:debug": "nest start --debug --watch", - "start:prod": "node dist/src/main.js", + "start:prod": "node dist/main", "lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix", "test": "jest", "test:watch": "jest --watch", "test:cov": "jest --coverage", "test:debug": "node --inspect-brk -r tsconfig-paths/register -r ts-node/register node_modules/.bin/jest --runInBand", - "test:e2e": "jest --config ./test/jest-e2e.json", - "generate:from-dsl": "node ../generation/generate.mjs --apply --dsl domain/TOiR.domain.dsl", - "generate:bundle-json": "node ../generation/generate.mjs --print-bundle-json --dsl domain/TOiR.domain.dsl", - "postinstall": "prisma generate" - }, - "prisma": { - "seed": "ts-node prisma/seed.ts" + "test:e2e": "jest --config ./test/jest-e2e.json" }, "dependencies": { - "@nestjs/common": "^10.0.0", - "@nestjs/config": "^4.0.3", - "@nestjs/core": "^10.0.0", - "@nestjs/platform-express": "^10.0.0", - "@prisma/client": "^5.22.0", - "jose": "^6.2.2", - "reflect-metadata": "^0.2.0", + "@prisma/client": "^6.19.0", + "@nestjs/common": "^11.0.1", + "@nestjs/core": "^11.0.1", + "@nestjs/platform-express": "^11.0.1", + "class-transformer": "^0.5.1", + "class-validator": "^0.14.2", + "jose": "^6.1.2", + "reflect-metadata": "^0.2.2", "rxjs": "^7.8.1" }, "devDependencies": { - "@nestjs/cli": "^10.0.0", - "@nestjs/schematics": "^10.0.0", - "@nestjs/testing": "^10.0.0", + "@eslint/eslintrc": "^3.2.0", + "@eslint/js": "^9.18.0", + "@nestjs/cli": "^11.0.0", + "@nestjs/schematics": "^11.0.0", + "@nestjs/testing": "^11.0.1", "@types/express": "^5.0.0", - "@types/jest": "^29.5.2", - "@types/node": "^20.3.1", - "@types/supertest": "^6.0.0", - "@typescript-eslint/eslint-plugin": "^8.0.0", - "@typescript-eslint/parser": "^8.0.0", - "eslint": "^8.0.0", - "eslint-config-prettier": "^9.0.0", - "eslint-plugin-prettier": "^5.0.0", - "jest": "^29.5.0", - "prettier": "^3.0.0", - "prisma": "^5.22.0", + "@types/jest": "^30.0.0", + "@types/node": "^24.0.0", + "@types/supertest": "^7.0.0", + "dotenv": "^17.2.3", + "eslint": "^9.18.0", + "eslint-config-prettier": "^10.0.1", + "eslint-plugin-prettier": "^5.2.2", + "globals": "^17.0.0", + "jest": "^30.0.0", + "prettier": "^3.4.2", + "prisma": "^6.19.0", "source-map-support": "^0.5.21", "supertest": "^7.0.0", - "ts-jest": "^29.1.0", - "ts-loader": "^9.4.3", + "ts-jest": "^29.2.5", + "ts-loader": "^9.5.2", "ts-node": "^10.9.2", "tsconfig-paths": "^4.2.0", - "typescript": "^5.1.3" + "typescript": "^5.7.3", + "typescript-eslint": "^8.20.0" }, "jest": { "moduleFileExtensions": [ diff --git a/server/prisma.config.ts b/server/prisma.config.ts new file mode 100644 index 0000000..8ded1a5 --- /dev/null +++ b/server/prisma.config.ts @@ -0,0 +1,16 @@ + +// This file was generated by Prisma and assumes you have installed the following: +// npm install --save-dev prisma dotenv +import "dotenv/config"; +import { defineConfig, env } from "prisma/config"; + +export default defineConfig({ + schema: "prisma/schema.prisma", + migrations: { + path: "prisma/migrations", + }, + engine: "classic", + datasource: { + url: env("DATABASE_URL"), + }, +}); diff --git a/server/prisma/migrations/20260322114233_baseline/migration.sql b/server/prisma/migrations/20260322114233_baseline/migration.sql deleted file mode 100644 index 20fec42..0000000 --- a/server/prisma/migrations/20260322114233_baseline/migration.sql +++ /dev/null @@ -1,68 +0,0 @@ --- CreateEnum -CREATE TYPE "EquipmentStatus" AS ENUM ('Active', 'Repair', 'Reserve', 'WriteOff'); - --- CreateEnum -CREATE TYPE "RepairKind" AS ENUM ('TO', 'TR', 'TRE', 'KR', 'AR', 'MP'); - --- CreateEnum -CREATE TYPE "RepairOrderStatus" AS ENUM ('Draft', 'Approved', 'InWork', 'Done', 'Cancelled'); - --- CreateTable -CREATE TABLE "EquipmentType" ( - "code" TEXT NOT NULL, - "name" TEXT NOT NULL, - "manufacturer" TEXT, - "maintenanceIntervalHours" INTEGER, - "overhaulIntervalHours" INTEGER, - - CONSTRAINT "EquipmentType_pkey" PRIMARY KEY ("code") -); - --- CreateTable -CREATE TABLE "Equipment" ( - "id" TEXT NOT NULL, - "inventoryNumber" TEXT NOT NULL, - "serialNumber" TEXT, - "name" TEXT NOT NULL, - "equipmentTypeCode" TEXT NOT NULL, - "status" "EquipmentStatus" NOT NULL DEFAULT 'Active', - "location" TEXT, - "commissionedAt" TIMESTAMP(3), - "totalEngineHours" DECIMAL(65,30), - "engineHoursSinceLastRepair" DECIMAL(65,30), - "lastRepairAt" TIMESTAMP(3), - "notes" TEXT, - - CONSTRAINT "Equipment_pkey" PRIMARY KEY ("id") -); - --- CreateTable -CREATE TABLE "RepairOrder" ( - "id" TEXT NOT NULL, - "number" TEXT NOT NULL, - "equipmentId" TEXT NOT NULL, - "repairKind" "RepairKind" NOT NULL, - "status" "RepairOrderStatus" NOT NULL DEFAULT 'Draft', - "plannedAt" TIMESTAMP(3) NOT NULL, - "startedAt" TIMESTAMP(3), - "completedAt" TIMESTAMP(3), - "contractor" TEXT, - "engineHoursAtRepair" DECIMAL(65,30), - "description" TEXT, - "notes" TEXT, - - CONSTRAINT "RepairOrder_pkey" PRIMARY KEY ("id") -); - --- CreateIndex -CREATE UNIQUE INDEX "Equipment_inventoryNumber_key" ON "Equipment"("inventoryNumber"); - --- CreateIndex -CREATE UNIQUE INDEX "RepairOrder_number_key" ON "RepairOrder"("number"); - --- AddForeignKey -ALTER TABLE "Equipment" ADD CONSTRAINT "Equipment_equipmentTypeCode_fkey" FOREIGN KEY ("equipmentTypeCode") REFERENCES "EquipmentType"("code") ON DELETE RESTRICT ON UPDATE CASCADE; - --- AddForeignKey -ALTER TABLE "RepairOrder" ADD CONSTRAINT "RepairOrder_equipmentId_fkey" FOREIGN KEY ("equipmentId") REFERENCES "Equipment"("id") ON DELETE RESTRICT ON UPDATE CASCADE; - diff --git a/server/prisma/migrations/20260403103310_init/migration.sql b/server/prisma/migrations/20260403103310_init/migration.sql new file mode 100644 index 0000000..870611d --- /dev/null +++ b/server/prisma/migrations/20260403103310_init/migration.sql @@ -0,0 +1,85 @@ +-- CreateEnum +CREATE TYPE "EquipmentStatus" AS ENUM ('Active', 'Repair', 'Reserve', 'WriteOff'); + +-- CreateEnum +CREATE TYPE "laborOperation" AS ENUM ('Manual', 'MachineManual', 'Machine'); + +-- CreateEnum +CREATE TYPE "EnumPeriodicityTO" AS ENUM ('Ежедневное', 'Еженедельное', 'Ежемесячное', 'Полугодовое', 'Годовое'); + +-- CreateEnum +CREATE TYPE "Role" AS ENUM ('Исполнитель', 'Подписант', 'Пользователь'); + +-- CreateEnum +CREATE TYPE "CategoryPart" AS ENUM ('Расходник', 'Запчасть', 'Инструмент', 'Спецодежда'); + +-- CreateEnum +CREATE TYPE "EquipmentType" AS ENUM ('Производственное', 'Энергетическое', 'Насосное', 'Компрессорное'); + +-- CreateTable +CREATE TABLE "Equipment" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "serialNumber" TEXT NOT NULL, + "inventoryNumber" TEXT NOT NULL, + "equipmentType" "EquipmentType" NOT NULL, + "dateOfInspection" TIMESTAMP(3), + "periodicityTO" "EnumPeriodicityTO" NOT NULL, + "location" TEXT, + "status" "EquipmentStatus" NOT NULL, + "commissionedAt" TIMESTAMP(3), + "totalEngineHours" DECIMAL(65,30), + "engineHoursSinceLastRepair" DECIMAL(65,30), + "lastRepairAt" TIMESTAMP(3), + "notes" TEXT, + "workAsPartOf" "laborOperation", + "fuelConsumed" DOUBLE PRECISION, + + CONSTRAINT "Equipment_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "Employee" ( + "code" TEXT NOT NULL, + "fullName" TEXT NOT NULL, + "role" "Role" NOT NULL, + "position" TEXT NOT NULL, + "bossCode" TEXT, + "price" DOUBLE PRECISION, + "phoneNumber" DOUBLE PRECISION, + + CONSTRAINT "Employee_pkey" PRIMARY KEY ("code") +); + +-- CreateTable +CREATE TABLE "Part" ( + "id" TEXT NOT NULL, + "name" TEXT NOT NULL, + "categories" "CategoryPart", + "price" DOUBLE PRECISION, + "description" TEXT, + "serialNumber" TEXT, + + CONSTRAINT "Part_pkey" PRIMARY KEY ("id") +); + +-- CreateTable +CREATE TABLE "CategoryResource" ( + "id" TEXT NOT NULL, + "partId" TEXT, + "employeeCode" TEXT, + + CONSTRAINT "CategoryResource_pkey" PRIMARY KEY ("id") +); + +-- CreateIndex +CREATE UNIQUE INDEX "Equipment_inventoryNumber_key" ON "Equipment"("inventoryNumber"); + +-- AddForeignKey +ALTER TABLE "Employee" ADD CONSTRAINT "Employee_bossCode_fkey" FOREIGN KEY ("bossCode") REFERENCES "Employee"("code") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CategoryResource" ADD CONSTRAINT "CategoryResource_partId_fkey" FOREIGN KEY ("partId") REFERENCES "Part"("id") ON DELETE SET NULL ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "CategoryResource" ADD CONSTRAINT "CategoryResource_employeeCode_fkey" FOREIGN KEY ("employeeCode") REFERENCES "Employee"("code") ON DELETE SET NULL ON UPDATE CASCADE; diff --git a/server/prisma/migrations/migration_lock.toml b/server/prisma/migrations/migration_lock.toml new file mode 100644 index 0000000..044d57c --- /dev/null +++ b/server/prisma/migrations/migration_lock.toml @@ -0,0 +1,3 @@ +# Please do not edit this file manually +# It should be added in your version-control system (e.g., Git) +provider = "postgresql" diff --git a/server/prisma/schema.prisma b/server/prisma/schema.prisma index e0556d8..14d3924 100644 --- a/server/prisma/schema.prisma +++ b/server/prisma/schema.prisma @@ -14,61 +14,86 @@ enum EquipmentStatus { WriteOff } -enum RepairKind { - TO - TR - TRE - KR - AR - MP +enum laborOperation { + Manual + MachineManual + Machine } -enum RepairOrderStatus { - Draft - Approved - InWork - Done - Cancelled +enum EnumPeriodicityTO { + EZHEDNEVNOE @map("Ежедневное") + EZHENEDELNOE @map("Еженедельное") + EZHEMESYACHNOE @map("Ежемесячное") + POLUGODOVOE @map("Полугодовое") + GODOVOE @map("Годовое") } -model EquipmentType { - code String @id - name String - manufacturer String? - maintenanceIntervalHours Int? - overhaulIntervalHours Int? - equipment Equipment[] +enum Role { + ISPOLNITEL @map("Исполнитель") + PODPISANT @map("Подписант") + POLZOVATEL @map("Пользователь") +} + +enum CategoryPart { + RASKHODNIK @map("Расходник") + ZAPCHAST @map("Запчасть") + INSTRUMENT @map("Инструмент") + SPETSODEZHDA @map("Спецодежда") +} + +enum EquipmentType { + PROIZVODSTVENNOE @map("Производственное") + ENERGETICHESKOE @map("Энергетическое") + NASOSNOE @map("Насосное") + KOMPRESSORNOE @map("Компрессорное") } model Equipment { - id String @id @default(uuid()) - inventoryNumber String @unique - serialNumber String? - name String - equipmentTypeCode String - status EquipmentStatus @default(Active) - location String? - commissionedAt DateTime? - totalEngineHours Decimal? + id String @id @default(uuid()) + name String + serialNumber String + inventoryNumber String @unique + equipmentType EquipmentType + dateOfInspection DateTime? + periodicityTO EnumPeriodicityTO + location String? + status EquipmentStatus + commissionedAt DateTime? + totalEngineHours Decimal? engineHoursSinceLastRepair Decimal? - lastRepairAt DateTime? - notes String? - equipmentType EquipmentType @relation(fields: [equipmentTypeCode], references: [code]) - repairOrders RepairOrder[] + lastRepairAt DateTime? + notes String? + workAsPartOf laborOperation? + fuelConsumed Float? } -model RepairOrder { - id String @id @default(uuid()) - number String @unique - equipmentId String - repairKind RepairKind - status RepairOrderStatus @default(Draft) - plannedAt DateTime - startedAt DateTime? - completedAt DateTime? - contractor String? - engineHoursAtRepair Decimal? - description String? - notes String? - equipment Equipment @relation(fields: [equipmentId], references: [id]) +model Employee { + code String @id + fullName String + role Role + position String + bossCode String? + boss Employee? @relation("EmployeeBoss", fields: [bossCode], references: [code]) + subordinates Employee[] @relation("EmployeeBoss") + price Float? + phoneNumber Float? + categoryResources CategoryResource[] +} + +model Part { + id String @id @default(uuid()) + name String + categories CategoryPart? + price Float? + description String? + serialNumber String? + categoryResources CategoryResource[] +} + +model CategoryResource { + id String @id @default(uuid()) + partId String? + employeeCode String? + part Part? @relation(fields: [partId], references: [id]) + employee Employee? @relation(fields: [employeeCode], references: [code]) } diff --git a/server/prisma/seed.ts b/server/prisma/seed.ts deleted file mode 100644 index 5300f3d..0000000 --- a/server/prisma/seed.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { PrismaClient } from '@prisma/client'; - -const prisma = new PrismaClient(); - -async function main() { - const equipmentTypes = [ - { - code: 'pump', - name: 'Насосный агрегат', - manufacturer: 'АО НасосПром', - maintenanceIntervalHours: 2000, - overhaulIntervalHours: 16000, - }, - { - code: 'compressor', - name: 'Компрессорная установка', - manufacturer: 'ОАО Компрессормаш', - maintenanceIntervalHours: 1500, - overhaulIntervalHours: 12000, - }, - { - code: 'generator', - name: 'Дизель-генератор', - manufacturer: 'АО ЭнергоМаш', - maintenanceIntervalHours: 500, - overhaulIntervalHours: 6000, - }, - { - code: 'valve', - name: 'Запорная арматура', - manufacturer: 'ЗАО АрматурПром', - maintenanceIntervalHours: 1000, - overhaulIntervalHours: 10000, - }, - { - code: 'sensor', - name: 'Датчик давления', - manufacturer: 'ООО ПриборСервис', - maintenanceIntervalHours: 800, - overhaulIntervalHours: 8000, - }, - { - code: 'motor', - name: 'Электродвигатель', - manufacturer: 'ПАО ЭлектроМотор', - maintenanceIntervalHours: 1200, - overhaulIntervalHours: 14000, - }, - { - code: 'fan', - name: 'Вентилятор', - manufacturer: 'АО ВентПром', - maintenanceIntervalHours: 700, - overhaulIntervalHours: 9000, - }, - { - code: 'heat-exchanger', - name: 'Теплообменник', - manufacturer: 'ОАО ТеплоТех', - maintenanceIntervalHours: 1800, - overhaulIntervalHours: 15000, - }, - { - code: 'filter', - name: 'Фильтровальная установка', - manufacturer: 'ООО ФильтрТех', - maintenanceIntervalHours: 600, - overhaulIntervalHours: 7000, - }, - { - code: 'separator', - name: 'Сепаратор', - manufacturer: 'АО СепараторМаш', - maintenanceIntervalHours: 1600, - overhaulIntervalHours: 13000, - }, - { - code: 'transformer', - name: 'Трансформатор', - manufacturer: 'ПАО ТрансЭнерго', - maintenanceIntervalHours: 2500, - overhaulIntervalHours: 20000, - }, - ] as const; - - for (const type of equipmentTypes) { - await prisma.equipmentType.upsert({ - where: { code: type.code }, - update: { ...type }, - create: { ...type }, - }); - } - - const equipmentRecords: { id: string; inventoryNumber: string; name: string }[] = []; - for (let i = 1; i <= 11; i++) { - const type = equipmentTypes[(i - 1) % equipmentTypes.length]; - const inventoryNumber = `INV-${String(i).padStart(3, '0')}`; - const serialNumber = `SN-2026-${String(i).padStart(4, '0')}`; - const record = await prisma.equipment.upsert({ - where: { inventoryNumber }, - update: { - serialNumber, - name: `${type.name} #${i}`, - equipmentTypeCode: type.code, - status: i % 5 === 0 ? 'Repair' : 'Active', - location: i % 2 === 0 ? `Площадка ${Math.ceil(i / 2)}` : `Цех ${Math.ceil(i / 3)}`, - commissionedAt: new Date(2022, (i - 1) % 12, 1 + ((i - 1) % 28)), - totalEngineHours: 1000 + i * 350, - engineHoursSinceLastRepair: 200 + i * 25, - }, - create: { - inventoryNumber, - serialNumber, - name: `${type.name} #${i}`, - equipmentTypeCode: type.code, - status: i % 5 === 0 ? 'Repair' : 'Active', - location: i % 2 === 0 ? `Площадка ${Math.ceil(i / 2)}` : `Цех ${Math.ceil(i / 3)}`, - commissionedAt: new Date(2022, (i - 1) % 12, 1 + ((i - 1) % 28)), - totalEngineHours: 1000 + i * 350, - engineHoursSinceLastRepair: 200 + i * 25, - }, - }); - equipmentRecords.push({ id: record.id, inventoryNumber: record.inventoryNumber, name: record.name }); - } - - const repairKinds = ['TO', 'TR', 'TRE', 'KR', 'AR', 'MP'] as const; - const statuses = ['Draft', 'Approved', 'InWork', 'Done', 'Cancelled'] as const; - - for (let i = 1; i <= 11; i++) { - const number = `RO-2026-${String(i).padStart(3, '0')}`; - const equipment = equipmentRecords[(i - 1) % equipmentRecords.length]; - await prisma.repairOrder.upsert({ - where: { number }, - update: { - equipmentId: equipment.id, - repairKind: repairKinds[(i - 1) % repairKinds.length], - status: statuses[(i - 1) % statuses.length], - plannedAt: new Date(2026, ((i - 1) % 12), 1 + ((i - 1) % 28)), - startedAt: i % 4 === 0 ? new Date(2026, ((i - 1) % 12), 2 + ((i - 1) % 28)) : null, - completedAt: i % 5 === 0 ? new Date(2026, ((i - 1) % 12), 5 + ((i - 1) % 28)) : null, - contractor: i % 3 === 0 ? 'ООО СервисРемонт' : 'АО ТехПодряд', - engineHoursAtRepair: 1000 + i * 350, - description: `Заявка на ремонт ${equipment.inventoryNumber} (${equipment.name})`, - notes: i % 2 === 0 ? 'Тестовая заметка' : null, - }, - create: { - number, - equipmentId: equipment.id, - repairKind: repairKinds[(i - 1) % repairKinds.length], - status: statuses[(i - 1) % statuses.length], - plannedAt: new Date(2026, ((i - 1) % 12), 1 + ((i - 1) % 28)), - startedAt: i % 4 === 0 ? new Date(2026, ((i - 1) % 12), 2 + ((i - 1) % 28)) : null, - completedAt: i % 5 === 0 ? new Date(2026, ((i - 1) % 12), 5 + ((i - 1) % 28)) : null, - contractor: i % 3 === 0 ? 'ООО СервисРемонт' : 'АО ТехПодряд', - engineHoursAtRepair: 1000 + i * 350, - description: `Заявка на ремонт ${equipment.inventoryNumber} (${equipment.name})`, - notes: i % 2 === 0 ? 'Тестовая заметка' : null, - }, - }); - } - - console.log('Seed data created successfully'); -} - -main() - .catch((e) => { - console.error(e); - process.exit(1); - }) - .finally(async () => { - await prisma.$disconnect(); - }); diff --git a/server/src/aid-export/README.md b/server/src/aid-export/README.md deleted file mode 100644 index b46ed50..0000000 --- a/server/src/aid-export/README.md +++ /dev/null @@ -1,87 +0,0 @@ -# AID export: OpenAPI + генератор приложения - -**Полное описание задачи, сценариев и CLI:** [docs/AID_EXPORT_README.md](../../../docs/AID_EXPORT_README.md) - -Ниже — краткая справка по HTTP-эндпоинтам. - ---- - -## 1. `api-format` → OpenAPI 3.0 - -`POST /aid/export/openapi` - -Внутри: `tools/api-format-to-openapi/convert.mjs` (режимы `deterministic` и `llm`). - -**Тело:** - -```json -{ - "apiFormat": { "apiFormatVersion": "1", "...": "..." }, - "mode": "deterministic" -} -``` - -**Ответ:** `{ "openapi": { ... } }` - ---- - -## 2. DSL → сгенерированное приложение (бандл или запись на диск) - -`POST /aid/export/app` - -Внутри: `generation/generate.mjs`. - -**Тело:** - -```json -{ - "dsl": "domain TOiR {\n ...\n}\n", - "apply": false -} -``` - -- **`apply` по умолчанию `false` (рекомендуется для AID):** ответ содержит `files` — карта **путь от корня репозитория → текст файла** (Prisma, Nest-модули, React Admin и обновлённые `app.module.ts` / `App.tsx`). **На диск ничего не пишется.** -- **`apply: true`:** выполняется тот же процесс, что и `npm run generate:from-dsl` с `--apply` — **перезапись файлов в рабочей копии** на машине, где запущен Nest. Включено только если в окружении задано **`AID_GENERATOR_ALLOW_APPLY=1`** (или `true`). Иначе `403 Forbidden`. - -**Ответ (бандл):** - -```json -{ - "applied": false, - "entityCount": 3, - "enumCount": 3, - "files": { - "server/prisma/schema.prisma": "...", - "server/src/modules/equipment/equipment.controller.ts": "...", - "client/src/App.tsx": "..." - } -} -``` - -**Ответ (apply):** - -```json -{ - "applied": true, - "message": "Generated 3 entities from ..." -} -``` - -### CLI-аналог бандла (без Nest) - -Из **корня репозитория**: - -```bash -node generation/generate.mjs --print-bundle-json --dsl examples/TOiR.domain.dsl > bundle.json -``` - ---- - -## Безопасность - -- Если в `.env` задан **`AID_EXPORT_API_KEY`**, для **обоих** эндпоинтов нужен заголовок **`X-AID-Export-Key`** с тем же значением. -- Не включайте **`AID_GENERATOR_ALLOW_APPLY`** на публичных инстансах без понимания рисков. - -## Требования - -- Запуск Nest с **cwd = `server/`** относительно корня репо, чтобы находились `../generation/generate.mjs` и `../tools/...`. diff --git a/server/src/aid-export/aid-export.controller.ts b/server/src/aid-export/aid-export.controller.ts deleted file mode 100644 index a92de55..0000000 --- a/server/src/aid-export/aid-export.controller.ts +++ /dev/null @@ -1,112 +0,0 @@ -import { - BadRequestException, - Body, - Controller, - ForbiddenException, - Headers, - HttpCode, - Post, - UnauthorizedException, -} from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { AidExportService, OpenApiExportMode } from './aid-export.service'; - -/** - * HTTP-экспортёры для AID: OpenAPI из api-format и генерация приложения из DSL. - */ -@Controller('aid/export') -export class AidExportController { - constructor( - private readonly aidExport: AidExportService, - private readonly config: ConfigService, - ) {} - - private assertExportKey(exportKey: string | undefined) { - const requiredKey = this.config.get('AID_EXPORT_API_KEY'); - if (requiredKey && exportKey !== requiredKey) { - throw new UnauthorizedException('Invalid or missing X-AID-Export-Key'); - } - } - - /** - * POST /aid/export/openapi - * Body: { "apiFormat": <объект api-format>, "mode"?: "deterministic" | "llm" } - * Response: { "openapi": } - * - * mode=llm требует OPENAI_API_KEY в окружении сервера. - * - * Если задан AID_EXPORT_API_KEY, клиент должен передать заголовок X-AID-Export-Key с тем же значением. - */ - @Post('openapi') - @HttpCode(200) - async exportOpenApi( - @Body() - body: { apiFormat?: unknown; mode?: string }, - @Headers('x-aid-export-key') exportKey?: string, - ) { - this.assertExportKey(exportKey); - - if (body == null || typeof body !== 'object' || body.apiFormat == null) { - throw new BadRequestException('Body must be a JSON object with an "apiFormat" property'); - } - if (typeof body.apiFormat !== 'object' || Array.isArray(body.apiFormat)) { - throw new BadRequestException('"apiFormat" must be a JSON object'); - } - - const mode: OpenApiExportMode = - body.mode === 'llm' ? 'llm' : 'deterministic'; - - const openapi = await this.aidExport.convertApiFormatToOpenApi( - body.apiFormat, - mode, - ); - return { openapi }; - } - - /** - * POST /aid/export/app - * Body: { "dsl": "<текст DSL как в examples/TOiR.domain.dsl>", "apply"?: boolean } - * - * По умолчанию `apply: false` — возвращается JSON с полем `files` (пути относительно корня репо → содержимое), - * без записи на диск (безопасно для вызова из AID). - * - * `apply: true` перезаписывает файлы в **текущей** рабочей копии репозитория на машине, где крутится Nest. - * Разрешено только если в окружении задано `AID_GENERATOR_ALLOW_APPLY=1` (или `true`). - */ - @Post('app') - @HttpCode(200) - async exportApp( - @Body() - body: { dsl?: string; apply?: boolean }, - @Headers('x-aid-export-key') exportKey?: string, - ) { - this.assertExportKey(exportKey); - - if (body == null || typeof body !== 'object') { - throw new BadRequestException('Body must be a JSON object'); - } - if (typeof body.dsl !== 'string' || !body.dsl.trim()) { - throw new BadRequestException('Body must include a non-empty string "dsl"'); - } - - const apply = body.apply === true; - if (apply) { - const allow = this.config.get('AID_GENERATOR_ALLOW_APPLY'); - if (allow !== '1' && allow !== 'true') { - throw new ForbiddenException( - 'apply=true is disabled. Set AID_GENERATOR_ALLOW_APPLY=1 on the server to allow writing generated files to disk.', - ); - } - const { message } = await this.aidExport.generateAppApply(body.dsl); - return { applied: true, message }; - } - - const bundle = await this.aidExport.generateAppBundle(body.dsl); - return { - applied: false, - entityCount: bundle.entityCount, - enumCount: bundle.enumCount, - files: bundle.files, - }; - } -} diff --git a/server/src/aid-export/aid-export.module.ts b/server/src/aid-export/aid-export.module.ts deleted file mode 100644 index 4aff7a4..0000000 --- a/server/src/aid-export/aid-export.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Module } from '@nestjs/common'; -import { AidExportController } from './aid-export.controller'; -import { AidExportService } from './aid-export.service'; - -@Module({ - controllers: [AidExportController], - providers: [AidExportService], -}) -export class AidExportModule {} diff --git a/server/src/aid-export/aid-export.service.ts b/server/src/aid-export/aid-export.service.ts deleted file mode 100644 index abbbac9..0000000 --- a/server/src/aid-export/aid-export.service.ts +++ /dev/null @@ -1,154 +0,0 @@ -import { Injectable, InternalServerErrorException } from '@nestjs/common'; -import { randomUUID } from 'crypto'; -import { execFile } from 'child_process'; -import { access, readFile, unlink, writeFile } from 'fs/promises'; -import { tmpdir } from 'os'; -import { join } from 'path'; -import { promisify } from 'util'; - -const execFileAsync = promisify(execFile); - -const LARGE_BUFFER = 64 * 1024 * 1024; - -export type OpenApiExportMode = 'deterministic' | 'llm'; - -export type AppGeneratorBundle = { - entityCount: number; - enumCount: number; - files: Record; -}; - -@Injectable() -export class AidExportService { - /** - * Путь к tools/api-format-to-openapi/convert.mjs относительно cwd процесса (обычно каталог server/). - */ - private resolveConvertScript(): string { - return join(process.cwd(), '..', 'tools', 'api-format-to-openapi', 'convert.mjs'); - } - - /** Путь к generation/generate.mjs относительно cwd = server/. */ - private resolveGenerateScript(): string { - return join(process.cwd(), '..', 'generation', 'generate.mjs'); - } - - async convertApiFormatToOpenApi( - apiFormat: unknown, - mode: OpenApiExportMode, - ): Promise> { - const script = this.resolveConvertScript(); - try { - await access(script); - } catch { - throw new InternalServerErrorException( - `Converter script not found at ${script}. Run the server with cwd = server/ from repo root.`, - ); - } - - const id = randomUUID(); - const inPath = join(tmpdir(), `api-format-${id}.json`); - const outPath = join(tmpdir(), `openapi-${id}.json`); - - try { - await writeFile(inPath, JSON.stringify(apiFormat), 'utf8'); - const { stderr } = await execFileAsync( - process.execPath, - [script, '--in', inPath, '--out', outPath, '--mode', mode], - { - env: { ...process.env }, - maxBuffer: 16 * 1024 * 1024, - }, - ); - if (stderr?.trim()) { - // convert.mjs пишет ошибки в stderr при падении; при успехе обычно пусто - console.warn('[aid-export]', stderr); - } - const raw = await readFile(outPath, 'utf8'); - return JSON.parse(raw) as Record; - } catch (e: unknown) { - const msg = e instanceof Error ? e.message : String(e); - throw new InternalServerErrorException(`OpenAPI conversion failed: ${msg}`); - } finally { - await unlink(inPath).catch(() => undefined); - await unlink(outPath).catch(() => undefined); - } - } - - /** - * DSL → снимок сгенерированных файлов (без записи в репозиторий). - * Использует `generation/generate.mjs --print-bundle-json`. - */ - async generateAppBundle(dsl: string): Promise { - const script = this.resolveGenerateScript(); - try { - await access(script); - } catch { - throw new InternalServerErrorException( - `Generator script not found at ${script}. Run the server with cwd = server/ from repo root.`, - ); - } - - const id = randomUUID(); - const dslPath = join(tmpdir(), `domain-${id}.dsl`); - - try { - await writeFile(dslPath, dsl, 'utf8'); - const { stdout, stderr } = await execFileAsync( - process.execPath, - [script, '--print-bundle-json', '--dsl', dslPath], - { - env: { ...process.env }, - maxBuffer: LARGE_BUFFER, - }, - ); - if (stderr?.trim()) console.warn('[aid-export][generate]', stderr); - const bundle = JSON.parse(stdout) as AppGeneratorBundle; - if (!bundle.files || typeof bundle.files !== 'object') { - throw new Error('Invalid bundle: missing files'); - } - return bundle; - } catch (e: unknown) { - const msg = e instanceof Error ? e.message : String(e); - throw new InternalServerErrorException(`App generator (bundle) failed: ${msg}`); - } finally { - await unlink(dslPath).catch(() => undefined); - } - } - - /** - * DSL → запись сгенерированного кода в рабочую копию репозитория (`--apply`). - * Опасно для публичных эндпоинтов; включать только осознанно. - */ - async generateAppApply(dsl: string): Promise<{ message: string }> { - const script = this.resolveGenerateScript(); - try { - await access(script); - } catch { - throw new InternalServerErrorException( - `Generator script not found at ${script}. Run the server with cwd = server/ from repo root.`, - ); - } - - const id = randomUUID(); - const dslPath = join(tmpdir(), `domain-${id}.dsl`); - - try { - await writeFile(dslPath, dsl, 'utf8'); - const { stdout, stderr } = await execFileAsync( - process.execPath, - [script, '--apply', '--dsl', dslPath], - { - env: { ...process.env }, - maxBuffer: LARGE_BUFFER, - }, - ); - if (stderr?.trim()) console.warn('[aid-export][generate-apply]', stderr); - return { message: (stdout || 'ok').trim() }; - } catch (e: unknown) { - const msg = e instanceof Error ? e.message : String(e); - throw new InternalServerErrorException(`App generator (apply) failed: ${msg}`); - } finally { - await unlink(dslPath).catch(() => undefined); - } - } -} diff --git a/server/src/app.controller.spec.ts b/server/src/app.controller.spec.ts new file mode 100644 index 0000000..d22f389 --- /dev/null +++ b/server/src/app.controller.spec.ts @@ -0,0 +1,22 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { AppController } from './app.controller'; +import { AppService } from './app.service'; + +describe('AppController', () => { + let appController: AppController; + + beforeEach(async () => { + const app: TestingModule = await Test.createTestingModule({ + controllers: [AppController], + providers: [AppService], + }).compile(); + + appController = app.get(AppController); + }); + + describe('root', () => { + it('should return "Hello World!"', () => { + expect(appController.getHello()).toBe('Hello World!'); + }); + }); +}); diff --git a/server/src/app.controller.ts b/server/src/app.controller.ts new file mode 100644 index 0000000..cce879e --- /dev/null +++ b/server/src/app.controller.ts @@ -0,0 +1,12 @@ +import { Controller, Get } from '@nestjs/common'; +import { AppService } from './app.service'; + +@Controller() +export class AppController { + constructor(private readonly appService: AppService) {} + + @Get() + getHello(): string { + return this.appService.getHello(); + } +} diff --git a/server/src/app.module.ts b/server/src/app.module.ts index ee523cb..f59feed 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -1,26 +1,23 @@ import { Module } from '@nestjs/common'; -import { ConfigModule } from '@nestjs/config'; import { AuthModule } from './auth/auth.module'; -import { validateEnvironment } from './config/env.validation'; -import { PrismaModule } from './prisma/prisma.module'; -import { HealthModule } from './health/health.module'; -import { EquipmentTypeModule } from './modules/equipment-type/equipment-type.module'; +import { HealthController } from './health/health.controller'; +import { CategoryResourceModule } from './modules/category-resource/category-resource.module'; +import { EmployeeModule } from './modules/employee/employee.module'; import { EquipmentModule } from './modules/equipment/equipment.module'; -import { RepairOrderModule } from './modules/repair-order/repair-order.module'; -import { AidExportModule } from './aid-export/aid-export.module'; +import { PartModule } from './modules/part/part.module'; +import { PriceListModule } from './modules/price-list/price-list.module'; +import { PrismaModule } from './prisma/prisma.module'; @Module({ - imports: [ConfigModule.forRoot({ - isGlobal: true, - validate: validateEnvironment, - }), - AuthModule, + imports: [ PrismaModule, - HealthModule, - AidExportModule, - EquipmentTypeModule, + AuthModule, EquipmentModule, - RepairOrderModule, + EmployeeModule, + PartModule, + CategoryResourceModule, + PriceListModule, ], + controllers: [HealthController], }) export class AppModule {} diff --git a/server/src/app.service.ts b/server/src/app.service.ts new file mode 100644 index 0000000..927d7cc --- /dev/null +++ b/server/src/app.service.ts @@ -0,0 +1,8 @@ +import { Injectable } from '@nestjs/common'; + +@Injectable() +export class AppService { + getHello(): string { + return 'Hello World!'; + } +} diff --git a/server/src/auth/auth.constants.ts b/server/src/auth/auth.constants.ts deleted file mode 100644 index 003c912..0000000 --- a/server/src/auth/auth.constants.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const IS_PUBLIC_KEY = 'isPublic'; -export const ROLES_KEY = 'roles'; - diff --git a/server/src/auth/auth.module.ts b/server/src/auth/auth.module.ts index 9611c7a..71050e3 100644 --- a/server/src/auth/auth.module.ts +++ b/server/src/auth/auth.module.ts @@ -1,22 +1,11 @@ -import { Module } from '@nestjs/common'; -import { APP_GUARD } from '@nestjs/core'; +import { Global, Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { JwtAuthGuard } from './guards/jwt-auth.guard'; import { RolesGuard } from './guards/roles.guard'; +@Global() @Module({ - providers: [ - AuthService, - { - provide: APP_GUARD, - useClass: JwtAuthGuard, - }, - { - provide: APP_GUARD, - useClass: RolesGuard, - }, - ], - exports: [AuthService], + providers: [AuthService, JwtAuthGuard, RolesGuard], + exports: [AuthService, JwtAuthGuard, RolesGuard], }) export class AuthModule {} - diff --git a/server/src/auth/auth.service.ts b/server/src/auth/auth.service.ts index b2d9b36..1b5873b 100644 --- a/server/src/auth/auth.service.ts +++ b/server/src/auth/auth.service.ts @@ -1,129 +1,96 @@ -import { Injectable, Logger, UnauthorizedException } from '@nestjs/common'; -import { ConfigService } from '@nestjs/config'; -import { createRemoteJWKSet, jwtVerify } from 'jose'; -import { RuntimeEnvironment } from '../config/env.validation'; -import { - AuthenticatedUser, - KeycloakJwtPayload, -} from './interfaces/authenticated-user.interface'; +import { Injectable, UnauthorizedException } from '@nestjs/common'; +import { createRemoteJWKSet, jwtVerify, type JWTPayload } from 'jose'; + +export interface AuthenticatedUser { + sub: string; + username?: string; + email?: string; + name?: string; + realmRoles: string[]; + payload: JWTPayload; +} @Injectable() export class AuthService { - private readonly logger = new Logger(AuthService.name); - private readonly issuerUrl: string; - private readonly audience: string; - private readonly explicitJwksUrl?: string; + private readonly KEYCLOAK_ISSUER_URL = + process.env.KEYCLOAK_ISSUER_URL ?? 'https://sso.greact.ru/realms/toir'; - private jwksResolverPromise: Promise> | null = + private readonly KEYCLOAK_AUDIENCE = + process.env.KEYCLOAK_AUDIENCE ?? 'toir-backend'; + + private readonly KEYCLOAK_JWKS_URL = process.env.KEYCLOAK_JWKS_URL; + + private jwksPromise: Promise> | null = null; - private jwksResolver: ReturnType | null = null; - constructor( - private readonly configService: ConfigService, - ) { - this.issuerUrl = this.configService.getOrThrow('KEYCLOAK_ISSUER_URL'); - this.audience = this.configService.getOrThrow('KEYCLOAK_AUDIENCE'); - this.explicitJwksUrl = this.configService.get('KEYCLOAK_JWKS_URL'); - } - - async verifyAccessToken(token: string): Promise { + async verifyBearerToken(token: string): Promise { try { - const jwksResolver = await this.getJwksResolver(); - - const { payload } = await jwtVerify(token, jwksResolver, { - issuer: this.issuerUrl, - audience: this.audience, + const JWKS = await this.resolveJwks(); + const { payload } = await jwtVerify(token, JWKS, { + issuer: this.KEYCLOAK_ISSUER_URL, + audience: this.KEYCLOAK_AUDIENCE, }); - return this.mapPayloadToUser(payload as KeycloakJwtPayload); + const realmAccess = payload.realm_access as + | { roles?: unknown } + | undefined; + const realmRoles = Array.isArray(realmAccess?.roles) + ? realmAccess.roles.filter( + (role): role is string => typeof role === 'string', + ) + : []; + + return { + sub: String(payload.sub ?? ''), + username: + typeof payload.preferred_username === 'string' + ? payload.preferred_username + : undefined, + email: typeof payload.email === 'string' ? payload.email : undefined, + name: typeof payload.name === 'string' ? payload.name : undefined, + realmRoles, + payload, + }; } catch (error) { - this.logger.warn( - `JWT verification failed: ${error instanceof Error ? error.message : 'Unknown error'}`, + throw new UnauthorizedException( + error instanceof Error ? error.message : 'Invalid access token', ); - throw new UnauthorizedException('Invalid or expired access token'); } } - private mapPayloadToUser(payload: KeycloakJwtPayload): AuthenticatedUser { - if (!payload.sub) { - throw new UnauthorizedException('Token subject is missing'); - } - - const roles = Array.isArray(payload.realm_access?.roles) - ? payload.realm_access.roles.filter( - (role): role is string => typeof role === 'string', - ) - : []; - - return { - sub: payload.sub, - username: payload.preferred_username, - name: payload.name, - email: payload.email, - roles, - claims: payload, - }; - } - - private async getJwksResolver() { - if (this.jwksResolver) { - return this.jwksResolver; - } - - if (!this.jwksResolverPromise) { - this.jwksResolverPromise = this.createJwksResolver() - .then((resolver) => { - this.jwksResolver = resolver; - return resolver; - }) - .finally(() => { - this.jwksResolverPromise = null; - }); - } - - return this.jwksResolverPromise; - } - - private async createJwksResolver() { - const jwksUrl = await this.resolveJwksUrl(); - this.logger.log(`Using JWKS URL: ${jwksUrl}`); - return createRemoteJWKSet(new URL(jwksUrl)); - } - - private async resolveJwksUrl(): Promise { - if (this.explicitJwksUrl) { - return this.explicitJwksUrl; - } - - const issuer = this.issuerUrl.replace(/\/+$/, ''); - const discoveryUrl = `${issuer}/.well-known/openid-configuration`; - - try { - const discoveryResponse = await fetch(discoveryUrl, { - headers: { - Accept: 'application/json', - }, - }); - - if (discoveryResponse.ok) { - const discoveryDocument = (await discoveryResponse.json()) as { - jwks_uri?: string; - }; - - if ( - typeof discoveryDocument.jwks_uri === 'string' && - discoveryDocument.jwks_uri.trim().length > 0 - ) { - return discoveryDocument.jwks_uri; + private async resolveJwks(): Promise> { + if (!this.jwksPromise) { + this.jwksPromise = (async () => { + if (this.KEYCLOAK_JWKS_URL) { + return createRemoteJWKSet(new URL(this.KEYCLOAK_JWKS_URL)); } - } - } catch (error) { - this.logger.warn( - `OIDC discovery failed at ${discoveryUrl}: ${error instanceof Error ? error.message : 'Unknown error'}`, - ); + + const discoveryUrl = new URL( + `${this.KEYCLOAK_ISSUER_URL}/.well-known/openid-configuration`, + ); + + try { + const response = await fetch(discoveryUrl); + if (response.ok) { + const discovery = (await response.json()) as { + jwks_uri?: unknown; + }; + if (typeof discovery.jwks_uri === 'string') { + return createRemoteJWKSet(new URL(discovery.jwks_uri)); + } + } + } catch { + // Fall through to the Keycloak certs endpoint. + } + + return createRemoteJWKSet( + new URL( + `${this.KEYCLOAK_ISSUER_URL}/protocol/openid-connect/certs`, + ), + ); + })(); } - return `${issuer}/protocol/openid-connect/certs`; + return this.jwksPromise; } } - diff --git a/server/src/auth/decorators/public.decorator.ts b/server/src/auth/decorators/public.decorator.ts index 41d2571..b3845e1 100644 --- a/server/src/auth/decorators/public.decorator.ts +++ b/server/src/auth/decorators/public.decorator.ts @@ -1,5 +1,4 @@ import { SetMetadata } from '@nestjs/common'; -import { IS_PUBLIC_KEY } from '../auth.constants'; +export const IS_PUBLIC_KEY = 'isPublic'; export const Public = () => SetMetadata(IS_PUBLIC_KEY, true); - diff --git a/server/src/auth/decorators/roles.decorator.ts b/server/src/auth/decorators/roles.decorator.ts index 0101ed5..e038e16 100644 --- a/server/src/auth/decorators/roles.decorator.ts +++ b/server/src/auth/decorators/roles.decorator.ts @@ -1,6 +1,4 @@ import { SetMetadata } from '@nestjs/common'; -import { ROLES_KEY } from '../auth.constants'; -import { RealmRole } from '../roles/realm-role.enum'; - -export const Roles = (...roles: RealmRole[]) => SetMetadata(ROLES_KEY, roles); +export const ROLES_KEY = 'roles'; +export const Roles = (...roles: string[]) => SetMetadata(ROLES_KEY, roles); diff --git a/server/src/auth/guards/jwt-auth.guard.ts b/server/src/auth/guards/jwt-auth.guard.ts index 0e6a938..cc7aaec 100644 --- a/server/src/auth/guards/jwt-auth.guard.ts +++ b/server/src/auth/guards/jwt-auth.guard.ts @@ -5,9 +5,13 @@ import { UnauthorizedException, } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { Request } from 'express'; -import { IS_PUBLIC_KEY } from '../auth.constants'; -import { AuthService } from '../auth.service'; +import type { Request } from 'express'; +import { AuthService, type AuthenticatedUser } from '../auth.service'; +import { IS_PUBLIC_KEY } from '../decorators/public.decorator'; + +type AuthenticatedRequest = Request & { + user?: AuthenticatedUser; +}; @Injectable() export class JwtAuthGuard implements CanActivate { @@ -21,34 +25,18 @@ export class JwtAuthGuard implements CanActivate { context.getHandler(), context.getClass(), ]); - if (isPublic) { return true; } - const request = context.switchToHttp().getRequest(); - const token = this.extractBearerToken(request); - - if (!token) { + const request = context.switchToHttp().getRequest(); + const header = request.headers.authorization; + if (!header?.startsWith('Bearer ')) { throw new UnauthorizedException('Missing bearer token'); } - request.user = await this.authService.verifyAccessToken(token); + const token = header.slice('Bearer '.length).trim(); + request.user = await this.authService.verifyBearerToken(token); return true; } - - private extractBearerToken(request: Request): string | null { - const authorization = request.headers.authorization; - if (!authorization) { - return null; - } - - const [scheme, token] = authorization.split(' '); - if (scheme?.toLowerCase() !== 'bearer' || !token) { - return null; - } - - return token; - } } - diff --git a/server/src/auth/guards/roles.guard.ts b/server/src/auth/guards/roles.guard.ts index 94f6393..2dd23e6 100644 --- a/server/src/auth/guards/roles.guard.ts +++ b/server/src/auth/guards/roles.guard.ts @@ -1,49 +1,30 @@ -import { - CanActivate, - ExecutionContext, - ForbiddenException, - Injectable, -} from '@nestjs/common'; +import { CanActivate, ExecutionContext, Injectable } from '@nestjs/common'; import { Reflector } from '@nestjs/core'; -import { Request } from 'express'; -import { IS_PUBLIC_KEY, ROLES_KEY } from '../auth.constants'; -import { RealmRole } from '../roles/realm-role.enum'; +import type { Request } from 'express'; +import { ROLES_KEY } from '../decorators/roles.decorator'; + +type RolesRequest = Request & { + user?: { + realmRoles?: string[]; + }; +}; @Injectable() export class RolesGuard implements CanActivate { constructor(private readonly reflector: Reflector) {} canActivate(context: ExecutionContext): boolean { - const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [ - context.getHandler(), - context.getClass(), - ]); - - if (isPublic) { - return true; - } - - const requiredRoles = - this.reflector.getAllAndOverride(ROLES_KEY, [ - context.getHandler(), - context.getClass(), - ]) ?? []; - - if (requiredRoles.length === 0) { - return true; - } - - const request = context.switchToHttp().getRequest(); - const userRoles = request.user?.roles ?? []; - const hasRequiredRole = requiredRoles.some((role) => - userRoles.includes(role), + const requiredRoles = this.reflector.getAllAndOverride( + ROLES_KEY, + [context.getHandler(), context.getClass()], ); - if (!hasRequiredRole) { - throw new ForbiddenException('Access denied: insufficient role'); + if (!requiredRoles || requiredRoles.length === 0) { + return true; } - return true; + const request = context.switchToHttp().getRequest(); + const realmRoles = request.user?.realmRoles ?? []; + return requiredRoles.some((role) => realmRoles.includes(role)); } } - diff --git a/server/src/auth/interfaces/authenticated-user.interface.ts b/server/src/auth/interfaces/authenticated-user.interface.ts deleted file mode 100644 index 30c4599..0000000 --- a/server/src/auth/interfaces/authenticated-user.interface.ts +++ /dev/null @@ -1,20 +0,0 @@ -import { JWTPayload } from 'jose'; - -export interface KeycloakJwtPayload extends JWTPayload { - preferred_username?: string; - name?: string; - email?: string; - realm_access?: { - roles?: string[]; - }; -} - -export interface AuthenticatedUser { - sub: string; - username?: string; - name?: string; - email?: string; - roles: string[]; - claims: KeycloakJwtPayload; -} - diff --git a/server/src/auth/interfaces/express-request.interface.ts b/server/src/auth/interfaces/express-request.interface.ts deleted file mode 100644 index 696ab5a..0000000 --- a/server/src/auth/interfaces/express-request.interface.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { AuthenticatedUser } from './authenticated-user.interface'; - -declare global { - namespace Express { - interface Request { - user?: AuthenticatedUser; - } - } -} - -export {}; - diff --git a/server/src/auth/roles/realm-role.enum.ts b/server/src/auth/roles/realm-role.enum.ts deleted file mode 100644 index e6856b9..0000000 --- a/server/src/auth/roles/realm-role.enum.ts +++ /dev/null @@ -1,6 +0,0 @@ -export enum RealmRole { - Admin = 'admin', - Editor = 'editor', - Viewer = 'viewer', -} - diff --git a/server/src/config/env.validation.ts b/server/src/config/env.validation.ts deleted file mode 100644 index 4fed5dc..0000000 --- a/server/src/config/env.validation.ts +++ /dev/null @@ -1,58 +0,0 @@ -export interface RuntimeEnvironment { - PORT: number; - DATABASE_URL: string; - CORS_ALLOWED_ORIGINS: string; - KEYCLOAK_ISSUER_URL: string; - KEYCLOAK_AUDIENCE: string; - KEYCLOAK_JWKS_URL?: string; -} - -function getRequiredString( - config: Record, - key: keyof RuntimeEnvironment, -): string { - const value = config[key]; - if (typeof value !== 'string' || !value.trim()) { - throw new Error(`Missing required environment variable: ${key}`); - } - return value.trim(); -} - -function getOptionalString( - config: Record, - key: keyof RuntimeEnvironment, -): string | undefined { - const value = config[key]; - if (typeof value !== 'string') { - return undefined; - } - const trimmed = value.trim(); - return trimmed.length > 0 ? trimmed : undefined; -} - -function parsePort(value: unknown): number { - if (value === undefined || value === null || value === '') { - return 3000; - } - - const port = Number(value); - if (!Number.isInteger(port) || port < 1 || port > 65535) { - throw new Error('Environment variable PORT must be an integer between 1 and 65535'); - } - - return port; -} - -export function validateEnvironment( - config: Record, -): RuntimeEnvironment { - return { - PORT: parsePort(config.PORT), - DATABASE_URL: getRequiredString(config, 'DATABASE_URL'), - CORS_ALLOWED_ORIGINS: getRequiredString(config, 'CORS_ALLOWED_ORIGINS'), - KEYCLOAK_ISSUER_URL: getRequiredString(config, 'KEYCLOAK_ISSUER_URL'), - KEYCLOAK_AUDIENCE: getRequiredString(config, 'KEYCLOAK_AUDIENCE'), - KEYCLOAK_JWKS_URL: getOptionalString(config, 'KEYCLOAK_JWKS_URL'), - }; -} - diff --git a/server/src/health/health.module.ts b/server/src/health/health.module.ts deleted file mode 100644 index 7476abe..0000000 --- a/server/src/health/health.module.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { Module } from '@nestjs/common'; -import { HealthController } from './health.controller'; - -@Module({ - controllers: [HealthController], -}) -export class HealthModule {} diff --git a/server/src/main.ts b/server/src/main.ts index 2f858bb..09c6af4 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -1,35 +1,26 @@ +import { ValidationPipe } from '@nestjs/common'; import { NestFactory } from '@nestjs/core'; -import { ConfigService } from '@nestjs/config'; import { AppModule } from './app.module'; -import { RuntimeEnvironment } from './config/env.validation'; async function bootstrap() { const app = await NestFactory.create(AppModule); - const configService = app.get>( - ConfigService, - ); - - const allowedOrigins = configService - .getOrThrow('CORS_ALLOWED_ORIGINS') + const allowedOrigins = (process.env.CORS_ALLOWED_ORIGINS ?? 'http://localhost:5173,https://toir-frontend.greact.ru') .split(',') .map((origin) => origin.trim()) .filter((origin) => origin.length > 0); + app.useGlobalPipes( + new ValidationPipe({ + whitelist: true, + transform: true, + }), + ); app.enableCors({ - origin: (origin, callback) => { - if (!origin || allowedOrigins.includes(origin)) { - callback(null, true); - return; - } - callback(new Error(`Origin ${origin} is not allowed by CORS`), false); - }, - methods: ['GET', 'HEAD', 'PUT', 'PATCH', 'POST', 'DELETE', 'OPTIONS'], - allowedHeaders: ['Authorization', 'Content-Type'], + origin: allowedOrigins, exposedHeaders: ['Content-Range'], - credentials: false, }); - const port = configService.get('PORT', 3000); - await app.listen(port); + await app.listen(Number(process.env.PORT ?? 3000)); } + bootstrap(); diff --git a/server/src/modules/category-resource/category-resource.controller.ts b/server/src/modules/category-resource/category-resource.controller.ts new file mode 100644 index 0000000..fd7cc85 --- /dev/null +++ b/server/src/modules/category-resource/category-resource.controller.ts @@ -0,0 +1,44 @@ +import { Body, Controller, Delete, Get, Param, Patch, Post, Query, Res, UseGuards } from '@nestjs/common'; +import type { Response } from 'express'; +import { Roles } from '../../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../../auth/guards/roles.guard'; +import { CreateCategoryResourceDto } from './dto/create-category-resource.dto'; +import { UpdateCategoryResourceDto } from './dto/update-category-resource.dto'; +import { CategoryResourceService } from './category-resource.service'; + +@Controller('category-resources') +@UseGuards(JwtAuthGuard, RolesGuard) +export class CategoryResourceController { + constructor(private readonly categoryResourceService: CategoryResourceService) {} + + @Roles('viewer', 'editor', 'admin') + @Get() + findAll(@Query() query: Record, @Res({ passthrough: true }) res: Response) { + return this.categoryResourceService.findAll(query, res); + } + + @Roles('viewer', 'editor', 'admin') + @Get(':id') + findOne(@Param('id') id: string) { + return this.categoryResourceService.findOne(id); + } + + @Roles('editor', 'admin') + @Post() + create(@Body() dto: CreateCategoryResourceDto) { + return this.categoryResourceService.create(dto); + } + + @Roles('editor', 'admin') + @Patch(':id') + update(@Param('id') id: string, @Body() dto: UpdateCategoryResourceDto) { + return this.categoryResourceService.update(id, dto); + } + + @Roles('admin') + @Delete(':id') + remove(@Param('id') id: string) { + return this.categoryResourceService.remove(id); + } +} diff --git a/server/src/modules/category-resource/category-resource.module.ts b/server/src/modules/category-resource/category-resource.module.ts new file mode 100644 index 0000000..9d4df6d --- /dev/null +++ b/server/src/modules/category-resource/category-resource.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { CategoryResourceController } from './category-resource.controller'; +import { CategoryResourceService } from './category-resource.service'; + +@Module({ + controllers: [CategoryResourceController], + providers: [CategoryResourceService], +}) +export class CategoryResourceModule {} diff --git a/server/src/modules/category-resource/category-resource.service.ts b/server/src/modules/category-resource/category-resource.service.ts new file mode 100644 index 0000000..babd7f6 --- /dev/null +++ b/server/src/modules/category-resource/category-resource.service.ts @@ -0,0 +1,142 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { Prisma } from '@prisma/client'; +import type { Response } from 'express'; +import { PrismaService } from '../../prisma/prisma.service'; +import { containsInsensitive, getSingle, parseRange, setListHeaders, toSortOrder } from '../shared/query-utils'; +import { mapCategoryResource } from '../shared/record-mappers'; +import { CreateCategoryResourceDto } from './dto/create-category-resource.dto'; +import { UpdateCategoryResourceDto } from './dto/update-category-resource.dto'; + +@Injectable() +export class CategoryResourceService { + constructor(private readonly prisma: PrismaService) {} + + async findAll(query: Record, res: Response) { + // React Admin list params: _start, _end, _sort, _order. + const { start, end, take } = parseRange(query); + const requestedSort = getSingle(query._sort) ?? 'id'; + const sortField = requestedSort === 'id' ? 'id' : requestedSort; + const sortOrder = toSortOrder(getSingle(query._order)); + const where: Prisma.CategoryResourceWhereInput = {}; + + const q = getSingle(query.q); + if (q) { + where.OR = [ + { part: { is: { name: containsInsensitive(q) } } }, + { employee: { is: { fullName: containsInsensitive(q) } } }, + ]; + } + + const partName = getSingle(query.partName); + if (partName) { + where.part = { is: { name: containsInsensitive(partName) } }; + } + + const employeeName = getSingle(query.employeeName); + if (employeeName) { + where.employee = { is: { fullName: containsInsensitive(employeeName) } }; + } + + let orderBy: Prisma.CategoryResourceOrderByWithRelationInput; + if (sortField === 'part') { + orderBy = { part: { name: sortOrder } }; + } else if (sortField === 'employee') { + orderBy = { employee: { fullName: sortOrder } }; + } else { + orderBy = { [sortField]: sortOrder } as Prisma.CategoryResourceOrderByWithRelationInput; + } + + const [items, total] = await this.prisma.$transaction([ + this.prisma.categoryResource.findMany({ + where, + include: { + part: true, + employee: { + include: { + boss: true, + subordinates: true, + }, + }, + }, + skip: start, + take: take, + orderBy, + }), + this.prisma.categoryResource.count({ where }), + ]); + + setListHeaders(res, 'category-resources', start, end, total); + return { + data: items.map((item) => mapCategoryResource(item)), + total: total, + }; + } + + async findOne(id: string) { + const item = await this.prisma.categoryResource.findUnique({ + where: { id }, + include: { + part: true, + employee: { + include: { + boss: true, + subordinates: true, + }, + }, + }, + }); + + if (!item) { + throw new NotFoundException('Category resource not found'); + } + + return mapCategoryResource(item); + } + + async create(dto: CreateCategoryResourceDto) { + const created = await this.prisma.categoryResource.create({ + data: { + partId: dto.partId ?? null, + employeeCode: dto.employeeCode ?? null, + }, + include: { + part: true, + employee: { + include: { + boss: true, + subordinates: true, + }, + }, + }, + }); + + return mapCategoryResource(created); + } + + async update(id: string, dto: UpdateCategoryResourceDto & { id?: string }) { + const { id: _id, ...mutableData } = dto; + const updated = await this.prisma.categoryResource.update({ + where: { id }, + data: { + ...(mutableData.partId !== undefined ? { partId: mutableData.partId } : {}), + ...(mutableData.employeeCode !== undefined ? { employeeCode: mutableData.employeeCode } : {}), + }, + include: { + part: true, + employee: { + include: { + boss: true, + subordinates: true, + }, + }, + }, + }); + + return mapCategoryResource(updated); + } + + async remove(id: string) { + await this.prisma.categoryResource.delete({ where: { id } }); + return { id }; + } +} diff --git a/server/src/modules/category-resource/dto/create-category-resource.dto.ts b/server/src/modules/category-resource/dto/create-category-resource.dto.ts new file mode 100644 index 0000000..0aeedc5 --- /dev/null +++ b/server/src/modules/category-resource/dto/create-category-resource.dto.ts @@ -0,0 +1,15 @@ +import { + IsOptional, + IsString, + IsUUID, +} from 'class-validator'; + +export class CreateCategoryResourceDto { + @IsOptional() + @IsUUID() + partId?: string; + + @IsOptional() + @IsString() + employeeCode?: string; +} diff --git a/server/src/modules/category-resource/dto/update-category-resource.dto.ts b/server/src/modules/category-resource/dto/update-category-resource.dto.ts new file mode 100644 index 0000000..41a6978 --- /dev/null +++ b/server/src/modules/category-resource/dto/update-category-resource.dto.ts @@ -0,0 +1,15 @@ +import { + IsOptional, + IsString, + IsUUID, +} from 'class-validator'; + +export class UpdateCategoryResourceDto { + @IsOptional() + @IsUUID() + partId?: string; + + @IsOptional() + @IsString() + employeeCode?: string; +} diff --git a/server/src/modules/employee/dto/create-employee.dto.ts b/server/src/modules/employee/dto/create-employee.dto.ts new file mode 100644 index 0000000..ee89fc2 --- /dev/null +++ b/server/src/modules/employee/dto/create-employee.dto.ts @@ -0,0 +1,28 @@ +import { IsEnum, IsNumber, IsOptional, IsString, IsUUID } from 'class-validator'; +import { Role } from '../../shared/dsl-enums'; + +export class CreateEmployeeDto { + @IsString() + code!: string; + + @IsString() + fullName!: string; + + @IsEnum(Role) + role!: Role; + + @IsString() + position!: string; + + @IsOptional() + @IsString() + boss?: string; + + @IsOptional() + @IsNumber() + price?: number; + + @IsOptional() + @IsNumber() + phoneNumber?: number; +} diff --git a/server/src/modules/employee/dto/update-employee.dto.ts b/server/src/modules/employee/dto/update-employee.dto.ts new file mode 100644 index 0000000..d6a52de --- /dev/null +++ b/server/src/modules/employee/dto/update-employee.dto.ts @@ -0,0 +1,28 @@ +import { IsEnum, IsNumber, IsOptional, IsString, IsUUID } from 'class-validator'; +import { Role } from '../../shared/dsl-enums'; + +export class UpdateEmployeeDto { + @IsOptional() + @IsString() + fullName?: string; + + @IsOptional() + @IsEnum(Role) + role?: Role; + + @IsOptional() + @IsString() + position?: string; + + @IsOptional() + @IsString() + boss?: string; + + @IsOptional() + @IsNumber() + price?: number; + + @IsOptional() + @IsNumber() + phoneNumber?: number; +} diff --git a/server/src/modules/employee/employee.controller.ts b/server/src/modules/employee/employee.controller.ts new file mode 100644 index 0000000..afc4614 --- /dev/null +++ b/server/src/modules/employee/employee.controller.ts @@ -0,0 +1,44 @@ +import { Body, Controller, Delete, Get, Param, Patch, Post, Query, Res, UseGuards } from '@nestjs/common'; +import type { Response } from 'express'; +import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; +import { Roles } from '../../auth/decorators/roles.decorator'; +import { RolesGuard } from '../../auth/guards/roles.guard'; +import { CreateEmployeeDto } from './dto/create-employee.dto'; +import { UpdateEmployeeDto } from './dto/update-employee.dto'; +import { EmployeeService } from './employee.service'; + +@Controller('employees') +@UseGuards(JwtAuthGuard, RolesGuard) +export class EmployeeController { + constructor(private readonly employeeService: EmployeeService) {} + + @Get() + @Roles('viewer', 'editor', 'admin') + findAll(@Query() query: Record, @Res({ passthrough: true }) response: Response) { + return this.employeeService.findAll(query, response); + } + + @Get(':code') + @Roles('viewer', 'editor', 'admin') + findOne(@Param('code') code: string) { + return this.employeeService.findOne(code); + } + + @Post() + @Roles('editor', 'admin') + create(@Body() dto: CreateEmployeeDto) { + return this.employeeService.create(dto); + } + + @Patch(':code') + @Roles('editor', 'admin') + update(@Param('code') code: string, @Body() dto: UpdateEmployeeDto) { + return this.employeeService.update(code, dto); + } + + @Delete(':code') + @Roles('admin') + remove(@Param('code') code: string) { + return this.employeeService.remove(code); + } +} diff --git a/server/src/modules/employee/employee.module.ts b/server/src/modules/employee/employee.module.ts new file mode 100644 index 0000000..a210abe --- /dev/null +++ b/server/src/modules/employee/employee.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { PrismaModule } from '../../prisma/prisma.module'; +import { EmployeeController } from './employee.controller'; +import { EmployeeService } from './employee.service'; + +@Module({ + imports: [PrismaModule], + controllers: [EmployeeController], + providers: [EmployeeService], +}) +export class EmployeeModule {} diff --git a/server/src/modules/employee/employee.service.ts b/server/src/modules/employee/employee.service.ts new file mode 100644 index 0000000..37127c5 --- /dev/null +++ b/server/src/modules/employee/employee.service.ts @@ -0,0 +1,145 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import type { Prisma } from '@prisma/client'; +import type { Response } from 'express'; +import { PrismaService } from '../../prisma/prisma.service'; +import { getFirst, setListHeaders, toArray, toDateValue, toDecimalValue, toNumberValue } from '../shared/query-utils'; +import { CreateEmployeeDto } from './dto/create-employee.dto'; +import { UpdateEmployeeDto } from './dto/update-employee.dto'; + +@Injectable() +export class EmployeeService { + constructor(private readonly prisma: PrismaService) {} + + private toNested(item: any): Record { + return { + id: item.code, + code: item.code, + fullName: item.fullName, + role: item.role, + position: item.position, + bossCode: item.bossCode, + price: item.price, + phoneNumber: item.phoneNumber, + }; + } + + private toRecord(item: any): Record { + return { + id: item.code, + code: item.code, + fullName: item.fullName, + role: item.role, + position: item.position, + bossCode: item.bossCode, + boss: item.boss ? this.toNested(item.boss) : null, + subordinates: Array.isArray(item.subordinates) ? item.subordinates.map((subordinate: any) => this.toNested(subordinate)) : [], + price: item.price, + phoneNumber: item.phoneNumber, + }; + } + async findAll(query: Record, response: Response): Promise[]> { + const start = Number(getFirst(query._start) ?? 0); + const end = Number(getFirst(query._end) ?? start + 25); + const take = Math.max(end - start, 0) || 25; + const where: Record = {}; + const q = getFirst(query.q); + if (q) { + where.OR = [ + { code: { contains: q, mode: 'insensitive' } }, + { fullName: { contains: q, mode: 'insensitive' } }, + { position: { contains: q, mode: 'insensitive' } }, + ]; + } + const code = getFirst(query.code); + if (code) where.code = { contains: code, mode: 'insensitive' }; + const fullName = getFirst(query.fullName); + if (fullName) where.fullName = { contains: fullName, mode: 'insensitive' }; + const position = getFirst(query.position); + if (position) where.position = { contains: position, mode: 'insensitive' }; + const roleValues = toArray(query.role); + if (roleValues.length > 0) where.role = { in: Array.from(new Set(roleValues)) }; + const boss = getFirst(query.boss); + if (boss) where.bossCode = boss; + const sortField = getFirst(query._sort) ?? 'code'; + const prismaSortField = sortField === 'id' ? 'code' : sortField; + const sortOrder = (String(getFirst(query._order) ?? 'ASC').toLowerCase() === 'desc' ? 'desc' : 'asc') as Prisma.SortOrder; + + const [items, total] = await this.prisma.$transaction([ + this.prisma.employee.findMany({ + where, + skip: start, + take, + orderBy: { [prismaSortField]: sortOrder }, + include: { + boss: true, + subordinates: true, + }, + }), + this.prisma.employee.count({ where }), + ]); + + setListHeaders(response, total, start, end, 'employees'); + return items.map((item) => this.toRecord(item)); + } + + async findOne(code: string): Promise> { + const item = await this.prisma.employee.findUnique({ where: { code }, + include: { + boss: true, + subordinates: true, + }, }); + if (!item) throw new NotFoundException('Employee not found'); + return this.toRecord(item); + } + + async create(dto: CreateEmployeeDto): Promise> { + const created = await this.prisma.employee.create({ data: this.prepareCreateData(dto) as any, + include: { + boss: true, + subordinates: true, + }, }); + return this.toRecord(created); + } + + async update(code: string, dto: UpdateEmployeeDto): Promise> { + const updated = await this.prisma.employee.update({ where: { code }, data: this.prepareUpdateData(dto) as any, + include: { + boss: true, + subordinates: true, + }, }); + return this.toRecord(updated); + } + + async remove(code: string): Promise> { + const deleted = await this.prisma.employee.delete({ where: { code }, + include: { + boss: true, + subordinates: true, + }, }); + return this.toRecord(deleted); + } + + private prepareCreateData(dto: CreateEmployeeDto): Record { + return { + code: dto.code, + fullName: dto.fullName, + role: dto.role, + position: dto.position, + bossCode: dto.boss ?? null, + price: toNumberValue(dto.price), + phoneNumber: toNumberValue(dto.phoneNumber), + }; + } + + private prepareUpdateData(dto: UpdateEmployeeDto): Record { + const { id, code: _pk, ...rest } = dto as Record & { id?: string; code?: string }; + return { + fullName: rest.fullName, + role: rest.role, + position: rest.position, + bossCode: rest.boss ?? null, + price: toNumberValue(rest.price), + phoneNumber: toNumberValue(rest.phoneNumber), + }; + } +} diff --git a/server/src/modules/equipment-type/dto/create-equipment-type.dto.ts b/server/src/modules/equipment-type/dto/create-equipment-type.dto.ts deleted file mode 100644 index 2f9d6ab..0000000 --- a/server/src/modules/equipment-type/dto/create-equipment-type.dto.ts +++ /dev/null @@ -1,7 +0,0 @@ -export class CreateEquipmentTypeDto { - code?: string; - name!: string; - manufacturer?: string; - maintenanceIntervalHours?: number; - overhaulIntervalHours?: number; -} diff --git a/server/src/modules/equipment-type/dto/update-equipment-type.dto.ts b/server/src/modules/equipment-type/dto/update-equipment-type.dto.ts deleted file mode 100644 index 7589324..0000000 --- a/server/src/modules/equipment-type/dto/update-equipment-type.dto.ts +++ /dev/null @@ -1,8 +0,0 @@ -export class UpdateEquipmentTypeDto { - id?: string; - code?: string; - name?: string; - manufacturer?: string; - maintenanceIntervalHours?: number; - overhaulIntervalHours?: number; -} diff --git a/server/src/modules/equipment-type/equipment-type.controller.ts b/server/src/modules/equipment-type/equipment-type.controller.ts deleted file mode 100644 index 3de454e..0000000 --- a/server/src/modules/equipment-type/equipment-type.controller.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Controller, Get, Post, Patch, Delete, Param, Body, Query, Res } from '@nestjs/common'; -import { Response } from 'express'; -import { Roles } from '../../auth/decorators/roles.decorator'; -import { RealmRole } from '../../auth/roles/realm-role.enum'; -import { EquipmentTypeService } from './equipment-type.service'; -import { CreateEquipmentTypeDto } from './dto/create-equipment-type.dto'; -import { UpdateEquipmentTypeDto } from './dto/update-equipment-type.dto'; - -@Controller('equipment-types') -export class EquipmentTypeController { - constructor(private readonly service: EquipmentTypeService) {} - - @Roles(RealmRole.Viewer, RealmRole.Editor, RealmRole.Admin) - @Get() - async findAll(@Query() query: any, @Res() res: Response) { - const result = await this.service.findAll(query); - res.set('Content-Range', `equipment-types ${query._start || 0}-${query._end || result.total}/${result.total}`); - res.set('Access-Control-Expose-Headers', 'Content-Range'); - return res.json(result.data); - } - - @Roles(RealmRole.Viewer, RealmRole.Editor, RealmRole.Admin) - @Get(':code') - findOne(@Param('code') id: string) { - return this.service.findOne(id); - } - - @Roles(RealmRole.Editor, RealmRole.Admin) - @Post() - create(@Body() dto: CreateEquipmentTypeDto) { - return this.service.create(dto); - } - - @Roles(RealmRole.Editor, RealmRole.Admin) - @Patch(':code') - update(@Param('code') id: string, @Body() dto: UpdateEquipmentTypeDto) { - return this.service.update(id, dto); - } - - @Roles(RealmRole.Admin) - @Delete(':code') - remove(@Param('code') id: string) { - return this.service.remove(id); - } -} diff --git a/server/src/modules/equipment-type/equipment-type.module.ts b/server/src/modules/equipment-type/equipment-type.module.ts deleted file mode 100644 index a4673e1..0000000 --- a/server/src/modules/equipment-type/equipment-type.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Module } from '@nestjs/common'; -import { EquipmentTypeController } from './equipment-type.controller'; -import { EquipmentTypeService } from './equipment-type.service'; - -@Module({ - controllers: [EquipmentTypeController], - providers: [EquipmentTypeService], -}) -export class EquipmentTypeModule {} diff --git a/server/src/modules/equipment-type/equipment-type.service.ts b/server/src/modules/equipment-type/equipment-type.service.ts deleted file mode 100644 index f6bcdbc..0000000 --- a/server/src/modules/equipment-type/equipment-type.service.ts +++ /dev/null @@ -1,90 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Prisma } from '@prisma/client'; -import { PrismaService } from '../../prisma/prisma.service'; -import { CreateEquipmentTypeDto } from './dto/create-equipment-type.dto'; -import { UpdateEquipmentTypeDto } from './dto/update-equipment-type.dto'; - -function serializeRecord(record: any) { - return { - ...record, - - - }; -} - -@Injectable() -export class EquipmentTypeService { - constructor(private readonly prisma: PrismaService) {} - - async findAll(query: { _start?: string; _end?: string; _sort?: string; _order?: string; [key: string]: any }) { - const start = parseInt(query._start) || 0; - const end = parseInt(query._end) || 10; - const take = end - start; - const skip = start; - const sortField = query._sort || 'code'; - const prismaSortField = sortField === 'id' ? 'code' : sortField; - const sortOrder = (query._order || 'ASC').toLowerCase() as 'asc' | 'desc'; - - const where: any = {}; - - if (query.q) { - const q = String(query.q); - const ors: any[] = []; - ors.push({ code: { contains: q, mode: 'insensitive' } }); - ors.push({ name: { contains: q, mode: 'insensitive' } }); - ors.push({ manufacturer: { contains: q, mode: 'insensitive' } }); - if (ors.length) where.OR = ors; - } - - if (query.code) where.code = { contains: query.code, mode: 'insensitive' }; - if (query.name) where.name = { contains: query.name, mode: 'insensitive' }; - if (query.manufacturer) where.manufacturer = { contains: query.manufacturer, mode: 'insensitive' }; - - - - // Enum multi-value support (e.g. status=A&status=B) - - - if (query.id) { - const ids = Array.isArray(query.id) ? query.id : [query.id]; - where.code = { in: ids }; - } - - const [data, total] = await Promise.all([ - this.prisma.equipmentType.findMany({ where, skip, take, orderBy: { [prismaSortField]: sortOrder } }), - this.prisma.equipmentType.count({ where }), - ]); - - const mapped = data.map((item: any) => ({ id: item.code, ...serializeRecord(item) })); - return { data: mapped, total }; - } - - async findOne(id: string) { - const record = await this.prisma.equipmentType.findUniqueOrThrow({ where: { code: id } as any }); - return { id: (record as any).code, ...serializeRecord(record) }; - } - - async create(dto: CreateEquipmentTypeDto) { - const data: any = { ...(dto as any) }; - - - - const record = await this.prisma.equipmentType.create({ data }); - return { id: (record as any).code, ...serializeRecord(record) }; - } - - async update(id: string, dto: UpdateEquipmentTypeDto) { - const { id: _pk, code, ...rest } = (dto as any); - const data: any = { ...rest }; - - - - const record = await this.prisma.equipmentType.update({ where: { code: id } as any, data }); - return { id: (record as any).code, ...serializeRecord(record) }; - } - - async remove(id: string) { - const record = await this.prisma.equipmentType.delete({ where: { code: id } as any }); - return { id: (record as any).code, ...serializeRecord(record) }; - } -} diff --git a/server/src/modules/equipment/dto/create-equipment.dto.ts b/server/src/modules/equipment/dto/create-equipment.dto.ts index a89cc8d..e223e0f 100644 --- a/server/src/modules/equipment/dto/create-equipment.dto.ts +++ b/server/src/modules/equipment/dto/create-equipment.dto.ts @@ -1,13 +1,58 @@ +import { IsEnum, IsNumber, IsOptional, IsString, IsUUID } from 'class-validator'; +import { EquipmentType, EnumPeriodicityTO, EquipmentStatus, laborOperation } from '../../shared/dsl-enums'; + export class CreateEquipmentDto { - inventoryNumber!: string; - serialNumber?: string; + @IsString() name!: string; - equipmentTypeCode!: string; - status!: string; + + @IsString() + serialNumber!: string; + + @IsString() + inventoryNumber!: string; + + @IsEnum(EquipmentType) + equipmentType!: EquipmentType; + + @IsOptional() + @IsString() + dateOfInspection?: string; + + @IsEnum(EnumPeriodicityTO) + periodicityTO!: EnumPeriodicityTO; + + @IsOptional() + @IsString() location?: string; + + @IsEnum(EquipmentStatus) + status!: EquipmentStatus; + + @IsOptional() + @IsString() commissionedAt?: string; + + @IsOptional() + @IsString() totalEngineHours?: string; + + @IsOptional() + @IsString() engineHoursSinceLastRepair?: string; + + @IsOptional() + @IsString() lastRepairAt?: string; + + @IsOptional() + @IsString() notes?: string; + + @IsOptional() + @IsEnum(laborOperation) + workAsPartOf?: laborOperation; + + @IsOptional() + @IsNumber() + fuelConsumed?: number; } diff --git a/server/src/modules/equipment/dto/update-equipment.dto.ts b/server/src/modules/equipment/dto/update-equipment.dto.ts index a44c3c9..f58766a 100644 --- a/server/src/modules/equipment/dto/update-equipment.dto.ts +++ b/server/src/modules/equipment/dto/update-equipment.dto.ts @@ -1,14 +1,64 @@ +import { IsEnum, IsNumber, IsOptional, IsString, IsUUID } from 'class-validator'; +import { EquipmentType, EnumPeriodicityTO, EquipmentStatus, laborOperation } from '../../shared/dsl-enums'; + export class UpdateEquipmentDto { - id?: string; - inventoryNumber?: string; - serialNumber?: string; + @IsOptional() + @IsString() name?: string; - equipmentTypeCode?: string; - status?: string; + + @IsOptional() + @IsString() + serialNumber?: string; + + @IsOptional() + @IsString() + inventoryNumber?: string; + + @IsOptional() + @IsEnum(EquipmentType) + equipmentType?: EquipmentType; + + @IsOptional() + @IsString() + dateOfInspection?: string; + + @IsOptional() + @IsEnum(EnumPeriodicityTO) + periodicityTO?: EnumPeriodicityTO; + + @IsOptional() + @IsString() location?: string; + + @IsOptional() + @IsEnum(EquipmentStatus) + status?: EquipmentStatus; + + @IsOptional() + @IsString() commissionedAt?: string; + + @IsOptional() + @IsString() totalEngineHours?: string; + + @IsOptional() + @IsString() engineHoursSinceLastRepair?: string; + + @IsOptional() + @IsString() lastRepairAt?: string; + + @IsOptional() + @IsString() notes?: string; + + @IsOptional() + @IsEnum(laborOperation) + workAsPartOf?: laborOperation; + + @IsOptional() + @IsNumber() + fuelConsumed?: number; } diff --git a/server/src/modules/equipment/equipment.controller.ts b/server/src/modules/equipment/equipment.controller.ts index 758ece3..7b823c0 100644 --- a/server/src/modules/equipment/equipment.controller.ts +++ b/server/src/modules/equipment/equipment.controller.ts @@ -1,45 +1,44 @@ -import { Controller, Get, Post, Patch, Delete, Param, Body, Query, Res } from '@nestjs/common'; -import { Response } from 'express'; +import { Body, Controller, Delete, Get, Param, Patch, Post, Query, Res, UseGuards } from '@nestjs/common'; +import type { Response } from 'express'; +import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; import { Roles } from '../../auth/decorators/roles.decorator'; -import { RealmRole } from '../../auth/roles/realm-role.enum'; -import { EquipmentService } from './equipment.service'; +import { RolesGuard } from '../../auth/guards/roles.guard'; import { CreateEquipmentDto } from './dto/create-equipment.dto'; import { UpdateEquipmentDto } from './dto/update-equipment.dto'; +import { EquipmentService } from './equipment.service'; -@Controller('equipment') +@Controller('equipments') +@UseGuards(JwtAuthGuard, RolesGuard) export class EquipmentController { - constructor(private readonly service: EquipmentService) {} + constructor(private readonly equipmentService: EquipmentService) {} - @Roles(RealmRole.Viewer, RealmRole.Editor, RealmRole.Admin) @Get() - async findAll(@Query() query: any, @Res() res: Response) { - const result = await this.service.findAll(query); - res.set('Content-Range', `equipment ${query._start || 0}-${query._end || result.total}/${result.total}`); - res.set('Access-Control-Expose-Headers', 'Content-Range'); - return res.json(result.data); + @Roles('viewer', 'editor', 'admin') + findAll(@Query() query: Record, @Res({ passthrough: true }) response: Response) { + return this.equipmentService.findAll(query, response); } - @Roles(RealmRole.Viewer, RealmRole.Editor, RealmRole.Admin) @Get(':id') + @Roles('viewer', 'editor', 'admin') findOne(@Param('id') id: string) { - return this.service.findOne(id); + return this.equipmentService.findOne(id); } - @Roles(RealmRole.Editor, RealmRole.Admin) @Post() + @Roles('editor', 'admin') create(@Body() dto: CreateEquipmentDto) { - return this.service.create(dto); + return this.equipmentService.create(dto); } - @Roles(RealmRole.Editor, RealmRole.Admin) @Patch(':id') + @Roles('editor', 'admin') update(@Param('id') id: string, @Body() dto: UpdateEquipmentDto) { - return this.service.update(id, dto); + return this.equipmentService.update(id, dto); } - @Roles(RealmRole.Admin) @Delete(':id') + @Roles('admin') remove(@Param('id') id: string) { - return this.service.remove(id); + return this.equipmentService.remove(id); } } diff --git a/server/src/modules/equipment/equipment.module.ts b/server/src/modules/equipment/equipment.module.ts index b9c9ac9..8ad5ec2 100644 --- a/server/src/modules/equipment/equipment.module.ts +++ b/server/src/modules/equipment/equipment.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common'; +import { PrismaModule } from '../../prisma/prisma.module'; import { EquipmentController } from './equipment.controller'; import { EquipmentService } from './equipment.service'; @Module({ + imports: [PrismaModule], controllers: [EquipmentController], providers: [EquipmentService], }) diff --git a/server/src/modules/equipment/equipment.service.ts b/server/src/modules/equipment/equipment.service.ts index 53a17b6..c2f9b44 100644 --- a/server/src/modules/equipment/equipment.service.ts +++ b/server/src/modules/equipment/equipment.service.ts @@ -1,102 +1,135 @@ -import { Injectable } from '@nestjs/common'; -import { Prisma } from '@prisma/client'; +import { Injectable, NotFoundException } from '@nestjs/common'; +import type { Prisma } from '@prisma/client'; +import type { Response } from 'express'; import { PrismaService } from '../../prisma/prisma.service'; +import { getFirst, setListHeaders, toArray, toDateValue, toDecimalValue, toNumberValue } from '../shared/query-utils'; import { CreateEquipmentDto } from './dto/create-equipment.dto'; import { UpdateEquipmentDto } from './dto/update-equipment.dto'; -function serializeRecord(record: any) { - return { - ...record, - totalEngineHours: record.totalEngineHours?.toString() ?? null, - engineHoursSinceLastRepair: record.engineHoursSinceLastRepair?.toString() ?? null, - commissionedAt: record.commissionedAt?.toISOString() ?? null, - lastRepairAt: record.lastRepairAt?.toISOString() ?? null, - }; -} - @Injectable() export class EquipmentService { constructor(private readonly prisma: PrismaService) {} - async findAll(query: { _start?: string; _end?: string; _sort?: string; _order?: string; [key: string]: any }) { - const start = parseInt(query._start) || 0; - const end = parseInt(query._end) || 10; - const take = end - start; - const skip = start; - const sortField = query._sort || 'inventoryNumber'; - const prismaSortField = sortField === 'id' ? 'id' : sortField; - const sortOrder = (query._order || 'ASC').toLowerCase() as 'asc' | 'desc'; - - const where: any = {}; - - if (query.q) { - const q = String(query.q); - const ors: any[] = []; - ors.push({ inventoryNumber: { contains: q, mode: 'insensitive' } }); - ors.push({ serialNumber: { contains: q, mode: 'insensitive' } }); - ors.push({ name: { contains: q, mode: 'insensitive' } }); - ors.push({ equipmentTypeCode: { contains: q, mode: 'insensitive' } }); - ors.push({ location: { contains: q, mode: 'insensitive' } }); - ors.push({ notes: { contains: q, mode: 'insensitive' } }); - if (ors.length) where.OR = ors; + private toRecord(item: any): Record { + return { + id: item.id, + name: item.name, + serialNumber: item.serialNumber, + inventoryNumber: item.inventoryNumber, + equipmentType: item.equipmentType, + dateOfInspection: item.dateOfInspection ? item.dateOfInspection.toISOString() : null, + periodicityTO: item.periodicityTO, + location: item.location, + status: item.status, + commissionedAt: item.commissionedAt ? item.commissionedAt.toISOString() : null, + totalEngineHours: item.totalEngineHours ? item.totalEngineHours.toString() : null, + engineHoursSinceLastRepair: item.engineHoursSinceLastRepair ? item.engineHoursSinceLastRepair.toString() : null, + lastRepairAt: item.lastRepairAt ? item.lastRepairAt.toISOString() : null, + notes: item.notes, + workAsPartOf: item.workAsPartOf, + fuelConsumed: item.fuelConsumed, + }; + } + async findAll(query: Record, response: Response): Promise[]> { + const start = Number(getFirst(query._start) ?? 0); + const end = Number(getFirst(query._end) ?? start + 25); + const take = Math.max(end - start, 0) || 25; + const where: Record = {}; + const q = getFirst(query.q); + if (q) { + where.OR = [ + { inventoryNumber: { contains: q, mode: 'insensitive' } }, + { serialNumber: { contains: q, mode: 'insensitive' } }, + { name: { contains: q, mode: 'insensitive' } }, + { location: { contains: q, mode: 'insensitive' } }, + { notes: { contains: q, mode: 'insensitive' } }, + ]; } + const inventoryNumber = getFirst(query.inventoryNumber); + if (inventoryNumber) where.inventoryNumber = { contains: inventoryNumber, mode: 'insensitive' }; + const serialNumber = getFirst(query.serialNumber); + if (serialNumber) where.serialNumber = { contains: serialNumber, mode: 'insensitive' }; + const name = getFirst(query.name); + if (name) where.name = { contains: name, mode: 'insensitive' }; + const location = getFirst(query.location); + if (location) where.location = { contains: location, mode: 'insensitive' }; + const statusValues = toArray(query.status); + if (statusValues.length > 0) where.status = { in: Array.from(new Set(statusValues)) }; + const equipmentTypeValues = toArray(query.equipmentTypeCode ?? query.equipmentType); + if (equipmentTypeValues.length > 0) where.equipmentType = { in: Array.from(new Set(equipmentTypeValues)) }; + const periodicityValues = toArray(query.equipmentPeriodicityTO ?? query.periodicityTO); + if (periodicityValues.length > 0) where.periodicityTO = { in: Array.from(new Set(periodicityValues)) }; + const workAsPartOfValues = toArray(query.workAsPartOf); + if (workAsPartOfValues.length > 0) where.workAsPartOf = { in: Array.from(new Set(workAsPartOfValues)) }; + const requestedSort = getFirst(query._sort) ?? 'inventoryNumber'; + const sortField = requestedSort === 'id' ? 'id' : requestedSort; + const sortOrder = (String(getFirst(query._order) ?? 'ASC').toLowerCase() === 'desc' ? 'desc' : 'asc') as Prisma.SortOrder; - if (query.inventoryNumber) where.inventoryNumber = { contains: query.inventoryNumber, mode: 'insensitive' }; - if (query.serialNumber) where.serialNumber = { contains: query.serialNumber, mode: 'insensitive' }; - if (query.name) where.name = { contains: query.name, mode: 'insensitive' }; - if (query.location) where.location = { contains: query.location, mode: 'insensitive' }; - if (query.notes) where.notes = { contains: query.notes, mode: 'insensitive' }; - - if (query.equipmentTypeCode) where.equipmentTypeCode = query.equipmentTypeCode; - - // Enum multi-value support (e.g. status=A&status=B) - if (query.status) { const vals = Array.isArray(query.status) ? query.status : [query.status]; where.status = vals.length > 1 ? { in: vals } : vals[0]; } - - if (query.id) { - const ids = Array.isArray(query.id) ? query.id : [query.id]; - where.id = { in: ids }; - } - - const [data, total] = await Promise.all([ - this.prisma.equipment.findMany({ where, skip, take, orderBy: { [prismaSortField]: sortOrder } }), + const [items, total] = await this.prisma.$transaction([ + this.prisma.equipment.findMany({ + where, + skip: start, + take, + orderBy: { [sortField]: sortOrder } + }), this.prisma.equipment.count({ where }), ]); - const mapped = data.map(serializeRecord); - return { data: mapped, total }; + setListHeaders(response, total, start, end, 'equipments'); + return items.map((item) => this.toRecord(item)); } - async findOne(id: string) { - const record = await this.prisma.equipment.findUniqueOrThrow({ where: { id: id } as any }); - return serializeRecord(record); + async findOne(id: string): Promise> { + const item = await this.prisma.equipment.findUnique({ where: { id } }); + if (!item) throw new NotFoundException('Equipment not found'); + return this.toRecord(item); } - async create(dto: CreateEquipmentDto) { - const data: any = { ...(dto as any) }; - if (data.commissionedAt) data.commissionedAt = new Date(data.commissionedAt); - if (data.lastRepairAt) data.lastRepairAt = new Date(data.lastRepairAt); - if (data.totalEngineHours) data.totalEngineHours = new Prisma.Decimal(data.totalEngineHours); - if (data.engineHoursSinceLastRepair) data.engineHoursSinceLastRepair = new Prisma.Decimal(data.engineHoursSinceLastRepair); - - const record = await this.prisma.equipment.create({ data }); - return serializeRecord(record); + async create(dto: CreateEquipmentDto): Promise> { + const created = await this.prisma.equipment.create({ data: this.prepareCreateData(dto) as any }); + return this.toRecord(created); } - async update(id: string, dto: UpdateEquipmentDto) { - const data: any = { ...(dto as any) }; - delete data.id; - delete data.id; - if (data.commissionedAt) data.commissionedAt = new Date(data.commissionedAt); - if (data.lastRepairAt) data.lastRepairAt = new Date(data.lastRepairAt); - if (data.totalEngineHours !== undefined && data.totalEngineHours !== null) data.totalEngineHours = new Prisma.Decimal(data.totalEngineHours); - if (data.engineHoursSinceLastRepair !== undefined && data.engineHoursSinceLastRepair !== null) data.engineHoursSinceLastRepair = new Prisma.Decimal(data.engineHoursSinceLastRepair); - - const record = await this.prisma.equipment.update({ where: { id: id } as any, data }); - return serializeRecord(record); + async update(id: string, dto: UpdateEquipmentDto): Promise> { + const updated = await this.prisma.equipment.update({ where: { id }, data: this.prepareUpdateData(dto) as any }); + return this.toRecord(updated); } - async remove(id: string) { - const record = await this.prisma.equipment.delete({ where: { id: id } as any }); - return serializeRecord(record); + async remove(id: string): Promise> { + const deleted = await this.prisma.equipment.delete({ where: { id } }); + return this.toRecord(deleted); + } + + private prepareCreateData(dto: CreateEquipmentDto): Record { + return { + name: dto.name, + serialNumber: dto.serialNumber, + inventoryNumber: dto.inventoryNumber, + equipmentType: dto.equipmentType, + dateOfInspection: toDateValue(dto.dateOfInspection), + periodicityTO: dto.periodicityTO, + location: dto.location ?? null, + status: dto.status, + commissionedAt: toDateValue(dto.commissionedAt), + totalEngineHours: toDecimalValue(dto.totalEngineHours), + engineHoursSinceLastRepair: toDecimalValue(dto.engineHoursSinceLastRepair), + lastRepairAt: toDateValue(dto.lastRepairAt), + notes: dto.notes ?? null, + workAsPartOf: dto.workAsPartOf ?? null, + fuelConsumed: toNumberValue(dto.fuelConsumed), + }; + } + + private prepareUpdateData(dto: UpdateEquipmentDto): Record { + const { id, ...rest } = dto as Record & { id?: string }; + return { + ...rest, + dateOfInspection: toDateValue(rest.dateOfInspection), + commissionedAt: toDateValue(rest.commissionedAt), + totalEngineHours: toDecimalValue(rest.totalEngineHours), + engineHoursSinceLastRepair: toDecimalValue(rest.engineHoursSinceLastRepair), + lastRepairAt: toDateValue(rest.lastRepairAt), + fuelConsumed: toNumberValue(rest.fuelConsumed), + }; } } diff --git a/server/src/modules/part/dto/create-part.dto.ts b/server/src/modules/part/dto/create-part.dto.ts new file mode 100644 index 0000000..9e1a723 --- /dev/null +++ b/server/src/modules/part/dto/create-part.dto.ts @@ -0,0 +1,28 @@ +import { + IsEnum, + IsNumber, + IsOptional, + IsString, +} from 'class-validator'; +import { CategoryPart } from '../../shared/dsl-enums'; + +export class CreatePartDto { + @IsString() + name!: string; + + @IsOptional() + @IsEnum(CategoryPart) + categories?: CategoryPart; + + @IsOptional() + @IsNumber() + price?: number; + + @IsOptional() + @IsString() + description?: string; + + @IsOptional() + @IsString() + serialNumber?: string; +} diff --git a/server/src/modules/part/dto/update-part.dto.ts b/server/src/modules/part/dto/update-part.dto.ts new file mode 100644 index 0000000..2e6acb3 --- /dev/null +++ b/server/src/modules/part/dto/update-part.dto.ts @@ -0,0 +1,29 @@ +import { + IsEnum, + IsNumber, + IsOptional, + IsString, +} from 'class-validator'; +import { CategoryPart } from '../../shared/dsl-enums'; + +export class UpdatePartDto { + @IsOptional() + @IsString() + name?: string; + + @IsOptional() + @IsEnum(CategoryPart) + categories?: CategoryPart; + + @IsOptional() + @IsNumber() + price?: number; + + @IsOptional() + @IsString() + description?: string; + + @IsOptional() + @IsString() + serialNumber?: string; +} diff --git a/server/src/modules/part/part.controller.ts b/server/src/modules/part/part.controller.ts new file mode 100644 index 0000000..49dafc9 --- /dev/null +++ b/server/src/modules/part/part.controller.ts @@ -0,0 +1,44 @@ +import { Body, Controller, Delete, Get, Param, Patch, Post, Query, Res, UseGuards } from '@nestjs/common'; +import type { Response } from 'express'; +import { Roles } from '../../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../../auth/guards/roles.guard'; +import { CreatePartDto } from './dto/create-part.dto'; +import { UpdatePartDto } from './dto/update-part.dto'; +import { PartService } from './part.service'; + +@Controller('parts') +@UseGuards(JwtAuthGuard, RolesGuard) +export class PartController { + constructor(private readonly partService: PartService) {} + + @Roles('viewer', 'editor', 'admin') + @Get() + findAll(@Query() query: Record, @Res({ passthrough: true }) res: Response) { + return this.partService.findAll(query, res); + } + + @Roles('viewer', 'editor', 'admin') + @Get(':id') + findOne(@Param('id') id: string) { + return this.partService.findOne(id); + } + + @Roles('editor', 'admin') + @Post() + create(@Body() dto: CreatePartDto) { + return this.partService.create(dto); + } + + @Roles('editor', 'admin') + @Patch(':id') + update(@Param('id') id: string, @Body() dto: UpdatePartDto) { + return this.partService.update(id, dto); + } + + @Roles('admin') + @Delete(':id') + remove(@Param('id') id: string) { + return this.partService.remove(id); + } +} diff --git a/server/src/modules/part/part.module.ts b/server/src/modules/part/part.module.ts new file mode 100644 index 0000000..ead150f --- /dev/null +++ b/server/src/modules/part/part.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { PartController } from './part.controller'; +import { PartService } from './part.service'; + +@Module({ + controllers: [PartController], + providers: [PartService], +}) +export class PartModule {} diff --git a/server/src/modules/part/part.service.ts b/server/src/modules/part/part.service.ts new file mode 100644 index 0000000..cf0e5d6 --- /dev/null +++ b/server/src/modules/part/part.service.ts @@ -0,0 +1,107 @@ +import { Injectable, NotFoundException } from '@nestjs/common'; +import { Prisma } from '@prisma/client'; +import type { Response } from 'express'; +import { PrismaService } from '../../prisma/prisma.service'; +import { containsInsensitive, getSingle, getStringArray, parseRange, setListHeaders, toSortOrder } from '../shared/query-utils'; +import { mapPart } from '../shared/record-mappers'; +import { CreatePartDto } from './dto/create-part.dto'; +import { UpdatePartDto } from './dto/update-part.dto'; + +@Injectable() +export class PartService { + constructor(private readonly prisma: PrismaService) {} + + async findAll(query: Record, res: Response) { + const { start, end, take } = parseRange(query); + const requestedSort = getSingle(query._sort) ?? 'name'; + const sortField = requestedSort === 'id' ? 'id' : requestedSort; + const sortOrder = toSortOrder(getSingle(query._order)); + const where: Record = {}; + + const q = getSingle(query.q); + if (q) { + where.OR = [ + { name: containsInsensitive(q) }, + { description: containsInsensitive(q) }, + { serialNumber: containsInsensitive(q) }, + ]; + } + + const name = getSingle(query.name); + if (name) { + where.name = containsInsensitive(name); + } + + const serialNumber = getSingle(query.serialNumber); + if (serialNumber) { + where.serialNumber = containsInsensitive(serialNumber); + } + + const categoryValues = getStringArray(query.categories); + if (categoryValues.length === 1) { + where.categories = categoryValues[0]; + } else if (categoryValues.length > 1) { + where.categories = { in: Array.from(new Set(categoryValues)) }; + } + + const [items, total] = await this.prisma.$transaction([ + this.prisma.part.findMany({ + where: where as Prisma.PartWhereInput, + skip: start, + take: take, + orderBy: { [sortField]: sortOrder } as Prisma.PartOrderByWithRelationInput, + }), + this.prisma.part.count({ where: where as Prisma.PartWhereInput }), + ]); + + setListHeaders(res, 'parts', start, end, total); + return { + data: items.map((item) => mapPart(item)), + total: total, + }; + } + + async findOne(id: string) { + const item = await this.prisma.part.findUnique({ where: { id } }); + if (!item) { + throw new NotFoundException('Part not found'); + } + + return mapPart(item); + } + + async create(dto: CreatePartDto) { + const created = await this.prisma.part.create({ + data: { + name: dto.name, + categories: dto.categories ?? null, + price: dto.price ?? null, + description: dto.description ?? null, + serialNumber: dto.serialNumber ?? null, + } as any, + }); + + return mapPart(created); + } + + async update(id: string, dto: UpdatePartDto & { id?: string }) { + const { id: _id, ...mutableData } = dto; + const updated = await this.prisma.part.update({ + where: { id }, + data: { + ...(mutableData.name !== undefined ? { name: mutableData.name } : {}), + ...(mutableData.categories !== undefined ? { categories: mutableData.categories } : {}), + ...(mutableData.price !== undefined ? { price: mutableData.price } : {}), + ...(mutableData.description !== undefined ? { description: mutableData.description } : {}), + ...(mutableData.serialNumber !== undefined ? { serialNumber: mutableData.serialNumber } : {}), + } as any, + }); + + return mapPart(updated); + } + + async remove(id: string) { + await this.prisma.part.delete({ where: { id } }); + return { id }; + } +} diff --git a/server/src/modules/price-list/dto/create-price-list.dto.ts b/server/src/modules/price-list/dto/create-price-list.dto.ts new file mode 100644 index 0000000..b064eb9 --- /dev/null +++ b/server/src/modules/price-list/dto/create-price-list.dto.ts @@ -0,0 +1 @@ +export class CreatePriceListDto {} diff --git a/server/src/modules/price-list/dto/update-price-list.dto.ts b/server/src/modules/price-list/dto/update-price-list.dto.ts new file mode 100644 index 0000000..9c2c4a7 --- /dev/null +++ b/server/src/modules/price-list/dto/update-price-list.dto.ts @@ -0,0 +1 @@ +export class UpdatePriceListDto {} diff --git a/server/src/modules/price-list/price-list.controller.ts b/server/src/modules/price-list/price-list.controller.ts new file mode 100644 index 0000000..37289b5 --- /dev/null +++ b/server/src/modules/price-list/price-list.controller.ts @@ -0,0 +1,17 @@ +import { Controller, Get, UseGuards } from '@nestjs/common'; +import { Roles } from '../../auth/decorators/roles.decorator'; +import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard'; +import { RolesGuard } from '../../auth/guards/roles.guard'; +import { PriceListService } from './price-list.service'; + +@Controller('price-list') +@UseGuards(JwtAuthGuard, RolesGuard) +export class PriceListController { + constructor(private readonly priceListService: PriceListService) {} + + @Roles('viewer', 'editor', 'admin') + @Get() + getRecord() { + return this.priceListService.getRecord(); + } +} diff --git a/server/src/modules/price-list/price-list.module.ts b/server/src/modules/price-list/price-list.module.ts new file mode 100644 index 0000000..a67833b --- /dev/null +++ b/server/src/modules/price-list/price-list.module.ts @@ -0,0 +1,9 @@ +import { Module } from '@nestjs/common'; +import { PriceListController } from './price-list.controller'; +import { PriceListService } from './price-list.service'; + +@Module({ + controllers: [PriceListController], + providers: [PriceListService], +}) +export class PriceListModule {} diff --git a/server/src/modules/price-list/price-list.service.ts b/server/src/modules/price-list/price-list.service.ts new file mode 100644 index 0000000..ba17113 --- /dev/null +++ b/server/src/modules/price-list/price-list.service.ts @@ -0,0 +1,35 @@ +import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../../prisma/prisma.service'; +import { mapPriceList } from '../shared/record-mappers'; + +@Injectable() +export class PriceListService { + constructor(private readonly prisma: PrismaService) {} + + async getRecord() { + const [employee, part] = await Promise.all([ + this.prisma.employee.findFirst({ + where: { + price: { + not: null, + }, + }, + orderBy: { + code: 'asc', + }, + }), + this.prisma.part.findFirst({ + where: { + price: { + not: null, + }, + }, + orderBy: { + name: 'asc', + }, + }), + ]); + + return mapPriceList(employee?.price, part?.price); + } +} diff --git a/server/src/modules/repair-order/dto/create-repair-order.dto.ts b/server/src/modules/repair-order/dto/create-repair-order.dto.ts deleted file mode 100644 index 229f25f..0000000 --- a/server/src/modules/repair-order/dto/create-repair-order.dto.ts +++ /dev/null @@ -1,13 +0,0 @@ -export class CreateRepairOrderDto { - number!: string; - equipmentId!: string; - repairKind!: string; - status!: string; - plannedAt!: string; - startedAt?: string; - completedAt?: string; - contractor?: string; - engineHoursAtRepair?: string; - description?: string; - notes?: string; -} diff --git a/server/src/modules/repair-order/dto/update-repair-order.dto.ts b/server/src/modules/repair-order/dto/update-repair-order.dto.ts deleted file mode 100644 index e42bcb1..0000000 --- a/server/src/modules/repair-order/dto/update-repair-order.dto.ts +++ /dev/null @@ -1,14 +0,0 @@ -export class UpdateRepairOrderDto { - id?: string; - number?: string; - equipmentId?: string; - repairKind?: string; - status?: string; - plannedAt?: string; - startedAt?: string; - completedAt?: string; - contractor?: string; - engineHoursAtRepair?: string; - description?: string; - notes?: string; -} diff --git a/server/src/modules/repair-order/repair-order.controller.ts b/server/src/modules/repair-order/repair-order.controller.ts deleted file mode 100644 index 83250c0..0000000 --- a/server/src/modules/repair-order/repair-order.controller.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { Controller, Get, Post, Patch, Delete, Param, Body, Query, Res } from '@nestjs/common'; -import { Response } from 'express'; -import { Roles } from '../../auth/decorators/roles.decorator'; -import { RealmRole } from '../../auth/roles/realm-role.enum'; -import { RepairOrderService } from './repair-order.service'; -import { CreateRepairOrderDto } from './dto/create-repair-order.dto'; -import { UpdateRepairOrderDto } from './dto/update-repair-order.dto'; - -@Controller('repair-orders') -export class RepairOrderController { - constructor(private readonly service: RepairOrderService) {} - - @Roles(RealmRole.Viewer, RealmRole.Editor, RealmRole.Admin) - @Get() - async findAll(@Query() query: any, @Res() res: Response) { - const result = await this.service.findAll(query); - res.set('Content-Range', `repair-orders ${query._start || 0}-${query._end || result.total}/${result.total}`); - res.set('Access-Control-Expose-Headers', 'Content-Range'); - return res.json(result.data); - } - - @Roles(RealmRole.Viewer, RealmRole.Editor, RealmRole.Admin) - @Get(':id') - findOne(@Param('id') id: string) { - return this.service.findOne(id); - } - - @Roles(RealmRole.Editor, RealmRole.Admin) - @Post() - create(@Body() dto: CreateRepairOrderDto) { - return this.service.create(dto); - } - - @Roles(RealmRole.Editor, RealmRole.Admin) - @Patch(':id') - update(@Param('id') id: string, @Body() dto: UpdateRepairOrderDto) { - return this.service.update(id, dto); - } - - @Roles(RealmRole.Admin) - @Delete(':id') - remove(@Param('id') id: string) { - return this.service.remove(id); - } -} diff --git a/server/src/modules/repair-order/repair-order.module.ts b/server/src/modules/repair-order/repair-order.module.ts deleted file mode 100644 index af0ab83..0000000 --- a/server/src/modules/repair-order/repair-order.module.ts +++ /dev/null @@ -1,9 +0,0 @@ -import { Module } from '@nestjs/common'; -import { RepairOrderController } from './repair-order.controller'; -import { RepairOrderService } from './repair-order.service'; - -@Module({ - controllers: [RepairOrderController], - providers: [RepairOrderService], -}) -export class RepairOrderModule {} diff --git a/server/src/modules/repair-order/repair-order.service.ts b/server/src/modules/repair-order/repair-order.service.ts deleted file mode 100644 index 9d26718..0000000 --- a/server/src/modules/repair-order/repair-order.service.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { Injectable } from '@nestjs/common'; -import { Prisma } from '@prisma/client'; -import { PrismaService } from '../../prisma/prisma.service'; -import { CreateRepairOrderDto } from './dto/create-repair-order.dto'; -import { UpdateRepairOrderDto } from './dto/update-repair-order.dto'; - -function serializeRecord(record: any) { - return { - ...record, - engineHoursAtRepair: record.engineHoursAtRepair?.toString() ?? null, - plannedAt: record.plannedAt?.toISOString() ?? null, - startedAt: record.startedAt?.toISOString() ?? null, - completedAt: record.completedAt?.toISOString() ?? null, - }; -} - -@Injectable() -export class RepairOrderService { - constructor(private readonly prisma: PrismaService) {} - - async findAll(query: { _start?: string; _end?: string; _sort?: string; _order?: string; [key: string]: any }) { - const start = parseInt(query._start) || 0; - const end = parseInt(query._end) || 10; - const take = end - start; - const skip = start; - const sortField = query._sort || 'number'; - const prismaSortField = sortField === 'id' ? 'id' : sortField; - const sortOrder = (query._order || 'ASC').toLowerCase() as 'asc' | 'desc'; - - const where: any = {}; - - if (query.q) { - const q = String(query.q); - const ors: any[] = []; - ors.push({ number: { contains: q, mode: 'insensitive' } }); - ors.push({ contractor: { contains: q, mode: 'insensitive' } }); - ors.push({ description: { contains: q, mode: 'insensitive' } }); - ors.push({ notes: { contains: q, mode: 'insensitive' } }); - if (ors.length) where.OR = ors; - } - - if (query.number) where.number = { contains: query.number, mode: 'insensitive' }; - if (query.contractor) where.contractor = { contains: query.contractor, mode: 'insensitive' }; - if (query.description) where.description = { contains: query.description, mode: 'insensitive' }; - if (query.notes) where.notes = { contains: query.notes, mode: 'insensitive' }; - - if (query.equipmentId) where.equipmentId = query.equipmentId; - - // Enum multi-value support (e.g. status=A&status=B) - if (query.repairKind) { const vals = Array.isArray(query.repairKind) ? query.repairKind : [query.repairKind]; where.repairKind = vals.length > 1 ? { in: vals } : vals[0]; } - if (query.status) { const vals = Array.isArray(query.status) ? query.status : [query.status]; where.status = vals.length > 1 ? { in: vals } : vals[0]; } - - if (query.id) { - const ids = Array.isArray(query.id) ? query.id : [query.id]; - where.id = { in: ids }; - } - - const [data, total] = await Promise.all([ - this.prisma.repairOrder.findMany({ where, skip, take, orderBy: { [prismaSortField]: sortOrder } }), - this.prisma.repairOrder.count({ where }), - ]); - - const mapped = data.map(serializeRecord); - return { data: mapped, total }; - } - - async findOne(id: string) { - const record = await this.prisma.repairOrder.findUniqueOrThrow({ where: { id: id } as any }); - return serializeRecord(record); - } - - async create(dto: CreateRepairOrderDto) { - const data: any = { ...(dto as any) }; - if (data.plannedAt) data.plannedAt = new Date(data.plannedAt); - if (data.startedAt) data.startedAt = new Date(data.startedAt); - if (data.completedAt) data.completedAt = new Date(data.completedAt); - if (data.engineHoursAtRepair) data.engineHoursAtRepair = new Prisma.Decimal(data.engineHoursAtRepair); - - const record = await this.prisma.repairOrder.create({ data }); - return serializeRecord(record); - } - - async update(id: string, dto: UpdateRepairOrderDto) { - const data: any = { ...(dto as any) }; - delete data.id; - delete data.id; - if (data.plannedAt) data.plannedAt = new Date(data.plannedAt); - if (data.startedAt) data.startedAt = new Date(data.startedAt); - if (data.completedAt) data.completedAt = new Date(data.completedAt); - if (data.engineHoursAtRepair !== undefined && data.engineHoursAtRepair !== null) data.engineHoursAtRepair = new Prisma.Decimal(data.engineHoursAtRepair); - - const record = await this.prisma.repairOrder.update({ where: { id: id } as any, data }); - return serializeRecord(record); - } - - async remove(id: string) { - const record = await this.prisma.repairOrder.delete({ where: { id: id } as any }); - return serializeRecord(record); - } -} diff --git a/server/src/modules/shared/dsl-enums.ts b/server/src/modules/shared/dsl-enums.ts new file mode 100644 index 0000000..cc80170 --- /dev/null +++ b/server/src/modules/shared/dsl-enums.ts @@ -0,0 +1,40 @@ +export enum EquipmentStatus { + Active = 'Active', + Repair = 'Repair', + Reserve = 'Reserve', + WriteOff = 'WriteOff', +} + +export enum laborOperation { + Manual = 'Manual', + MachineManual = 'MachineManual', + Machine = 'Machine', +} + +export enum EnumPeriodicityTO { + EZHEDNEVNOE = 'Ежедневное', + EZHENEDELNOE = 'Еженедельное', + EZHEMESYACHNOE = 'Ежемесячное', + POLUGODOVOE = 'Полугодовое', + GODOVOE = 'Годовое', +} + +export enum Role { + ISPOLNITEL = 'Исполнитель', + PODPISANT = 'Подписант', + POLZOVATEL = 'Пользователь', +} + +export enum CategoryPart { + RASKHODNIK = 'Расходник', + ZAPCHAST = 'Запчасть', + INSTRUMENT = 'Инструмент', + SPETSODEZHDA = 'Спецодежда', +} + +export enum EquipmentType { + PROIZVODSTVENNOE = 'Производственное', + ENERGETICHESKOE = 'Энергетическое', + NASOSNOE = 'Насосное', + KOMPRESSORNOE = 'Компрессорное', +} diff --git a/server/src/modules/shared/query-utils.ts b/server/src/modules/shared/query-utils.ts new file mode 100644 index 0000000..32c5615 --- /dev/null +++ b/server/src/modules/shared/query-utils.ts @@ -0,0 +1,103 @@ +import type { Response } from 'express'; + +import { Prisma } from '@prisma/client'; + +export type QueryValue = unknown; + +export function getSingle(value: QueryValue): string | undefined { + if (Array.isArray(value)) { + const first = value[0]; + return typeof first === 'string' ? first : first === undefined || first === null ? undefined : String(first); + } + + if (typeof value === 'string') { + return value; + } + + if (value === undefined || value === null) { + return undefined; + } + + return String(value); +} + +export function getFirst(value: QueryValue): string | undefined { + return getSingle(value); +} + +export function getStringArray(value: QueryValue): string[] { + if (Array.isArray(value)) { + return value + .filter((item) => item !== undefined && item !== null && item !== '') + .map((item) => String(item)); + } + + if (value === undefined || value === null || value === '') { + return []; + } + + return [String(value)]; +} + +export function toArray(value: QueryValue): string[] { + return getStringArray(value); +} + +export function parseRange(query: Record): { start: number; end: number; take: number } { + const start = Number(getFirst(query._start) ?? 0); + const end = Number(getFirst(query._end) ?? start + 25); + const safeStart = Number.isFinite(start) && start >= 0 ? start : 0; + const safeEnd = Number.isFinite(end) && end > safeStart ? end : safeStart + 25; + + return { + start: safeStart, + end: safeEnd, + take: safeEnd - safeStart, + }; +} + +export function toSortOrder(value: string | undefined): 'asc' | 'desc' { + return String(value ?? 'ASC').toLowerCase() === 'desc' ? 'desc' : 'asc'; +} + +export function containsInsensitive(value: string) { + return { + contains: value, + mode: 'insensitive' as const, + }; +} + +export function toNumberValue(value: unknown): number | null | undefined { + if (value === undefined) return undefined; + if (value === null || value === '') return null; + const numeric = Number(value); + return Number.isNaN(numeric) ? undefined : numeric; +} + +export function toDateValue(value: unknown): Date | null | undefined { + if (value === undefined) return undefined; + if (value === null || value === '') return null; + return new Date(String(value)); +} + +export function toDecimalValue(value: unknown): Prisma.Decimal | null | undefined { + if (value === undefined) return undefined; + if (value === null || value === '') return null; + return new Prisma.Decimal(String(value)); +} + +export function setListHeaders( + response: Response, + totalOrResource: number | string, + start: number, + end: number, + resourceOrTotal: string | number, +): void { + const total = typeof totalOrResource === 'number' ? totalOrResource : Number(resourceOrTotal); + const resource = typeof totalOrResource === 'string' ? totalOrResource : String(resourceOrTotal); + const safeStart = Number.isFinite(start) ? start : 0; + const safeEnd = Number.isFinite(end) ? end : safeStart; + const contentEnd = total === 0 ? safeStart : Math.min(Math.max(safeEnd - 1, safeStart), Math.max(total - 1, safeStart)); + response.setHeader('Content-Range', `${resource} ${safeStart}-${contentEnd}/${total}`); + response.setHeader('Access-Control-Expose-Headers', 'Content-Range'); +} diff --git a/server/src/modules/shared/record-mappers.ts b/server/src/modules/shared/record-mappers.ts new file mode 100644 index 0000000..683e782 --- /dev/null +++ b/server/src/modules/shared/record-mappers.ts @@ -0,0 +1,85 @@ +function decimalToString(value: unknown): string | null { + if (value === null || value === undefined) { + return null; + } + + return String(value); +} + +function isoDate(value: unknown): string | null { + if (!value) { + return null; + } + + const date = value instanceof Date ? value : new Date(String(value)); + return Number.isNaN(date.getTime()) ? null : date.toISOString(); +} + +export function mapEquipment(record: any) { + return { + id: record.id, + name: record.name, + serialNumber: record.serialNumber, + inventoryNumber: record.inventoryNumber, + equipmentType: record.equipmentType, + dateOfInspection: isoDate(record.dateOfInspection), + periodicityTO: record.periodicityTO, + location: record.location ?? null, + status: record.status, + commissionedAt: isoDate(record.commissionedAt), + totalEngineHours: decimalToString(record.totalEngineHours), + engineHoursSinceLastRepair: decimalToString(record.engineHoursSinceLastRepair), + lastRepairAt: isoDate(record.lastRepairAt), + notes: record.notes ?? null, + workAsPartOf: record.workAsPartOf ?? null, + fuelConsumed: record.fuelConsumed ?? null, + }; +} + +export function mapEmployee(record: any, shallow = false): any { + return { + id: record.code, + code: record.code, + fullName: record.fullName, + role: record.role, + position: record.position, + bossCode: record.bossCode ?? record.boss?.code ?? null, + boss: shallow || !record.boss ? null : mapEmployee(record.boss, true), + subordinates: shallow + ? [] + : Array.isArray(record.subordinates) + ? record.subordinates.map((item: any) => mapEmployee(item, true)) + : [], + price: record.price ?? null, + phoneNumber: record.phoneNumber ?? null, + }; +} + +export function mapPart(record: any) { + return { + id: record.id, + name: record.name, + categories: record.categories ?? null, + price: record.price ?? null, + description: record.description ?? null, + serialNumber: record.serialNumber ?? null, + }; +} + +export function mapCategoryResource(record: any) { + return { + id: record.id, + partId: record.partId ?? record.part?.id ?? null, + employeeCode: record.employeeCode ?? record.employee?.code ?? null, + part: record.part ? mapPart(record.part) : null, + employee: record.employee ? mapEmployee(record.employee, true) : null, + }; +} + +export function mapPriceList(costOfWorkingHours: number | null | undefined, partPrice: number | null | undefined) { + return { + id: 'price-list', + costOfWorkingHours: costOfWorkingHours ?? 0, + partPrice: partPrice ?? 0, + }; +} diff --git a/server/src/prisma/prisma.module.ts b/server/src/prisma/prisma.module.ts index 7207426..930cb0f 100644 --- a/server/src/prisma/prisma.module.ts +++ b/server/src/prisma/prisma.module.ts @@ -2,8 +2,5 @@ import { Global, Module } from '@nestjs/common'; import { PrismaService } from './prisma.service'; @Global() -@Module({ - providers: [PrismaService], - exports: [PrismaService], -}) +@Module({ providers: [PrismaService], exports: [PrismaService] }) export class PrismaModule {} diff --git a/server/src/prisma/prisma.service.ts b/server/src/prisma/prisma.service.ts index 359f950..086add4 100644 --- a/server/src/prisma/prisma.service.ts +++ b/server/src/prisma/prisma.service.ts @@ -3,7 +3,7 @@ import { PrismaClient } from '@prisma/client'; @Injectable() export class PrismaService extends PrismaClient implements OnModuleInit { - async onModuleInit() { + async onModuleInit(): Promise { await this.$connect(); } } diff --git a/server/test/app.e2e-spec.ts b/server/test/app.e2e-spec.ts index ce1c8f7..a767839 100644 --- a/server/test/app.e2e-spec.ts +++ b/server/test/app.e2e-spec.ts @@ -1,83 +1,29 @@ import { Test, TestingModule } from '@nestjs/testing'; import { INestApplication } from '@nestjs/common'; -import * as request from 'supertest'; -import { AuthService } from '../src/auth/auth.service'; -import { AuthenticatedUser } from '../src/auth/interfaces/authenticated-user.interface'; -import { PrismaService } from '../src/prisma/prisma.service'; +import request from 'supertest'; +import { App } from 'supertest/types'; import { AppModule } from './../src/app.module'; -describe('Auth and Health (e2e)', () => { - let app: INestApplication; - let authServiceMock: { - verifyAccessToken: jest.Mock, [string]>; - }; - - beforeAll(async () => { - process.env.PORT = '3000'; - process.env.DATABASE_URL = - process.env.DATABASE_URL ?? - 'postgresql://postgres:postgres@localhost:5432/toir'; - process.env.CORS_ALLOWED_ORIGINS = - process.env.CORS_ALLOWED_ORIGINS ?? - 'http://localhost:5173,https://toir-frontend.greact.ru'; - process.env.KEYCLOAK_ISSUER_URL = - process.env.KEYCLOAK_ISSUER_URL ?? 'https://sso.greact.ru/realms/toir'; - process.env.KEYCLOAK_AUDIENCE = - process.env.KEYCLOAK_AUDIENCE ?? 'toir-backend'; - - authServiceMock = { - verifyAccessToken: jest.fn, [string]>(), - }; +describe('AppController (e2e)', () => { + let app: INestApplication; + beforeEach(async () => { const moduleFixture: TestingModule = await Test.createTestingModule({ imports: [AppModule], - }) - .overrideProvider(AuthService) - .useValue(authServiceMock) - .overrideProvider(PrismaService) - .useValue({}) - .compile(); + }).compile(); app = moduleFixture.createNestApplication(); await app.init(); }); - afterAll(async () => { + it('/ (GET)', () => { + return request(app.getHttpServer()) + .get('/') + .expect(200) + .expect('Hello World!'); + }); + + afterEach(async () => { await app.close(); }); - - beforeEach(() => { - authServiceMock.verifyAccessToken.mockReset(); - }); - - it('/health (GET) is public', () => { - return request(app.getHttpServer()) - .get('/health') - .expect(200) - .expect({ status: 'ok' }); - }); - - it('/equipment (GET) requires authentication', () => { - return request(app.getHttpServer()).get('/equipment').expect(401); - }); - - it('/equipment (POST) returns 403 for authenticated viewer role', async () => { - authServiceMock.verifyAccessToken.mockResolvedValue({ - sub: 'viewer-user', - username: 'viewer-user', - roles: ['viewer'], - claims: { - sub: 'viewer-user', - realm_access: { - roles: ['viewer'], - }, - }, - }); - - await request(app.getHttpServer()) - .post('/equipment') - .set('Authorization', 'Bearer viewer-token') - .send({}) - .expect(403); - }); }); diff --git a/server/test/jest-e2e.json b/server/test/jest-e2e.json index e9166a0..e9d912f 100644 --- a/server/test/jest-e2e.json +++ b/server/test/jest-e2e.json @@ -5,8 +5,5 @@ "testRegex": ".e2e-spec.ts$", "transform": { "^.+\\.(t|j)s$": "ts-jest" - }, - "moduleNameMapper": { - "^jose$": "/mocks/jose.ts" } } diff --git a/server/test/mocks/jose.ts b/server/test/mocks/jose.ts deleted file mode 100644 index 97a9d97..0000000 --- a/server/test/mocks/jose.ts +++ /dev/null @@ -1,8 +0,0 @@ -export const createRemoteJWKSet = () => { - return async () => ({}) as never; -}; - -export const jwtVerify = async () => { - return { payload: {} }; -}; - diff --git a/server/tsconfig.json b/server/tsconfig.json index 95f5641..57f9635 100644 --- a/server/tsconfig.json +++ b/server/tsconfig.json @@ -1,21 +1,25 @@ { "compilerOptions": { - "module": "commonjs", + "module": "nodenext", + "moduleResolution": "nodenext", + "resolvePackageJsonExports": true, + "esModuleInterop": true, + "isolatedModules": true, "declaration": true, "removeComments": true, "emitDecoratorMetadata": true, "experimentalDecorators": true, "allowSyntheticDefaultImports": true, - "target": "ES2021", + "target": "ES2023", "sourceMap": true, "outDir": "./dist", "baseUrl": "./", "incremental": true, "skipLibCheck": true, - "strictNullChecks": false, - "noImplicitAny": false, - "strictBindCallApply": false, - "forceConsistentCasingInFileNames": false, - "noFallthroughCasesInSwitch": false + "strictNullChecks": true, + "forceConsistentCasingInFileNames": true, + "noImplicitAny": true, + "strictBindCallApply": true, + "noFallthroughCasesInSwitch": true } } diff --git a/tools/api-summary-to-openapi.mjs b/tools/api-summary-to-openapi.mjs new file mode 100644 index 0000000..278e2f8 --- /dev/null +++ b/tools/api-summary-to-openapi.mjs @@ -0,0 +1,301 @@ +// Deterministic OpenAPI 3.0.3 generator from api-summary.json / toir.api.dsl. +// +// This script is part of the Tier 1 deterministic preprocessing layer. +// It converts the canonical api-summary (produced by tools/api-summary.mjs) +// into a valid OpenAPI 3.0.3 document. +// +// Usage: +// node tools/api-summary-to-openapi.mjs --out openapi.json +// npm run generate:openapi +// +// No LLM involvement. The output is reproducible from DSL + this script alone. + +import { writeFileSync } from 'node:fs'; +import path from 'node:path'; +import { buildApiSummary } from './api-summary.mjs'; + +const rootDir = process.cwd(); + +// --------------------------------------------------------------------------- +// DSL scalar → OpenAPI type +// --------------------------------------------------------------------------- + +function dslTypeToOpenApi(dslType) { + switch (dslType) { + case 'uuid': + return { type: 'string', format: 'uuid' }; + case 'string': + return { type: 'string' }; + case 'text': + return { type: 'string' }; + case 'integer': + return { type: 'integer', format: 'int32' }; + case 'number': + return { type: 'number' }; + case 'decimal': + return { type: 'string', format: 'decimal' }; + case 'date': + return { type: 'string', format: 'date-time' }; + case 'boolean': + return { type: 'boolean' }; + default: + // enum names and DTO references handled by caller + return null; + } +} + +// --------------------------------------------------------------------------- +// Resolve a DSL field type to an OpenAPI schema reference or inline schema. +// dtoNames — Set of known DTO names in the api summary. +// enumNames — Set of known enum names (derived from type mappings table). +// --------------------------------------------------------------------------- + +function resolveFieldType(dslType, dtoNames, enumNames) { + if (!dslType) return { type: 'object' }; + + // Array type: "DTO.Foo[]" + if (dslType.endsWith('[]')) { + const inner = dslType.slice(0, -2); + return { type: 'array', items: resolveFieldType(inner, dtoNames, enumNames) }; + } + + // Scalar + const scalar = dslTypeToOpenApi(dslType); + if (scalar) return scalar; + + // DTO reference + if (dtoNames.has(dslType)) { + return { $ref: `#/components/schemas/${dslType.replace(/^DTO\./, '')}` }; + } + + // Enum reference + if (enumNames.has(dslType)) { + return { $ref: `#/components/schemas/${dslType}` }; + } + + // Unknown — emit as string with x-dsl-type annotation + return { type: 'string', 'x-dsl-type': dslType }; +} + +// --------------------------------------------------------------------------- +// Build OpenAPI schema object from a DTO definition +// --------------------------------------------------------------------------- + +function buildDtoSchema(dto, dtoNames, enumNames) { + const properties = {}; + const required = []; + + for (const field of dto.fields) { + const schema = resolveFieldType(field.type, dtoNames, enumNames); + if (field.description && schema.$ref) { + // OpenAPI 3.0.3: $ref cannot have sibling keys — wrap with allOf + properties[field.name] = { allOf: [schema], description: field.description }; + } else { + if (field.description) schema.description = field.description; + properties[field.name] = schema; + } + if (field.required) required.push(field.name); + } + + const schema = { + type: 'object', + properties, + }; + + if (dto.description) schema.description = dto.description; + if (required.length > 0) schema.required = required; + + return schema; +} + +// --------------------------------------------------------------------------- +// Convert DSL HTTP method to OpenAPI method key +// --------------------------------------------------------------------------- + +function methodKey(method) { + return (method ?? 'get').toLowerCase(); +} + +// --------------------------------------------------------------------------- +// Build OpenAPI path item for an endpoint +// --------------------------------------------------------------------------- + +function buildPathOperation(endpoint, apiDescription, dtoNames, enumNames) { + const operation = {}; + + if (endpoint.description) operation.summary = endpoint.description; + + // Security — all endpoints require bearer auth + operation.security = [{ bearerAuth: [] }]; + + // Tags — derive from API name or path + const tag = apiDescription ? apiDescription.replace(/^API управления\s+/i, '').replace(/ами$/, '') : undefined; + if (tag) operation.tags = [tag]; + + // Parameters — detect path params by matching attribute names against {param} in the path + const pathTemplate = endpoint.path ?? ''; + const pathParamNames = new Set( + [...pathTemplate.matchAll(/\{(\w+)\}/g)].map((m) => m[1]), + ); + const pathParams = endpoint.attributes.filter((a) => pathParamNames.has(a.name)); + if (pathParams.length > 0) { + operation.parameters = pathParams.map((p) => ({ + name: p.name, + in: 'path', + required: true, + schema: resolveFieldType(p.type, dtoNames, enumNames), + ...(p.description ? { description: p.description } : {}), + })); + } + + // Request body + const requestAttr = endpoint.attributes.find((a) => a.name === 'request'); + if (requestAttr) { + operation.requestBody = { + required: true, + content: { + 'application/json': { + schema: resolveFieldType(requestAttr.type, dtoNames, enumNames), + }, + }, + }; + } + + // Response + const responseAttr = endpoint.attributes.find((a) => a.name === 'response'); + const responseSchema = responseAttr + ? resolveFieldType(responseAttr.type, dtoNames, enumNames) + : { type: 'object' }; + + const successCode = endpoint.method === 'POST' && !endpoint.path?.endsWith('/page') ? '201' : '200'; + + operation.responses = { + [successCode]: { + description: 'Success', + content: { + 'application/json': { + schema: responseSchema, + }, + }, + }, + '401': { description: 'Unauthorized' }, + '403': { description: 'Forbidden' }, + }; + + if (endpoint.method === 'DELETE') { + operation.responses = { + '204': { description: 'No content' }, + '401': { description: 'Unauthorized' }, + '403': { description: 'Forbidden' }, + '404': { description: 'Not found' }, + }; + delete operation.responses['201']; + } + + return operation; +} + +// --------------------------------------------------------------------------- +// Main builder +// --------------------------------------------------------------------------- + +function buildOpenApi(summary) { + const dtoNames = new Set(summary.dtos.map((d) => d.name)); + + // Build enum map from api-summary enums block (fully declared enums with values) + const enumMap = new Map((summary.enums ?? []).map((e) => [e.name, e])); + + // Also collect enum names referenced in DTO fields that are not in the declared enums + // (covers cases where enums are declared in domain.dsl but referenced in api.dsl) + const enumNames = new Set(enumMap.keys()); + for (const dto of summary.dtos) { + for (const field of dto.fields) { + const t = field.type?.replace('[]', ''); + if (t && !dtoNames.has(t) && !dslTypeToOpenApi(t)) { + enumNames.add(t); + } + } + } + + // Schemas — one per DTO + const schemas = {}; + for (const dto of summary.dtos) { + const schemaName = dto.name.replace(/^DTO\./, ''); + schemas[schemaName] = buildDtoSchema(dto, dtoNames, enumNames); + } + + // Enum schemas — use actual values when available, otherwise annotate as opaque string enum + for (const enumName of enumNames) { + const enumDef = enumMap.get(enumName); + if (enumDef && enumDef.values.length > 0) { + schemas[enumName] = { + type: 'string', + enum: enumDef.values.map((v) => v.name), + 'x-enum-labels': Object.fromEntries( + enumDef.values.filter((v) => v.label).map((v) => [v.name, v.label]), + ), + ...(enumDef.description ? { description: enumDef.description } : {}), + }; + } else { + schemas[enumName] = { + type: 'string', + 'x-dsl-enum': enumName, + description: `Enum: ${enumName} (values defined in domain/*.api.dsl)`, + }; + } + } + + // Paths + const paths = {}; + for (const api of summary.apis) { + for (const endpoint of api.endpoints) { + if (!endpoint.path) continue; + const pathKey = endpoint.path; + if (!paths[pathKey]) paths[pathKey] = {}; + const opKey = methodKey(endpoint.method); + paths[pathKey][opKey] = buildPathOperation(endpoint, api.description, dtoNames, enumNames); + } + } + + return { + openapi: '3.0.3', + info: { + title: 'KIS-TOiR API', + description: + 'Equipment maintenance management system. Generated from domain/toir.api.dsl via tools/api-summary-to-openapi.mjs.', + version: '1.0.0', + }, + servers: [ + { + url: '/api', + description: 'Default server', + }, + ], + components: { + securitySchemes: { + bearerAuth: { + type: 'http', + scheme: 'bearer', + bearerFormat: 'JWT', + }, + }, + schemas, + }, + paths, + }; +} + +// --------------------------------------------------------------------------- +// CLI +// --------------------------------------------------------------------------- + +const args = process.argv.slice(2); +const outIndex = args.indexOf('--out'); +const outPath = outIndex !== -1 ? args[outIndex + 1] : 'openapi.json'; + +const summary = buildApiSummary(rootDir); +const openApiDoc = buildOpenApi(summary); +const outputPath = path.resolve(rootDir, outPath); + +writeFileSync(outputPath, `${JSON.stringify(openApiDoc, null, 2)}\n`, 'utf8'); +console.log(`Generated ${path.relative(rootDir, outputPath)}`); diff --git a/tools/api-summary.mjs b/tools/api-summary.mjs new file mode 100644 index 0000000..b5e2279 --- /dev/null +++ b/tools/api-summary.mjs @@ -0,0 +1,389 @@ +import { readdirSync, readFileSync } from 'node:fs'; +import path from 'node:path'; + +// --------------------------------------------------------------------------- +// Helpers +// --------------------------------------------------------------------------- + +function stripInlineComment(line) { + let inString = false; + let result = ''; + + for (let index = 0; index < line.length; index += 1) { + const current = line[index]; + const next = line[index + 1]; + + if (current === '"' && line[index - 1] !== '\\') { + inString = !inString; + result += current; + continue; + } + + if (!inString && current === '/' && next === '/') { + break; + } + + result += current; + } + + return result.trim(); +} + +// --------------------------------------------------------------------------- +// File discovery +// --------------------------------------------------------------------------- + +export function getApiDslFiles(rootDir) { + const domainDir = path.join(rootDir, 'domain'); + try { + return readdirSync(domainDir, { withFileTypes: true }) + .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.api.dsl')) + .map((entry) => path.join(domainDir, entry.name)) + .sort((left, right) => left.localeCompare(right)); + } catch { + return []; + } +} + +// --------------------------------------------------------------------------- +// Parser +// +// Parses all *.api.dsl files using a stack-based approach. +// This is the single canonical parser for the API DSL. +// +// Supported top-level blocks: +// enum { description?; value { label?; } } +// dto DTO. { description?; attribute { ... } } +// api API. { description?; endpoint { ... } } +// +// DTO attribute modifiers (any order inside the attribute block): +// type ; +// description "..."; +// map Entity.field; +// sync Entity.field; (alias for map — used for computed/aggregate fields) +// is required; +// is nullable; +// is unique; +// key primary; +// label "..."; +// +// Endpoint modifiers: +// label "METHOD /path"; +// description "..."; +// attribute { type ; description?; } +// +// Returns: +// { +// files: string[], +// enums: EnumBlock[], +// dtos: DtoBlock[], +// apis: ApiBlock[], +// } +// +// EnumBlock = { name, description, values: EnumValue[] } +// EnumValue = { name, label } +// DtoBlock = { name, description, fields: DtoField[] } +// DtoField = { name, type, required, nullable, unique, primary, description, map, label } +// ApiBlock = { name, description, endpoints: Endpoint[] } +// Endpoint = { name, label, method, path, description, attributes: EndpointAttr[] } +// EndpointAttr = { name, type, description } +// --------------------------------------------------------------------------- + +export function parseApiDsl(rootDir) { + const files = getApiDslFiles(rootDir); + const enums = []; + const dtos = []; + const apis = []; + const stack = []; + + for (const filePath of files) { + const content = readFileSync(filePath, 'utf8'); + const lines = content.split(/\r?\n/); + + for (const rawLine of lines) { + const line = stripInlineComment(rawLine); + if (!line) continue; + + const top = stack.at(-1); + + // ── Top-level: enum block ────────────────────────────────────────── + const enumMatch = line.match(/^enum\s+([A-Za-z][A-Za-z0-9_]*)\s*\{$/); + if (!top && enumMatch) { + const enumBlock = { name: enumMatch[1], description: null, values: [] }; + enums.push(enumBlock); + stack.push({ type: 'enum', ref: enumBlock }); + continue; + } + + // ── Top-level: dto block ─────────────────────────────────────────── + const dtoMatch = line.match(/^dto\s+(DTO\.\w+)\s*\{$/); + if (!top && dtoMatch) { + const dto = { name: dtoMatch[1], description: null, fields: [] }; + dtos.push(dto); + stack.push({ type: 'dto', ref: dto }); + continue; + } + + // ── Top-level: api block ─────────────────────────────────────────── + const apiMatch = line.match(/^api\s+(API\.\w+)\s*\{$/); + if (!top && apiMatch) { + const api = { name: apiMatch[1], description: null, endpoints: [] }; + apis.push(api); + stack.push({ type: 'api', ref: api }); + continue; + } + + // ── Inside enum ─────────────────────────────────────────────────── + if (top?.type === 'enum') { + const descMatch = line.match(/^description\s+"(.*)"\s*;$/); + if (descMatch) { + top.ref.description = descMatch[1]; + continue; + } + + const valueMatch = line.match(/^value\s+([^\s{]+)\s*\{$/); + if (valueMatch) { + const enumValue = { name: valueMatch[1], label: null }; + top.ref.values.push(enumValue); + stack.push({ type: 'enumValue', ref: enumValue }); + continue; + } + } + + // ── Inside enum value ───────────────────────────────────────────── + if (top?.type === 'enumValue') { + const labelMatch = line.match(/^label\s+"(.*)"\s*;$/); + if (labelMatch) { + top.ref.label = labelMatch[1]; + continue; + } + } + + // ── Inside dto ──────────────────────────────────────────────────── + if (top?.type === 'dto') { + const descMatch = line.match(/^description\s+"(.*)"\s*;$/); + if (descMatch) { + top.ref.description = descMatch[1]; + continue; + } + + const attrMatch = line.match(/^attribute\s+(\w+)\s*\{$/); + if (attrMatch) { + const field = { + name: attrMatch[1], + type: null, + required: false, + nullable: false, + unique: false, + primary: false, + description: null, + map: null, + label: null, + }; + top.ref.fields.push(field); + stack.push({ type: 'dtoField', ref: field }); + continue; + } + } + + // ── Inside dto attribute field ──────────────────────────────────── + if (top?.type === 'dtoField') { + const typeMatch = line.match(/^type\s+(.+?)\s*;$/); + if (typeMatch) { + top.ref.type = typeMatch[1]; + continue; + } + + if (/^is\s+required\s*;$/.test(line)) { + top.ref.required = true; + continue; + } + + if (/^is\s+nullable\s*;$/.test(line)) { + top.ref.nullable = true; + continue; + } + + if (/^is\s+unique\s*;$/.test(line)) { + top.ref.unique = true; + continue; + } + + if (/^key\s+primary\s*;$/.test(line)) { + top.ref.primary = true; + continue; + } + + const descMatch = line.match(/^description\s+"(.*)"\s*;$/); + if (descMatch) { + top.ref.description = descMatch[1]; + continue; + } + + // map Entity.field; — canonical field mapping + const mapMatch = line.match(/^map\s+(\w+)\.(\w+)\s*;$/); + if (mapMatch) { + top.ref.map = `${mapMatch[1]}.${mapMatch[2]}`; + continue; + } + + // sync Entity.field; — aggregate / computed field mapping (treated as map) + const syncMatch = line.match(/^sync\s+(\w+)\.(\w+)\s*;$/); + if (syncMatch) { + top.ref.map = `${syncMatch[1]}.${syncMatch[2]}`; + top.ref.sync = true; + continue; + } + + const labelMatch = line.match(/^label\s+"(.*)"\s*;$/); + if (labelMatch) { + top.ref.label = labelMatch[1]; + continue; + } + } + + // ── Inside api ──────────────────────────────────────────────────── + if (top?.type === 'api') { + const descMatch = line.match(/^description\s+"(.*)"\s*;$/); + if (descMatch) { + top.ref.description = descMatch[1]; + continue; + } + + const epMatch = line.match(/^endpoint\s+(\w+)\s*\{$/); + if (epMatch) { + const ep = { + name: epMatch[1], + label: null, + method: null, + path: null, + description: null, + attributes: [], + }; + top.ref.endpoints.push(ep); + stack.push({ type: 'endpoint', ref: ep }); + continue; + } + } + + // ── Inside endpoint ─────────────────────────────────────────────── + if (top?.type === 'endpoint') { + const labelMatch = line.match(/^label\s+"([^"]+)"\s*;$/); + if (labelMatch) { + top.ref.label = labelMatch[1]; + const parts = labelMatch[1].split(' ', 2); + top.ref.method = parts[0]?.toUpperCase() ?? null; + top.ref.path = parts[1] ?? null; + continue; + } + + const descMatch = line.match(/^description\s+"(.*)"\s*;$/); + if (descMatch) { + top.ref.description = descMatch[1]; + continue; + } + + const attrMatch = line.match(/^attribute\s+(\w+)\s*\{$/); + if (attrMatch) { + const attr = { name: attrMatch[1], type: null, description: null }; + top.ref.attributes.push(attr); + stack.push({ type: 'endpointAttr', ref: attr }); + continue; + } + } + + // ── Inside endpoint attribute ───────────────────────────────────── + if (top?.type === 'endpointAttr') { + const typeMatch = line.match(/^type\s+(.+?)\s*;$/); + if (typeMatch) { + top.ref.type = typeMatch[1]; + continue; + } + + const descMatch = line.match(/^description\s+"(.*)"\s*;$/); + if (descMatch) { + top.ref.description = descMatch[1]; + continue; + } + } + + // ── Closing brace — pop the stack ───────────────────────────────── + if (/^}\s*;?$/.test(line)) { + stack.pop(); + } + } + } + + return { files, enums, dtos, apis }; +} + +// --------------------------------------------------------------------------- +// Summary builder +// +// Produces the serialisable api-summary.json object. +// --------------------------------------------------------------------------- + +export function buildApiSummary(rootDir) { + const { files, enums, dtos, apis } = parseApiDsl(rootDir); + + // Detect duplicate DTO names + const dtoNames = new Set(); + for (const dto of dtos) { + if (dtoNames.has(dto.name)) { + throw new Error(`Duplicate DTO definition: ${dto.name}`); + } + dtoNames.add(dto.name); + } + + // Detect duplicate API names + const apiNames = new Set(); + for (const api of apis) { + if (apiNames.has(api.name)) { + throw new Error(`Duplicate API definition: ${api.name}`); + } + apiNames.add(api.name); + } + + return { + sourceFiles: files.map((filePath) => + path.relative(rootDir, filePath).replaceAll('\\', '/'), + ), + enums: enums.map((e) => ({ + name: e.name, + description: e.description, + values: e.values.map((v) => ({ name: v.name, label: v.label })), + })), + dtos: dtos.map((dto) => ({ + name: dto.name, + description: dto.description, + fields: dto.fields.map((field) => ({ + name: field.name, + type: field.type, + required: field.required, + nullable: field.nullable, + unique: field.unique, + primary: field.primary, + description: field.description, + map: field.map, + sync: field.sync ?? false, + label: field.label, + })), + })), + apis: apis.map((api) => ({ + name: api.name, + description: api.description, + endpoints: api.endpoints.map((ep) => ({ + name: ep.name, + label: ep.label, + method: ep.method, + path: ep.path, + description: ep.description, + attributes: ep.attributes.map((attr) => ({ + name: attr.name, + type: attr.type, + description: attr.description, + })), + })), + })), + }; +} diff --git a/tools/dsl-summary.mjs b/tools/dsl-summary.mjs deleted file mode 100644 index 823736b..0000000 --- a/tools/dsl-summary.mjs +++ /dev/null @@ -1,357 +0,0 @@ -import { readdirSync, readFileSync } from 'node:fs'; -import path from 'node:path'; - -function stripInlineComment(line) { - let inString = false; - let result = ''; - - for (let index = 0; index < line.length; index += 1) { - const current = line[index]; - const next = line[index + 1]; - - if (current === '"' && line[index - 1] !== '\\') { - inString = !inString; - result += current; - continue; - } - - if (!inString && current === '/' && next === '/') { - break; - } - - result += current; - } - - return result.trim(); -} - -function parseDefaultValue(rawValue) { - const trimmed = rawValue.trim(); - if (trimmed.startsWith('"') && trimmed.endsWith('"')) { - return trimmed.slice(1, -1); - } - - if (/^-?\d+$/.test(trimmed)) { - return Number(trimmed); - } - - return trimmed; -} - -export function getDslFiles(rootDir) { - const domainDir = path.join(rootDir, 'domain'); - - return readdirSync(domainDir, { withFileTypes: true }) - .filter((entry) => entry.isFile() && entry.name.toLowerCase().endsWith('.dsl')) - .map((entry) => path.join(domainDir, entry.name)) - .sort((left, right) => left.localeCompare(right)); -} - -function getOverrideFiles(rootDir) { - const overridesDir = path.join(rootDir, 'overrides'); - const files = ['api-overrides.dsl', 'ui-overrides.dsl'] - .map((name) => path.join(overridesDir, name)) - .filter((filePath) => { - try { - return readdirSync(path.dirname(filePath)).includes(path.basename(filePath)); - } catch { - return false; - } - }); - - return files; -} - -export function parseDslFiles(rootDir) { - const dslFiles = getDslFiles(rootDir); - const enums = []; - const entities = []; - const stack = []; - - for (const filePath of dslFiles) { - const contents = readFileSync(filePath, 'utf8'); - const lines = contents.split(/\r?\n/); - - for (const rawLine of lines) { - const line = stripInlineComment(rawLine); - if (!line) { - continue; - } - - const top = stack.at(-1); - - const enumMatch = line.match(/^enum\s+([A-Za-z][A-Za-z0-9_]*)\s*\{$/); - if (!top && enumMatch) { - const enumDefinition = { name: enumMatch[1], values: [] }; - enums.push(enumDefinition); - stack.push({ type: 'enum', ref: enumDefinition }); - continue; - } - - const entityMatch = line.match(/^entity\s+([A-Za-z][A-Za-z0-9_]*)\s*\{$/); - if (!top && entityMatch) { - const entityDefinition = { - name: entityMatch[1], - primaryKey: null, - fields: [], - foreignKeys: [], - }; - entities.push(entityDefinition); - stack.push({ type: 'entity', ref: entityDefinition }); - continue; - } - - const valueMatch = line.match(/^value\s+([A-Za-z][A-Za-z0-9_]*)\s*\{$/); - if (top?.type === 'enum' && valueMatch) { - const enumValue = { name: valueMatch[1] }; - top.ref.values.push(enumValue); - stack.push({ type: 'enumValue', ref: enumValue }); - continue; - } - - const attributeMatch = line.match(/^attribute\s+([A-Za-z][A-Za-z0-9_]*)\s*\{$/); - if (top?.type === 'entity' && attributeMatch) { - const field = { - name: attributeMatch[1], - type: null, - required: false, - unique: false, - default: null, - }; - top.ref.fields.push(field); - stack.push({ type: 'field', ref: field, entity: top.ref }); - continue; - } - - if (top?.type === 'field' && /^key\s+foreign\s*\{$/.test(line)) { - stack.push({ type: 'foreignKey', ref: top.ref, entity: top.entity }); - continue; - } - - if (top?.type === 'enumValue') { - const labelMatch = line.match(/^label\s+"(.*)"\s*;$/); - if (labelMatch) { - top.ref.label = labelMatch[1]; - continue; - } - } - - if (top?.type === 'field') { - const typeMatch = line.match(/^type\s+([A-Za-z][A-Za-z0-9_]*)\s*;$/); - if (typeMatch) { - top.ref.type = typeMatch[1]; - continue; - } - - if (/^key\s+primary\s*;$/.test(line)) { - top.ref.primary = true; - top.entity.primaryKey = top.ref.name; - continue; - } - - const defaultMatch = line.match(/^default\s+(.+)\s*;$/); - if (defaultMatch) { - top.ref.default = parseDefaultValue(defaultMatch[1]); - continue; - } - - if (/^is\s+required\s*;$/.test(line)) { - top.ref.required = true; - continue; - } - - if (/^is\s+unique\s*;$/.test(line)) { - top.ref.unique = true; - continue; - } - } - - if (top?.type === 'foreignKey') { - const relatesMatch = line.match( - /^relates\s+([A-Za-z][A-Za-z0-9_]*)\.([A-Za-z][A-Za-z0-9_]*)\s*;$/, - ); - if (relatesMatch) { - const foreignKey = { - field: top.ref.name, - references: { - entity: relatesMatch[1], - field: relatesMatch[2], - }, - }; - - top.ref.foreignKey = foreignKey.references; - top.entity.foreignKeys.push(foreignKey); - continue; - } - } - - if (/^}\s*;?$/.test(line)) { - stack.pop(); - } - } - } - - return { dslFiles, enums, entities }; -} - -function assertNoDomainRedefinition(line, filePath) { - const blocked = [/^\s*entity\s+/i, /^\s*enum\s+/i, /^\s*attribute\s+/i, /^\s*key\s+primary/i, /^\s*key\s+foreign/i]; - for (const pattern of blocked) { - if (pattern.test(line)) { - throw new Error( - `Override file ${path.basename(filePath)} attempts to redefine domain structure: ${line.trim()}`, - ); - } - } -} - -export function parseOverrides(rootDir, entities) { - const overrideFiles = getOverrideFiles(rootDir); - const entityNames = new Set(entities.map((entity) => entity.name)); - const fieldNames = new Set( - entities.flatMap((entity) => entity.fields.map((field) => `${entity.name}.${field.name}`)), - ); - - const api = { resources: {} }; - const ui = { fields: {} }; - - for (const filePath of overrideFiles) { - const isApi = filePath.endsWith('api-overrides.dsl'); - const isUi = filePath.endsWith('ui-overrides.dsl'); - const lines = readFileSync(filePath, 'utf8').split(/\r?\n/); - - for (const rawLine of lines) { - const line = stripInlineComment(rawLine); - if (!line) { - continue; - } - - assertNoDomainRedefinition(line, filePath); - - if (isApi) { - const match = line.match(/^resource\s+([A-Za-z][A-Za-z0-9_]*)\s+path\s+"([^"]+)"\s*;$/); - if (!match) { - throw new Error( - `Unsupported API override syntax in ${path.basename(filePath)}: ${line}`, - ); - } - const [, entityName, resourcePath] = match; - if (!entityNames.has(entityName)) { - throw new Error(`API override references unknown entity ${entityName}`); - } - api.resources[entityName] = { path: resourcePath }; - } - - if (isUi) { - const match = line.match( - /^field\s+([A-Za-z][A-Za-z0-9_]*)\.([A-Za-z][A-Za-z0-9_]*)\s+widget\s+"([^"]+)"\s*;$/, - ); - if (!match) { - throw new Error( - `Unsupported UI override syntax in ${path.basename(filePath)}: ${line}`, - ); - } - const [, entityName, fieldName, widget] = match; - const key = `${entityName}.${fieldName}`; - if (!fieldNames.has(key)) { - throw new Error(`UI override references unknown field ${key}`); - } - ui.fields[key] = { widget }; - } - } - } - - return { api, ui, sourceFiles: overrideFiles.map((filePath) => path.relative(rootDir, filePath).replaceAll('\\', '/')) }; -} - -export function buildDomainSummary(rootDir) { - const { dslFiles, enums, entities } = parseDslFiles(rootDir); - parseOverrides(rootDir, entities); - const entityByName = new Map(entities.map((entity) => [entity.name, entity])); - const enumByName = new Map(enums.map((entry) => [entry.name, entry])); - - for (const entity of entities) { - if (!entity.primaryKey) { - throw new Error(`Entity ${entity.name} is missing a primary key`); - } - - const primaryKeyField = entity.fields.find((field) => field.name === entity.primaryKey); - if (!primaryKeyField) { - throw new Error( - `Entity ${entity.name} declares primary key ${entity.primaryKey}, but the field is missing`, - ); - } - - if (entity.fields.some((field) => !field.type)) { - throw new Error(`Entity ${entity.name} has attributes with missing type`); - } - - for (const foreignKey of entity.foreignKeys) { - const targetEntity = entityByName.get(foreignKey.references.entity); - if (!targetEntity) { - throw new Error( - `Foreign key ${entity.name}.${foreignKey.field} references missing entity ${foreignKey.references.entity}`, - ); - } - - const targetField = targetEntity.fields.find( - (field) => field.name === foreignKey.references.field, - ); - if (!targetField) { - throw new Error( - `Foreign key ${entity.name}.${foreignKey.field} references missing field ${foreignKey.references.entity}.${foreignKey.references.field}`, - ); - } - - const sourceField = entity.fields.find((field) => field.name === foreignKey.field); - if (sourceField && sourceField.type !== targetField.type) { - throw new Error( - `Foreign key ${entity.name}.${foreignKey.field} type ${sourceField.type} does not match ${foreignKey.references.entity}.${foreignKey.references.field} type ${targetField.type}`, - ); - } - } - - const fieldNames = new Set(); - for (const field of entity.fields) { - if (fieldNames.has(field.name)) { - throw new Error(`Entity ${entity.name} has duplicate field ${field.name}`); - } - fieldNames.add(field.name); - } - } - - const entityNames = new Set(); - for (const entity of entities) { - if (entityNames.has(entity.name)) { - throw new Error(`Duplicate entity definition: ${entity.name}`); - } - entityNames.add(entity.name); - } - - for (const [enumName, enumDefinition] of enumByName.entries()) { - const valueNames = new Set(); - for (const value of enumDefinition.values) { - if (valueNames.has(value.name)) { - throw new Error(`Enum ${enumName} has duplicate value ${value.name}`); - } - valueNames.add(value.name); - } - } - - return { - sourceFiles: dslFiles.map((filePath) => path.relative(rootDir, filePath).replaceAll('\\', '/')), - entities: entities.map((entity) => ({ - name: entity.name, - primaryKey: entity.primaryKey, - fields: entity.fields.map((field) => ({ - name: field.name, - type: field.type, - required: field.required, - unique: field.unique, - default: field.default, - })), - foreignKeys: entity.foreignKeys, - })), - enums, - }; -} diff --git a/tools/eval/README.md b/tools/eval/README.md new file mode 100644 index 0000000..5f4f318 --- /dev/null +++ b/tools/eval/README.md @@ -0,0 +1,106 @@ +# Eval Harness — Rule 6 + +Fixture-based regression tests for generated artifacts. + +## Why this exists + +> "Evals are the test suite for your prompts. You would never ship code without tests; +> don't ship prompts without evals." — Anthropic Engineering + +The validation gate (`tools/validate-generation.mjs`) checks **existence** and **structural compliance**. +The eval harness checks **semantic correctness**: are the right patterns present in the generated code? +Do the generated files actually follow the rules in `prompts/`? + +Together they enforce: +- Gate: "file exists, field names present, auth seams wired" +- Evals: "DTO has class-validator decorators, FK uses ReferenceInput, date uses DateInput, guard is present" + +## Usage + +```bash +# Run all evals +npm run eval:generation + +# Run evals for one entity +node tools/eval/run-evals.mjs --entity equipment + +# Verbose output (show each file being checked) +node tools/eval/run-evals.mjs --verbose +``` + +## Fixture format + +Each fixture lives in `tools/eval/fixtures//`: + +``` +fixtures/ + equipment/ + meta.json ← what this fixture tests + backend.assertions.json ← patterns the NestJS files must satisfy + frontend.assertions.json ← patterns the React Admin files must satisfy + repair-order/ + meta.json + backend.assertions.json + frontend.assertions.json +``` + +### `meta.json` + +```json +{ + "entity": "Equipment", + "kebab": "equipment", + "resource": "equipment", + "description": "...", + "tests": ["dto-decorator-coverage", "auth-guards", ...] +} +``` + +### `*.assertions.json` + +Each file entry supports: + +| Key | Type | Meaning | +|-----|------|---------| +| `path` | string | Relative path from repo root | +| `must_contain` | string[] | Each string must appear as a literal substring | +| `must_not_contain` | string[] | Each string must NOT appear | +| `must_match_regex` | string[] | Each pattern must match (multiline dot-all) | +| `must_not_match_regex` | string[] | Each pattern must NOT match | +| `comment` | string | Human-readable explanation of what is being tested | + +## Eval-driven development workflow + +This is the critical principle from Anthropic and Google: + +1. **Write the failing eval first.** When you change a prompt or add a rule, add an + assertion that captures the new expectation *before* re-generating. +2. **Run evals**: `npm run eval:generation` → see failures. +3. **Re-generate** the affected entity (following the generation workflow in `AGENTS.md`). +4. **Run evals again**: all pass → the change is verified. +5. **Commit both** the updated fixture and the regenerated artifacts together. + +A passing eval after a prompt change confirms the LLM followed the new rule. +A failing eval before a prompt change tells you exactly which prior contract was broken. + +## Adding a new entity fixture + +When adding a new entity to `domain/toir.api.dsl` and generating its backend + frontend: + +1. Create `tools/eval/fixtures//meta.json` +2. Create `tools/eval/fixtures//backend.assertions.json` with at minimum: + - controller: `@Controller(...)`, `@UseGuards(`, `JwtAuthGuard`, HTTP methods + - create_dto: `from 'class-validator'`, required fields with `!:`, `@IsString(`, `@IsOptional(` + - update_dto: `from 'class-validator'`, fields with `?:`, `@IsOptional(` +3. Create `tools/eval/fixtures//frontend.assertions.json` with at minimum: + - create: `ReferenceInput` for FK fields, `NumberInput` for numeric, `DateInput` for date, `SelectInput` for enum + - show: `ReferenceField` for FK fields, `DateField` for date +4. Run `npm run eval:generation` to verify the fixture catches real issues. + +## Integration with git hooks + +The pre-commit hook (installed by `npm run install-hooks`) runs both: +1. `node tools/validate-generation.mjs --artifacts-only` — existence gate +2. `npm run eval:generation` — semantic eval gate + +Both must pass before a commit is accepted. diff --git a/tools/eval/fixtures/equipment/backend.assertions.json b/tools/eval/fixtures/equipment/backend.assertions.json new file mode 100644 index 0000000..b54bb7e --- /dev/null +++ b/tools/eval/fixtures/equipment/backend.assertions.json @@ -0,0 +1,79 @@ +{ + "entity": "Equipment", + "files": { + "controller": { + "path": "server/src/modules/equipment/equipment.controller.ts", + "must_contain": [ + "@Controller('equipments')", + "@UseGuards(", + "JwtAuthGuard", + "@Get()", + "@Post()", + "@Get(':id')", + "@Patch(':id')", + "@Delete(':id')" + ], + "must_not_contain": [ + "@Put(':id')", + "@Post(':id')" + ], + "must_match_regex": [ + "@Delete\\(':id'\\)[\\s\\S]{0,80}@Roles\\('admin'\\)|@Roles\\('admin'\\)[\\s\\S]{0,80}@Delete\\(':id'\\)" + ], + "comment": "Equipment controller must expose the CRUD verbs expected by the DSL-compatible React Admin contract." + }, + "service": { + "path": "server/src/modules/equipment/equipment.service.ts", + "must_contain": [ + "setListHeaders(response", + "_start", + "_end", + "_sort", + "_order" + ], + "must_match_regex": [ + "mode.*insensitive|insensitive.*mode", + "status.*in\\b|\\bin\\b.*status" + ], + "comment": "Service must translate React Admin list params into Prisma filters and delegate header wiring through the shared helper." + }, + "create_dto": { + "path": "server/src/modules/equipment/dto/create-equipment.dto.ts", + "must_contain": [ + "from 'class-validator'", + "inventoryNumber!:", + "name!:", + "equipmentType!:", + "periodicityTO!:", + "status!:", + "@IsString(", + "@IsOptional(", + "@IsEnum(" + ], + "must_not_contain": [ + "id?:", + "id!:" + ], + "comment": "Required fields use '!' suffix; optional fields use '?' with @IsOptional(); enum fields use @IsEnum(); class-validator must be imported." + }, + "update_dto": { + "path": "server/src/modules/equipment/dto/update-equipment.dto.ts", + "must_contain": [ + "from 'class-validator'", + "inventoryNumber?:", + "name?:", + "equipmentType?:", + "status?:", + "@IsOptional(", + "@IsString(", + "@IsEnum(" + ], + "must_not_contain": [ + "inventoryNumber!:", + "name!:", + "status!:" + ], + "comment": "Update DTO: all fields are optional ('?' suffix with @IsOptional())." + } + } +} diff --git a/tools/eval/fixtures/equipment/frontend.assertions.json b/tools/eval/fixtures/equipment/frontend.assertions.json new file mode 100644 index 0000000..0243086 --- /dev/null +++ b/tools/eval/fixtures/equipment/frontend.assertions.json @@ -0,0 +1,57 @@ +{ + "entity": "Equipment", + "resource": "equipment", + "files": { + "list": { + "path": "client/src/resources/equipment/EquipmentList.tsx", + "must_contain": [ + "List", + "FilterButton", + "TextField", + "inventoryNumber" + ], + "must_match_regex": [ + "SelectArrayInput", + "source=\"status\"" + ], + "comment": "Equipment list must expose filter UI directly and keep enum filters." + }, + "create": { + "path": "client/src/resources/equipment/EquipmentCreate.tsx", + "must_contain": [ + "Create", + "SimpleForm", + "SelectInput" + ], + "must_match_regex": [ + "NumberInput[\\s\\S]{0,300}source=\"totalEngineHours\"|source=\"totalEngineHours\"[\\s\\S]{0,300}NumberInput", + "DateInput[\\s\\S]{0,300}source=\"dateOfInspection\"|source=\"dateOfInspection\"[\\s\\S]{0,300}DateInput", + "SelectInput[\\s\\S]{0,300}source=\"status\"|source=\"status\"[\\s\\S]{0,300}SelectInput" + ], + "comment": "Equipment create form must keep type-correct inputs for enum, date, and decimal/number fields." + }, + "edit": { + "path": "client/src/resources/equipment/EquipmentEdit.tsx", + "must_contain": [ + "Edit", + "SimpleForm", + "SelectInput" + ], + "must_match_regex": [ + "NumberInput[\\s\\S]{0,300}source=\"totalEngineHours\"|source=\"totalEngineHours\"[\\s\\S]{0,300}NumberInput", + "DateInput[\\s\\S]{0,300}source=\"dateOfInspection\"|source=\"dateOfInspection\"[\\s\\S]{0,300}DateInput" + ], + "comment": "Equipment edit form must keep the same type-correctness guarantees as create." + }, + "show": { + "path": "client/src/resources/equipment/EquipmentShow.tsx", + "must_contain": [ + "Show", + "SimpleShowLayout", + "TextField", + "inventoryNumber" + ], + "comment": "Show must display key fields including inventoryNumber." + } + } +} diff --git a/tools/eval/fixtures/equipment/meta.json b/tools/eval/fixtures/equipment/meta.json new file mode 100644 index 0000000..82285b9 --- /dev/null +++ b/tools/eval/fixtures/equipment/meta.json @@ -0,0 +1,15 @@ +{ + "entity": "Equipment", + "kebab": "equipment", + "resource": "equipment", + "description": "Standard entity: UUID primary key, multiple enum fields, decimal fields, date fields, no FK reference to other entities", + "tests": [ + "dto-decorator-coverage", + "auth-guards-per-http-method", + "content-range-header-pattern", + "enum-filter-in-operator", + "q-filter-contains-pattern", + "react-admin-component-types", + "class-validator-import" + ] +} diff --git a/tools/eval/fixtures/repair-order/backend.assertions.json b/tools/eval/fixtures/repair-order/backend.assertions.json new file mode 100644 index 0000000..7d65d94 --- /dev/null +++ b/tools/eval/fixtures/repair-order/backend.assertions.json @@ -0,0 +1,62 @@ +{ + "entity": "CategoryResource", + "files": { + "controller": { + "path": "server/src/modules/category-resource/category-resource.controller.ts", + "must_contain": [ + "@Controller('category-resources')", + "@UseGuards(", + "JwtAuthGuard", + "@Get()", + "@Post()", + "@Get(':id')", + "@Patch(':id')", + "@Delete(':id')" + ], + "must_not_contain": [ + "@Put(':id')" + ], + "must_match_regex": [ + "@Delete\\(':id'\\)[\\s\\S]{0,120}@Roles\\('admin'\\)|@Roles\\('admin'\\)[\\s\\S]{0,120}@Delete\\(':id'\\)" + ] + }, + "service": { + "path": "server/src/modules/category-resource/category-resource.service.ts", + "must_contain": [ + "setListHeaders", + "_start", + "_end", + "partId", + "employeeCode" + ], + "must_match_regex": [ + "part:\\s*\\{\\s*is:\\s*\\{\\s*name", + "employee:\\s*\\{\\s*is:\\s*\\{\\s*fullName" + ] + }, + "create_dto": { + "path": "server/src/modules/category-resource/dto/create-category-resource.dto.ts", + "must_contain": [ + "from 'class-validator'", + "partId?:", + "employeeCode?:", + "@IsUUID(", + "@IsString(", + "@IsOptional(" + ], + "must_not_contain": [ + "id?:", + "id!:" + ] + }, + "update_dto": { + "path": "server/src/modules/category-resource/dto/update-category-resource.dto.ts", + "must_contain": [ + "from 'class-validator'", + "@IsOptional(", + "partId?:", + "employeeCode?:" + ] + } + } +} diff --git a/tools/eval/fixtures/repair-order/frontend.assertions.json b/tools/eval/fixtures/repair-order/frontend.assertions.json new file mode 100644 index 0000000..0880db1 --- /dev/null +++ b/tools/eval/fixtures/repair-order/frontend.assertions.json @@ -0,0 +1,53 @@ +{ + "entity": "CategoryResource", + "resource": "category-resources", + "files": { + "list": { + "path": "client/src/resources/category-resource/CategoryResourceList.tsx", + "must_contain": [ + "List", + "FilterButton", + "ReferenceField" + ], + "must_match_regex": [ + "ReferenceField[\\s\\S]{0,200}reference=\"parts\"|reference=\"parts\"[\\s\\S]{0,200}ReferenceField", + "ReferenceField[\\s\\S]{0,200}reference=\"employees\"|reference=\"employees\"[\\s\\S]{0,200}ReferenceField" + ] + }, + "create": { + "path": "client/src/resources/category-resource/CategoryResourceCreate.tsx", + "must_contain": [ + "Create", + "SimpleForm" + ], + "must_match_regex": [ + "ReferenceInput[\\s\\S]{0,200}reference=\"parts\"|reference=\"parts\"[\\s\\S]{0,200}ReferenceInput", + "ReferenceInput[\\s\\S]{0,200}reference=\"employees\"|reference=\"employees\"[\\s\\S]{0,200}ReferenceInput", + "AutocompleteInput[\\s\\S]{0,200}filterToQuery|filterToQuery[\\s\\S]{0,200}AutocompleteInput" + ] + }, + "edit": { + "path": "client/src/resources/category-resource/CategoryResourceEdit.tsx", + "must_contain": [ + "Edit", + "SimpleForm" + ], + "must_match_regex": [ + "ReferenceInput[\\s\\S]{0,200}reference=\"parts\"|reference=\"parts\"[\\s\\S]{0,200}ReferenceInput", + "ReferenceInput[\\s\\S]{0,200}reference=\"employees\"|reference=\"employees\"[\\s\\S]{0,200}ReferenceInput" + ] + }, + "show": { + "path": "client/src/resources/category-resource/CategoryResourceShow.tsx", + "must_contain": [ + "Show", + "SimpleShowLayout", + "ReferenceField" + ], + "must_match_regex": [ + "ReferenceField[\\s\\S]{0,200}reference=\"parts\"|reference=\"parts\"[\\s\\S]{0,200}ReferenceField", + "ReferenceField[\\s\\S]{0,200}reference=\"employees\"|reference=\"employees\"[\\s\\S]{0,200}ReferenceField" + ] + } + } +} diff --git a/tools/eval/fixtures/repair-order/meta.json b/tools/eval/fixtures/repair-order/meta.json new file mode 100644 index 0000000..9b36218 --- /dev/null +++ b/tools/eval/fixtures/repair-order/meta.json @@ -0,0 +1,13 @@ +{ + "entity": "CategoryResource", + "kebab": "category-resource", + "resource": "category-resources", + "description": "Current FK-heavy entity: UUID PK with references to Part and Employee. Tests reference wiring, autocomplete filters, and protected CRUD routes.", + "tests": [ + "dto-decorator-coverage", + "auth-guards", + "fk-reference-input", + "fk-reference-field", + "content-range-header" + ] +} diff --git a/tools/eval/run-evals.mjs b/tools/eval/run-evals.mjs new file mode 100644 index 0000000..6aadd7c --- /dev/null +++ b/tools/eval/run-evals.mjs @@ -0,0 +1,184 @@ +#!/usr/bin/env node +/** + * tools/eval/run-evals.mjs + * + * Rule 6 — Eval harness: fixture-based regression tests for generated artifacts. + * + * Philosophy: + * - Evals are the test suite for prompts. Never ship a prompt change without + * running evals first. + * - Use deterministic pattern/regex checks ("reference-free" grading) rather + * than golden snapshot comparison. Patterns are maintainable; snapshots are + * brittle. + * - Eval-driven development: write a failing eval FIRST, then update the prompt + * or re-generate to make it pass. + * + * Usage: + * node tools/eval/run-evals.mjs # run all fixtures + * node tools/eval/run-evals.mjs --entity equipment + * node tools/eval/run-evals.mjs --verbose + */ + +import { existsSync, readFileSync, readdirSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const rootDir = path.resolve(__dirname, '../..'); +const fixturesDir = path.join(__dirname, 'fixtures'); + +const args = new Set(process.argv.slice(2)); +const verbose = args.has('--verbose') || args.has('-v'); +const entityFilter = (() => { + const idx = process.argv.indexOf('--entity'); + return idx !== -1 ? process.argv[idx + 1] : null; +})(); + +// --------------------------------------------------------------------------- +// Assertion engine +// --------------------------------------------------------------------------- + +let totalChecks = 0; +let totalFailures = 0; +const failures = []; + +function readArtifact(relativePath) { + const filePath = path.join(rootDir, relativePath); + if (!existsSync(filePath)) return null; + return readFileSync(filePath, 'utf8'); +} + +function runFileAssertions(filePath, fileSpec, entityLabel) { + const content = readArtifact(filePath); + + if (content === null) { + totalChecks++; + totalFailures++; + failures.push({ entity: entityLabel, file: filePath, check: 'file-exists', result: 'FAIL', detail: `File not found: ${filePath}` }); + return; + } + + if (verbose) { + console.log(` [${entityLabel}] Checking ${filePath}`); + } + + for (const expected of fileSpec.must_contain ?? []) { + totalChecks++; + if (!content.includes(expected)) { + totalFailures++; + failures.push({ entity: entityLabel, file: filePath, check: 'must_contain', result: 'FAIL', detail: `Missing: ${expected}` }); + } + } + + for (const forbidden of fileSpec.must_not_contain ?? []) { + totalChecks++; + if (content.includes(forbidden)) { + totalFailures++; + failures.push({ entity: entityLabel, file: filePath, check: 'must_not_contain', result: 'FAIL', detail: `Forbidden pattern found: ${forbidden}` }); + } + } + + for (const patternStr of fileSpec.must_match_regex ?? []) { + totalChecks++; + try { + const re = new RegExp(patternStr); + if (!re.test(content)) { + totalFailures++; + failures.push({ entity: entityLabel, file: filePath, check: 'must_match_regex', result: 'FAIL', detail: `Regex not matched: ${patternStr}` }); + } + } catch (e) { + totalFailures++; + failures.push({ entity: entityLabel, file: filePath, check: 'must_match_regex', result: 'ERROR', detail: `Bad regex: ${patternStr} — ${e.message}` }); + } + } + + for (const patternStr of fileSpec.must_not_match_regex ?? []) { + totalChecks++; + try { + const re = new RegExp(patternStr); + if (re.test(content)) { + totalFailures++; + failures.push({ entity: entityLabel, file: filePath, check: 'must_not_match_regex', result: 'FAIL', detail: `Forbidden regex matched: ${patternStr}` }); + } + } catch (e) { + totalFailures++; + failures.push({ entity: entityLabel, file: filePath, check: 'must_not_match_regex', result: 'ERROR', detail: `Bad regex: ${patternStr} — ${e.message}` }); + } + } +} + +function runFixture(fixtureDir) { + const metaPath = path.join(fixtureDir, 'meta.json'); + if (!existsSync(metaPath)) return; + + const meta = JSON.parse(readFileSync(metaPath, 'utf8')); + const { entity, kebab } = meta; + + if (entityFilter && kebab !== entityFilter && entity.toLowerCase() !== entityFilter.toLowerCase()) { + return; + } + + if (verbose) { + console.log(`\n[EVAL] ${entity} — ${meta.description ?? ''}`); + } + + const backendPath = path.join(fixtureDir, 'backend.assertions.json'); + if (existsSync(backendPath)) { + const spec = JSON.parse(readFileSync(backendPath, 'utf8')); + for (const [key, fileSpec] of Object.entries(spec.files ?? {})) { + runFileAssertions(fileSpec.path, fileSpec, `${entity}/${key}`); + } + } + + const frontendPath = path.join(fixtureDir, 'frontend.assertions.json'); + if (existsSync(frontendPath)) { + const spec = JSON.parse(readFileSync(frontendPath, 'utf8')); + for (const [key, fileSpec] of Object.entries(spec.files ?? {})) { + runFileAssertions(fileSpec.path, fileSpec, `${entity}/${key}`); + } + } +} + +// --------------------------------------------------------------------------- +// Main +// --------------------------------------------------------------------------- + +const fixtureDirs = readdirSync(fixturesDir, { withFileTypes: true }) + .filter((d) => d.isDirectory()) + .map((d) => path.join(fixturesDir, d.name)); + +for (const dir of fixtureDirs) { + runFixture(dir); +} + +// --------------------------------------------------------------------------- +// Report +// --------------------------------------------------------------------------- + +console.log(''); +console.log('══════════════════════════════════════════════'); +console.log(' KIS-TOiR Eval Report'); +console.log('══════════════════════════════════════════════'); +console.log(` Fixtures: ${fixtureDirs.length}`); +console.log(` Checks: ${totalChecks}`); +console.log(` Passed: ${totalChecks - totalFailures}`); +console.log(` Failed: ${totalFailures}`); +console.log('══════════════════════════════════════════════'); + +if (failures.length > 0) { + console.log(''); + console.log('Failures:'); + for (const f of failures) { + console.log(` [${f.result}] ${f.entity} — ${f.file}`); + console.log(` ${f.check}: ${f.detail}`); + } + console.log(''); + console.log('To fix: update the prompt or re-generate the failing entity, then re-run evals.'); + console.log('To update a fixture (intentional change): edit tools/eval/fixtures//*.assertions.json'); + console.log(''); + process.exit(1); +} + +console.log(''); +console.log('All evals passed.'); +console.log(''); diff --git a/tools/generate-domain-summary.mjs b/tools/generate-api-summary.mjs similarity index 65% rename from tools/generate-domain-summary.mjs rename to tools/generate-api-summary.mjs index 806015e..5cbfc7f 100644 --- a/tools/generate-domain-summary.mjs +++ b/tools/generate-api-summary.mjs @@ -1,13 +1,13 @@ import { mkdirSync, writeFileSync } from 'node:fs'; import path from 'node:path'; -import { buildDomainSummary } from './dsl-summary.mjs'; +import { buildApiSummary } from './api-summary.mjs'; const rootDir = process.cwd(); -const outputPath = path.join(rootDir, 'domain-summary.json'); +const outputPath = path.join(rootDir, 'api-summary.json'); mkdirSync(path.dirname(outputPath), { recursive: true }); -const summary = buildDomainSummary(rootDir); +const summary = buildApiSummary(rootDir); writeFileSync(outputPath, `${JSON.stringify(summary, null, 2)}\n`, 'utf8'); console.log(`Generated ${path.relative(rootDir, outputPath)}`); diff --git a/tools/hooks/pre-commit b/tools/hooks/pre-commit new file mode 100644 index 0000000..a0a36a6 --- /dev/null +++ b/tools/hooks/pre-commit @@ -0,0 +1,5 @@ +#!/bin/sh +# Pre-commit hook: runs the generation validation gate and eval harness. +# Install with: npm run install-hooks + +node tools/validate-generation.mjs --artifacts-only && node tools/eval/run-evals.mjs diff --git a/tools/install-hooks.mjs b/tools/install-hooks.mjs new file mode 100644 index 0000000..227319d --- /dev/null +++ b/tools/install-hooks.mjs @@ -0,0 +1,19 @@ +import { copyFileSync, chmodSync, mkdirSync, existsSync } from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const root = path.resolve(__dirname, '..'); +const hooksDir = path.join(root, '.git', 'hooks'); +const src = path.join(root, 'tools', 'hooks', 'pre-commit'); +const dest = path.join(hooksDir, 'pre-commit'); + +if (!existsSync(path.join(root, '.git'))) { + console.error('Not a git repository. Run from the repo root.'); + process.exit(1); +} + +mkdirSync(hooksDir, { recursive: true }); +copyFileSync(src, dest); +try { chmodSync(dest, 0o755); } catch { /* Windows */ } +console.log('Installed pre-commit hook → .git/hooks/pre-commit'); diff --git a/tools/validate-generation.mjs b/tools/validate-generation.mjs index e7209b0..23f2424 100644 --- a/tools/validate-generation.mjs +++ b/tools/validate-generation.mjs @@ -1,12 +1,7 @@ import { existsSync, readFileSync, readdirSync } from 'node:fs'; import path from 'node:path'; import { spawnSync } from 'node:child_process'; -import { - buildDomainSummary, - getDslFiles, - parseDslFiles, - parseOverrides, -} from './dsl-summary.mjs'; +import { getApiDslFiles, parseApiDsl, buildApiSummary } from './api-summary.mjs'; const rootDir = process.cwd(); const args = new Set(process.argv.slice(2)); @@ -125,7 +120,7 @@ function validateBuildChecks() { 'README.md', 'package.json', 'domain/dsl-spec.md', - 'domain-summary.json', + 'api-summary.json', 'server/prisma/schema.prisma', 'server/.env.example', 'client/.env.example', @@ -137,23 +132,22 @@ function validateBuildChecks() { 'prompts/validation-rules.md', ]); - const dslFiles = getDslFiles(rootDir).map((filePath) => path.relative(rootDir, filePath).replaceAll('\\', '/')); - assertCondition(dslFiles.length > 0, 'Expected at least one domain/*.dsl file'); + // rule: AGENTS.md §Tier-1 — api.dsl must exist + const apiDslFiles = getApiDslFiles(rootDir); + assertCondition(apiDslFiles.length > 0, 'Expected at least one domain/*.api.dsl file'); - const actualSummaryRaw = readIfExists('domain-summary.json'); - if (actualSummaryRaw) { - const expectedSummary = JSON.stringify(buildDomainSummary(rootDir), null, 2); - assertCondition( - actualSummaryRaw.trim() === expectedSummary, - 'domain-summary.json is out of date. Run `npm run generate:domain-summary`.', - ); - } - - try { - const { entities } = parseDslFiles(rootDir); - parseOverrides(rootDir, entities); - } catch (error) { - failures.push(`Override validation failed: ${error.message}`); + // rule: AGENTS.md §Tier-2 — api-summary.json must match parsed api.dsl + const actualApiSummaryRaw = readIfExists('api-summary.json'); + if (actualApiSummaryRaw) { + try { + const expectedApiSummary = JSON.stringify(buildApiSummary(rootDir), null, 2); + assertCondition( + actualApiSummaryRaw.trim() === expectedApiSummary, + 'api-summary.json is out of date. Run `npm run generate:api-summary`.', + ); + } catch (error) { + failures.push(`api-summary.json freshness check failed: ${error.message}`); + } } const { server, client } = getWorkspaceInfo(); @@ -241,12 +235,32 @@ function validateAuthChecks() { } function validateNaturalKeyChecks() { - const summary = parseJson('domain-summary.json'); - if (!summary) { + // rule: AGENTS.md §Tier-2 — derive natural-key entities from api-summary.json + // A natural-key entity is identified by a root DTO (DTO.X — not Create/Update/Filter/...) + // that has a field annotated with `key primary` where the field name is not 'id'. + const apiSummaryRaw = readIfExists('api-summary.json'); + if (!apiSummaryRaw) { return; } - const naturalKeyEntities = summary.entities.filter((entity) => entity.primaryKey !== 'id'); + let apiSummary; + try { + apiSummary = JSON.parse(apiSummaryRaw); + } catch { + return; + } + + const naturalKeyEntities = []; + for (const dto of apiSummary.dtos ?? []) { + // Only root DTOs: DTO.X (not DTO.XCreate / Update / Filter / ListRequest / ListResponse / Page*) + if (/Create$|Update$|Filter$|ListRequest$|ListResponse$|PageRequest$|PageInfo$/.test(dto.name)) continue; + + const entityName = dto.name.replace(/^DTO\./, ''); + const primaryField = (dto.fields ?? []).find((f) => f.primary === true && f.name !== 'id'); + if (primaryField) { + naturalKeyEntities.push({ name: entityName, primaryKey: primaryField.name }); + } + } for (const entity of naturalKeyEntities) { const moduleName = kebabCase(entity.name); @@ -349,7 +363,9 @@ function validateRuntimeContractChecks() { requireFile('docker-compose.yml'); const compose = readIfExists('docker-compose.yml') ?? ''; assertCondition(/image:\s*postgres:16/.test(compose), 'docker-compose must provision postgres:16'); - assertCondition(!/keycloak/i.test(compose), 'docker-compose must remain PostgreSQL-only'); + const hasKeycloakService = + /^\s{2}keycloak\s*:/m.test(compose) || /image:\s*.*keycloak/i.test(compose); + assertCondition(!hasKeycloakService, 'docker-compose must remain PostgreSQL-only (no Keycloak container)'); const serverEnvExample = readIfExists('server/.env.example') ?? ''; assertCondition( @@ -482,11 +498,296 @@ function validateRuntimeExecutionChecks() { runCommand('npx', ['prisma', 'db', 'seed'], serverDir, 'Prisma seed failed'); } +// --------------------------------------------------------------------------- +// Output contract checks — Rule 4 (grounded and schema-bound outputs) +// +// Verify that generated artifacts conform to the output contracts declared in +// prompts/backend-rules.md and prompts/frontend-rules.md. +// All checks are deterministic regex / substring patterns. +// --------------------------------------------------------------------------- + +// DSL field type → expected class-validator decorator (pattern fragment). +// rule: backend-rules.md §Type mappings +const DSL_TYPE_TO_CV_DECORATOR = { + uuid: '@IsUUID(', + string: '@IsString(', + text: '@IsString(', + integer: ['@IsInt(', '@IsNumber('], + number: '@IsNumber(', + decimal: '@IsString(', + date: '@IsString(', + boolean: '@IsBoolean(', +}; + +function escapeRegexStr(s) { + return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); +} + +// Check that a class-validator decorator appears within 400 chars before fieldName +function fieldHasDecorator(content, fieldName, decoratorFragment) { + const pattern = new RegExp( + `${escapeRegexStr(decoratorFragment)}[\\s\\S]{0,400}${escapeRegexStr(fieldName)}[?!]?\\s*:`, + ); + return pattern.test(content); +} + +function validateDtoDecoratorCoverage() { + const { dtos, enums } = parseApiDsl(rootDir); + const enumNames = new Set(enums.map((e) => e.name)); + + for (const dto of dtos) { + const createMatch = dto.name.match(/^DTO\.(\w+)Create$/); + const updateMatch = dto.name.match(/^DTO\.(\w+)Update$/); + const match = createMatch ?? updateMatch; + if (!match) continue; + + const kebab = kebabCase(match[1]); + const prefix = createMatch ? 'create' : 'update'; + const dtoPath = `server/src/modules/${kebab}/dto/${prefix}-${kebab}.dto.ts`; + const content = readIfExists(dtoPath) ?? ''; + if (!content) continue; + + // rule: backend-rules.md §Type mappings — every DTO must import class-validator + assertCondition( + /from 'class-validator'/.test(content), + `${dtoPath}: missing import from 'class-validator' — rule: backend-rules.md §Type mappings`, + ); + + for (const field of dto.fields) { + const { name, type, nullable, required } = field; + if (!type) continue; + + // Skip DTO reference types — validated by @ValidateNested separately + if (type.startsWith('DTO.')) continue; + + // rule: backend-rules.md — nullable/optional fields must carry @IsOptional() + if (!required || nullable) { + assertCondition( + fieldHasDecorator(content, name, '@IsOptional('), + `${dtoPath}: field '${name}' is optional/nullable but missing @IsOptional()`, + ); + } + + // rule: backend-rules.md §Type mappings — type-correct decorator + const bareType = type.replace('[]', ''); + if (enumNames.has(bareType)) { + assertCondition( + fieldHasDecorator(content, name, `@IsEnum(${bareType}`), + `${dtoPath}: field '${name}' has enum type '${bareType}' but missing @IsEnum(${bareType})`, + ); + } else { + const expected = DSL_TYPE_TO_CV_DECORATOR[bareType]; + if (expected) { + const options = Array.isArray(expected) ? expected : [expected]; + const found = options.some((opt) => fieldHasDecorator(content, name, opt)); + assertCondition( + found, + `${dtoPath}: field '${name}' has type '${bareType}' but missing ${options.join(' or ')}`, + ); + } + } + } + } +} + +function validateControllerGuards() { + // rule: backend-rules.md §Backend auth defaults — every controller needs JwtAuthGuard + const { apis } = parseApiDsl(rootDir); + for (const api of apis) { + const resourceName = api.name.replace(/^API\./, ''); + const kebab = kebabCase(resourceName); + const controllerPath = `server/src/modules/${kebab}/${kebab}.controller.ts`; + const content = readIfExists(controllerPath) ?? ''; + if (!content) continue; + + // UseGuards must appear at the class level (within first 800 chars before the class declaration) + assertCondition( + /@UseGuards\s*\(/.test(content), + `${controllerPath}: missing @UseGuards(...) — all controllers must guard their routes`, + ); + + // JwtAuthGuard or equivalent JWT guard must be referenced + assertCondition( + /JwtAuthGuard|JwtGuard|AuthGuard/.test(content), + `${controllerPath}: controller must use JwtAuthGuard (or equivalent) — rule: backend-rules.md §Backend auth defaults`, + ); + + // rule: backend-rules.md §Backend auth defaults — DELETE must be admin-only + if (api.endpoints.some((ep) => ep.method === 'DELETE')) { + assertCondition( + /@Roles\s*\([^)]*'admin'/.test(content) || /@Roles\s*\([^)]*"admin"/.test(content), + `${controllerPath}: DELETE endpoints require @Roles('admin') — rule: backend-rules.md §Backend auth defaults`, + ); + } + } +} + +function validateFrontendComponentTypes() { + // rule: frontend-rules.md §Resource generation — type-safe component mapping + const { dtos, enums } = parseApiDsl(rootDir); + const enumNames = new Set(enums.map((e) => e.name)); + + for (const dto of dtos) { + const createMatch = dto.name.match(/^DTO\.(\w+)Create$/); + if (!createMatch) continue; + + const resourceName = createMatch[1]; + const kebab = kebabCase(resourceName); + const createPath = `client/src/resources/${kebab}/${resourceName}Create.tsx`; + const editPath = `client/src/resources/${kebab}/${resourceName}Edit.tsx`; + + for (const componentPath of [createPath, editPath]) { + const content = readIfExists(componentPath) ?? ''; + if (!content) continue; + + for (const field of dto.fields) { + const { name, type } = field; + if (!type) continue; + const bareType = type.replace('[]', ''); + + if (bareType === 'integer' || bareType === 'number' || bareType === 'decimal') { + // rule: frontend-rules.md — integer/number/decimal → NumberInput, never TextInput + const usesNumberInput = content.includes(`source="${name}"`) && + new RegExp(`NumberInput[^>]*source="${escapeRegexStr(name)}"|source="${escapeRegexStr(name)}"[^>]*NumberInput`).test(content); + // Only flag if TextInput is clearly used for a numeric field + const usesTextForNumeric = new RegExp( + `TextInput[\\s\\S]{0,200}source="${escapeRegexStr(name)}"`, + ).test(content); + assertCondition( + !usesTextForNumeric, + `${componentPath}: field '${name}' has type '${bareType}' but uses TextInput — must use NumberInput`, + ); + } + + if (bareType === 'date') { + const usesTextForDate = new RegExp( + `TextInput[\\s\\S]{0,200}source="${escapeRegexStr(name)}"`, + ).test(content); + assertCondition( + !usesTextForDate, + `${componentPath}: field '${name}' has type 'date' but uses TextInput — must use DateInput`, + ); + } + } + } + } +} + +// --------------------------------------------------------------------------- +// api.dsl coverage checks +// +// The API DSL is parsed by tools/api-summary.mjs — the single canonical +// parser. This section contains only mechanical gate logic; no DSL parsing +// or generation semantics live here. +// --------------------------------------------------------------------------- + +function validateApiDslCoverage() { + const apiDslFiles = getApiDslFiles(rootDir); + if (apiDslFiles.length === 0) { + warn('No domain/*.api.dsl files found. Skipping api.dsl coverage checks.'); + return; + } + + // rule: AGENTS.md §Tier-3 generation zones, backend-rules.md §api-dsl-as-source + const { apis, dtos } = parseApiDsl(rootDir); + + for (const api of apis) { + // api.name is "API.Equipment"; derive the kebab resource name + const resourceName = api.name.replace(/^API\./, ''); + const kebab = kebabCase(resourceName); + + // rule: backend-rules.md §module-file-structure + requireFiles([ + `server/src/modules/${kebab}/${kebab}.module.ts`, + `server/src/modules/${kebab}/${kebab}.controller.ts`, + `server/src/modules/${kebab}/${kebab}.service.ts`, + `server/src/modules/${kebab}/dto/create-${kebab}.dto.ts`, + `server/src/modules/${kebab}/dto/update-${kebab}.dto.ts`, + ]); + + // rule: frontend-rules.md §resource-file-structure + requireFiles([ + `client/src/resources/${kebab}/${resourceName}List.tsx`, + `client/src/resources/${kebab}/${resourceName}Create.tsx`, + `client/src/resources/${kebab}/${resourceName}Edit.tsx`, + `client/src/resources/${kebab}/${resourceName}Show.tsx`, + ]); + + const controllerContent = + readIfExists(`server/src/modules/${kebab}/${kebab}.controller.ts`) ?? ''; + if (!controllerContent) continue; + + // rule: backend-rules.md §endpoint-http-method-mapping + for (const ep of api.endpoints) { + if (!ep.label) continue; + const isPageEndpoint = (ep.path ?? '').endsWith('/page'); + + let found = false; + if (ep.method === 'GET') { + found = controllerContent.includes('@Get('); + } else if (ep.method === 'POST' && isPageEndpoint) { + found = controllerContent.includes('@Post(') || controllerContent.includes('@Get('); + } else if (ep.method === 'POST') { + found = controllerContent.includes('@Post('); + } else if (ep.method === 'PUT') { + found = controllerContent.includes('@Put(') || controllerContent.includes('@Patch('); + } else if (ep.method === 'DELETE') { + found = controllerContent.includes('@Delete('); + } + + assertCondition( + found, + `${api.name} endpoint ${ep.name} (${ep.label}): no matching HTTP handler in controller`, + ); + } + } + + // rule: backend-rules.md §DTO-field-coverage + for (const dto of dtos) { + const createMatch = dto.name.match(/^DTO\.(\w+)Create$/); + const updateMatch = dto.name.match(/^DTO\.(\w+)Update$/); + + if (createMatch) { + const kebab = kebabCase(createMatch[1]); + const dtoPath = `server/src/modules/${kebab}/dto/create-${kebab}.dto.ts`; + const content = readIfExists(dtoPath) ?? ''; + if (content) { + for (const field of dto.fields) { + assertCondition( + content.includes(field.name), + `${dto.name} field '${field.name}' missing from ${dtoPath}`, + ); + } + } + } + + if (updateMatch) { + const kebab = kebabCase(updateMatch[1]); + const dtoPath = `server/src/modules/${kebab}/dto/update-${kebab}.dto.ts`; + const content = readIfExists(dtoPath) ?? ''; + if (content) { + for (const field of dto.fields) { + assertCondition( + content.includes(field.name), + `${dto.name} field '${field.name}' missing from ${dtoPath}`, + ); + } + } + } + } +} + +// --------------------------------------------------------------------------- + validateBuildChecks(); validateAuthChecks(); validateNaturalKeyChecks(); validateRealmChecks(); validateRuntimeContractChecks(); +validateApiDslCoverage(); +validateDtoDecoratorCoverage(); +validateControllerGuards(); +validateFrontendComponentTypes(); if (!artifactsOnly) { validateBuildExecutionChecks();